Present-value model & variance bounds: Price equals the discounted stream of expected future payouts. Under rational expectations, the variance of price can’t exceed the variance of the discounted-cash-flow process; Shiller showed stock prices are too volatile relative to this bound (“Do Stock Prices Move Too Much…”, AER 1981). American Economic Association
Campbell–Shiller log-linear identity: A log-linearization of the present-value model links the log dividend-price ratio to expected future returns and dividend growth, yielding testable decompositions widely used in asset-pricing. Stern School of Business
Key PDFs (authoritative):
Shiller (1981) AER — “Do Stock Prices Move Too Much to be Justified by Subsequent Changes in Dividends?” (classic variance-bounds test). American Economic Association
Campbell & Shiller (1988, RFS/NBER) — “The Dividend-Price Ratio and Expectations of Future Dividends and Discount Factors” (log-linear present-value relation). Stern School of Business
Nobel Prize 2013 press release (PDF) — award to Fama, Hansen, Shiller “for their empirical analysis of asset prices.” NobelPrize.org
Shiller’s Nobel Prize lecture (PDF): “Speculative Asset Prices.” NobelPrize.org
Nobel “Popular Science” background (PDF): “Trendspotting in asset markets.” NobelPrize.org
Code
// ============================================================================
// Shiller_EURUSD.c — Zorro / lite-C demonstration script (EUR/USD)
// ============================================================================
// GOAL
// -----
// This script illustrates two classic ideas often discussed in the
// 'variance bounds' / 'dividend–price ratio' (dp) literature:
//
// Part A) An *illustrative* check of "excess volatility":
// compare the variance of price P_t to a discounted-sum proxy P*_t
// constructed from a toy 'dividend' series D_t (here: carry proxy).
//
// Part B) A very simple *return predictability* example using the
// dividend-price ratio dp_t := log(D_t / P_t) to predict future
// K-day returns via a rolling univariate OLS slope.
//
// NOTES (Zorro / lite-C specifics)
// --------------------------------
// • This is a research demo; it trades with a minimal rule only to show how to
// translate a scalar signal into actions. It is *not* a strategy.
// • Positive series index in Zorro points to the PAST. Example: X[1] is
// the previous bar, X[K] is K bars ago; X[0] is the current bar.
// • `vars` are rolling series returned by `series(...)`; `var` is a scalar.
// • `asset("EUR/USD")` selects the EUR/USD symbol defined by your asset list.
// • `Lots` is set to 1 purely for a visible action in Part B.
//
// SAFETY / ROBUSTNESS
// -------------------
// • We clamp denominators with a small epsilon to avoid log(0) or division by 0.
// • Window sizes and horizons are explicitly checked against LookBack and Bar.
//
// VERSION
// -------
// Tested with Zorro 2.x / lite-C syntax.
// ============================================================================
function run()
{
// ------------------------------------------------------------------------
// SESSION / DATA SETTINGS
// ------------------------------------------------------------------------
BarPeriod = 1440; // 1440 minutes = 1 day bars
StartDate = 2010; // start year (use your data span)
LookBack = 600; // bars held in rolling series (must cover max windows)
asset("EUR/USD");
set(PLOTNOW); // auto-plot series as they are produced
// ------------------------------------------------------------------------
// PRICE SERIES
// ------------------------------------------------------------------------
// P_t: close price; R1: 1-bar (1-day) log return (not used later, kept for ref)
vars P = series(priceClose()); // P[0]=today, P[1]=yesterday, ...
vars R1 = series(log(P[0]/P[1])); // ln(P_t / P_{t-1})
// ------------------------------------------------------------------------
// "DIVIDEND" PROXY D_t (here: a constant carry series for demonstration)
// ------------------------------------------------------------------------
// In FX, a carry-like proxy could be the interest rate differential.
// Here we just set a constant daily carry to mimic ~1.5% per annum.
var eps = 1e-12; // small epsilon for safe divisions
var carryDaily = 0.015/252.; // ? 1.5% p.a. / 252 trading days
vars D = series(carryDaily); // D_t aligned with bars
// =========================================================================
// PART A: "Ex post" discounted-sum proxy P*_t for an excess-volatility check
// =========================================================================
// Construct a simple discounted sum of past D_t as a toy P*_t proxy:
// P*_t ? ?_{k=1..Kmax} D_{t-k} / (1+r_d)^k
// This is ONLY an illustration, not a proper present-value model.
int Kmax = 126; // look-back horizon (~6 months)
var r_d = 0.0001; // daily discount ? 0.01% (~2.5% p.a.)
vars Px = series(0); // rolling proxy P*_t
if (Bar > LookBack)
{
// Build discounted sum from *past* values of D (D[1]..D[Kmax])
var sumDisc = 0;
var disc = 1;
int k;
for (k=1; k<=Kmax; k++)
{
disc /= (1 + r_d); // (1+r)^(-k)
var Dp = D[k]; // D_{t-k}
sumDisc += disc * Dp;
}
Px[0] = sumDisc; // write current P*_t proxy
// Compare rolling variances of P and P* over a window W
int W = 500;
if (Bar > LookBack + Kmax + W)
{
// Means
var meanP = 0, meanPx = 0;
int i;
for (i=0; i<W; i++) { meanP += P[i]; meanPx += Px[i]; }
meanP /= (var)W;
meanPx /= (var)W;
// Sample variances
var varP = 0, varPx = 0;
for (i=0; i<W; i++) {
var a = P[i]-meanP;
var b = Px[i]-meanPx;
varP += a*a;
varPx += b*b;
}
varP /= (var)(W-1);
varPx /= (var)(W-1);
// Plots for visual inspection
plot("Var(P)", varP, NEW, 0);
plot("Var(P*)", varPx, 0, 0);
// Console line every ~50 bars
if (Bar%50==0)
printf("\n[EXCESS VOL] W=%d Var(P)=%.6g Var(P*)=%.6g ratio=%.3f",
W, varP, varPx, varP/(varPx+eps));
}
}
// =========================================================================
// PART B: Return predictability via the dividend-price ratio, dp_t
// =========================================================================
// We compute:
// dp_t := log(D_t / P_t)
// and regress *past* K-day realized returns on dp over a rolling window Wreg
// to estimate a slope 'beta'. A positive beta implies higher dp predicts
// higher future returns (in this toy setup).
//
// Then we convert the instantaneous dp z-score into a small long/short
// trade signal, clipped and scaled to the range [-0.5, +0.5] (Lev).
int K = 20; // horizon (~1 month)
vars DP = series(log(max(eps, D[0]) / max(eps, P[0]))); // dp_t series
vars RK = series(log(P[0]/P[K])); // realized K-day return
int Wreg = 500; // regression window
if (Bar > LookBack + K + Wreg)
{
// ----------------------------
// Rolling univariate OLS slope
// ----------------------------
var sumX=0, sumY=0, sumXX=0, sumXY=0;
int i;
for (i=0;i<Wreg;i++){
var x = DP[i]; // predictor
var y = RK[i]; // response (past K-day return)
sumX += x;
sumY += y;
sumXX += x*x;
sumXY += x*y;
}
var meanX = sumX/Wreg;
var meanY = sumY/Wreg;
var denom = sumXX - Wreg*meanX*meanX;
var beta = 0; // OLS slope
if (denom != 0)
beta = (sumXY - Wreg*meanX*meanY)/denom;
plot("beta(dp->Kret)", beta, NEW, 0);
// ----------------------------
// z-score of current dp_t
// ----------------------------
var meanDP=0, varDP=0;
for (i=0;i<Wreg;i++) meanDP += DP[i];
meanDP/=Wreg;
for (i=0;i<Wreg;i++){ var d=DP[i]-meanDP; varDP += d*d; }
varDP /= (Wreg-1);
var sDP = sqrt(max(eps,varDP));
var zDP = (DP[0]-meanDP)/sDP;
// Clip z to avoid huge outliers
var zClip = zDP;
if (zClip > 2) zClip = 2;
if (zClip < -2) zClip = -2;
// Direction follows beta sign
var sig = 0;
if (beta > 0) sig = zClip;
else if (beta < 0) sig = -zClip;
// ----------------------------
// POSITION TRANSLATION
// ----------------------------
// Map raw signal in [-2,+2] to leverage-like knob in [-1,+1],
// then cap at ±0.5 to keep actions small in the demo.
var Target = sig; // raw -2..+2
var MaxLev = 0.5; // clamp bound
var Lev = Target/2.0; // scale to -1..+1 then cap
if (Lev > MaxLev) Lev = MaxLev;
if (Lev < -MaxLev) Lev = -MaxLev;
// Minimal action rule:
// if Lev is meaningfully positive => long; if negative => short; else flat.
// We keep Lots=1 for visibility; no money management here.
if (Lev > 0.05) { exitShort(); enterLong(); Lots = 1; }
else if (Lev < -0.05) { exitLong(); enterShort(); Lots = 1; }
else { exitLong(); exitShort(); }
// Plots for monitoring
plot("z(dp)", zDP, 0, 0);
plot("lev", Lev, 0, 0);
}
// End of run() — Zorro handles bar stepping automatically.
}
the strategy encodes the Campbell–Shiller insight that high dividend/“carry” yields relative to price predict higher future returns, wraps that valuation in a z-scored ????????????, and lets a linear neural unit blend it with momentum/volatility states to produce directional edge; trades are taken when the learned edge clears a small threshold.
Code
// ============================================================================
// Shiller NN EUR/USD — ML trading demo (Zorro / lite-C)
//
// • Learner: PERCEPTRON (+BALANCED) on a compact 8-feature vector.
// • Trigger: edge = predL - predS with a simple RSI + momentum fallback.
// • Plots: (name, value, color, style); panels anchored correctly.
// ============================================================================
#include <default.c>
// ===== Configuration =====
#define LOOKBACK 200 // Bars required before running the model/logic
#define BarMins 1440 // Daily bars
#define LotsFixed 1 // Position size per entry
#define EdgeMin 0.05 // ML edge threshold (predL - predS) to trigger a trade
// ===== Safe helpers =====
var safe_div(var a, var b)
{
if (invalid(a) || invalid(b) || b == 0) return 0;
return a/b;
}
var safe_logratio(var a, var b)
{
if (invalid(a) || invalid(b) || b == 0) return 0;
var r = a/b;
if (invalid(r) || r <= 0) return 0;
return log(r);
}
// NOTE: plot(name, value, color, style)
// We'll pass color first, then style flags (e.g., LINE, NEW, MAIN, etc.)
void plot_safe(string name, var v, int color, int style)
{
if (!invalid(v) && v > -1e-12 && v < 1e12)
plot(name, v, color, style);
}
// ===== Main strategy =====
function run()
{
// Session (file-free; no RULES ? no rule files created/loaded)
StartDate = 2010;
EndDate = 2025;
BarPeriod = BarMins;
LookBack = LOOKBACK;
Capital = 10000;
set(PARAMETERS|PLOTNOW);
Hedge = 0;
asset("EUR/USD");
// Base series
vars PC = series(priceClose()); // Close series for returns/momentum
vars PX = series(price()); // Price series for RSI
// Derived series (allocated once, updated every bar)
static vars R1; if (!R1) R1 = series(0); // 1-bar log return
static vars MOM5; if (!MOM5) MOM5 = series(0); // 5-bar momentum
static vars MOM20; if (!MOM20) MOM20 = series(0); // 20-bar momentum
static vars DP; if (!DP) DP = series(0); // dp proxy = log(D/P)
if (Bar > 0) R1[0] = safe_logratio(PC[0], PC[1]); else R1[0] = 0;
if (Bar > 5) MOM5[0] = safe_logratio(PC[0], PC[5]); else MOM5[0] = 0;
if (Bar > 20) MOM20[0] = safe_logratio(PC[0], PC[20]); else MOM20[0] = 0;
// Constant carry vs price ? dp_t = log(D_t / P_t) (simple demonstration proxy)
var carryDaily = 0.015/252.; // ~1.5% p.a.
if (Bar > 0) DP[0] = safe_logratio(carryDaily, PC[0]); else DP[0] = 0;
// Indicators with warmup guards
var vola = 0; if (Bar >= 21) vola = StdDev(R1, 20);
var atr20 = 0; if (Bar >= 21) atr20 = ATR(20);
var rsi14 = 50; if (Bar >= 15) rsi14 = RSI(PX, 14);
// dp z-score (window Wz)
var zDP = 0; int Wz = 500;
if (Bar >= Wz) {
int i; var mean = 0, s2 = 0;
for (i = 0; i < Wz; i++) mean += DP[i];
mean /= (var)Wz;
for (i = 0; i < Wz; i++) { var d = DP[i] - mean; s2 += d*d; }
var sd = sqrt(max(1e-12, s2/(Wz - 1)));
if (sd > 0) {
zDP = (DP[0] - mean) / sd;
if (zDP > 10) zDP = 10;
if (zDP < -10) zDP = -10;
}
}
if (Bar < LOOKBACK) {
// Anchor price panel early so the chart opens immediately
plot_safe("Close", priceClose(), 0, MAIN|LINE);
return;
}
// Feature vector (?20)
var F[8];
F[0] = zDP;
F[1] = MOM5[0];
F[2] = MOM20[0];
F[3] = vola;
F[4] = rsi14/100.;
F[5] = safe_div(atr20, PC[0]);
F[6] = R1[0];
F[7] = safe_logratio(PC[0], PC[10]);
// Built-in ML (file-free: no RULES)
var predL = adviseLong (PERCEPTRON + BALANCED, 0, F, 8);
var predS = adviseShort(PERCEPTRON + BALANCED, 0, F, 8);
// Trading logic: edge trigger with simple fallback
var edge = predL - predS;
Lots = LotsFixed;
int didTrade = 0;
if (!invalid(edge) && edge > EdgeMin) {
exitShort(); enterLong(); didTrade = 1;
} else if (!invalid(edge) && edge < -EdgeMin) {
exitLong(); enterShort(); didTrade = 1;
}
if (!didTrade) {
if (rsi14 > 55 && MOM5[0] > 0) { exitShort(); enterLong(); didTrade = 1; }
else if (rsi14 < 45 && MOM5[0] < 0) { exitLong(); enterShort(); didTrade = 1; }
else { exitLong(); exitShort(); }
}
// ===== Plots (color, style) =====
// Price on the main chart
plot_safe("Close", priceClose(), 0, MAIN|LINE);
// Open a NEW indicator panel with 'edge', then add other lines to the same panel
plot_safe("edge", edge, 0, NEW|LINE); // NEW opens the panel
plot_safe("predL", predL, 0, LINE);
plot_safe("predS", predS, 0, LINE);
plot_safe("RSI", rsi14, 0, LINE);
plot_safe("z(dp)", zDP, 0, LINE);
}
A walkford version :
Code
// ============================================================================
// BLA03x10_WFO_train_test.c
// EUR/USD — Single-script ML trading with explicit Train/Test separation.
// Zorro / lite-C
//
// • Train run (click “Train”): writes per-cycle RULE files (no TESTNOW).
// • Test/Trade run (click “Test” or “Trade”): loads the RULE files created in Train.
// • Learner: PERCEPTRON + BALANCED
// • WFO supported via NumWFOCycles + DataHorizon (same in Train & Test).
// • Consistent asset() + algo() + advise* signature in both phases.
// • No ternary operator; guarded indicators; compact 8-feature vector.
// ============================================================================
#include <default.c>
// ===== Switches / Knobs =====
#define USE_WFO 1 // 0 = single IS block; 1 = Walk-Forward (IS/OOS cycles)
#define WFO_CYCLES 6 // number of WFO cycles (Train & Test must match)
#define WFO_OOS_BARS 252 // OOS length per cycle in bars (daily ? 1y)
#define LOOKBACK 200 // warmup bars before any model/logic
#define BarMins 1440 // 1D bars
#define LotsFixed 1 // fixed position size
#define EdgeMin 0.05 // edge = predL - predS threshold
// ===== Safe helpers =====
var safe_div(var a,var b){ if(invalid(a)||invalid(b)||b==0) return 0; return a/b; }
var safe_logratio(var a,var b){
if(invalid(a)||invalid(b)||b==0) return 0;
var r=a/b; if(invalid(r)||r<=0) return 0; return log(r);
}
// plot(name, value, color, style)
void plot_safe(string n, var v, int c, int s){ if(!invalid(v)&&v>-1e12&&v<1e12) plot(n,v,c,s); }
// ===== Main =====
function run()
{
// -------- Session (identical in Train and Test except RULES/Hedge flags) --------
StartDate = 2010;
EndDate = 2025;
BarPeriod = BarMins;
LookBack = LOOKBACK;
Capital = 10000;
if(USE_WFO){
NumWFOCycles = WFO_CYCLES;
DataHorizon = WFO_OOS_BARS; // OOS size per cycle
} else {
NumWFOCycles = 1;
DataHorizon = 0;
}
set(PARAMETERS|PLOTNOW); // common flags
// Explicit Train/Test separation (no TESTNOW auto phase switching)
if(Train){
set(RULES); // write RULE files during Train only
Hedge = 2; // generate both L/S samples while training
} else {
Hedge = 0; // Test/Trade loads RULE files, no writing
}
asset("EUR/USD");
algo("dp"); // part of RULE key: <Asset>:<Algo>:<L|S>
// -------- Base series --------
vars PC = series(priceClose()); // close for returns/momentum
vars PX = series(price()); // price for RSI
// -------- Derived series (allocated once, updated every bar) --------
static vars R1; if(!R1) R1 = series(0); // 1-bar log return
static vars MOM5; if(!MOM5) MOM5 = series(0); // 5-bar log momentum
static vars MOM20; if(!MOM20) MOM20 = series(0); // 20-bar log momentum
static vars DP; if(!DP) DP = series(0); // dp proxy = log(D/P)
if(Bar > 0) R1[0] = safe_logratio(PC[0],PC[1]); else R1[0] = 0;
if(Bar > 5) MOM5[0] = safe_logratio(PC[0],PC[5]); else MOM5[0] = 0;
if(Bar > 20) MOM20[0] = safe_logratio(PC[0],PC[20]); else MOM20[0] = 0;
var carryDaily = 0.015/252.;
if(Bar > 0) DP[0] = safe_logratio(carryDaily, PC[0]); else DP[0] = 0;
// -------- Indicators with guards --------
var vola = 0; if(Bar >= 21) vola = StdDev(R1,20);
var atr20 = 0; if(Bar >= 21) atr20 = ATR(20);
var rsi14 = 50; if(Bar >= 15) rsi14 = RSI(PX,14);
// -------- dp z-score (window Wz) --------
var zDP = 0; int Wz = 500;
if(Bar >= Wz){
int i; var mean=0, s2=0;
for(i=0;i<Wz;i++) mean += DP[i];
mean /= (var)Wz;
for(i=0;i<Wz;i++){ var d = DP[i]-mean; s2 += d*d; }
var sd = sqrt(max(1e-12, s2/(Wz-1)));
if(sd > 0){
zDP = (DP[0]-mean)/sd;
if(zDP > 10) zDP = 10;
if(zDP < -10) zDP = -10;
}
}
// Anchor main panel early
if(Bar < LOOKBACK){
plot_safe("Close", priceClose(), BLACK, MAIN|LINE);
return;
}
// -------- Feature vector (? 20) --------
var F[8];
F[0] = zDP;
F[1] = MOM5[0];
F[2] = MOM20[0];
F[3] = vola;
F[4] = rsi14/100.;
F[5] = safe_div(atr20, PC[0]);
F[6] = R1[0];
F[7] = safe_logratio(PC[0], PC[10]);
// -------- Built-in ML calls (IDENTICAL in Train & Test) --------
// Important: same method flags, same slot number, same feature length & order,
// same asset() and algo() ? ensures matching rule keys and file names.
var predL = adviseLong (PERCEPTRON + BALANCED, 0, F, 8);
var predS = adviseShort(PERCEPTRON + BALANCED, 0, F, 8);
// -------- Execution logic (common) --------
var edge = predL - predS;
Lots = LotsFixed;
int didTrade = 0;
if(!invalid(edge) && edge > EdgeMin){ exitShort(); enterLong(); didTrade = 1; }
else if(!invalid(edge) && edge < -EdgeMin){ exitLong(); enterShort(); didTrade = 1; }
if(!didTrade){
if(rsi14 > 55 && MOM5[0] > 0) { exitShort(); enterLong(); didTrade = 1; }
else if(rsi14 < 45 && MOM5[0] < 0) { exitLong(); enterShort(); didTrade = 1; }
else { exitLong(); exitShort(); }
}
// -------- Plots --------
plot_safe("Close", priceClose(), BLACK, MAIN|LINE);
plot_safe("edge", edge, GREEN, NEW|LINE);
plot_safe("predL", predL, BLUE, LINE);
plot_safe("predS", predS, RED, LINE);
plot_safe("RSI", rsi14, 0x404040, LINE);
plot_safe("z(dp)", zDP, 0x800080, LINE);
}
I have a question for all my Dear fellow programmers, If you had a book of one single strategy, 500 pages, explaining with detailed descriptions how to develop and come about complex strategies, how much would you be willing to pay for a copy of the book?
Thank you for your attention, and thoughtful consideration?
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;
}