This script is a Zorro trading strategy that trades EUR/USD on 5-minute bars using the Parabolic SAR for direction and an optional momentum filter:


It optimizes five core parameters: SAR_STEP (SAR sensitivity), DRAW_BEFORE_SL_POINTS (how far price must move against you before a delayed stop is placed),
STOP_POINTS (fixed stop-loss in pips), TAKEPROFIT_POINTS (fixed take-profit in pips),MAX_HOLD_BARS (maximum number of bars a trade may stay open).

On each bar it:

Computes SAR and recent price movement. Generates long/short signals based on whether SAR is below or above price, refined by momentum and basic filters (spread, session, news stubs).
Closes trades either when an opposite SAR signal appears or when they exceed MAX_HOLD_BARS.
Opens 1-lot market orders (no position sizing logic yet) with the optimized SL/TP and uses SarTMF to apply a delayed stop-loss once the trade has moved against you by a configurable amount.
It also logs detailed per-bar info to GoldSarDebug.csv for debugging and analysis.

With a little creative mind this one can improve alot.


Code
// ======================================================
//  SAR MOMENTUM BOT
//  SAR direction entries, optional momentum filter,
//  + optimizable SAR step / delayed SL / StopLoss / TakeProfit / Max holding time
// ======================================================

#include <default.c>
#include <stdio.h>   // for file-based debug logging

//------------------------------------------------------//
//  USER PARAMETERS (defaults – some are overwritten by optimize())
//------------------------------------------------------//

// Bar timeframe for SAR etc. (minutes)
int      TRADE_TIMEFRAME      = 5;       // M5

// Parabolic SAR params (Zorro SAR(Step,Min,Max))
var      SAR_STEP             = 0.02;
var      SAR_MAX              = 0.20;
var      SAR_MIN              = 0.02;    // usually same as step

// Distance from price for pending order (in pips)
int      SAR_BUFFER_POINTS    = 25;

// Trading session
int      USE_GMT_TIME         = 1;       // (unused – hour(0) is used)
int      SESSION_START_HOUR   = 7;       // 0..23
int      SESSION_END_HOUR     = 21;      // 0..23

// News filter (stub – always off unless implemented)
int      USE_NEWS_FILTER      = 0;

// Lot size modes
#define LOT_FIXED_LOT         0
#define LOT_BALANCE_PERCENT   1
#define LOT_EQUITY_PERCENT    2
#define LOT_FIXED_RISK_MONEY  3

int      LotMode            = LOT_BALANCE_PERCENT;
var      FIXED_LOT_SIZE     = 0.10;
var      RISK_PERCENT       = 0.5;     // for BALANCE/EQUITY
var      FIXED_RISK_MONEY   = 50.0;    // account currency

// Momentum filter window (seconds)
int      ACTIVATE_WINDOW_SEC  = 900;   // default ~15 minutes
int      ACTIVATE_MOVE_POINTS = 10;    // min move in window (pips)

// Pending expiry / staleness windows (seconds)
int      PENDING_EXPIRE_SEC   = 30;   // hard life of pending (unused with market)
int      STALE_WINDOW_SEC     = 15;   // after this, require min move
int      STALE_MOVE_POINTS    = 30;   // else cancel

// Delayed stop-loss (used only by TMF when Stop==0)
int      DRAW_BEFORE_SL_POINTS  = 60;  // move against trade before SL is drawn
int      EXTRA_SL_BUFFER_POINTS = 25;  // SL extra buffer beyond current price

// Concurrency & spread
int      MAX_CONCURRENT_ORDERS  = 1;   // informational only
var      MAX_SPREAD_POINTS      = 20;  // in pips

// Explicit exits (in pips, will be converted to price units by *PIP)
// THESE THREE + SAR_STEP + DRAW_BEFORE_SL_POINTS ARE OPTIMIZED
int      STOP_POINTS            = 40;   // fixed SL in pips
int      TAKEPROFIT_POINTS      = 60;   // fixed TP in pips
int      MAX_HOLD_BARS          = 72;   // max bars a trade can stay open

//------------------------------------------------------//
//  INTERNAL STATE
//------------------------------------------------------//

