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