// ======================================================
// 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;
}