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;
}
This script implements a basic trend-following trading strategy on the EUR/USD pair using 15-minute bars, enhanced with a custom linear regression slope calculation for precise trend detection. The price series is analyzed using a 20-period simple moving average and a 14-period RSI, which is normalized between 0 and 1 for threshold-based decision-making. Instead of relying on Zorro’s built-in slope()—which uses an undocumented, fixed internal period—the script defines a custom slopeN() function, allowing dynamic control over the regression window (in this case, 10 bars). This flexibility enables fine-tuning and optimization, which is crucial for adapting to varying market conditions. The strategy enters a long position when the price crosses above the moving average and the RSI indicates the market is not overbought. Risk controls are introduced using the ATR to calculate stop levels and clamp the trade volume, ensuring trades remain within defined bounds. The approach demonstrates how integrating custom statistical functions in lite-C enhances Zorro's built-in capabilities for more adaptable and data-driven trading logic.
Code
var slopeN(vars Data, int Period)
{
var SX = 0, SY = 0, SXY = 0, SXX = 0;
int i;
for(i = 0; i < Period; i++) {
SX += i;
SY += Data[i];
SXY += i*Data[i];
SXX += i*i;
}
var Nom = Period*SXY - SX*SY;
var Denom = Period*SXX - SX*SX;
if(Denom == 0.) return 0.;
return Nom / Denom;
}
function run()
{
set(PARAMETERS);
BarPeriod = 15;
StartDate = 20220101;
asset("EUR/USD");
vars Price = series(priceClose());
var sma = SMA(Price, 20);
var rsi = RSI(Price, 14);
var slopeVal = slopeN(Price, 10);
var trend = sign(slopeVal);
var risk = slider(1, 5000, 1000, 10000, "Risk", "Risk setting");
var atr = ATR(14);
var stopLevel = clamp(atr*2, 10, 50);
var normRSI = clamp(rsi/100, 0, 1);
var tradeVol = clamp(10000 / fix0(atr), 1, 5);
if(crossOver(Price, sma) && normRSI < 0.7)
enterLong();
plot("RSI", normRSI, NEW, RED);
}
This script implements a fully panel-driven EUR/USD trend-following strategy that uses a custom linear regression slope as its main trend filter and exposes all key parameters through a Zorro control panel. The LinearRegressionSlope function computes the slope of a regression line over a configurable number of bars (SlopePeriod), and this slope is used, together with a configurable SMA (SmaPeriod) and RSI (RsiPeriod), to assess trend and overbought/oversold conditions. Global parameters define whether trading is enabled, the risk per trade as a percentage of equity, ATR period and ATR-based stop multiplier, as well as minimum and maximum stop distances and position sizes. A click handler reacts to user edits in the panel: it toggles trading on/off, updates numeric parameters entered in the panel cells, and provides a “ResetEq” button to reset capital for demo/testing. In run(), all parameters are first sanitized with clamp() to keep them within sensible bounds, then indicators (SMA, RSI, ATR) and the regression slope are calculated, the slope is displayed in the panel, and position size is derived from risk percent, equity, and stop size. The panel is created once at INITRUN with labeled rows for each parameter and updated every bar for the live slope value, while the trading logic simply enters a long position when trading is enabled, price crosses above the SMA, and normalized RSI is below 0.7, plotting the normalized RSI for visual monitoring.
Code
//////////////////////////////////////////////////////////
// Linear regression slope with custom period
//////////////////////////////////////////////////////////
var LinearRegressionSlope(vars Data, int Period)
{
var sumX = 0;
var sumY = 0;
var sumXY = 0;
var sumX2 = 0;
int i;
for(i = 0; i < Period; i++) {
sumX += i;
sumY += Data[i];
sumXY += i * Data[i];
sumX2 += i * i;
}
var numerator = Period * sumXY - sumX * sumY;
var denominator = Period * sumX2 - sumX * sumX;
if(denominator != 0)
return numerator / denominator;
else
return 0;
}
//////////////////////////////////////////////////////////
// Global Parameters (editable via panel)
//////////////////////////////////////////////////////////
int EnableTrade = 1; // trading on/off
var InputRiskPct = 1.0; // risk per trade in percent
int SlopePeriod = 10; // bars used for regression slope
int SmaPeriod = 20; // SMA period
int RsiPeriod = 14; // RSI period
int AtrPeriod = 14; // ATR period
var StopATRMult = 2.0; // stop = ATR * this
var StopMin = 10; // min stop (price distance)
var StopMax = 50; // max stop
var PosMin = 1; // min position size
var PosMax = 5; // max position size
var DisplaySlope = 0; // for live slope display in the panel
//////////////////////////////////////////////////////////
// Click handler for panel interactions
//////////////////////////////////////////////////////////
function click(int row,int col)
{
string Text;
if(!is(RUNNING)) return; // ignore when script not running
if(row < 0) return; // ignore Result/Action/Asset events
Text = panelGet(row,col);
// Row / column mapping
// 0: Enable trading (button)
// 1: Risk %
// 2: Slope period
// 3: SMA period
// 4: RSI period
// 5: ATR period
// 6: Stop ATR mult
// 7: Stop min
// 8: Stop max
// 9: Pos min
// 10: Pos max
// 11: Reset equity button
if(row == 0 && col == 1) {
// Toggle trading
if(EnableTrade)
EnableTrade = 0;
else
EnableTrade = 1;
panelSet(0,1, ifelse(EnableTrade,"On","Off"),0,0,4);
}
else if(row == 1 && col == 1) {
InputRiskPct = atof(Text);
}
else if(row == 2 && col == 1) {
SlopePeriod = atoi(Text);
}
else if(row == 3 && col == 1) {
SmaPeriod = atoi(Text);
}
else if(row == 4 && col == 1) {
RsiPeriod = atoi(Text);
}
else if(row == 5 && col == 1) {
AtrPeriod = atoi(Text);
}
else if(row == 6 && col == 1) {
StopATRMult = atof(Text);
}
else if(row == 7 && col == 1) {
StopMin = atof(Text);
}
else if(row == 8 && col == 1) {
StopMax = atof(Text);
}
else if(row == 9 && col == 1) {
PosMin = atof(Text);
}
else if(row == 10 && col == 1) {
PosMax = atof(Text);
}
else if(row == 11 && col == 0) {
// Reset equity button
Capital = 100000; // demo reset
}
}
//////////////////////////////////////////////////////////
// Main strategy
//////////////////////////////////////////////////////////
function run()
{
vars Price;
var sma, rsi, slopeVal, trend, atr, stopLevel, normRSI;
var tradeRisk, posSize;
string txt;
set(PARAMETERS);
BarPeriod = 15;
StartDate = 20220101;
asset("EUR/USD");
// Clamp and sanitize parameter ranges to avoid nonsense
SlopePeriod = clamp(SlopePeriod, 5, 200);
SmaPeriod = clamp(SmaPeriod, 2, 200);
RsiPeriod = clamp(RsiPeriod, 2, 200);
AtrPeriod = clamp(AtrPeriod, 2, 200);
StopATRMult = clamp(StopATRMult, 0.5, 10);
StopMin = clamp(StopMin, 1, 500);
StopMax = clamp(StopMax, StopMin, 1000);
PosMin = clamp(PosMin, 0.01, 100);
PosMax = clamp(PosMax, PosMin, 1000);
InputRiskPct = clamp(InputRiskPct, 0.1, 20); // 0.1% .. 20%
Price = series(priceClose());
sma = SMA(Price, SmaPeriod);
rsi = RSI(Price, RsiPeriod);
slopeVal = LinearRegressionSlope(Price, SlopePeriod);
DisplaySlope = slopeVal;
trend = sign(slopeVal);
atr = ATR(AtrPeriod);
stopLevel = clamp(atr * StopATRMult, StopMin, StopMax);
normRSI = clamp(rsi / 100, 0, 1);
// Risk-based position sizing using percentage from panel
tradeRisk = InputRiskPct / 100 * Equity;
posSize = clamp(tradeRisk / fix0(stopLevel), PosMin, PosMax);
// Panel setup only once at init
if(is(INITRUN))
{
// 12 rows, 2 columns, default color (0), cell width 80 px
panel(12,2,0,80);
// Row 0: Enable trading button
panelSet(0,0,"Enable",0,0,1);
panelSet(0,1, ifelse(EnableTrade,"On","Off"),0,0,4);
// Row 1: Risk %
panelSet(1,0,"Risk %",0,0,1);
txt = strf("%.2f",InputRiskPct);
panelSet(1,1,txt,0,0,2);
// Row 2: Slope period
panelSet(2,0,"SlopePer",0,0,1);
txt = strf("%i",SlopePeriod);
panelSet(2,1,txt,0,0,2);
// Row 3: SMA period
panelSet(3,0,"SMA Per",0,0,1);
txt = strf("%i",SmaPeriod);
panelSet(3,1,txt,0,0,2);
// Row 4: RSI period
panelSet(4,0,"RSI Per",0,0,1);
txt = strf("%i",RsiPeriod);
panelSet(4,1,txt,0,0,2);
// Row 5: ATR period
panelSet(5,0,"ATR Per",0,0,1);
txt = strf("%i",AtrPeriod);
panelSet(5,1,txt,0,0,2);
// Row 6: Stop ATR mult
panelSet(6,0,"Stop xATR",0,0,1);
txt = strf("%.2f",StopATRMult);
panelSet(6,1,txt,0,0,2);
// Row 7: Stop min
panelSet(7,0,"StopMin",0,0,1);
txt = strf("%.2f",StopMin);
panelSet(7,1,txt,0,0,2);
// Row 8: Stop max
panelSet(8,0,"StopMax",0,0,1);
txt = strf("%.2f",StopMax);
panelSet(8,1,txt,0,0,2);
// Row 9: Pos min
panelSet(9,0,"PosMin",0,0,1);
txt = strf("%.2f",PosMin);
panelSet(9,1,txt,0,0,2);
// Row 10: Pos max
panelSet(10,0,"PosMax",0,0,1);
txt = strf("%.2f",PosMax);
panelSet(10,1,txt,0,0,2);
// Row 11: Reset + Slope display (label; value updated each bar)
panelSet(11,0,"ResetEq",0,0,4);
txt = strf("%.4f",DisplaySlope);
panelSet(11,1,txt,0,0,1);
}
// Update slope display every bar
txt = strf("%.4f",DisplaySlope);
panelSet(11,1,txt,0,0,1);
// Trading logic
if(EnableTrade && crossOver(Price, sma) && normRSI < 0.7)
enterLong(posSize);
plot("RSI", normRSI, NEW, RED);
}
An ML-driven EUR/USD strategy that treats the market as an information process: it measures the Shannon entropy of recent up/down returns (how “random” vs “structured” the tape looks), combines it with recent drift and volatility, and uses Zorro’s built-in adviseLong() machine learning to predict the next-day return and scale long/short exposure accordingly.
Code
// ============================================================================
// Shiller_EURUSD_EntropyML_HistoryBounds.c — Zorro / lite-C (EUR/USD)
// ============================================================================
function run()
{
// ------------------------------------------------------------------------
// SESSION / DATA SETTINGS (SET THESE BEFORE asset()!)
// ------------------------------------------------------------------------
BarPeriod = 1440; // 1 day bars
// "Use whatever history is available":
// StartDate earlier than history -> clamps to first bar.
// EndDate = 0 -> runs to end of available history by default.
StartDate = 20250101;
EndDate = 0;
LookBack = 250;
// RULES required for advise* ML training/loading
set(PLOTNOW|RULES);
asset("EUR/USD");
algo("EntropyML");
// ------------------------------------------------------------------------
// PRICE SERIES
// ------------------------------------------------------------------------
var eps = 1e-12;
vars P = series(priceClose());
vars R1 = series(log(max(eps,P[0]) / max(eps,P[1])));
// ------------------------------------------------------------------------
// "DIVIDEND" / CARRY PROXY (Part A)
// ------------------------------------------------------------------------
var carryDaily = 0.015/252.;
vars D = series(carryDaily);
// ------------------------------------------------------------------------
// Create feature series UNCONDITIONALLY (good practice)
// ------------------------------------------------------------------------
vars EntS = series(0);
vars StdevS = series(0);
vars MeanRS = series(0);
// =========================================================================
// PART A: Excess volatility illustration (scaled down)
// =========================================================================
int Kmax = 60;
var r_d = 0.0001;
vars Px = series(0);
if (Bar > LookBack)
{
var sumDisc = 0;
var disc = 1;
int k;
for (k = 1; k <= Kmax; k++)
{
disc /= (1 + r_d);
sumDisc += disc * D[k];
}
Px[0] = sumDisc;
int W = 80;
if (Bar > LookBack + Kmax + W)
{
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;
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);
plot("Var(P)", varP, NEW, 0);
plot("Var(P*)", varPx, 0, 0);
if (Bar % 20 == 0)
printf("\n[EXCESS VOL] W=%d Var(P)=%.6g Var(P*)=%.6g ratio=%.3f",
W, varP, varPx, varP/(varPx + eps));
}
}
// =========================================================================
// PART B: ENTROPY + MACHINE LEARNING (adviseLong)
// =========================================================================
int Wml = 60;
if (Bar > LookBack + Wml + 5)
{
int up = 0, down = 0;
int i;
for (i = 1; i <= Wml; i++)
{
if (R1[i] > 0) up++;
else if (R1[i] < 0) down++;
}
var p_up = ifelse((up + down) > 0, (var)up / (var)(up + down), 0.5);
var p_dn = 1 - p_up;
var entropy = 0;
if (p_up > 0 && p_dn > 0)
entropy = -p_up*log(p_up) - p_dn*log(p_dn);
var meanR = 0;
for (i = 1; i <= Wml; i++) meanR += R1[i];
meanR /= (var)Wml;
var varR = 0;
for (i = 1; i <= Wml; i++)
{
var d = R1[i] - meanR;
varR += d*d;
}
var stdev = sqrt(max(eps, varR/(var)(Wml-1)));
EntS[0] = entropy;
StdevS[0] = stdev;
MeanRS[0] = meanR;
int Method = PERCEPTRON + FUZZY + BALANCED;
var Objective = R1[0];
int Offset = ifelse(Train, 1, 0);
var Prediction = adviseLong(Method, Objective,
EntS[Offset], StdevS[Offset], MeanRS[Offset]);
var Lev = clamp(Prediction * 10, -0.5, 0.5);
if (Lev > 0.05) { exitShort(); enterLong(); Lots = 1; }
else if (Lev < -0.05) { exitLong(); enterShort(); Lots = 1; }
else { exitLong(); exitShort(); }
plot("Entropy", entropy, NEW, 0);
plot("Pred", Prediction, 0, 0);
plot("Lev", Lev, 0, 0);
}
}
EigenGlyph Cascade is a trading strategy that treats the market like a living expression that can be simplified, transformed, and interpreted through symbolic reasoning. Instead of staring at price as raw numbers, it constructs a compact “language” of market behavior by translating two time scales into a small set of descriptive tokens. Each token represents a structural property of the recent price narrative, the way a symbolic mathematician would reduce a complicated statement into a more meaningful form.
At its core, the strategy builds two parallel views of the same instrument, a fast lens and a slow lens. From each lens it derives a picture of how returns relate to their own delayed echoes. This creates a small relationship structure that can be read like a symbolic object: it has intensity, balance, and alignment. The intensity reflects how strongly one pattern dominates the others, similar to how a rewritten expression may reveal a leading term. The balance reflects the overall “size” of variation in that relationship, like the total weight of terms in a simplified form. The alignment reflects how consistently the present and delayed behaviors move together, like whether two parts of an expression reinforce or cancel each other.
Alongside this, EigenGlyph Cascade adds a second symbolic layer inspired by valuation logic. It builds a discounted reference track from a carry assumption and compares how much the price wanders relative to that reference. This produces a stable measure of excess wandering, which behaves like a sanity check on regime behavior: some regimes are orderly, others are turbulent, and the strategy wants a vocabulary that distinguishes them.
These symbolic tokens are then normalized into a consistent scale so that a learning engine can treat them like well-formed symbols rather than mismatched magnitudes. A perceptron-based learner is used as a rule synthesizer: during training it “writes” a mapping from tokens to directional preference, guided by realized outcomes. During trading it “reads” the current token sequence and outputs a long score and a short score, which are combined into a single decision strength. That strength is then translated into a bounded exposure command, ensuring the final action remains controlled even when the symbolic signal becomes emphatic.
In spirit, the strategy behaves like a symbolic pipeline: observe, encode, simplify, infer, and act. It does not attempt to predict the market as a closed-form truth. Instead, it continuously rewrites the incoming price story into a small symbolic summary, and trades based on the meaning of that summary across two time scales.
Code
// ============================================================================
// EIG2TF - OPT
//
// Strategy overview
// ---------------
// This strategy trades EUR/USD using a two-timeframe feature set that combines:
//
// (1) A valuation-style volatility ratio (EV)
// - Builds a discounted cashflow proxy Px* from a constant carry approximation.
// - Computes EV = Var(P) / Var(Px*), a relative “excess volatility” measure.
//
// (2) A 2D covariance eigen-structure (“Eigen dominance”)
// - Uses lagged return pairs (R[t], R[t+L]) to build a 2×2 covariance matrix.
// - Extracts:
// Dom = ?max / ?min (dominant eigenvalue ratio; anisotropy / regime strength)
// Tr = ?1 + ?2 (matrix trace; total variance in the 2D embedding)
// Corr = covXY / sqrt(covXX*covYY) (correlation of the lagged return pair)
//
// (3) A machine-learning decision layer (Perceptron, fuzzy, balanced)
// - Learns to map normalized features into long/short preferences.
// - Training uses a returns-based objective (RETURNS), while prediction uses the
// standard classifier output.
// - The final trading signal is the difference between long and short scores,
// scaled into a bounded leverage command.
//
// Data and execution model
// ------------------------
// - BarPeriod = 1440 (daily bars).
// - Two feature timeframes are used (TF1 and TF2), with TF2 constrained to be > TF1.
// - A warmup period is enforced to ensure all rolling windows and lags are valid.
// - Training and testing/trading are separated by DataSplit (Train) and full-data
// evaluation (Test/Trade).
//
// Parameterization
// ----------------
// The strategy exposes a set of tunable parameters, optimized by Zorro:
//
// Timeframes
// TF1, TF2 Feature aggregation timeframes
//
// EV construction / rolling windows
// Kmax1, W1 Discount horizon and variance window for TF1 EV
// Kmax2, W2 Discount horizon and variance window for TF2 EV
//
// Eigen dominance embedding
// Weig1, L1 Covariance window and lag for TF1 eigen features
// Weig2, L2 Covariance window and lag for TF2 eigen features
//
// Trading / risk mapping
// LevScale Multiplier from ML score to leverage command
// MaxLev Absolute leverage cap
// PredThr Minimum leverage magnitude required to hold a position
// HoldBars Time-based exit horizon (maximum holding duration)
// DomThr Regime intensity threshold for dominance logic (kept for
// interpretability/logging, while ML uses all features)
//
// Fixed strategy constant
// EVThr A fixed EV threshold used as a reference level.
//
// Logging
// -------
// A CSV log is produced each run, containing:
// - Session metadata (timestamp, mode, bar, TFs)
// - Parameter values (optimized inputs)
// - Feature values (Dom/Tr/Corr/EV on both timeframes)
// - ML outputs (PredL, PredS, Pred) and final leverage command (Lev)
// ============================================================================
void deletePars(string Pattern)
{
string f = file_next(Pattern);
while(f)
{
file_delete(f);
file_delete(strf("Data\\%s",f));
f = file_next(0);
}
file_next(0);
}
function run()
{
// ------------------------------------------------------------------------
// Session setup
// ------------------------------------------------------------------------
BarPeriod = 1440;
StartDate = 20100101;
EndDate = 0;
set(PLOTNOW|RULES|LOGFILE|PARAMETERS);
asset("EUR/USD");
algo("EIG2TF_OPT15");
var eps = 1e-12;
// ------------------------------------------------------------------------
// Train/Test split and effective history length
// ------------------------------------------------------------------------
if(Train) DataSplit = 50;
else DataSplit = 0;
if(Train) LookBack = 2600;
else LookBack = 600;
// ------------------------------------------------------------------------
// Parameter file handling (per algo name)
// ------------------------------------------------------------------------
if(is(FIRSTINITRUN) && Train)
deletePars("Data\\EIG2TF_OPT15*.par");
// ------------------------------------------------------------------------
// CSV log file initialization
// ------------------------------------------------------------------------
string LogFN = "Log\\EIG2TF_OPT15.csv";
if(is(FIRSTINITRUN))
{
file_delete(LogFN);
file_append(LogFN,"Date,Time,Mode,Bar,TF1,TF2,");
file_append(LogFN,"Kmax1,W1,Weig1,L1,Kmax2,W2,Weig2,L2,");
file_append(LogFN,"LevScale,MaxLev,PredThr,HoldBars,DomThr,EVThr,");
file_append(LogFN,"Dom1,Tr1,Corr1,Dom2,Tr2,Corr2,EV1,EV2,");
file_append(LogFN,"PredL,PredS,Pred,Lev\n");
}
// ------------------------------------------------------------------------
// Optimized parameters (15 total)
// ------------------------------------------------------------------------
int TF1 = (int)optimize("TF1", 1, 1, 3, 1);
int TF2 = (int)optimize("TF2", 5, 2, 12, 1);
if(TF2 <= TF1) TF2 = TF1 + 1;
if(TF2 > 12) TF2 = 12;
int Kmax1 = (int)optimize("Kmax1", 60, 20, 120, 1);
int W1 = (int)optimize("W1", 80, 30, 150, 1);
int Weig1 = (int)optimize("Weig1", 80, 30, 200, 1);
int L1 = (int)optimize("L1", 5, 1, 20, 1);
int Kmax2 = (int)optimize("Kmax2", 24, 10, 80, 1);
int W2 = (int)optimize("W2", 40, 20, 120, 1);
int Weig2 = (int)optimize("Weig2", 40, 20, 150, 1);
int L2 = (int)optimize("L2", 2, 1, 10, 1);
var LevScale = optimize("LevScale", 10, 2, 30, 1);
var MaxLev = optimize("MaxLev", 0.5, 0.1, 1.0, 0.1);
var PredThr = optimize("PredThr", 0.02, 0.0, 0.20, 0.01);
int HoldBars = (int)optimize("HoldBars", 5, 1, 30, 1);
var DomThr = optimize("DomThr", 1.5, 1.1, 5.0, 0.1);
// Reference level for EV (kept fixed in this configuration)
var EVThr = 1.0;
// ------------------------------------------------------------------------
// Carry proxy and discount rate (constant model inputs)
// ------------------------------------------------------------------------
var carryDaily = 0.015/252.;
var r_d = 0.0001;
// ------------------------------------------------------------------------
// Timeframe 1 series and feature buffers
// ------------------------------------------------------------------------
TimeFrame = TF1;
vars P1 = series(priceClose());
vars R1tf = series(log(max(eps,P1[0]) / max(eps,P1[1])));
vars D1 = series(carryDaily*(var)TF1);
vars Px1 = series(0);
vars EV1S = series(0);
vars Dom1S = series(0);
vars Tr1S = series(0);
vars Corr1S = series(0);
// ------------------------------------------------------------------------
// Timeframe 2 series and feature buffers
// ------------------------------------------------------------------------
TimeFrame = TF2;
vars P2 = series(priceClose());
vars R2tf = series(log(max(eps,P2[0]) / max(eps,P2[1])));
vars D2 = series(carryDaily*(var)TF2);
vars Px2 = series(0);
vars EV2S = series(0);
vars Dom2S = series(0);
vars Tr2S = series(0);
vars Corr2S = series(0);
// Back to base timeframe for execution
TimeFrame = 1;
// ------------------------------------------------------------------------
// Warmup: ensure all rolling windows and lags are populated
// ------------------------------------------------------------------------
int NeedTF1 = max(max(Kmax1, W1), (Weig1 + L1 + 2));
int NeedTF2 = max(max(Kmax2, W2), (Weig2 + L2 + 2));
int WarmupBars = max(TF1 * NeedTF1, TF2 * NeedTF2) + 10;
if(Bar < WarmupBars)
return;
// ============================================================
// Feature block A: EV (excess volatility proxy)
// ============================================================
TimeFrame = TF1;
{
var sumDisc1=0, disc1=1;
int k;
for(k=1;k<=Kmax1;k++){ disc1/=(1+r_d); sumDisc1 += disc1*D1[k]; }
Px1[0]=sumDisc1;
var meanP1=0, meanPx1=0; int i;
for(i=0;i<W1;i++){ meanP1+=P1[i]; meanPx1+=Px1[i]; }
meanP1/=W1; meanPx1/=W1;
var varP1=0, varPx1=0;
for(i=0;i<W1;i++){
var a=P1[i]-meanP1, b=Px1[i]-meanPx1;
varP1+=a*a; varPx1+=b*b;
}
varP1/=(W1-1); varPx1/=(W1-1);
EV1S[0]=varP1/(varPx1+eps);
}
TimeFrame = TF2;
{
var sumDisc2=0, disc2=1;
int k2;
for(k2=1;k2<=Kmax2;k2++){ disc2/=(1+r_d); sumDisc2 += disc2*D2[k2]; }
Px2[0]=sumDisc2;
var meanP2=0, meanPx2=0; int j;
for(j=0;j<W2;j++){ meanP2+=P2[j]; meanPx2+=Px2[j]; }
meanP2/=W2; meanPx2/=W2;
var varP2=0, varPx2=0;
for(j=0;j<W2;j++){
var a=P2[j]-meanP2, b=Px2[j]-meanPx2;
varP2+=a*a; varPx2+=b*b;
}
varP2/=(W2-1); varPx2/=(W2-1);
EV2S[0]=varP2/(varPx2+eps);
}
// ============================================================
// Feature block B: Eigen dominance (Dom, Tr, Corr)
// ============================================================
TimeFrame = TF1;
{
int i; var meanX=0, meanY=0;
for(i=1;i<=Weig1;i++){ meanX+=R1tf[i]; meanY+=R1tf[i+L1]; }
meanX/=Weig1; meanY/=Weig1;
var covXX=0,covYY=0,covXY=0;
for(i=1;i<=Weig1;i++){
var dx=R1tf[i]-meanX, dy=R1tf[i+L1]-meanY;
covXX+=dx*dx; covYY+=dy*dy; covXY+=dx*dy;
}
covXX/=(Weig1-1); covYY/=(Weig1-1); covXY/=(Weig1-1);
var trace=covXX+covYY;
var det=covXX*covYY-covXY*covXY;
var root=sqrt(max(0, trace*trace-4*det));
var lam1=0.5*(trace+root), lam2=0.5*(trace-root);
var lamMax=ifelse(lam1>=lam2,lam1,lam2);
var lamMin=ifelse(lam1>=lam2,lam2,lam1);
Dom1S[0]=lamMax/(lamMin+eps);
Tr1S[0]=trace;
Corr1S[0]=clamp(covXY/sqrt(max(eps,covXX*covYY)),-1,1);
}
TimeFrame = TF2;
{
int j; var meanX2=0, meanY2=0;
for(j=1;j<=Weig2;j++){ meanX2+=R2tf[j]; meanY2+=R2tf[j+L2]; }
meanX2/=Weig2; meanY2/=Weig2;
var covXX2=0,covYY2=0,covXY2=0;
for(j=1;j<=Weig2;j++){
var dx2=R2tf[j]-meanX2, dy2=R2tf[j+L2]-meanY2;
covXX2+=dx2*dx2; covYY2+=dy2*dy2; covXY2+=dx2*dy2;
}
covXX2/=(Weig2-1); covYY2/=(Weig2-1); covXY2/=(Weig2-1);
var trace2=covXX2+covYY2;
var det2=covXX2*covYY2-covXY2*covXY2;
var root2=sqrt(max(0, trace2*trace2-4*det2));
var lam12=0.5*(trace2+root2), lam22=0.5*(trace2-root2);
var lamMax2=ifelse(lam12>=lam22,lam12,lam22);
var lamMin2=ifelse(lam12>=lam22,lam22,lam12);
Dom2S[0]=lamMax2/(lamMin2+eps);
Tr2S[0]=trace2;
Corr2S[0]=clamp(covXY2/sqrt(max(eps,covXX2*covYY2)),-1,1);
}
// ============================================================
// ML + trading execution
// ============================================================
TimeFrame = 1;
int MethodTrain = PERCEPTRON + FUZZY + BALANCED + RETURNS;
int MethodPred = PERCEPTRON + FUZZY + BALANCED;
// Input normalization: keeps features comparable in scale for the perceptron.
var Sig[8];
Sig[0] = clamp(log(max(eps,Dom1S[0])), -2, 2);
Sig[1] = clamp(0.25*log(max(eps,Tr1S[0])), -2, 2);
Sig[2] = Corr1S[0];
Sig[3] = clamp(log(max(eps,Dom2S[0])), -2, 2);
Sig[4] = clamp(0.25*log(max(eps,Tr2S[0])), -2, 2);
Sig[5] = Corr2S[0];
Sig[6] = clamp(log(max(eps,EV1S[0])), -2, 2);
Sig[7] = clamp(log(max(eps,EV2S[0])), -2, 2);
var PredL=0, PredS=0, Pred=0, Lev=0;
// Time-based exit: closes positions after HoldBars.
if(NumOpenTotal > 0)
for(open_trades)
if(TradeIsOpen && TradeBars >= HoldBars)
exitTrade(ThisTrade);
// Training alternates long/short entries to produce samples for both sides.
static int Flip = 0;
if(Train)
{
if(NumOpenTotal == 0)
{
Flip = 1 - Flip;
if(Flip)
{
adviseLong(MethodTrain,0,Sig,8);
Lots=1; enterLong();
}
else
{
adviseShort(MethodTrain,0,Sig,8);
Lots=1; enterShort();
}
}
}
else
{
// Wait until the ML rule set is available in the non-training pass.
if(is(LOOKBACK)) return;
PredL = adviseLong(MethodPred,0,Sig,8);
PredS = adviseShort(MethodPred,0,Sig,8);
// Convert long-vs-short preference into a bounded leverage target.
Pred = (PredL - PredS) / 100.0;
Lev = clamp(Pred*LevScale, -MaxLev, MaxLev);
if(Lev > PredThr) { exitShort(); Lots=1; enterLong(); }
else if(Lev < -PredThr) { exitLong(); Lots=1; enterShort(); }
else { exitLong(); exitShort(); }
}
// ------------------------------------------------------------------------
// Diagnostics and CSV log output
// ------------------------------------------------------------------------
string ModeStr="Trade";
if(Train) ModeStr="Train"; else if(Test) ModeStr="Test";
file_append(LogFN, strf("%04i-%02i-%02i,%02i:%02i,%s,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%.4f,%.3f,%.4f,%d,%.4f,%.4f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.4f,%.4f,%.4f,%.4f\n",
year(0),month(0),day(0), hour(0),minute(0),
ModeStr, Bar, TF1, TF2,
Kmax1, W1, Weig1, L1, Kmax2, W2, Weig2, L2,
LevScale, MaxLev, PredThr, HoldBars, DomThr, EVThr,
Dom1S[0],Tr1S[0],Corr1S[0],
Dom2S[0],Tr2S[0],Corr2S[0],
EV1S[0],EV2S[0],
PredL,PredS,Pred,Lev
));
}
Twin Horizon Bandit Weaver is an adaptive trading strategy that watches the market through two horizons and learns which internal settings work best as conditions change. The first horizon is the quick listener that reacts to recent movement. The second horizon is the slow listener that provides context and steadiness. These two horizons are not just different chart speeds. They are separate views of the same market story, one focused on immediate rhythm and the other focused on the broader cadence.
From each horizon, the strategy builds two families of clues. The first family is a value pressure clue that compares how price behaves versus a gently drifting reference that represents carry like influence. If price swings wildly while the reference changes smoothly, the value pressure clue becomes stronger. The second family is a structure clue that asks whether recent returns look like they are dominated by one main direction or spread across competing directions. It also captures how active the structure is overall and whether the paired return streams tend to move together or against each other.
Those horizon clues are packaged into a compact signal set and passed into a lightweight learning module that outputs a preference for long exposure and a preference for short exposure. In training mode, the strategy forces learning by taking alternating long and short trades so the learner receives outcomes and does not stall. In trading mode, it converts the two preferences into a single directional intent and then shapes that intent into a position decision.
The most distinctive feature is the parameter orchestra. Instead of fixing window lengths, thresholds, and scaling values forever, the strategy assigns one small bandit agent to each parameter. Each agent has a short menu of allowed choices. When the strategy is flat, all agents pick their choices for the next episode. Most of the time they exploit what has worked best so far, but occasionally they explore a different option to avoid getting stuck.
Rewards are taken from the realized account change when a trade closes. That reward is shared back to all bandit agents that participated in the episode, nudging their chosen settings toward or away from future selection. Risk control stays simple: trades are not allowed to linger beyond a chosen holding limit, entries require a minimum confidence threshold, and a dominance filter can require at least one horizon to show clear directional structure before allowing intent to remain active. The strategy also logs each episode so you can audit what it chose, what it saw, and how it learned.
Code
// ============================================================================
// EIG2TF - RL15 (15 bandit agents: one per parameter)
//
// FIXES INCLUDED (lite-C / Zorro):
// - No string-array initializer: ParName[] is filled by initParNames().
// - No randf(): use random(Max).
// - No NumClosedTotal: reward detected by transition (PrevOpenTotal>0 && NumOpenTotal==0),
// reward computed as Balance - LastBalance.
// - CSV header + format strings are single literals (no split string concatenation).
// - DO NOT return during LOOKBACK in Train (otherwise forced training trades never happen).
// - Bootstrap in Test/Trade if model not trained (PredL==0 && PredS==0): open a random trade.
// ============================================================================
#define NPAR 15
#define MAXARMS 64
#define EPSILON 0.10
#define ALPHA 0.10
#define P_INT 0
#define P_VAR 1
// Parameter indices
#define P_TF1 0
#define P_TF2 1
#define P_KMAX1 2
#define P_W1 3
#define P_WEIG1 4
#define P_L1 5
#define P_KMAX2 6
#define P_W2 7
#define P_WEIG2 8
#define P_L2 9
#define P_LEVSCALE 10
#define P_MAXLEV 11
#define P_PREDTHR 12
#define P_HOLDBARS 13
#define P_DOMTHR 14
// -----------------------------
// RL/bandit storage
// -----------------------------
string ParName[NPAR];
void initParNames()
{
ParName[P_TF1] = "TF1";
ParName[P_TF2] = "TF2";
ParName[P_KMAX1] = "Kmax1";
ParName[P_W1] = "W1";
ParName[P_WEIG1] = "Weig1";
ParName[P_L1] = "L1";
ParName[P_KMAX2] = "Kmax2";
ParName[P_W2] = "W2";
ParName[P_WEIG2] = "Weig2";
ParName[P_L2] = "L2";
ParName[P_LEVSCALE] = "LevScale";
ParName[P_MAXLEV] = "MaxLev";
ParName[P_PREDTHR] = "PredThr";
ParName[P_HOLDBARS] = "HoldBars";
ParName[P_DOMTHR] = "DomThr";
}
int ParType[NPAR] =
{
P_INT,P_INT,P_INT,P_INT,P_INT,P_INT,P_INT,P_INT,P_INT,P_INT,
P_VAR,P_VAR,P_VAR,P_INT,P_VAR
};
// Defaults / ranges / steps
var ParDef[NPAR];
var ParMin[NPAR];
var ParMax[NPAR];
var ParStep[NPAR];
// Learned value estimates and counts
var Q[NPAR][MAXARMS];
int Ncnt[NPAR][MAXARMS];
int ArmsCount[NPAR];
int CurArm[NPAR];
// Helper: clamp arm count to MAXARMS
int calcArms(var mn, var mx, var stp)
{
if(stp <= 0) return 1;
int n = (int)floor((mx - mn)/stp + 1.000001);
if(n < 1) n = 1;
if(n > MAXARMS) n = MAXARMS;
return n;
}
var armValue(int p, int a)
{
var v = ParMin[p] + (var)a*ParStep[p];
if(v < ParMin[p]) v = ParMin[p];
if(v > ParMax[p]) v = ParMax[p];
if(ParType[p] == P_INT) v = (var)(int)(v + 0.5);
return v;
}
int bestArm(int p)
{
int a, best = 0;
var bestQ = Q[p][0];
for(a=1; a<ArmsCount[p]; a++)
if(Q[p][a] > bestQ) { bestQ = Q[p][a]; best = a; }
return best;
}
int selectArm(int p)
{
// epsilon-greedy using random(Max)
if(random(1) < EPSILON)
return (int)random((var)ArmsCount[p]); // 0..ArmsCount-1
return bestArm(p);
}
void updateArm(int p, int a, var reward)
{
Q[p][a] = Q[p][a] + ALPHA*(reward - Q[p][a]);
Ncnt[p][a] += 1;
}
// Initialize parameter table and RL tables
void initParams()
{
// Keep arms <= MAXARMS
ParDef[P_TF1] = 1; ParMin[P_TF1] = 1; ParMax[P_TF1] = 3; ParStep[P_TF1] = 1;
ParDef[P_TF2] = 5; ParMin[P_TF2] = 2; ParMax[P_TF2] = 12; ParStep[P_TF2] = 1;
ParDef[P_KMAX1] = 60; ParMin[P_KMAX1] = 20; ParMax[P_KMAX1] = 120; ParStep[P_KMAX1] = 2;
ParDef[P_W1] = 80; ParMin[P_W1] = 30; ParMax[P_W1] = 150; ParStep[P_W1] = 2;
ParDef[P_WEIG1] = 80; ParMin[P_WEIG1] = 30; ParMax[P_WEIG1] = 200; ParStep[P_WEIG1] = 5;
ParDef[P_L1] = 5; ParMin[P_L1] = 1; ParMax[P_L1] = 20; ParStep[P_L1] = 1;
ParDef[P_KMAX2] = 24; ParMin[P_KMAX2] = 10; ParMax[P_KMAX2] = 80; ParStep[P_KMAX2] = 2;
ParDef[P_W2] = 40; ParMin[P_W2] = 20; ParMax[P_W2] = 120; ParStep[P_W2] = 2;
ParDef[P_WEIG2] = 40; ParMin[P_WEIG2] = 20; ParMax[P_WEIG2] = 150; ParStep[P_WEIG2] = 5;
ParDef[P_L2] = 2; ParMin[P_L2] = 1; ParMax[P_L2] = 10; ParStep[P_L2] = 1;
ParDef[P_LEVSCALE] = 10; ParMin[P_LEVSCALE] = 2; ParMax[P_LEVSCALE] = 30; ParStep[P_LEVSCALE] = 1;
ParDef[P_MAXLEV] = 0.5; ParMin[P_MAXLEV] = 0.1; ParMax[P_MAXLEV] = 1.0; ParStep[P_MAXLEV] = 0.1;
ParDef[P_PREDTHR] = 0.02; ParMin[P_PREDTHR] = 0.00; ParMax[P_PREDTHR] = 0.20; ParStep[P_PREDTHR] = 0.01;
ParDef[P_HOLDBARS] = 5; ParMin[P_HOLDBARS] = 1; ParMax[P_HOLDBARS] = 30; ParStep[P_HOLDBARS] = 1;
ParDef[P_DOMTHR] = 1.5; ParMin[P_DOMTHR] = 1.1; ParMax[P_DOMTHR] = 5.0; ParStep[P_DOMTHR] = 0.1;
int p, a;
for(p=0; p<NPAR; p++)
{
ArmsCount[p] = calcArms(ParMin[p], ParMax[p], ParStep[p]);
CurArm[p] = 0;
for(a=0; a<ArmsCount[p]; a++)
{
Q[p][a] = 0;
Ncnt[p][a] = 0;
}
}
}
// Pick new parameter actions when flat
void pickParams()
{
int p;
for(p=0; p<NPAR; p++)
CurArm[p] = selectArm(p);
}
function run()
{
// ------------------------------------------------------------------------
// Session setup
// ------------------------------------------------------------------------
BarPeriod = 1440;
StartDate = 20100101;
EndDate = 0;
set(PLOTNOW|RULES|LOGFILE);
asset("EUR/USD");
algo("EIG2TF_RL15");
var eps = 1e-12;
if(Train) DataSplit = 50;
else DataSplit = 0;
// IMPORTANT: keep enough history for your biggest windows
LookBack = 2600;
// ------------------------------------------------------------------------
// One-time init
// ------------------------------------------------------------------------
static int Inited = 0;
static int PrevOpenTotal = 0;
static var LastBalance = 0;
string LogFN = "Log\\EIG2TF_RL15.csv";
if(is(FIRSTINITRUN))
{
Inited = 0;
PrevOpenTotal = 0;
LastBalance = 0;
file_delete(LogFN);
file_append(LogFN,"Date,Time,Mode,Bar,TF1,TF2,Kmax1,W1,Weig1,L1,Kmax2,W2,Weig2,L2,LevScale,MaxLev,PredThr,HoldBars,DomThr,Dom1,Tr1,Corr1,Dom2,Tr2,Corr2,EV1,EV2,PredL,PredS,Pred,Lev,Reward\n");
}
if(!Inited)
{
// optional deterministic randomness:
// seed(12345);
initParNames();
initParams();
pickParams();
LastBalance = Balance;
PrevOpenTotal = NumOpenTotal;
Inited = 1;
}
// ------------------------------------------------------------------------
// Convert chosen arms -> concrete parameter values
// ------------------------------------------------------------------------
int TF1 = (int)armValue(P_TF1, CurArm[P_TF1]);
int TF2 = (int)armValue(P_TF2, CurArm[P_TF2]);
if(TF2 <= TF1) TF2 = TF1 + 1;
if(TF2 > 12) TF2 = 12;
int Kmax1 = (int)armValue(P_KMAX1, CurArm[P_KMAX1]);
int W1 = (int)armValue(P_W1, CurArm[P_W1]);
int Weig1 = (int)armValue(P_WEIG1, CurArm[P_WEIG1]);
int L1 = (int)armValue(P_L1, CurArm[P_L1]);
int Kmax2 = (int)armValue(P_KMAX2, CurArm[P_KMAX2]);
int W2 = (int)armValue(P_W2, CurArm[P_W2]);
int Weig2 = (int)armValue(P_WEIG2, CurArm[P_WEIG2]);
int L2 = (int)armValue(P_L2, CurArm[P_L2]);
var LevScale = armValue(P_LEVSCALE, CurArm[P_LEVSCALE]);
var MaxLev = armValue(P_MAXLEV, CurArm[P_MAXLEV]);
var PredThr = armValue(P_PREDTHR, CurArm[P_PREDTHR]);
int HoldBars = (int)armValue(P_HOLDBARS, CurArm[P_HOLDBARS]);
var DomThr = armValue(P_DOMTHR, CurArm[P_DOMTHR]);
// ------------------------------------------------------------------------
// Carry proxy + discount rate
// ------------------------------------------------------------------------
var carryDaily = 0.015/252.;
var r_d = 0.0001;
// ------------------------------------------------------------------------
// Timeframe 1 series and feature buffers
// ------------------------------------------------------------------------
TimeFrame = TF1;
vars P1 = series(priceClose());
vars R1tf = series(log(max(eps,P1[0]) / max(eps,P1[1])));
vars D1 = series(carryDaily*(var)TF1);
vars Px1 = series(0);
vars EV1S = series(0);
vars Dom1S = series(0);
vars Tr1S = series(0);
vars Corr1S = series(0);
// ------------------------------------------------------------------------
// Timeframe 2 series and feature buffers
// ------------------------------------------------------------------------
TimeFrame = TF2;
vars P2 = series(priceClose());
vars R2tf = series(log(max(eps,P2[0]) / max(eps,P2[1])));
vars D2 = series(carryDaily*(var)TF2);
vars Px2 = series(0);
vars EV2S = series(0);
vars Dom2S = series(0);
vars Tr2S = series(0);
vars Corr2S = series(0);
// back to base timeframe
TimeFrame = 1;
// ------------------------------------------------------------------------
// Warmup gate
// ------------------------------------------------------------------------
int NeedTF1 = max(max(Kmax1, W1), (Weig1 + L1 + 2));
int NeedTF2 = max(max(Kmax2, W2), (Weig2 + L2 + 2));
int WarmupBars = max(TF1 * NeedTF1, TF2 * NeedTF2) + 10;
if(Bar < WarmupBars) return;
// KEY FIX: do NOT return during LOOKBACK in Train
if(is(LOOKBACK) && !Train) return;
// ============================================================
// Feature block A: EV ratios
// ============================================================
TimeFrame = TF1;
{
var sumDisc1=0, disc1=1; int k;
for(k=1;k<=Kmax1;k++){ disc1/=(1+r_d); sumDisc1 += disc1*D1[k]; }
Px1[0]=sumDisc1;
var meanP1=0, meanPx1=0; int i;
for(i=0;i<W1;i++){ meanP1+=P1[i]; meanPx1+=Px1[i]; }
meanP1/=W1; meanPx1/=W1;
var varP1=0, varPx1=0;
for(i=0;i<W1;i++){
var a=P1[i]-meanP1, b=Px1[i]-meanPx1;
varP1+=a*a; varPx1+=b*b;
}
varP1/=(W1-1); varPx1/=(W1-1);
EV1S[0]=varP1/(varPx1+eps);
}
TimeFrame = TF2;
{
var sumDisc2=0, disc2=1; int k2;
for(k2=1;k2<=Kmax2;k2++){ disc2/=(1+r_d); sumDisc2 += disc2*D2[k2]; }
Px2[0]=sumDisc2;
var meanP2=0, meanPx2=0; int j;
for(j=0;j<W2;j++){ meanP2+=P2[j]; meanPx2+=Px2[j]; }
meanP2/=W2; meanPx2/=W2;
var varP2=0, varPx2=0;
for(j=0;j<W2;j++){
var a=P2[j]-meanP2, b=Px2[j]-meanPx2;
varP2+=a*a; varPx2+=b*b;
}
varP2/=(W2-1); varPx2/=(W2-1);
EV2S[0]=varP2/(varPx2+eps);
}
// ============================================================
// Feature block B: Eigen dominance
// ============================================================
TimeFrame = TF1;
{
int i; var meanX=0, meanY=0;
for(i=1;i<=Weig1;i++){ meanX+=R1tf[i]; meanY+=R1tf[i+L1]; }
meanX/=Weig1; meanY/=Weig1;
var covXX=0,covYY=0,covXY=0;
for(i=1;i<=Weig1;i++){
var dx=R1tf[i]-meanX, dy=R1tf[i+L1]-meanY;
covXX+=dx*dx; covYY+=dy*dy; covXY+=dx*dy;
}
covXX/=(Weig1-1); covYY/=(Weig1-1); covXY/=(Weig1-1);
var trace=covXX+covYY;
var det=covXX*covYY-covXY*covXY;
var root=sqrt(max(0, trace*trace-4*det));
var lam1=0.5*(trace+root), lam2=0.5*(trace-root);
var lamMax=ifelse(lam1>=lam2,lam1,lam2);
var lamMin=ifelse(lam1>=lam2,lam2,lam1);
Dom1S[0]=lamMax/(lamMin+eps);
Tr1S[0]=trace;
Corr1S[0]=clamp(covXY/sqrt(max(eps,covXX*covYY)),-1,1);
}
TimeFrame = TF2;
{
int j; var meanX2=0, meanY2=0;
for(j=1;j<=Weig2;j++){ meanX2+=R2tf[j]; meanY2+=R2tf[j+L2]; }
meanX2/=Weig2; meanY2/=Weig2;
var covXX2=0,covYY2=0,covXY2=0;
for(j=1;j<=Weig2;j++){
var dx2=R2tf[j]-meanX2, dy2=R2tf[j+L2]-meanY2;
covXX2+=dx2*dx2; covYY2+=dy2*dy2; covXY2+=dx2*dy2;
}
covXX2/=(Weig2-1); covYY2/=(Weig2-1); covXY2/=(Weig2-1);
var trace2=covXX2+covYY2;
var det2=covXX2*covYY2-covXY2*covXY2;
var root2=sqrt(max(0, trace2*trace2-4*det2));
var lam12=0.5*(trace2+root2), lam22=0.5*(trace2-root2);
var lamMax2=ifelse(lam12>=lam22,lam12,lam22);
var lamMin2=ifelse(lam12>=lam22,lam22,lam12);
Dom2S[0]=lamMax2/(lamMin2+eps);
Tr2S[0]=trace2;
Corr2S[0]=clamp(covXY2/sqrt(max(eps,covXX2*covYY2)),-1,1);
}
// back to base timeframe
TimeFrame = 1;
// ============================================================
// ML + trading
// ============================================================
int MethodTrain = PERCEPTRON + FUZZY + BALANCED + RETURNS;
int MethodPred = PERCEPTRON + FUZZY + BALANCED;
var Sig[8];
Sig[0] = clamp(log(max(eps,Dom1S[0])), -2, 2);
Sig[1] = clamp(0.25*log(max(eps,Tr1S[0])), -2, 2);
Sig[2] = Corr1S[0];
Sig[3] = clamp(log(max(eps,Dom2S[0])), -2, 2);
Sig[4] = clamp(0.25*log(max(eps,Tr2S[0])), -2, 2);
Sig[5] = Corr2S[0];
Sig[6] = clamp(log(max(eps,EV1S[0])), -2, 2);
Sig[7] = clamp(log(max(eps,EV2S[0])), -2, 2);
// Exit after HoldBars
if(NumOpenTotal > 0)
for(open_trades)
if(TradeBars >= HoldBars)
exitTrade(ThisTrade);
var PredL=0, PredS=0, Pred=0, Lev=0;
static int Flip = 0;
if(Train)
{
// Forced trades so ML gets samples
if(NumOpenTotal == 0)
{
Flip = 1 - Flip;
LastBalance = Balance;
if(Flip) { adviseLong(MethodTrain,0,Sig,8); Lots=1; enterLong(); }
else { adviseShort(MethodTrain,0,Sig,8); Lots=1; enterShort(); }
}
}
else
{
PredL = adviseLong(MethodPred,0,Sig,8);
PredS = adviseShort(MethodPred,0,Sig,8);
// Bootstrap if model not trained / no signal
if(NumOpenTotal == 0 && PredL == 0 && PredS == 0)
{
LastBalance = Balance;
if(random(1) < 0.5) { Lots=1; enterLong(); }
else { Lots=1; enterShort(); }
}
else
{
Pred = (PredL - PredS) / 100.0;
Lev = clamp(Pred*LevScale, -MaxLev, MaxLev);
// Apply Dom filter only when signal is nonzero
if(Lev != 0 && Dom1S[0] < DomThr && Dom2S[0] < DomThr)
Lev = 0;
if(Lev > PredThr) { exitShort(); Lots=1; enterLong(); }
else if(Lev < -PredThr) { exitLong(); Lots=1; enterShort(); }
else { exitLong(); exitShort(); }
}
}
// ============================================================
// RL update: reward when going from in-position -> flat
// ============================================================
var Reward = 0;
if(PrevOpenTotal > 0 && NumOpenTotal == 0)
{
Reward = Balance - LastBalance;
if(Reward != 0)
{
int p;
for(p=0; p<NPAR; p++)
updateArm(p, CurArm[p], Reward);
}
pickParams();
LastBalance = Balance;
}
PrevOpenTotal = NumOpenTotal;
// ------------------------------------------------------------------------
// CSV log output (single literal format string)
// ------------------------------------------------------------------------
string ModeStr="Trade";
if(Train) ModeStr="Train"; else if(Test) ModeStr="Test";
file_append(LogFN, strf("%04i-%02i-%02i,%02i:%02i,%s,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%.4f,%.3f,%.4f,%d,%.4f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.8f,%.4f,%.4f,%.4f,%.4f,%.6f\n",
year(0),month(0),day(0), hour(0),minute(0),
ModeStr, Bar, TF1, TF2,
Kmax1, W1, Weig1, L1, Kmax2, W2, Weig2, L2,
LevScale, MaxLev, PredThr, HoldBars, DomThr,
Dom1S[0],Tr1S[0],Corr1S[0],
Dom2S[0],Tr2S[0],Corr2S[0],
EV1S[0],EV2S[0],
PredL,PredS,Pred,Lev,
Reward
));
// Plots (optional)
plot("TF1_Dom", Dom1S[0], NEW, 0);
plot("TF2_Dom", Dom2S[0], 0, 0);
plot("EV_TF1", EV1S[0], 0, 0);
plot("EV_TF2", EV2S[0], 0, 0);
plot("Pred", Pred, 0, 0);
plot("Lev", Lev, 0, 0);
plot("Reward", Reward, 0, 0);
}
FractalBandit Compass is a two layer learning strategy built for daily bar trading on EUR USD, combining adaptive parameter selection with supervised machine learning that learns from realized trade outcomes. The core idea is to let the strategy continuously reshape its own settings based on performance, while simultaneously learning when to trade using a stable set of market descriptors computed on two separate timeframes.
The first layer is a group of reinforcement learning bandit agents, one agent per strategy parameter. Each agent treats the possible settings of its parameter as a set of discrete choices, similar to selecting one option from a menu. Examples of these settings include the first timeframe choice, the offset that defines the second timeframe, window lengths for fractal dimension estimation, window lengths for slope measurement, window lengths for volatility estimation, and risk shaping controls such as leverage scaling, maximum leverage, prediction threshold, and holding time. When the system is flat and ready to begin a new trade episode, every agent selects an option using an exploration and exploitation routine. Most of the time it exploits the best known option for that parameter, but sometimes it explores alternative options to avoid getting stuck with a locally good choice that may stop working later. This makes the strategy flexible across changing regimes.
The second layer is the market signal engine, which produces a compact feature vector from two timeframes. On each timeframe, the strategy measures a Katz style fractal dimension estimate to describe how path like or trend like the recent price movement appears. It also measures a price slope over a configurable window to summarize direction and persistence, and it measures volatility using the standard deviation of log returns over a configurable window to summarize turbulence. These six values form the feature signature used by the machine learning model.
Machine learning is trained using a returns based mode so the model learns from the eventual success or failure of trades rather than from a single bar label. During training runs, the strategy forces alternating long and short entries when flat. This is not meant to be profitable by itself; it is meant to guarantee that the model sees a broad variety of examples in different market conditions and does not stall with no trades and no learning. During test and trade runs, the model produces long and short preferences from the same feature signature. If the model has not learned enough yet and outputs no meaningful preference, the strategy bootstraps a small trade using a simple directional hint from the combined slope, or a random choice when direction is unclear. This ensures the learning loop continues to generate outcomes.
The episode boundary is defined by the strategy returning to flat after having an open position. At that point, the reward is computed as the change in account balance since the start of the episode. That single reward is then used to update every bandit agent on the exact option it chose for the episode. Over time, parameter options that consistently lead to better episodes gain higher value estimates, and the agents increasingly favor them while still occasionally testing alternatives. The result is a self tuning strategy that adapts both its market interpretation settings and its execution behavior, aiming to remain robust as market structure shifts.
Code
// ============================================================================
// Fractal Learner - RL Bandits for Parameters + 2TF ML (RETURNS)
// File: Fractal_EURUSD_2TF_RL_Bandits_v1.c (Zorro / lite-C)
//
// What changed vs optimize():
// - Replaced optimize() with epsilon-greedy multi-armed bandits (one agent per parameter).
// - When flat, agents pick new parameter "arms".
// - A trade is opened (Train: alternating long/short; Test/Trade: ML-driven + bootstrap).
// - When positions go flat again, reward = Balance delta, and all agents update Q for used arms.
// - Next episode starts with new parameter picks.
//
// lite-C safe:
// - No adjacent string literals in one file_append call (header uses multiple calls)
// - No inline/ternary helpers; uses function keyword
// - strf format string is ONE literal
// ============================================================================
#define NPAR 12
#define MAXARMS 64
#define EPSILON 0.10
#define ALPHA 0.10
#define P_INT 0
#define P_VAR 1
// Parameter indices (RL-controlled)
#define P_TF1 0
#define P_TF2D 1
#define P_FDLEN1 2
#define P_SLOPELEN1 3
#define P_VOLLEN1 4
#define P_FDLEN2 5
#define P_SLOPELEN2 6
#define P_VOLLEN2 7
#define P_LEVSCALE 8
#define P_MAXLEV 9
#define P_PREDTHR 10
#define P_HOLDBARS 11
// -----------------------------
// RL storage
// -----------------------------
string ParName[NPAR];
int ParType[NPAR] =
{
P_INT, // TF1
P_INT, // TF2d
P_INT, // FDLen1
P_INT, // SlopeLen1
P_INT, // VolLen1
P_INT, // FDLen2
P_INT, // SlopeLen2
P_INT, // VolLen2
P_VAR, // LevScale
P_VAR, // MaxLev
P_VAR, // PredThr
P_INT // HoldBars
};
var ParMin[NPAR];
var ParMax[NPAR];
var ParStep[NPAR];
var Q[NPAR][MAXARMS];
int Ncnt[NPAR][MAXARMS];
int ArmsCount[NPAR];
int CurArm[NPAR];
void initParNames()
{
ParName[P_TF1] = "TF1";
ParName[P_TF2D] = "TF2d";
ParName[P_FDLEN1] = "FDLen1";
ParName[P_SLOPELEN1] = "SlopeLen1";
ParName[P_VOLLEN1] = "VolLen1";
ParName[P_FDLEN2] = "FDLen2";
ParName[P_SLOPELEN2] = "SlopeLen2";
ParName[P_VOLLEN2] = "VolLen2";
ParName[P_LEVSCALE] = "LevScale";
ParName[P_MAXLEV] = "MaxLev";
ParName[P_PREDTHR] = "PredThr";
ParName[P_HOLDBARS] = "HoldBars";
}
int calcArms(var mn, var mx, var stp)
{
if(stp <= 0) return 1;
int n = (int)floor((mx - mn)/stp + 1.000001);
if(n < 1) n = 1;
if(n > MAXARMS) n = MAXARMS;
return n;
}
var armValue(int p, int a)
{
var v = ParMin[p] + (var)a * ParStep[p];
if(v < ParMin[p]) v = ParMin[p];
if(v > ParMax[p]) v = ParMax[p];
if(ParType[p] == P_INT) v = (var)(int)(v + 0.5);
return v;
}
int bestArm(int p)
{
int a, best = 0;
var bestQ = Q[p][0];
for(a=1; a<ArmsCount[p]; a++)
{
if(Q[p][a] > bestQ)
{
bestQ = Q[p][a];
best = a;
}
}
return best;
}
int selectArm(int p)
{
if(random(1) < EPSILON)
return (int)random((var)ArmsCount[p]);
return bestArm(p);
}
void updateArm(int p, int a, var reward)
{
Q[p][a] = Q[p][a] + ALPHA*(reward - Q[p][a]);
Ncnt[p][a] += 1;
}
void initParamsRL()
{
// Ranges roughly matching your old optimize() ranges/steps
ParMin[P_TF1] = 1; ParMax[P_TF1] = 3; ParStep[P_TF1] = 1;
ParMin[P_TF2D] = 1; ParMax[P_TF2D] = 11; ParStep[P_TF2D] = 1;
ParMin[P_FDLEN1] = 20; ParMax[P_FDLEN1] = 220; ParStep[P_FDLEN1] = 5;
ParMin[P_SLOPELEN1] = 20; ParMax[P_SLOPELEN1] = 200; ParStep[P_SLOPELEN1] = 5;
ParMin[P_VOLLEN1] = 20; ParMax[P_VOLLEN1] = 200; ParStep[P_VOLLEN1] = 5;
ParMin[P_FDLEN2] = 10; ParMax[P_FDLEN2] = 160; ParStep[P_FDLEN2] = 5;
ParMin[P_SLOPELEN2] = 10; ParMax[P_SLOPELEN2] = 140; ParStep[P_SLOPELEN2] = 5;
ParMin[P_VOLLEN2] = 10; ParMax[P_VOLLEN2] = 140; ParStep[P_VOLLEN2] = 5;
ParMin[P_LEVSCALE] = 2; ParMax[P_LEVSCALE] = 30; ParStep[P_LEVSCALE] = 1;
ParMin[P_MAXLEV] = 0.1; ParMax[P_MAXLEV] = 1.0; ParStep[P_MAXLEV] = 0.1;
ParMin[P_PREDTHR] = 0.0; ParMax[P_PREDTHR] = 0.20; ParStep[P_PREDTHR] = 0.01;
ParMin[P_HOLDBARS] = 1; ParMax[P_HOLDBARS] = 30; ParStep[P_HOLDBARS] = 1;
int p, a;
for(p=0; p<NPAR; p++)
{
ArmsCount[p] = calcArms(ParMin[p], ParMax[p], ParStep[p]);
CurArm[p] = 0;
for(a=0; a<ArmsCount[p]; a++)
{
Q[p][a] = 0;
Ncnt[p][a] = 0;
}
}
}
void pickParams()
{
int p;
for(p=0; p<NPAR; p++)
CurArm[p] = selectArm(p);
}
// -----------------------------
// Feature helpers (your originals, lite-C safe)
// -----------------------------
function fractalDimKatz(vars P, int N)
{
if(N < 2) return 1.0;
var L = 0;
int i;
for(i=0; i<N-1; i++)
L += abs(P[i] - P[i+1]);
var d = 0;
for(i=1; i<N; i++)
{
var di = abs(P[i] - P[0]);
if(di > d) d = di;
}
if(L <= 0 || d <= 0) return 1.0;
var n = (var)N;
var fd = log(n) / (log(n) + log(d / L));
return clamp(fd, 1.0, 2.0);
}
function linSlope(vars P, int N)
{
if(N < 2) return 0;
var sumT=0, sumP=0, sumTT=0, sumTP=0;
int i;
for(i=0; i<N; i++)
{
var t = (var)i;
sumT += t;
sumP += P[i];
sumTT += t*t;
sumTP += t*P[i];
}
var denom = (var)N*sumTT - sumT*sumT;
if(abs(denom) < 1e-12) return 0;
return ((var)N*sumTP - sumT*sumP) / denom;
}
function stdevReturns(vars R, int N)
{
if(N < 2) return 0;
var mean = 0;
int i;
for(i=0; i<N; i++) mean += R[i];
mean /= (var)N;
var v = 0;
for(i=0; i<N; i++)
{
var d = R[i] - mean;
v += d*d;
}
v /= (var)(N-1);
return sqrt(max(0, v));
}
function run()
{
// ------------------------------------------------------------------------
// SESSION / DATA SETTINGS
// ------------------------------------------------------------------------
BarPeriod = 1440;
StartDate = 20100101;
EndDate = 0;
set(PLOTNOW|RULES|LOGFILE);
asset("EUR/USD");
algo("FRACTAL2TF_EUR_RL_v1");
var eps = 1e-12;
DataSplit = 50;
// LookBack must cover the MAX possible TF * MAX window.
// Max TF is 12, max window here is 220, plus padding.
LookBack = 3000;
// ------------------------------------------------------------------------
// One-time init
// ------------------------------------------------------------------------
static int Inited = 0;
static int PrevOpenTotal = 0;
static var LastBalance = 0;
static int Flip = 0; // for forced Train trades
string LogFN = "Log\\FRACTAL2TF_EUR_RL_v1.csv";
if(is(FIRSTINITRUN))
{
file_delete(LogFN);
// lite-C safe header (multiple calls)
file_append(LogFN,"Date,Time,Mode,Bar,");
file_append(LogFN,"TF1,TF2,TF2d,FDLen1,SlopeLen1,VolLen1,FDLen2,SlopeLen2,VolLen2,");
file_append(LogFN,"LevScale,MaxLev,PredThr,HoldBars,");
file_append(LogFN,"FD1,Slope1,Vol1,FD2,Slope2,Vol2,");
file_append(LogFN,"PredL,PredS,Pred,Lev,Reward\n");
Inited = 0;
PrevOpenTotal = 0;
LastBalance = 0;
Flip = 0;
}
if(!Inited)
{
initParNames();
initParamsRL();
pickParams();
LastBalance = Balance;
PrevOpenTotal = NumOpenTotal;
Inited = 1;
}
// ------------------------------------------------------------------------
// Convert chosen arms -> parameter values (current episode)
// ------------------------------------------------------------------------
int TF1 = (int)armValue(P_TF1, CurArm[P_TF1]);
int TF2d = (int)armValue(P_TF2D, CurArm[P_TF2D]);
int TF2 = TF1 + TF2d;
if(TF2 > 12) TF2 = 12;
int FDLen1 = (int)armValue(P_FDLEN1, CurArm[P_FDLEN1]);
int SlopeLen1 = (int)armValue(P_SLOPELEN1, CurArm[P_SLOPELEN1]);
int VolLen1 = (int)armValue(P_VOLLEN1, CurArm[P_VOLLEN1]);
int FDLen2 = (int)armValue(P_FDLEN2, CurArm[P_FDLEN2]);
int SlopeLen2 = (int)armValue(P_SLOPELEN2, CurArm[P_SLOPELEN2]);
int VolLen2 = (int)armValue(P_VOLLEN2, CurArm[P_VOLLEN2]);
var LevScale = armValue(P_LEVSCALE, CurArm[P_LEVSCALE]);
var MaxLev = armValue(P_MAXLEV, CurArm[P_MAXLEV]);
var PredThr = armValue(P_PREDTHR, CurArm[P_PREDTHR]);
int HoldBars = (int)armValue(P_HOLDBARS, CurArm[P_HOLDBARS]);
// ------------------------------------------------------------------------
// Build series (2 TF)
// ------------------------------------------------------------------------
TimeFrame = TF1;
vars P1 = series(priceClose());
vars R1 = series(log(max(eps,P1[0]) / max(eps,P1[1])));
vars FD1S = series(0);
vars Slope1S = series(0);
vars Vol1S = series(0);
TimeFrame = TF2;
vars P2 = series(priceClose());
vars R2 = series(log(max(eps,P2[0]) / max(eps,P2[1])));
vars FD2S = series(0);
vars Slope2S = series(0);
vars Vol2S = series(0);
TimeFrame = 1;
// Warmup gate based on current episode params
int Need1 = max(max(FDLen1, SlopeLen1), VolLen1) + 5;
int Need2 = max(max(FDLen2, SlopeLen2), VolLen2) + 5;
int WarmupBars = max(TF1*Need1, TF2*Need2) + 10;
if(Bar < WarmupBars)
return;
// Do NOT block TRAIN during LOOKBACK
if(is(LOOKBACK) && !Train)
return;
// ------------------------------------------------------------------------
// Compute features
// ------------------------------------------------------------------------
TimeFrame = TF1;
FD1S[0] = fractalDimKatz(P1, FDLen1);
Slope1S[0] = linSlope(P1, SlopeLen1);
Vol1S[0] = stdevReturns(R1, VolLen1);
TimeFrame = TF2;
FD2S[0] = fractalDimKatz(P2, FDLen2);
Slope2S[0] = linSlope(P2, SlopeLen2);
Vol2S[0] = stdevReturns(R2, VolLen2);
TimeFrame = 1;
// Feature vector for ML
var Sig[6];
Sig[0] = FD1S[0];
Sig[1] = Slope1S[0];
Sig[2] = Vol1S[0];
Sig[3] = FD2S[0];
Sig[4] = Slope2S[0];
Sig[5] = Vol2S[0];
// ------------------------------------------------------------------------
// Trading logic
// ------------------------------------------------------------------------
int MethodBase = PERCEPTRON + FUZZY + BALANCED;
int MethodRet = MethodBase + RETURNS;
var PredL=0, PredS=0, Pred=0, Lev=0;
// time-based exit
if(NumOpenTotal > 0)
for(open_trades)
if(TradeIsOpen && TradeBars >= HoldBars)
exitTrade(ThisTrade);
if(Train)
{
// Forced alternating trades so ML always gets samples
if(NumOpenTotal == 0)
{
Flip = 1 - Flip;
LastBalance = Balance; // episode start for reward
if(Flip)
{
adviseLong(MethodRet, 0, Sig, 6);
Lots = 1; enterLong();
}
else
{
adviseShort(MethodRet, 0, Sig, 6);
Lots = 1; enterShort();
}
}
}
else
{
PredL = adviseLong(MethodBase, 0, Sig, 6);
PredS = adviseShort(MethodBase, 0, Sig, 6);
// Bootstrap if model has no signal yet
if(NumOpenTotal == 0 && PredL == 0 && PredS == 0)
{
LastBalance = Balance; // episode start for reward
var s = Sig[1] + Sig[4];
if(s > 0) { Lots=1; enterLong(); }
else if(s < 0) { Lots=1; enterShort(); }
else
{
if(random(1) < 0.5) { Lots=1; enterLong(); }
else { Lots=1; enterShort(); }
}
}
else
{
Pred = PredL - PredS;
Lev = clamp(Pred * LevScale, -MaxLev, MaxLev);
if(Lev > PredThr) { exitShort(); Lots=1; enterLong(); }
else if(Lev < -PredThr) { exitLong(); Lots=1; enterShort(); }
else { exitLong(); exitShort(); }
}
}
// ------------------------------------------------------------------------
// RL reward + update (episode ends when we go from having positions to flat)
// ------------------------------------------------------------------------
var Reward = 0;
if(PrevOpenTotal > 0 && NumOpenTotal == 0)
{
Reward = Balance - LastBalance;
if(Reward != 0)
{
int p;
for(p=0; p<NPAR; p++)
updateArm(p, CurArm[p], Reward);
}
// Next episode parameters
pickParams();
LastBalance = Balance;
}
PrevOpenTotal = NumOpenTotal;
// ------------------------------------------------------------------------
// Logging (one-literal format string)
// ------------------------------------------------------------------------
string ModeStr = "Trade";
if(Train) ModeStr = "Train";
else if(Test) ModeStr = "Test";
file_append(LogFN, strf("%04i-%02i-%02i,%02i:%02i,%s,%d,%d,%d,%d,%d,%d,%d,%d,%d,%d,%.6f,%.3f,%.6f,%d,%.6f,%.8f,%.8f,%.6f,%.8f,%.8f,%.8f,%.8f,%.8f,%.6f,%.6f\n",
year(0),month(0),day(0), hour(0),minute(0),
ModeStr, Bar,
TF1, TF2, TF2d,
FDLen1, SlopeLen1, VolLen1,
FDLen2, SlopeLen2, VolLen2,
LevScale, MaxLev, PredThr, HoldBars,
Sig[0], Sig[1], Sig[2], Sig[3], Sig[4], Sig[5],
PredL, PredS, Pred, Lev, Reward
));
// ------------------------------------------------------------------------
// Plots
// ------------------------------------------------------------------------
plot("FD_TF1", Sig[0], NEW, 0);
plot("FD_TF2", Sig[3], 0, 0);
plot("Slope_TF1", Sig[1], 0, 0);
plot("Slope_TF2", Sig[4], 0, 0);
plot("Vol_TF1", Sig[2], 0, 0);
plot("Vol_TF2", Sig[5], 0, 0);
plot("Pred", Pred, 0, 0);
plot("Lev", Lev, 0, 0);
plot("Reward", Reward, 0, 0);
}