Docs / Strategies

Strategies

A Strategy script runs on every live price tick with full access to the trade execution API, position management, and sub-indicators calculated in real time.


How Strategies Work

1Init() → Load sub-indicators
2Bar update → proxy.calculate() runs
3Tick() → reads proxy outputs, executes trades

Strategy A — Internal Math

All indicator logic written in proxy .calculate() blocks. Bollinger Bands mean-reversion grid with progressive lot scaling and trailing stop.

PowerScaler.tbcTDSL
about { author: "CoreXTrader"; version: "1.2.0"; }

parameters {
  int    period = 20; double multiplier = 2.0;
  double Lot = 0.01; int MaxEntries = 5;
  int    TrailStart = 200; int TrailStep = 50;
}
  display {
        outputs: 3 
        bb.lower { type: "line"; color: "#50c426ff"; name: "Lower Band"; width: 1; }; 
        bb.upper { type: "line"; color: "#c42626ff"; name: "Upper Band"; width: 1; }; 
        bb.middle { type: "line"; color: "#b0aaaaff"; name: "Middle Band"; width: 0.5; };                   
        fill { buffer_a: 0; buffer_b: 1; color: "rgba(19, 75, 228, 0.66)"; };
        fill { buffer_a: 1; buffer_b: 2; color: "rgba(0, 189, 31, 0.72)"; };
    }

struct Bands { double upper; double middle; double lower; }

Init() { 
    Load(bb); 
}

bb.calculate() {
  double closes[] = close();
  int len = candle_count();
  int start = Chart.resume_index > 0 ? Chart.resume_index - 1 : period - 1;
  series double up[]; series double mid[]; series double lo[];
  up[] = create_array(len); mid[] = create_array(len); lo[] = create_array(len);
  for (int i = start; i < len; i = i + 1) {
    double sum = 0.0;
    for (int j = 0; j < period; j = j + 1) { sum = sum + closes[i-j]; }
    double mean = sum / period;
    mid[i] = mean;
    double sq = 0.0;
    for (int j = 0; j < period; j = j + 1) { double d = closes[i-j] - mean; sq = sq + (d*d); }
    double std_dev = Math.Sqrt(sq / period);
    up[i] = mean + (multiplier * std_dev);
    lo[i] = mean - (multiplier * std_dev);
  }
  return Bands { upper: up, middle: mid, lower: lo };
}

int TotalPositions(int side) {
  int count = 0;
  for (int i = 0; i < Position.Count(); i = i + 1) {
    Position.SetIndex(i);
    if (Position.Direction() == side) { count = count + 1; }
  }
  return count;
}

Tick() {
  double upper  = bb.upper(0); double middle = bb.middle(0); double lower = bb.lower(0);
  double bid = Symbol.Bid(); double ask = Symbol.Ask(); string sym = Symbol();
  double point = Symbol.Point();

  // 1. Progressive lot scaling (0.01, 0.02, 0.03...)
  double next_lot_long  = Symbol.NormalizeVolume(Lot * (1 + TotalPositions(Side.Long)));
  double next_lot_short = Symbol.NormalizeVolume(Lot * (1 + TotalPositions(Side.Short)));

  if (ask < lower  && TotalPositions(Side.Long)  < MaxEntries) { Trade.Buy(sym,  next_lot_long); }
  if (bid > upper  && TotalPositions(Side.Short) < MaxEntries) { Trade.Sell(sym, next_lot_short); }
  
  if (bid > middle && TotalPositions(Side.Long)  > 0) { Trade.CloseAll(sym, Side.Long); }
  if (ask < middle && TotalPositions(Side.Short) > 0) { Trade.CloseAll(sym, Side.Short); }

  // 2. Trailing Stop
  for (int i = 0; i < Position.Count(); i = i + 1) {
    Position.SetIndex(i);
    if (Position.Symbol() == sym) {
      int ticket = Position.Ticket(); 
      double sl = Position.SL();

      if (Position.Direction() == Side.Long && (bid - Position.OpenPrice()) > TrailStart * point) {
        double new_sl = Symbol.NormalizePrice(bid - TrailStep * point);
        if (new_sl > sl) { 
        Trade.Modify(ticket, sym, new_sl, Position.TP());
         }
      }
      if (Position.Direction() == Side.Short && (Position.OpenPrice() - ask) > TrailStart * point) {
        double new_sl = Symbol.NormalizePrice(ask + TrailStep * point);
        if (new_sl < sl || sl == 0.0) { 
        Trade.Modify(ticket, sym, new_sl, Position.TP()); 
        }
      }
    }
  }
}

Strategy B — Native Engine Indicators

Bind native indicator engines in Init() for maximum performance. RSI + MACD trend-following example.