#define HISTORY_SIZE 200

var   PriceHistory[HISTORY_SIZE];   // tick Bid approximation
var   TimeHistory[HISTORY_SIZE];    // windows DATE stamps
var   SpreadHistory[HISTORY_SIZE];  // spread in pips

// Last SAR value (updated per bar in run())
var   SarValue = 0;

// Optional status text for UI / debugging
string RestrictionReason = "";

// small debug switch (set to 1 if you want printf logs)
int    DEBUG_LOG = 0;

// debug counter: how many times entry conditions were true
int    EntrySignalCount = 0;

// helper switches for backtest
int    RELAX_FILTERS_FOR_BACKTEST      = 1;  // 1 = no spread/session/momentum/news blocking
int    USE_MARKET_ORDERS_FOR_BACKTEST  = 1;  // 1 = Entry=0 (market), 0 = pending stops

// file-based debug logging
FILE*  DebugFile = 0;       // will write GoldSarDebug.csv

//------------------------------------------------------//
//  FORWARD DECLARATIONS
//------------------------------------------------------//

void  ShiftHistoryArrays();
int   IsWithinTradingSession();
int   IsNewsTime();
void  ComputeRecentMove(var nowDate, int windowSec, var *pMaxUp, var *pMaxDown);
int   CountAllOrdersForThisEA();
int   NotExistingPendingBuyStop();
int   NotExistingPendingSellStop();
var   ComputeLotSize(int mode, var riskPercent, var fixedRiskMoney, var fixedLot, int stopPoints);
var   NormalizeLot(var lot);

// Trade management function: delayed SL + pending expiry/staleness
int   SarTMF(var placementDate);

// we'll call tick() from run()
void  tick();

// debug logging helper
void  LogDebugState(var nowDate, var barPrice, var spreadPips,
                    var moveUp, var moveDown,
                    int sarBelowBar, int sarAboveBar,
                    int longSignal, int shortSignal,
                    var lotsLogged);

//------------------------------------------------------//
//  INIT & MAIN BAR LOOP
//------------------------------------------------------//

function run()
{
	// enable optimization & log file (same idea as the simple script)
	set(PARAMETERS + LOGFILE);

	// global test / bar settings
	BarPeriod = TRADE_TIMEFRAME;  // e.g. 5-minute bars
	StartDate = 2005;             // adjust as you like
	EndDate   = 0;
	LookBack  = 200;

	// select the asset exactly as in the simple script
	asset("EUR/USD");

	// one-time initialization stuff
	if (is(INITRUN)) 
	{
		// init histories
		int i;
		for (i = 0; i < HISTORY_SIZE; i++) {
			PriceHistory[i]  = 0;
			TimeHistory[i]   = 0;
			SpreadHistory[i] = 0;
		}

		RestrictionReason = "INIT";

		// open debug CSV (overwrite each run)
		DebugFile = fopen("GoldSarDebug.csv","w");
		if (DebugFile) {
			fprintf(DebugFile,"Bar;Date;Price;SAR;SpreadPips;MoveUp;MoveDown;");
			fprintf(DebugFile,"SarBelowBar;SarAboveBar;LongSignal;ShortSignal;TotalOrders;LotMode;Lots;RestrictionReason\n");
			fflush(DebugFile);
		}
	}

	// EXITRUN cleanup
	if (is(EXITRUN)) {
		if (DEBUG_LOG)
			printf("\nExitRun: EntrySignalCount = %d",EntrySignalCount);
		if (DebugFile) {
			fclose(DebugFile);
			DebugFile = 0;
		}
		return;
	}

	//--------------------------------------------------
	// OPTIMIZED PARAMETERS (5 params total)
	//  1) SAR_STEP              – SAR sensitivity
	//     (SAR_MIN follows it)
	//  2) DRAW_BEFORE_SL_POINTS – distance before delayed SL activates
	//  3) STOP_POINTS           – fixed SL in pips
	//  4) TAKEPROFIT_POINTS     – fixed TP in pips
	//  5) MAX_HOLD_BARS         – max bar age of trade
	//--------------------------------------------------

	// 1 – SAR step (0.01 .. 0.06, step 0.01)
	SAR_STEP = optimize(0.02, 0.01, 0.06, 0.01);
	SAR_MIN  = SAR_STEP;       // tie min to step
	// SAR_MAX stays fixed at 0.20

	// 2 – delayed SL activation distance (20..120 pips, step 10)
	DRAW_BEFORE_SL_POINTS = (int)optimize(60, 20, 120, 10);

	// 3 – Fixed SL in pips (20..120, step 10)
	STOP_POINTS = (int)optimize(40, 20, 120, 10);

	// 4 – Fixed TP in pips (20..240, step 20)
	TAKEPROFIT_POINTS = (int)optimize(60, 20, 240, 20);

	// 5 – Max holding time in bars (24..288, step 24)
	MAX_HOLD_BARS = (int)optimize(72, 24, 288, 24);

	//--------------------------------------------------
	// INDICATORS (bar-based)
	//--------------------------------------------------

	// Compute current SAR on bar data (one SAR series)
	SarValue = SAR(SAR_STEP, SAR_MIN, SAR_MAX);

	// Let tick() handle trade logic
	tick();
}

