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