TrendFilter.tbc — Native RSI + MACDTDSL
parameters {
  int rsi_period = 14; double rsi_oversold = 30.0; double rsi_overbought = 70.0;
  int macd_fast = 12; int macd_slow = 26; int macd_signal = 9;
  double lot = 0.1; int sl_pips = 150; int tp_pips = 300;
}
  display {
    outputs: 4
    NativeRSI.value      { type: "line"; color: "#3b82f6"; name: "RSI"; separate_window: true; };
    NativeMACD.line      { type: "line"; color: "#10b981"; name: "MACD Line"; separate_window: true; };
    NativeMACD.signal    { type: "line"; color: "#ef4444"; name: "Signal"; };
    NativeMACD.histogram { type: "histogram"; color: "#9ca3af"; name: "Histogram"; };
  }

struct RSIResult  { double value; }
struct MACDResult { double line; double signal; double histogram; }

Init() {
  Native.Source(i.RSI);  Native.Init(Symbol(), Timeframe);
  Native.Parameter(rsi_period, Price.Close); Native.Bind(NativeRSI);

  Native.Source(i.MACD); Native.Init(Symbol(), Timeframe);
  Native.Parameter(macd_fast, macd_slow, macd_signal, Price.Close); Native.Bind(NativeMACD);
}

NativeRSI.calculate() {
  int len = candle_count();
  series double rsi_buf[]; rsi_buf[] = create_array(len);
  Native.Fetch(0, rsi_buf);
  return RSIResult { value: rsi_buf };
}

NativeMACD.calculate() {
  int len = candle_count();
  series double ml[]; series double ms[]; series double mh[];
  ml[] = create_array(len); ms[] = create_array(len); mh[] = create_array(len);
  Native.Fetch(0, ml); Native.Fetch(1, ms); Native.Fetch(2, mh);
  return MACDResult { line: ml, signal: ms, histogram: mh };
}

Tick() {
  double rsi_now = NativeRSI.value(0); double rsi_prev = NativeRSI.value(1);
  double hist_now = NativeMACD.histogram(0); double hist_prev = NativeMACD.histogram(1);
  string sym = Symbol(); double point = Symbol.Point();
  double ask = Symbol.Ask(); double bid = Symbol.Bid();

  if (Position.Count() > 0) { return; }

  if (rsi_prev < rsi_oversold && rsi_now >= rsi_oversold && hist_prev < 0.0 && hist_now >= 0.0) {
    Trade.Buy(sym, lot, Symbol.NormalizePrice(ask - sl_pips*point), Symbol.NormalizePrice(ask + tp_pips*point));
  }
  if (rsi_prev > rsi_overbought && rsi_now <= rsi_overbought && hist_prev > 0.0 && hist_now <= 0.0) {
    Trade.Sell(sym, lot, Symbol.NormalizePrice(bid + sl_pips*point), Symbol.NormalizePrice(bid - tp_pips*point));
  }
}

Strategy C — Custom External Source

BandRider.tbc — Custom external indicatorTDSL
parameters { int Bands_period = 20; double Bands_mult = 2.0; double lot = 0.1; int sl_pips = 100; int tp_pips = 200; int magic = 77001; }
  display {
    outputs: 2
    Bands.upper { type: "line"; color: "#dc2626"; name: "Upper Band"; };
    Bands.lower { type: "line"; color: "#16a34a"; name: "Lower Band"; };
  }

struct BandSignal { double upper; double lower; double buy_signal; double sell_signal; }

Init() {
  Custom.Source("Bands Arrow.tbc"); Custom.Init(Symbol(), Timeframe);
  Custom.Parameter(Bands_period, Bands_mult); Custom.Bind(Bands);
}

Bands.calculate() {
  int len = candle_count();
  series double up[]; series double lo[]; series double bs[]; series double ss[];
  up[] = create_array(len); lo[] = create_array(len); 
  bs[] = create_array(len); ss[] = create_array(len);
  Custom.Fetch(0, up); 
  Custom.Fetch(1, lo); 
  Custom.Fetch(2, bs); 
  Custom.Fetch(3, ss);
  return BandSignal { upper: up, lower: lo, buy_signal: bs, sell_signal: ss };
}

Tick() {
  double upper = Bands.upper(0); double lower = Bands.lower(0);
  double buy_sig = Bands.buy_signal(0); double sell_sig = Bands.sell_signal(0);
  double point = Symbol.Point(); string sym = Symbol();

  if (buy_sig > 0.0 || sell_sig > 0.0) {
    Trade.CancelAll(sym, Side.Long, magic); Trade.CancelAll(sym, Side.Short, magic);
  }
  if (buy_sig > 0.0) {
    double entry = Symbol.NormalizePrice(upper + 2.0 * point);
    Trade.BuyStop(sym, lot, entry, entry - sl_pips*point, entry + tp_pips*point, "BandRider Buy", magic);
  }
  if (sell_sig > 0.0) {
    double entry = Symbol.NormalizePrice(lower - 2.0 * point);
    Trade.SellStop(sym, lot, entry, entry + sl_pips*point, entry - tp_pips*point, "BandRider Sell", magic);
  }
}