//------------------------------------------------------//
//  TICK-BASED MAIN LOGIC (per incoming bar)
//------------------------------------------------------//

function tick()
{
	asset("EUR/USD");

	// ------------------------------
	// Mid price and spread
	// ------------------------------
	var mid = price();        // bar price (close)

	// Zorro's Spread is in pips for FX
	var spreadPips  = Spread;          // pips
	var spreadPrice = spreadPips*PIP;  // convert to price units

	// Approximate Bid/Ask from mid and spread in price units
	var bid = mid - 0.5*spreadPrice;
	var ask = mid + 0.5*spreadPrice;

	// Also keep a direct "price" for SAR comparison
	var barPrice = mid;

	// Current time in DATE format
	var nowDate = wdate(0);   // current bar time in Windows DATE

	// Update history (always, even during warmup)
	ShiftHistoryArrays();
	PriceHistory[0]  = bid;
	TimeHistory[0]   = nowDate;
	SpreadHistory[0] = spreadPips;

	// momentum & signal variables
	var moveUp = 0, moveDown = 0;
	int sarBelowBar = 0, sarAboveBar = 0;
	int longSignal = 0, shortSignal = 0;

	// ------------------------------
	// WARMUP: fill history & SAR first
	// ------------------------------
	if (Bar < LookBack) {
		RestrictionReason = "Warming up history";
		LogDebugState(nowDate, barPrice, spreadPips,
			0, 0, 0, 0, 0, 0, 0);
		return;
	}

	// ------------------------------
	// TIME EXIT: close trades that are too old
	// ------------------------------
	if (MAX_HOLD_BARS > 0 && (NumOpenLong > 0 || NumOpenShort > 0)) {
		int needExit = 0;
		for (open_trades) {
			int ageBars = Bar - TradeBarOpen;
			if (ageBars >= MAX_HOLD_BARS) {
				needExit = 1;
				// IMPORTANT FIX: use break_trades instead of plain break
				break_trades;
			}
		}
		if (needExit) {
			exitLong();
			exitShort();
		}
	}

	// recalc orders after possible time exits
	int totalOrders = CountAllOrdersForThisEA();

	// precompute SAR vs bar price for logging & signals
	sarBelowBar = (SarValue < barPrice);
	sarAboveBar = (SarValue > barPrice);

	// ------------------------------
	// BASIC FILTERS: SPREAD / SESSION / NEWS
	// ------------------------------	
	if (!RELAX_FILTERS_FOR_BACKTEST && spreadPips > MAX_SPREAD_POINTS) {
		RestrictionReason = "Spread too high";
		LogDebugState(nowDate, barPrice, spreadPips,
			moveUp, moveDown, sarBelowBar, sarAboveBar, 0, 0, 0);
		return;
	}

	if (!RELAX_FILTERS_FOR_BACKTEST && !IsWithinTradingSession()) {
		RestrictionReason = "Outside trading session";
		LogDebugState(nowDate, barPrice, spreadPips,
			moveUp, moveDown, sarBelowBar, sarAboveBar, 0, 0, 0);
		return;
	}

	if (!RELAX_FILTERS_FOR_BACKTEST && USE_NEWS_FILTER && IsNewsTime()) {
		RestrictionReason = "News filter active";
		LogDebugState(nowDate, barPrice, spreadPips,
			moveUp, moveDown, sarBelowBar, sarAboveBar, 0, 0, 0);
		return;
	}

	RestrictionReason = "OK";

	// ------------------------------
	// MOMENTUM CHECK (bar-based)
	// ------------------------------
	ComputeRecentMove(nowDate, ACTIVATE_WINDOW_SEC, &moveUp, &moveDown);

	if (!RELAX_FILTERS_FOR_BACKTEST && ACTIVATE_MOVE_POINTS > 0) { 
		if (moveUp < ACTIVATE_MOVE_POINTS && fabs(moveDown) < ACTIVATE_MOVE_POINTS) {
			RestrictionReason = "Not enough momentum";
			LogDebugState(nowDate, barPrice, spreadPips,
				moveUp, moveDown, sarBelowBar, sarAboveBar, 0, 0, 0);
			return;
		}
	}

	// ------------------------------
	// SAR VALUE & TREND BIAS
	// ------------------------------
	if (SarValue == 0) {
		RestrictionReason = "SAR unavailable";
		LogDebugState(nowDate, barPrice, spreadPips,
			moveUp, moveDown, sarBelowBar, sarAboveBar, 0, 0, 0);
		return;
	}

	// ------------------------------
	// BUILD SIGNALS (LONG / SHORT)
	// ------------------------------
	longSignal  = sarBelowBar;
	shortSignal = sarAboveBar;

	if (!RELAX_FILTERS_FOR_BACKTEST && ACTIVATE_MOVE_POINTS > 0) {
		if (longSignal && moveUp < ACTIVATE_MOVE_POINTS)
			longSignal = 0;
		if (shortSignal && fabs(moveDown) < ACTIVATE_MOVE_POINTS)
			shortSignal = 0;
	}

	// ------------------------------
	// EXIT LOGIC: flip on opposite SAR signal
	// ------------------------------
	if (NumOpenLong > 0 && shortSignal)
		exitLong();
	if (NumOpenShort > 0 && longSignal)
		exitShort();

	// recalc after flip exits
	totalOrders = CountAllOrdersForThisEA();

	// ------------------------------
	// DEBUG: entry condition monitoring
	// ------------------------------
	if (longSignal) {
		EntrySignalCount++;
		if (DEBUG_LOG) {
			printf("\nBar %d LONG signal: moveUp=%.1f, SAR=%.5f, bid=%.5f, ask=%.5f, barPrice=%.5f",
				Bar, moveUp, SarValue, bid, ask, barPrice);
		}
	}
	if (shortSignal) {
		EntrySignalCount++;
		if (DEBUG_LOG) {
			printf("\nBar %d SHORT signal: moveDown=%.1f, SAR=%.5f, bid=%.5f, ask=%.5f, barPrice=%.5f",
				Bar, moveDown, SarValue, bid, ask, barPrice);
		}
	}

	// ------------------------------
	// PLACE BUY / SELL (market orders in backtest)
	// ------------------------------
	if (totalOrders == 0)
	{
		// Stop / TP in PRICE units
		Stop       = STOP_POINTS       * PIP;
		TakeProfit = TAKEPROFIT_POINTS * PIP;
		Trail      = 0;
		OrderDuration = PENDING_EXPIRE_SEC;

		// BUY
		if (longSignal) {
			int stopPointsB = DRAW_BEFORE_SL_POINTS + EXTRA_SL_BUFFER_POINTS;

			var lotB = 1;
			Lots  = lotB;

			if (USE_MARKET_ORDERS_FOR_BACKTEST)
				Entry = 0;
			else
				Entry = SAR_BUFFER_POINTS; // pips offset if you ever use pending

			if (DEBUG_LOG) {
				printf("\nBar %d  enterLong: Lots=%.2f EntryOffset=%g stopPoints=%d StopPips=%d TPpips=%d",
					Bar, Lots, (var)Entry, stopPointsB, STOP_POINTS, TAKEPROFIT_POINTS);
			}

			enterLong(SarTMF, nowDate);

			LogDebugState(nowDate, barPrice, spreadPips,
				moveUp, moveDown, sarBelowBar, sarAboveBar,
				longSignal, shortSignal, Lots);

			return;
		}

		// SELL
		if (shortSignal) {
			int stopPointsS = DRAW_BEFORE_SL_POINTS + EXTRA_SL_BUFFER_POINTS;

			var lotS = 1;
			Lots  = lotS;

			if (USE_MARKET_ORDERS_FOR_BACKTEST)
				Entry = 0;
			else
				Entry = SAR_BUFFER_POINTS;

			if (DEBUG_LOG) {
				printf("\nBar %d  enterShort: Lots=%.2f EntryOffset=%g stopPoints=%d StopPips=%d TPpips=%d",
					Bar, Lots, (var)Entry, stopPointsS, STOP_POINTS, TAKEPROFIT_POINTS);
			}

			enterShort(SarTMF, nowDate);

			LogDebugState(nowDate, barPrice, spreadPips,
				moveUp, moveDown, sarBelowBar, sarAboveBar,
				longSignal, shortSignal, Lots);

			return;
		}
	}

	// no trade placed on this bar -> log state
	LogDebugState(nowDate, barPrice, spreadPips,
		moveUp, moveDown, sarBelowBar, sarAboveBar,
		0, 0, 0);
}

