Description
Custom levels Rough Draft
Comments (0)
0/2000
Loading comments…
Source code
function calculate(bars, ctx) {
// CnC Paper Strategy v1.0
// Executes paper trades directly from the real CnC signal pipeline
// (TEAMS / GLOBEX / ML / etc, already win-rate-gated, anti-flip-cooled,
// and wall-confluence-scored server-side) instead of recomputing a
// separate, simpler entry system inside the strategy script itself.
// This reads ctx.alerts.last - the exact same field the CnC Pro
// Indicator script already reads to display signals on the chart - so
// whatever the indicator SHOWS you, this strategy actually TRADES.
const last = bars.length - 1;
if (last < 5) return;
// == Inputs ================================================================
const qty = ctx.input('Quantity', 1, { min: 1, max: 10 });
const maxTradesPerDay = ctx.input('Max Trades Per Day', 10, { min: 1, max: 50 });
const maxDailyLossDollars = ctx.input('Max Daily Loss ($)', 500, { min: 50, max: 5000 });
const startHour = ctx.input('Start Hour (ET)', 9, { min: 0, max: 23 });
const endHour = ctx.input('End Hour (ET)', 16, { min: 0, max: 23 });
const minScore = ctx.input('Min Signal Score to Trade', 4, { min: 0, max: 10 });
const allowedCategories = ctx.input('Allowed Categories (comma-sep, blank = all)', '');
const useTrailing = ctx.input('Use Trailing Stop', false);
const trailOffsetPts = ctx.input('Trail Offset (pts)', 3, { min: 0.5, max: 20, step: 0.5 });
// == Session gate ===========================================================
if (!ctx.time.inSession(last, startHour, endHour)) return;
// == Daily loss limit - flatten and stop trading for the day ===============
if (ctx.strategy.account.dayPnl <= -Math.abs(maxDailyLossDollars)) {
if (ctx.strategy.position.side !== 'flat') {
ctx.strategy.flattenAll();
ctx.log('Daily loss limit ($' + maxDailyLossDollars + ') reached - flattening all, no new entries today');
}
return;
}
// == Read the real CnC signal - same field the indicator script reads =====
const cncAlert = ctx.alerts && ctx.alerts.last;
if (!cncAlert || !cncAlert.data) return;
const d = cncAlert.data; // { direction, entry, stop, target, score, category, signal_id, ... }
const sigId = d.signal_id || String(cncAlert.ts || bars[last].timestamp);
if (!ctx.state.lastTradedId) ctx.state.lastTradedId = null;
if (sigId === ctx.state.lastTradedId) return; // already acted on (or skipped) this exact signal
const dir = d.direction === 'long' || d.direction === 'BULLISH' ? 'long'
: d.direction === 'short' || d.direction === 'BEARISH' ? 'short' : null;
const entryPx = d.entry != null ? d.entry : d.entry_price;
const stopPx = d.stop != null ? d.stop : null;
const targetPx = d.target != null ? d.target : null;
const score = d.score || 0;
const category = d.category || '';
// == Quality gates - these mirror what the server already enforced, ========
// == plus a couple more that only make sense at execution time =============
if (!dir || entryPx == null || stopPx == null || targetPx == null) {
ctx.state.lastTradedId = sigId; // malformed signal - don't keep re-checking it every bar
return;
}
if (score < minScore) {
ctx.log('Skipping signal ' + category + ' ' + dir + ' - score ' + score + ' below min ' + minScore);
ctx.state.lastTradedId = sigId;
return;
}
const allowList = allowedCategories.split(',').map(s => s.trim()).filter(Boolean);
if (allowList.length && allowList.indexOf(category) === -1) {
ctx.log('Skipping signal - category ' + category + ' not in allow list');
ctx.state.lastTradedId = sigId;
return;
}
if (ctx.strategy.account.todayTrades >= maxTradesPerDay) {
ctx.log('Daily trade limit (' + maxTradesPerDay + ') reached - skipping ' + category + ' ' + dir);
ctx.state.lastTradedId = sigId;
return;
}
// Never stack or flip on top of an existing position - one trade at a time
if (ctx.strategy.position.side !== 'flat') {
ctx.log('Skipping signal - already in a ' + ctx.strategy.position.side + ' position');
ctx.state.lastTradedId = sigId;
return;
}
// == Execute the bracket order using the server's own entry/stop/target ====
// (not recomputed here - the server already did all the work of deciding
// where these levels should be, including ATR/expected-move sizing)
const tradeId = 'cnc_' + category + '_' + dir;
ctx.strategy.entry(tradeId, dir, {
qty: qty,
bracket: {
stopLoss: { price: stopPx },
takeProfit: [{ price: targetPx }],
},
});
if (useTrailing) {
const tickSize = ctx.syminfo.tickSize || 0.25;
ctx.strategy.setTrail(tradeId, {
trailOffset: Math.max(1, Math.round(trailOffsetPts / tickSize)),
unit: 'ticks',
});
}
ctx.state.lastTradedId = sigId;
ctx.log('CnC ' + category + ' ' + dir.toUpperCase() + ' entry @ ' + entryPx.toFixed(2) +
' | stop ' + stopPx.toFixed(2) + ' | target ' + targetPx.toFixed(2) +
' | score ' + score);
// == End of day flatten =====================================================
if (ctx.time.hour(last) >= 15 && ctx.time.minute(last) >= 55) {
if (ctx.strategy.position.side !== 'flat') {
ctx.strategy.flattenAll();
ctx.log('End of day flattening');
}
}
}