Strategy D — Hybrid Extension

Fetch data from native engines or external scripts, then perform custom secondary calculations (e.g. Bollinger Bands derived from a Native EMA).

HybridOrchestrator.tbcTDSL
parameters {
  int ema_period = 50; double multiplier = 2.0; double lot = 0.1;
}
  display {
    outputs: 3
    MySystem.upper { type: "line"; color: "#dc2626"; name: "Upper (Hybrid)"; };
    MySystem.mid   { type: "line"; color: "#3b82f6"; name: "EMA Base"; };
    MySystem.lower { type: "line"; color: "#16a34a"; name: "Lower (Hybrid)"; };
  }

struct HybridResult { double upper; double mid; double lower; }

Init() {
  // 1. Bind Native Engine (EMA)
  Native.Source(i.EMA); Native.Init(Symbol(), Timeframe);
  Native.Parameter(ema_period, Price.Close);
  Native.Bind(MySystem);

  // 2. Bind Custom External Script (Volatility)
  Custom.Source("Keltner Vol.tbc"); Custom.Init(Symbol(), Timeframe);
  Custom.Parameter(20, 1.5);
  Custom.Bind(MySystem);
}

MySystem.calculate() {
  int len = candle_count();
  series double ema_buf[]; series double vol_buf[];
  series double up[]; series double lo[];
  
  ema_buf[] = create_array(len); vol_buf[] = create_array(len);
  up[] = create_array(len); lo[] = create_array(len);

  // Fetch from BOTH Native and Custom sources
  Native.Fetch(0, ema_buf);
  Custom.Fetch(0, vol_buf);

  // Combine values for secondary proprietary math
  for (int i = Chart.resume_index; i < len; i++) {
    up[i] = ema_buf[i] + (multiplier * vol_buf[i]);
    lo[i] = ema_buf[i] - (multiplier * vol_buf[i]);
  }
  return HybridResult { upper: up, mid: ema_buf, lower: lo };
}

Tick() {
  if (Symbol.Ask() < MySystem.lower(0)) { Trade.Buy(Symbol(), lot); }
  if (Symbol.Bid() > MySystem.upper(0)) { Trade.CloseAll(Symbol(), Side.Long); }
}

Advanced Management Patterns

Use these patterns to filter positions by magic number, exclude specific symbols, or implement complex scaling logic using continue; and logic operators.

Filtering & Management HelpersTDSL
// 1. Trailing Stop that excludes a specific symbol
void TrailingExcept(string ignore_sym, int pips) {
  double pt = Symbol.Point();
  for (int i = 0; i < Position.Count(); i = i + 1) {
    Position.SetIndex(i);
    if (Position.Symbol() == ignore_sym) { continue; } // Skip this symbol
    
    int tkt = Position.Ticket(); double sl = Position.SL();
    if (Position.Direction() == Side.Long && (Symbol.Bid() - Position.OpenPrice()) > pips * pt) {
      double nsl = Symbol.NormalizePrice(Symbol.Bid() - (pips/2) * pt);
      if (nsl > sl) { Trade.Modify(tkt, Position.Symbol(), nsl, Position.TP()); }
    }
  }
}

// 2. Close only positions with a specific Magic Number
void CloseByMagic(int target_magic) {
  for (int i = Position.Count() - 1; i >= 0; i = i - 1) {
    Position.SetIndex(i);
    if (Position.Magic() == target_magic) {
      Trade.Close(Position.Ticket());
    }
  }
}

// 3. Count positions excluding a specific Magic Number
int CountExternal(int my_magic) {
  int count = 0;
  for (int i = 0; i < Position.Count(); i = i + 1) {
    Position.SetIndex(i);
    if (Position.Magic() != my_magic) { count = count + 1; }
  }
  return count;
}

// 4. Calculate total profit for a specific Symbol + Magic pair
double GetNetProfit(string sym, int magic) {
  double total = 0.0;
  for (int i = 0; i < Position.Count(); i = i + 1) {
    Position.SetIndex(i);
    if (Position.Symbol() == sym && Position.Magic() == magic) {
      total = total + Position.Profit();
    }
  }
  return total;
}
Utility PatternsTDSL
// Simple New Bar detection
bool is_new_bar() {
  static datetime last = 0; datetime cur = time(0);
  if (last != cur) { last = cur; return true; } return false;
}

// Check if we are within a trading window
bool is_trade_allowed() {
  if (Env.IsBacktest()) { return true; } // Always trade in backtest
  return Time.InSession("London") || Time.InSession("NewYork");
}

// Normalized Lot Calculation
double GetRiskLot(double risk_pct, int sl_pips) {
  double risk_val = Account.Balance() * (risk_pct / 100.0);
  double lot = risk_val / (sl_pips * Symbol.Point() * Symbol.TickValue());
  return Symbol.NormalizeVolume(lot);
}