//------------------------------------------------------//
//  DEBUG LOGGING HELPER
//------------------------------------------------------//

void LogDebugState(var nowDate, var barPrice, var spreadPips,
                   var moveUp, var moveDown,
                   int sarBelowBar, int sarAboveBar,
                   int longSignal, int shortSignal,
                   var lotsLogged)
{
	if (!DebugFile || Bar < LookBack)
		return;

	fprintf(DebugFile,
		"%d;%.6f;%.5f;%.5f;%.1f;%.1f;%.1f;%d;%d;%d;%d;%d;%d;%.2f;%s\n",
		Bar, nowDate, barPrice, SarValue, spreadPips,
		moveUp, moveDown,
		sarBelowBar, sarAboveBar,
		longSignal, shortSignal,
		CountAllOrdersForThisEA(),
		LotMode, lotsLogged,
		RestrictionReason
	);
	fflush(DebugFile);
}

//------------------------------------------------------//
//  HISTORY SHIFT
//------------------------------------------------------//

void ShiftHistoryArrays()
{
	int i;
	for (i = HISTORY_SIZE-1; i > 0; i--) {
		PriceHistory[i]  = PriceHistory[i-1];
		TimeHistory[i]   = TimeHistory[i-1];
		SpreadHistory[i] = SpreadHistory[i-1];
	}
}

