// ============================================================================
// LocalOT_EA.c
// ============================================================================
// 1. Zorro calls run() once at initialization and then once at the end of every
// completed bar. This script is therefore bar-driven, not tick-driven.
//
// 2. The script sets BarPeriod = 60 in INITRUN, so the intended working
// timeframe is H1. The annualization constant ANNUAL_H1 is based on
// 252 trading days * 24 H1 bars per day.
//
// 3. Stop and TakeProfit are set as price distances before entering a trade.
// This follows Zorro's normal trade setup order.
//
// 4. This script intentionally avoids the C ternary operator. lite-C supports
// ifelse(), but this script uses plain if/else branches for clarity.
//
// 5. The mathematical routines are a practical implementation of the original
// EA logic, not a full academic local-volatility/optimal-transport library.
// They should be tested and validated on the target asset before any live
// use.
//
// 6. Broker-specific lot constraints, margin rules, spread, commission, and pip
// cost must be checked in the selected Zorro asset list. The variable
// InpMaxLots is only a local safety cap.
//
// DATA FLOW SUMMARY
// -----------------
// Every bar after LookBack:
//
// 1. loadBars()
// Loads recent closes, log returns, and realized volatility estimates.
//
// 2. calibrateAll() or lightUpdate()
// Full recalibration is done every InpRetrainEvery bars.
// Between full calibrations, only rate/proxy and local beta are updated.
//
// 3. signalLocalVolOT()
// Builds a directional signal from:
// - realized/reference volatility ratio,
// - momentum,
// - Hull-White short-rate proxy,
// - lambda adjustment.
//
// 4. openLocalVolTrade()
// Sets Stop, TakeProfit, Lots, then enters long or short.
//
// 5. shouldCloseLocalVolOT()
// Exits when volatility has normalized.
//
// 6. ddExceeded()
// Closes positions if the strategy drawdown exceeds InpMaxDD.
//
// RISK WARNING
// ------------
// This is research/educational code. It is not investment advice, and it should
// not be used live without independent review, backtesting, forward testing,
// broker-cost validation, and robust risk controls.
//
// ============================================================================
// ---------------------------------------------------------------------------
// Global constants
// ---------------------------------------------------------------------------
// Maximum number of observations stored in the fixed-size arrays below.
// lite-C supports normal arrays; fixed arrays are simple and predictable here.
// Keep this above InpCalibBars + InpVolWindow + 5.
#define MAX_OBS 600
// Number of H1 bars used for annualizing hourly variance.
// 252 trading days * 24 hourly bars. This matches the original H1 assumption.
#define ANNUAL_H1 (252.0*24.0)
// ---------------------------------------------------------------------------
// User parameters
// ---------------------------------------------------------------------------
// ----- CEV model parameters -----
// Reference annual volatility. Example: 0.10 means 10 percent annual vol.
var InpSigmaRef = 0.10;
// CEV elasticity gamma. Values below 1 imply negative skew behavior.
var InpGammaRef = 0.85;
// Cost exponent p used in the simplified optimal-transport beta formula.
var InpCostExp = 4.0;
// ----- Hull-White short-rate model parameters -----
// Prior mean-reversion speed "a" for the short-rate proxy.
var InpHW_a = 0.40;
// Prior short-rate volatility.
var InpHW_SigmaR = 0.015;
// Prior initial short rate.
var InpHW_r0 = 0.025;
// Prior correlation between price/log-return shocks and short-rate shocks.
var InpCorrelRef = -0.30;
// Bayesian prior weight used when blending input priors with empirical
// estimates. 0 means rely only on data; 1 means rely only on priors.
var InpPriorWeight = 0.40;
// ----- Calibration parameters -----
// Number of bars used for calibration windows.
int InpCalibBars = 500;
// Window length, in bars, for realized volatility estimation.
int InpVolWindow = 20;
// Full recalibration interval, in bars.
int InpRetrainEvery = 48;
// Lambda update step. Lambda is a scalar correction term used in the
// potential/proxy calculation.
var InpLambdaStep = 0.02;
// Retained for parity with the original EA. The current lite-C version does not
// use it as a stopping criterion.
var InpTol = 1e-4;
// ----- Signal parameters -----
// In mean-reversion mode, trades are considered when realized/reference
// volatility is above this ratio.
var InpVolRatioHigh = 1.25;
// In trend-following mode, trades are considered when realized/reference
// volatility is below this ratio.
var InpVolRatioLow = 0.75;
// Exit threshold. In mean-reversion mode, exit when ratio falls below this.
// In trend-following mode, exit when ratio rises above 1 / this value.
var InpVolRatioExit = 1.05;
// Number of bars used for the simple momentum component.
int InpMomBars = 8;
// 1 = mean-reversion mode; 0 = trend-following mode.
int InpMeanRevMode = 1;
// ----- Risk and execution parameters -----
// Percent of approximate strategy equity to risk per trade.
var InpRiskPct = 1.0;
// Maximum drawdown, in percent, from this script's equity peak. If exceeded,
// all open positions are closed.
var InpMaxDD = 10.0;
// Stop-loss multiplier. SL distance is based on local volatility.
var InpSL_k = 2.5;
// Take-profit multiplier. TP is proportional to SL.
var InpTP_k = 3.5;
// Local cap used by lotsFromSL(). This is not a broker query.
var InpMaxLots = 100.0;
// ---------------------------------------------------------------------------
// Model state variables
// ---------------------------------------------------------------------------
// These variables hold the current calibrated model state. They are global
// because run() is called repeatedly by Zorro and local variables do not retain
// values between calls.
// Current calibrated CEV volatility.
var GM_SigmaCev;
// Current calibrated CEV elasticity.
var GM_GammaCev;
// Current calibrated Hull-White mean-reversion speed.
var GM_HWA;
// Current calibrated Hull-White short-rate volatility.
var GM_HWSigmaR;
// Current calibrated Hull-White drift/intercept proxy.
// In a standard form dr = (b - a*r)dt + sigma_r dW, this is the "b" term.
var GM_HWB0;
// Current calibrated return/rate correlation.
var GM_XiCorr;
// Current CEV reference variance at the latest price.
var GM_SigmaB2;
// Current local/OT-adjusted variance used for SL/TP sizing.
var GM_Beta11Star;
// Current lambda correction parameter.
var GM_Lambda;
// Current short-rate proxy.
var GM_RCurrent;
// Becomes 1 after the first successful full calibration.
int GM_Valid;
// Counts bars since the last full calibration.
int GM_BarsSinceCalib;
// ---------------------------------------------------------------------------
// Potential/proxy coefficients
// ---------------------------------------------------------------------------
// The original EA uses a local quadratic potential approximation. These
// coefficients store that approximation around the current log-price and rate.
// phi(z) is not directly evaluated in this script, but phi_z and phi_zz are
// used by lema412().
var GPhi_C0;
var GPhi_C1;
var GPhi_C2;
var GPhi_C3;
var GPhi_Z0;
var GPhi_R0;
// ---------------------------------------------------------------------------
// Rolling data arrays
// ---------------------------------------------------------------------------
// Index convention:
// index 0 = current/latest bar
// index 1 = previous bar
// index i = i bars ago
//
// GPrice stores prices.
// GLRet stores log returns log(price[i] / price[i+1]).
// GRVol stores realized annualized volatility estimates.
// GRate stores the short-rate proxy path.
var GPrice[MAX_OBS];
var GLRet[MAX_OBS];
var GRVol[MAX_OBS];
var GRate[MAX_OBS];
// Number of valid price observations in GPrice.
int GNp;
// Number of valid realized-vol observations in GRVol.
int GNr;
// Becomes 1 after the first calibration and banner print.
int GStarted;
// Strategy-equity peak used for drawdown control.
var GEqPeak;
// ===========================================================================
// Utility functions
// ===========================================================================
// ---------------------------------------------------------------------------
// phiDz
// ---------------------------------------------------------------------------
// Returns first derivative of the local quadratic potential approximation
// with respect to log price Z.
//
// With the current approximation:
// phi_z(Z) = C1 + 2*C2*(Z - Z0)
//
// Parameters:
// Z - log price at which the derivative is evaluated.
//
// Returns:
// First derivative phi_z.
// ---------------------------------------------------------------------------
var phiDz(var Z)
{
return GPhi_C1 + 2.0*GPhi_C2*(Z - GPhi_Z0);
}
// ---------------------------------------------------------------------------
// phiDzz
// ---------------------------------------------------------------------------
// Returns second derivative of the local quadratic potential approximation.
//
// With the current approximation:
// phi_zz = 2*C2
//
// Returns:
// Second derivative phi_zz.
// ---------------------------------------------------------------------------
var phiDzz()
{
return 2.0*GPhi_C2;
}
// ---------------------------------------------------------------------------
// strategyEquity
// ---------------------------------------------------------------------------
// Calculates an approximate strategy equity value from Zorro's global capital
// and profit variables.
//
// This is used for risk sizing and drawdown checks. For portfolio-level
// scripts, confirm whether Capital + ProfitClosed + ProfitOpen is the desired
// equity base.
//
// Returns:
// Approximate current strategy equity.
// ---------------------------------------------------------------------------
var strategyEquity()
{
// Zorro-native approximation of the strategy equity.
// Capital is the initial capital; ProfitClosed and ProfitOpen are strategy P/L.
var E = Capital + ProfitClosed + ProfitOpen;
if(E <= 0.0)
E = Capital;
return E;
}
// ---------------------------------------------------------------------------
// initModel
// ---------------------------------------------------------------------------
// Initializes all model-state variables to their prior/default values.
//
// Called from run() during INITRUN. This is important because static/global
// variables can persist between script restarts when auto-compilation behavior
// does not force a fresh process.
//
// Parameters:
// none
//
// Returns:
// nothing
// ---------------------------------------------------------------------------
void initModel()
{
GM_SigmaCev = InpSigmaRef;
GM_GammaCev = InpGammaRef;
GM_HWA = InpHW_a;
GM_HWSigmaR = InpHW_SigmaR;
GM_HWB0 = InpHW_r0 * InpHW_a;
GM_XiCorr = InpCorrelRef;
GM_SigmaB2 = InpSigmaRef * InpSigmaRef;
GM_Beta11Star = InpSigmaRef * InpSigmaRef;
GM_Lambda = 0.0;
GM_RCurrent = InpHW_r0;
GM_Valid = 0;
GM_BarsSinceCalib = 0;
GPhi_C0 = 0.0;
GPhi_C1 = 0.0;
GPhi_C2 = 0.0;
GPhi_C3 = 0.0;
GPhi_Z0 = 0.0;
GPhi_R0 = 0.0;
GNp = 0;
GNr = 0;
GStarted = 0;
GEqPeak = Capital;
}
// ---------------------------------------------------------------------------
// loadBars
// ---------------------------------------------------------------------------
// Loads recent prices and calculates log returns and realized volatilities.
//
// The function uses Zorro priceClose(i), where i is the bar offset:
// priceClose(0) = current/latest completed bar close
// priceClose(1) = previous bar close
//
// It fills:
// GPrice[0..GNp-1]
// GLRet[0..GNp-2]
// GRVol[0..GNr-1]
//
// Realized volatility is calculated from InpVolWindow log returns and then
// annualized by ANNUAL_H1.
//
// Returns:
// 1 when enough valid data was loaded.
// 0 when data is insufficient or invalid.
// ---------------------------------------------------------------------------
int loadBars()
{
int Need = InpCalibBars + InpVolWindow + 5;
if(Need > MAX_OBS)
Need = MAX_OBS;
if(Need < InpVolWindow + 25)
return 0;
if(Bar < LookBack)
return 0;
GNp = Need;
int i;
for(i = 0; i < GNp; i++)
{
var C = priceClose(i);
if(C <= 0.0)
return 0;
GPrice[i] = C;
}
// Log returns in most-recent-first order.
for(i = 0; i < GNp-1; i++)
{
if(GPrice[i+1] <= 0.0)
GLRet[i] = 0.0;
else
GLRet[i] = log(GPrice[i] / GPrice[i+1]);
}
GNr = GNp - InpVolWindow;
if(GNr < 1)
return 0;
// Rolling sample variance of log returns, then annualized volatility.
for(i = 0; i < GNr; i++)
{
var S = 0.0;
var S2 = 0.0;
int j;
for(j = i; j < i + InpVolWindow; j++)
{
S += GLRet[j];
S2 += GLRet[j]*GLRet[j];
}
var VarH1 = (S2 - S*S/InpVolWindow) / (InpVolWindow - 1);
if(VarH1 < 0.0)
VarH1 = 0.0;
GRVol[i] = sqrt(VarH1 * ANNUAL_H1);
}
return 1;
}
// ---------------------------------------------------------------------------
// estimateRate
// ---------------------------------------------------------------------------
// Estimates the current short-rate proxy from realized price drift.
//
// Original rationale:
// E[dlog(S)] approx r - 0.5*sigma^2
// Therefore:
// r approx drift + 0.5*sigma^2
//
// Since spot price data does not directly contain a short-rate curve, this is
// only a practical proxy. The estimate is blended with InpHW_r0 using
// InpPriorWeight.
//
// The function also generates a smooth Hull-White-style rate path in GRate.
// This path is used by calibHW() and calibCorr().
//
// Parameters:
// none
//
// Returns:
// nothing
// ---------------------------------------------------------------------------
void estimateRate()
{
int W = InpCalibBars;
if(W > GNp - 1)
W = GNp - 1;
if(W < 1)
return;
var SumRet = 0.0;
int i;
for(i = 0; i < W; i++)
SumRet += GLRet[i];
var DriftAnn = (SumRet / W) * ANNUAL_H1;
var RVol0 = InpSigmaRef;
if(GNr > 0)
RVol0 = GRVol[0];
var REst = DriftAnn + 0.5*RVol0*RVol0;
REst = clamp(REst,-0.05,0.20);
// Bayesian-style blend of empirical rate proxy and prior rate.
GM_RCurrent = (1.0 - InpPriorWeight) * REst + InpPriorWeight * InpHW_r0;
// Build a decaying proxy path around the current rate.
var A = GM_HWA;
var BA = GM_HWB0 / max(A,0.001);
for(i = 0; i < GNp; i++)
{
var T = i / ANNUAL_H1;
var Decay = exp(-A*T);
GRate[i] = GM_RCurrent*Decay + BA*(1.0 - Decay);
}
GRate[0] = GM_RCurrent;
}
// ===========================================================================
// Calibration blocks
// ===========================================================================
// ---------------------------------------------------------------------------
// calibCEV
// ---------------------------------------------------------------------------
// Calibrates a simplified CEV relationship from realized volatility and price.
//
// The CEV variance scaling is represented by:
//
// sigma(S) = sigma_ref * (S/S0)^(gamma - 1)
//
// Taking logs gives:
//
// log(sigma) = log(sigma_ref) + (gamma - 1)*log(S/S0)
//
// This function runs a simple linear regression of log(realized vol) on
// log(price). The slope implies gamma - 1. The intercept implies sigma.
//
// Empirical estimates are clamped and then blended with the user priors.
//
// Parameters:
// none
//
// Returns:
// nothing
// ---------------------------------------------------------------------------
void calibCEV()
{
int N = GNr - 1;
if(N > InpCalibBars)
N = InpCalibBars;
if(N < 20)
return;
var SX = 0.0;
var SY = 0.0;
var SXX = 0.0;
var SXY = 0.0;
int Cnt = 0;
int i;
for(i = 0; i < N; i++)
{
if(GRVol[i] < 1e-8)
continue;
if(GPrice[i] <= 0.0)
continue;
var Y = log(GRVol[i]);
var X = log(GPrice[i]);
SX += X;
SY += Y;
SXX += X*X;
SXY += X*Y;
Cnt += 1;
}
if(Cnt < 15)
return;
var D = Cnt*SXX - SX*SX;
if(abs(D) < 1e-12)
return;
var Slope = (Cnt*SXY - SX*SY) / D;
var Intercept = (SY - Slope*SX) / Cnt;
var GammaNew = Slope + 1.0;
var SigmaNew = exp(Intercept);
// Clamp empirical estimates to prevent unstable model parameters.
GammaNew = clamp(GammaNew,0.30,1.70);
SigmaNew = clamp(SigmaNew,0.01,3.0);
// Blend empirical estimate with prior.
var W = InpPriorWeight;
GM_GammaCev = W*InpGammaRef + (1.0-W)*GammaNew;
GM_SigmaCev = W*InpSigmaRef + (1.0-W)*SigmaNew;
printf("\n[CEV] sigma=%.4f gamma=%.4f n=%i prior_w=%.2f",
GM_SigmaCev, GM_GammaCev, Cnt, W);
}
// ---------------------------------------------------------------------------
// calibHW
// ---------------------------------------------------------------------------
// Calibrates a simplified Hull-White short-rate proxy.
//
// Model form used here:
//
// dr = (b - a*r) dt + sigma_r dW
//
// The function estimates alpha and beta from:
//
// dr approx alpha + beta*r
//
// Then:
// a approx -beta / dt
// b approx alpha / dt
//
// sigma_r is estimated from residual variance. All estimates are clamped and
// blended with priors to avoid degenerate values.
//
// Parameters:
// none
//
// Returns:
// nothing
// ---------------------------------------------------------------------------
void calibHW()
{
int N = GNp - 2;
if(N > InpCalibBars)
N = InpCalibBars;
if(N < 20)
return;
var DT = 1.0 / ANNUAL_H1;
var SX = 0.0;
var SY = 0.0;
var SXX = 0.0;
var SXY = 0.0;
var SRes = 0.0;
int Cnt = 0;
int i;
for(i = 0; i < N-1; i++)
{
var R0 = GRate[i+1];
var DR = GRate[i] - R0;
SX += R0;
SY += DR;
SXX += R0*R0;
SXY += R0*DR;
Cnt += 1;
}
if(Cnt < 10)
return;
var D = Cnt*SXX - SX*SX;
var AOls = 0.0;
var BOls = 0.0;
var SROls = 0.0;
if(abs(D) > 1e-14)
{
var Beta = (Cnt*SXY - SX*SY) / D;
var Alpha = (SY - Beta*SX) / Cnt;
AOls = -Beta / DT;
BOls = Alpha / DT;
for(i = 0; i < Cnt; i++)
{
var R0b = GRate[i+1];
var DRb = GRate[i] - R0b;
var Res = DRb - (Alpha + Beta*R0b);
SRes += Res*Res;
}
SROls = sqrt(SRes / Cnt / DT);
}
// Prior-blended and clamped estimates.
var W = InpPriorWeight;
GM_HWA = max(0.05, W*InpHW_a + (1.0-W)*clamp(AOls,0.05,5.0));
GM_HWB0 = W*(InpHW_r0*InpHW_a) + (1.0-W)*BOls;
GM_HWSigmaR = max(0.005, W*InpHW_SigmaR + (1.0-W)*clamp(SROls,0.005,0.50));
printf("\n[HW] a=%.4f sigmar=%.4f b0=%.4f",
GM_HWA, GM_HWSigmaR, GM_HWB0);
}
// ---------------------------------------------------------------------------
// calibCorr
// ---------------------------------------------------------------------------
// Estimates the empirical correlation between:
// - changes in the short-rate proxy, and
// - log returns.
//
// The estimate is blended with InpCorrelRef. If the empirical correlation is
// very close to zero, the prior weight is increased. This prevents a flat or
// noisy rate proxy from completely removing the model's directional component.
//
// Parameters:
// none
//
// Returns:
// nothing
// ---------------------------------------------------------------------------
void calibCorr()
{
int N = GNp;
if(GNr < N)
N = GNr;
N -= 2;
if(N > InpCalibBars)
N = InpCalibBars;
if(N < 20)
return;
var SX = 0.0;
var SY = 0.0;
var SXX = 0.0;
var SYY = 0.0;
var SXY = 0.0;
int Cnt = 0;
int i;
for(i = 0; i < N-1; i++)
{
var DR = GRate[i] - GRate[i+1];
var DZ = GLRet[i];
SX += DR;
SY += DZ;
SXX += DR*DR;
SYY += DZ*DZ;
SXY += DR*DZ;
Cnt += 1;
}
if(Cnt < 10)
return;
var Cov = (SXY - SX*SY/Cnt) / Cnt;
var VX = (SXX - SX*SX/Cnt) / Cnt;
var VY = (SYY - SY*SY/Cnt) / Cnt;
if(VX < 1e-14)
return;
if(VY < 1e-14)
return;
var XiOls = Cov / sqrt(VX*VY);
XiOls = clamp(XiOls,-0.999,0.999);
var W = InpPriorWeight;
// If data suggests near-zero correlation, avoid over-trusting the noisy
// estimate. This implements the source EA's fix for flat-rate cases.
if(abs(XiOls) < 0.05)
W = 0.80;
GM_XiCorr = W*InpCorrelRef + (1.0-W)*XiOls;
printf("\n[CORR] xi=%.4f OLS=%.4f prior_w=%.2f",
GM_XiCorr, XiOls, W);
}
// ===========================================================================
// Mathematical core
// ===========================================================================
// ---------------------------------------------------------------------------
// refVar
// ---------------------------------------------------------------------------
// Calculates the CEV reference variance at a given log price.
//
// The CEV variance proxy is:
//
// sigma_b^2(S) = sigma_cev^2 * (S/S0)^(2*(gamma_cev - 1))
//
// Parameters:
// LogPrice - log(S), where S is the price.
//
// Returns:
// Reference variance. A small positive floor is applied.
// ---------------------------------------------------------------------------
var refVar(var LogPrice)
{
var S = exp(LogPrice);
var S0 = GPrice[GNp - 1];
if(S0 <= 0.0)
S0 = GPrice[0];
var Expon = 2.0*(GM_GammaCev - 1.0);
var Ratio = S / S0;
if(Ratio < 0.0001)
Ratio = 0.0001;
return max(1e-10, GM_SigmaCev*GM_SigmaCev*pow(Ratio,Expon));
}
// ---------------------------------------------------------------------------
// buildPhi
// ---------------------------------------------------------------------------
// Builds a local quadratic potential approximation around current log price
// and current short-rate proxy.
//
// The approximation is intentionally simple. It uses:
// - CEV reference variance,
// - current realized variance,
// - current rate proxy.
//
// Parameters:
// Z0 - current log price.
// R0 - current short-rate proxy.
//
// Returns:
// nothing
// ---------------------------------------------------------------------------
void buildPhi(var Z0, var R0)
{
GPhi_Z0 = Z0;
GPhi_R0 = R0;
var SB2 = refVar(Z0);
var RV2 = SB2;
if(GNr > 0)
RV2 = GRVol[0]*GRVol[0];
// Mix of reference variance and realized variance. The floor prevents
// division by zero or excessive curvature.
var Mix = 0.5*(SB2 + RV2);
Mix = max(Mix,1e-8);
GPhi_C2 = 1.0 / Mix;
GPhi_C1 = (R0 - 0.5*Mix) / Mix;
GPhi_C0 = GM_Lambda;
GPhi_C3 = GPhi_C1 / max(GM_HWA,0.05);
}
// ---------------------------------------------------------------------------
// lema412
// ---------------------------------------------------------------------------
// Practical implementation of the EA's Lemma 4.12-inspired beta calculation.
//
// Inputs are simplified/local quantities:
// PhiZZ - second derivative of potential.
// PhiZ - first derivative of potential.
// SigmaB2 - CEV reference variance.
// XiRef - return/rate correlation.
// Sigr2 - short-rate variance.
// P - cost exponent.
//
// The function returns beta11*, an adjusted local variance used for stop-loss
// and take-profit sizing.
//
// Stability guards:
// - Diff floor prevents invalid powers.
// - D clamp prevents explosive amplification when SigmaB2 is small.
// - Final beta is clamped to [0.5*SigmaB2, 2.0*SigmaB2].
//
// Returns:
// Adjusted local variance beta11*.
// ---------------------------------------------------------------------------
var lema412(var PhiZZ, var PhiZ, var SigmaB2, var XiRef, var Sigr2, var P)
{
var S = XiRef*XiRef*Sigr2;
var Diff = SigmaB2 - S;
if(Diff < 1e-12)
return max(SigmaB2,S + 1e-12);
var DMax = 2.0 / SigmaB2;
var D = PhiZZ - PhiZ;
D = clamp(D,-DMax,DMax);
var DiffP = pow(Diff,P);
var A = DiffP * D / (2.0*(P*P - 1.0));
var Disc = A*A + 4.0*pow(Diff,2.0*P);
if(Disc < 0.0)
Disc = 0.0;
var U = (A + sqrt(Disc))*0.5;
if(U <= 0.0)
return SigmaB2;
var Beta11 = S + pow(U,1.0/P);
return clamp(Beta11,0.50*SigmaB2,2.0*SigmaB2);
}
// ---------------------------------------------------------------------------
// calibrateAll
// ---------------------------------------------------------------------------
// Runs a full model calibration cycle:
//
// 1. CEV calibration.
// 2. Hull-White proxy calibration.
// 3. Correlation calibration.
// 4. Rate proxy update.
// 5. Potential/proxy build.
// 6. beta11* calculation.
// 7. lambda update.
//
// This function sets GM_Valid = 1 when complete.
//
// Parameters:
// none
//
// Returns:
// nothing
// ---------------------------------------------------------------------------
void calibrateAll()
{
printf("\n[LocalVolOT] Full calibration");
calibCEV();
calibHW();
calibCorr();
estimateRate();
var Z0 = log(GPrice[0]);
var R0 = GM_RCurrent;
buildPhi(Z0,R0);
var SB2 = refVar(Z0);
var Sigr2 = GM_HWSigmaR*GM_HWSigmaR;
GM_SigmaB2 = SB2;
GM_Beta11Star = lema412(phiDzz(),phiDz(Z0),SB2,GM_XiCorr,Sigr2,InpCostExp);
var RV2 = SB2;
if(GNr > 0)
RV2 = GRVol[0]*GRVol[0];
// Lambda is adjusted toward the difference between realized variance and
// the CEV reference variance.
var Grad = RV2 - GM_SigmaB2;
GM_Lambda += InpLambdaStep * Grad;
GM_Lambda = clamp(GM_Lambda,-3.0,3.0);
GM_Valid = 1;
GM_BarsSinceCalib = 0;
var VolRatio = 1.0;
if(GM_SigmaB2 > 1e-10)
VolRatio = sqrt(RV2) / sqrt(GM_SigmaB2);
printf("\n[OT] sig_ref=%.2f%% sig_star=%.2f%% rv=%.2f%% ratio=%.3f lambda=%.4f r=%.3f%% xi=%.4f",
sqrt(GM_SigmaB2)*100.0,
sqrt(GM_Beta11Star)*100.0,
sqrt(RV2)*100.0,
VolRatio,
GM_Lambda,
GM_RCurrent*100.0,
GM_XiCorr);
}
// ---------------------------------------------------------------------------
// lightUpdate
// ---------------------------------------------------------------------------
// Performs a lighter per-bar model update between full calibrations.
//
// It updates:
// - short-rate proxy,
// - potential/proxy coefficients,
// - CEV reference variance,
// - beta11*.
//
// It does not rerun the CEV/HW/correlation regressions.
//
// Parameters:
// none
//
// Returns:
// nothing
// ---------------------------------------------------------------------------
void lightUpdate()
{
estimateRate();
var Z0 = log(GPrice[0]);
buildPhi(Z0,GM_RCurrent);
var SB2 = refVar(Z0);
var Sigr2 = GM_HWSigmaR*GM_HWSigmaR;
GM_SigmaB2 = SB2;
GM_Beta11Star = lema412(phiDzz(),phiDz(Z0),SB2,GM_XiCorr,Sigr2,InpCostExp);
}
// ===========================================================================
// Signal functions
// ===========================================================================
// ---------------------------------------------------------------------------
// signalLocalVolOT
// ---------------------------------------------------------------------------
// Generates the trade direction signal.
//
// Returns:
// +1 = long signal.
// -1 = short signal.
// 0 = no signal.
//
// Main components:
// VolRatio:
// realized volatility / CEV reference volatility.
//
// Mom:
// average recent log return over InpMomBars.
//
// RateDir:
// short-rate drift proxy scaled by rate volatility and correlation.
//
// LambdaDir:
// direction implied by the lambda correction.
//
// Dir:
// weighted sum of momentum, rate direction, and lambda direction.
//
// Signal interpretation:
// Mean-reversion mode:
// Only act when VolRatio is high. Fade the directional component.
//
// Trend-following mode:
// Only act when VolRatio is low. Follow the directional component.
// ---------------------------------------------------------------------------
int signalLocalVolOT()
{
if(!GM_Valid)
return 0;
var RV = GM_SigmaCev;
if(GNr > 0)
RV = GRVol[0];
var SB = sqrt(GM_SigmaB2);
if(SB < 1e-8)
return 0;
var VolRatio = RV / SB;
int MB = InpMomBars;
if(MB < 2)
MB = 2;
if(MB > GNp - 1)
MB = GNp - 1;
var Mom = 0.0;
int i;
for(i = 0; i < MB; i++)
Mom += GLRet[i];
Mom /= MB;
var DT = 1.0 / ANNUAL_H1;
var R0 = GM_RCurrent;
var DR = (GM_HWB0 - GM_HWA*R0) * DT;
var RateZ = 0.0;
if(GM_HWSigmaR > 1e-6)
RateZ = DR / (GM_HWSigmaR * sqrt(DT));
var RateDir = GM_XiCorr * RateZ;
var LambdaDir = -GM_Lambda * 0.5;
var Dir = 0.60*Mom + 0.25*RateDir*abs(GM_XiCorr) + 0.15*LambdaDir;
int Sig = 0;
if(InpMeanRevMode)
{
// High realized/reference volatility: fade the direction.
if(VolRatio >= InpVolRatioHigh)
{
if(Dir < -1e-5)
Sig = 1;
else if(Dir > 1e-5)
Sig = -1;
else if(RateDir > 0.01 && GM_XiCorr > 0.05)
Sig = -1;
else if(RateDir < -0.01 && GM_XiCorr < -0.05)
Sig = 1;
}
}
else
{
// Low realized/reference volatility: follow the direction.
if(VolRatio <= InpVolRatioLow)
{
if(Dir < -1e-5)
Sig = -1;
else if(Dir > 1e-5)
Sig = 1;
}
}
return Sig;
}
// ---------------------------------------------------------------------------
// shouldCloseLocalVolOT
// ---------------------------------------------------------------------------
// Decides whether existing positions should be closed because volatility has
// normalized.
//
// Returns:
// 1 = close open positions.
// 0 = keep positions open.
//
// Logic:
// Mean-reversion mode:
// close when VolRatio < InpVolRatioExit.
//
// Trend-following mode:
// close when VolRatio > 1 / InpVolRatioExit.
// ---------------------------------------------------------------------------
int shouldCloseLocalVolOT()
{
if(!GM_Valid)
return 0;
var RV = GM_SigmaCev;
if(GNr > 0)
RV = GRVol[0];
var SB = sqrt(GM_SigmaB2);
if(SB < 1e-8)
return 0;
var VolRatio = RV / SB;
if(InpMeanRevMode)
{
if(VolRatio < InpVolRatioExit)
return 1;
}
else
{
if(VolRatio > 1.0/InpVolRatioExit)
return 1;
}
return 0;
}
// ===========================================================================
// Execution and risk functions
// ===========================================================================
// ---------------------------------------------------------------------------
// calcStops
// ---------------------------------------------------------------------------
// Calculates stop-loss and take-profit distances.
//
// The stop distance combines:
// 1. Price move implied by local beta11* volatility.
// 2. A Hull-White/correlation contribution.
//
// The final Stop and TakeProfit values are distances in price units, not
// absolute price levels. They are later assigned to Zorro's Stop and
// TakeProfit variables before entering a trade.
//
// Parameters:
// Dir - trade direction. Currently not used directly, retained for
// source parity and future directional adjustments.
// SLDist - output pointer for stop-loss distance.
// TPDist - output pointer for take-profit distance.
//
// Returns:
// nothing
// ---------------------------------------------------------------------------
void calcStops(int Dir,var* SLDist,var* TPDist)
{
var S = GPrice[0];
var SigAnn = sqrt(GM_Beta11Star);
var SigH1 = SigAnn / sqrt(ANNUAL_H1);
var DistS = InpSL_k * SigH1 * S;
var HWContrib = GM_HWSigmaR * S * abs(GM_XiCorr) / sqrt(ANNUAL_H1);
var SL = sqrt(DistS*DistS + HWContrib*HWContrib);
var TP = SL * (InpTP_k / InpSL_k);
// MT5 source used 50 points, usually about 5 pips on 5-digit FX.
// Zorro's PIP variable converts this to the current asset's price units.
var MinD = 5.0 * PIP;
SL = max(SL,MinD);
TP = max(TP,MinD);
*SLDist = SL;
*TPDist = TP;
}
// ---------------------------------------------------------------------------
// lotsFromSL
// ---------------------------------------------------------------------------
// Converts stop-loss distance into a Zorro Lots value from risk percent.
//
// Approximate formula:
//
// risk cash = strategy equity * InpRiskPct / 100
// risk per lot = (SL distance / PIP) * PIPCost
// lots = floor(risk cash / risk per lot)
//
// If PIP or PIPCost are unavailable, the function falls back to 1 lot.
//
// Parameters:
// SLDist - stop-loss distance in price units.
//
// Returns:
// Number of lots to trade, clamped to [1, InpMaxLots] when possible.
// ---------------------------------------------------------------------------
var lotsFromSL(var SLDist)
{
if(SLDist <= 0.0)
return 0.0;
if(PIP <= 0.0)
return 1.0;
if(PIPCost <= 0.0)
return 1.0;
var RiskCash = strategyEquity() * InpRiskPct / 100.0;
var RiskPerLot = (SLDist / PIP) * PIPCost;
if(RiskPerLot <= 0.0)
return 1.0;
var L = floor(RiskCash / RiskPerLot);
if(L < 1.0)
L = 1.0;
if(InpMaxLots > 0.0)
L = min(L,InpMaxLots);
return L;
}
// ---------------------------------------------------------------------------
// openLocalVolTrade
// ---------------------------------------------------------------------------
// Opens a long or short trade from the model signal.
//
// Flow:
// 1. Calculate SL/TP distances.
// 2. Assign Zorro Stop and TakeProfit before entry.
// 3. Calculate Lots.
// 4. Close opposite position if present.
// 5. Enter new position if no same-direction position is open.
//
// Parameters:
// Dir - +1 for long, -1 for short.
//
// Returns:
// nothing
// ---------------------------------------------------------------------------
void openLocalVolTrade(int Dir)
{
var SLDist = 0.0;
var TPDist = 0.0;
calcStops(Dir,&SLDist,&TPDist);
// Zorro reads Stop, TakeProfit, and Lots when enterLong/enterShort is
// called, so they must be set before entry.
Stop = SLDist;
TakeProfit = TPDist;
Lots = lotsFromSL(SLDist);
if(Lots <= 0.0)
return;
var RV = GM_SigmaCev;
if(GNr > 0)
RV = GRVol[0];
var SB = sqrt(GM_SigmaB2);
var Ratio = 0.0;
if(SB > 0.0)
Ratio = RV / SB;
if(Dir > 0)
{
if(NumOpenShort > 0)
exitShort();
if(NumOpenLong == 0)
{
enterLong();
printf("\n[TRADE] BUY lots=%.2f stop=%.5f tp=%.5f sig_star=%.2f%% ratio=%.3f",
Lots, SLDist, TPDist, sqrt(GM_Beta11Star)*100.0, Ratio);
}
}
else if(Dir < 0)
{
if(NumOpenLong > 0)
exitLong();
if(NumOpenShort == 0)
{
enterShort();
printf("\n[TRADE] SELL lots=%.2f stop=%.5f tp=%.5f sig_star=%.2f%% ratio=%.3f",
Lots, SLDist, TPDist, sqrt(GM_Beta11Star)*100.0, Ratio);
}
}
}
// ---------------------------------------------------------------------------
// closeLocalVolPositions
// ---------------------------------------------------------------------------
// Closes all positions opened in the current long/short direction context.
//
// Parameters:
// Why - text printed to the Zorro log.
//
// Returns:
// nothing
// ---------------------------------------------------------------------------
void closeLocalVolPositions(string Why)
{
if(NumOpenLong > 0)
exitLong();
if(NumOpenShort > 0)
exitShort();
printf("\n[CLOSE] %s",Why);
}
// ---------------------------------------------------------------------------
// ddExceeded
// ---------------------------------------------------------------------------
// Checks whether the strategy drawdown exceeds InpMaxDD.
//
// The drawdown base is the peak of strategyEquity() observed since the script
// started. If the current equity falls below that peak by more than InpMaxDD
// percent, the function returns 1.
//
// Returns:
// 1 = maximum drawdown exceeded.
// 0 = drawdown is within limits.
// ---------------------------------------------------------------------------
int ddExceeded()
{
var E = strategyEquity();
if(E > GEqPeak)
GEqPeak = E;
if(GEqPeak <= 0.0)
return 0;
var DD = (GEqPeak - E) / GEqPeak * 100.0;
if(DD > InpMaxDD)
return 1;
return 0;
}
// ---------------------------------------------------------------------------
// printBanner
// ---------------------------------------------------------------------------
// Prints the initial calibrated model state to the Zorro log.
//
// Parameters:
// none
//
// Returns:
// nothing
// ---------------------------------------------------------------------------
void printBanner()
{
var RV = GM_SigmaCev;
if(GNr > 0)
RV = GRVol[0];
printf("\n==================================================");
printf("\nLocalVolOT v2 - Zorro lite-C conversion");
printf("\nCEV: sigma=%.3f gamma=%.3f",GM_SigmaCev,GM_GammaCev);
printf("\nHW: a=%.3f sigmar=%.4f r0=%.3f%%",GM_HWA,GM_HWSigmaR,GM_RCurrent*100.0);
printf("\nCorr: xi=%.4f prior_w=%.2f",GM_XiCorr,InpPriorWeight);
printf("\nVols: sig_ref=%.2f%% sig_star=%.2f%% rv=%.2f%%",
sqrt(GM_SigmaB2)*100.0, sqrt(GM_Beta11Star)*100.0, RV*100.0);
if(InpMeanRevMode)
printf("\nMode: MEAN-REVERSION risk=%.1f%%",InpRiskPct);
else
printf("\nMode: TREND-FOLLOW risk=%.1f%%",InpRiskPct);
printf("\n==================================================");
}
// ===========================================================================
// Zorro strategy entry point
// ===========================================================================
// ---------------------------------------------------------------------------
// run
// ---------------------------------------------------------------------------
// Main Zorro strategy function.
//
// Zorro execution behavior:
// - On the first initialization pass, is(INITRUN) is true.
// - During the LookBack period, is(LOOKBACK) is true.
// - After LookBack, this function runs once per completed bar.
// - On script exit, is(EXITRUN) is true.
//
// Main flow after LookBack:
// 1. Load recent bars.
// 2. Perform initial/full/light calibration.
// 3. Check drawdown limit.
// 4. Print model state.
// 5. Close existing trades if vol normalized.
// 6. Otherwise evaluate a new signal and enter if applicable.
//
// Returns:
// nothing
// ---------------------------------------------------------------------------
function run()
{
if(is(INITRUN))
{
// H1 timeframe, matching PERIOD_H1 in the MT5 source.
BarPeriod = 60;
// Need enough bars for calibration, realized-vol window, and a margin.
LookBack = InpCalibBars + InpVolWindow + 10;
// Default starting capital. Adjust to your test account assumptions.
Capital = 10000;
// This strategy is designed to hold at most one long or one short.
MaxLong = 1;
MaxShort = 1;
// Use available local history for LookBack in live mode where possible.
set(PRELOAD);
initModel();
return;
}
if(is(EXITRUN))
{
printf("\n[LocalVolOT v2] Exit. lambda=%.4f beta11=%.6f xi=%.4f",
GM_Lambda, GM_Beta11Star, GM_XiCorr);
return;
}
// Do not trade during LookBack. Indicators/model arrays are not yet ready.
if(is(LOOKBACK))
return;
if(!loadBars())
{
printf("\n[LocalVolOT] Not enough data yet.");
return;
}
// First post-LookBack bar: initialize equity peak and perform full model
// calibration. No trade is entered on this bar.
if(!GStarted)
{
GEqPeak = strategyEquity();
calibrateAll();
printBanner();
GStarted = 1;
return;
}
GM_BarsSinceCalib += 1;
// Hard strategy-level drawdown control.
if(ddExceeded())
{
closeLocalVolPositions("Maximum drawdown exceeded");
return;
}
// Full recalibration schedule.
if(!GM_Valid)
calibrateAll();
else if(GM_BarsSinceCalib >= InpRetrainEvery)
calibrateAll();
else
lightUpdate();
if(!GM_Valid)
return;
// Log the current model state every bar for easier debugging.
var RV = GM_SigmaCev;
if(GNr > 0)
RV = GRVol[0];
var SB = sqrt(GM_SigmaB2);
var Ratio = 0.0;
if(SB > 0.0)
Ratio = RV / SB;
printf("\n[BAR %i] rv=%.2f%% sig_ref=%.2f%% ratio=%.3f sig_star=%.2f%% lambda=%.4f r=%.3f%%",
Bar,
RV*100.0,
SB*100.0,
Ratio,
sqrt(GM_Beta11Star)*100.0,
GM_Lambda,
GM_RCurrent*100.0);
// Existing position management. This script exits positions when the
// volatility ratio normalizes; Stop and TakeProfit can also close trades.
if(NumOpenLong + NumOpenShort > 0)
{
if(shouldCloseLocalVolOT())
closeLocalVolPositions("Vol normalized");
return;
}
// No open position: evaluate and trade a fresh signal.
int Sig = signalLocalVolOT();
if(Sig != 0)
openLocalVolTrade(Sig);
}