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');
    }
  }
}