//------------------------------------------------------//
//  SESSION FILTER (hour-based)
//------------------------------------------------------//

int IsWithinTradingSession()
{
	if (SESSION_START_HOUR == SESSION_END_HOUR)
		return 1;

	int hourNow = hour(0);   // bar/tick time in BarZone

	if (SESSION_START_HOUR < SESSION_END_HOUR) {
		if (hourNow >= SESSION_START_HOUR && hourNow < SESSION_END_HOUR)
			return 1;
		else
			return 0;
	} else {
		if (hourNow >= SESSION_START_HOUR || hourNow < SESSION_END_HOUR)
			return 1;
		else
			return 0;
	}
}

//------------------------------------------------------//
//  NEWS FILTER – STUB
//------------------------------------------------------//

int IsNewsTime()
{
	if (!USE_NEWS_FILTER)
		return 0;

	return 0;
}

//------------------------------------------------------//
//  RECENT PRICE MOVE (bar-based window derived from seconds)
//------------------------------------------------------//

void ComputeRecentMove(var nowDate, int windowSec, var *pMaxUp, var *pMaxDown)
{
	var maxUp   = 0;
	var maxDown = 0;

	var secondsPerBar = 60*TRADE_TIMEFRAME;      // e.g. 5min -> 300s
	int windowBars = (int)(windowSec / secondsPerBar);
	if (windowBars < 1) windowBars = 1;
	if (windowBars >= HISTORY_SIZE) windowBars = HISTORY_SIZE-1;

	var refPrice = PriceHistory[0];

	int i;
	for (i = 1; i <= windowBars; i++) {
		if (PriceHistory[i] == 0)
			continue;

		var deltaPoints = (refPrice - PriceHistory[i]) / PIP; // in pips

		if (deltaPoints > maxUp)
			maxUp = deltaPoints;
		if (deltaPoints < maxDown)
			maxDown = deltaPoints;
	}

	*pMaxUp   = maxUp;
	*pMaxDown = maxDown;
}

//------------------------------------------------------//
//  LOT SIZE CALCULATION (using PIPCost)
//------------------------------------------------------//

var ComputeLotSize(int mode,
                   var riskPercent,
                   var fixedRiskMoney,
                   var fixedLot,
                   int stopPoints)
{
	if (stopPoints <= 0) {
		return NormalizeLot(fixedLot);
	}

	var riskPerLotMoney = stopPoints * PIPCost;
	var lot;

	if (mode == LOT_FIXED_LOT) {
		lot = fixedLot;
	} 
	else if (mode == LOT_BALANCE_PERCENT) {
		var accBal = Balance;
		var maxRiskMoney = accBal * (riskPercent/100.);
		lot = maxRiskMoney / riskPerLotMoney;
	} 
	else if (mode == LOT_EQUITY_PERCENT) {
		var accEq  = Equity;
		var maxRiskMoney = accEq * (riskPercent/100.);
		lot = maxRiskMoney / riskPerLotMoney;
	} 
	else if (mode == LOT_FIXED_RISK_MONEY) {
		lot = fixedRiskMoney / riskPerLotMoney;
	} 
	else {
		lot = fixedLot;
	}

	return NormalizeLot(lot);
}

//------------------------------------------------------//
//  LOT NORMALIZATION – basic integer clipping
//------------------------------------------------------//

var NormalizeLot(var lot)
{
	if (lot <= 0)
		return 0;

	if (lot < 1)
		lot = 1;

	lot = floor(lot);
	return lot;
}

//------------------------------------------------------//
//  ORDER COUNT FOR THIS SCRIPT / ASSET
//------------------------------------------------------//

int CountAllOrdersForThisEA()
{
	return NumOpenLong + NumOpenShort + NumPendingLong + NumPendingShort;
}

//------------------------------------------------------//
//  EXISTING PENDING CHECKS
//------------------------------------------------------//

int NotExistingPendingBuyStop()
{
	if (NumPendingLong > 0)
		return 0;
	return 1;
}

int NotExistingPendingSellStop()
{
	if (NumPendingShort > 0)
		return 0;
	return 1;
}

//------------------------------------------------------//
//  TMF: MANAGE OPEN & PENDING TRADES
//------------------------------------------------------//

int SarTMF(var placementDate)
{
	var nowDate = wdate(0);
	var ageSec  = (nowDate - placementDate) * 24*60*60;

	// ---- Pending trades: expiry & staleness ----
	if (TradeIsPending) {
		// Could use ageSec, STALE_WINDOW_SEC, etc. if you
		// re-enable pending orders.
		return 0;
	}

	// ---- Open trades: delayed SL activation ----
	if (TradeIsOpen) {
		if (TradeStopLimit == 0.) {
			var curPrice = price();
			var lossPoints;

			if (TradeIsLong)
				lossPoints = (TradePriceOpen - curPrice) / PIP;
			else
				lossPoints = (curPrice - TradePriceOpen) / PIP;

			if (lossPoints >= DRAW_BEFORE_SL_POINTS) {
				if (TradeIsLong)
					TradeStopLimit = curPrice - EXTRA_SL_BUFFER_POINTS*PIP;
				else
					TradeStopLimit = curPrice + EXTRA_SL_BUFFER_POINTS*PIP;
			}
		}
	}

	return 0;
}

Last edited by TipmyPip; 4 hours ago.