This strategy is a multi-asset FX portfolio engine that trades a fixed universe of currency pairs using two entry “personalities” under one shared, adaptive risk allocator. It is designed for walk-forward evaluation and parameter optimization, and it runs on hourly bars with a lookback large enough to support indicator and model calls. The strategy behaves like an equity-aware mixer: it continuously observes each asset’s own equity contribution, compares it to a smoothed baseline, and then redistributes margin toward the components that are outperforming their local baseline while suppressing those that are falling behind.

At the core is a three-stage pipeline:

Equity deviation allocator (portfolio layer):
For each asset, the strategy builds a component equity curve from the sum of long and short equity. It then applies a low-pass filter to create a slow reference line, representing the component’s expected equity path. The difference between the live equity and its filtered reference becomes a distance signal. Positive distance means the component is currently “above its own trend,” while non-positive distance means it is lagging. A running pool of positive distance across all assets is maintained, and each component earns a relative weight equal to its own positive distance share of that pool, capped by a maximum concentration limit. This produces a bounded, performance-conditioned component weight that expresses where the portfolio should “lean” right now.

Learned margin throttle (risk layer):
Once a component has positive distance and therefore a nonzero weight, the strategy calls a perceptron advisor on a compact feature vector built from momentum, volatility, equity, filtered equity, the distance itself, and the current weight. The perceptron output is not used to decide direction; instead it scales risk. The output is converted into a margin multiplier that increases more quickly when positive and decreases more cautiously when negative, with separate sensitivity divisors and strict minimum/maximum clamps. Final margin for the component becomes a base margin fraction times the component weight times account capital, further modulated by the learned factor. If the component distance is not positive, the strategy forces the component weight to zero and assigns only a minimal baseline margin, keeping exposure controlled during underperformance.

Dual entry engines with model gating (signal layer):
The strategy can run one of two algos per asset: an RSI-based multi-timeframe engine and a roof-filter “digital” turning-point engine. The RSI engine uses a higher-timeframe low-pass trend filter to define directional context and a lower-timeframe RSI to define execution timing; trades are only taken when direction agrees with the trend filter. The digi engine uses a roof filter to isolate a band-limited price component and triggers on peak/valley events that indicate local turning points. In both engines, entries are gated by model confidence: decision-tree based advisors for the RSI path and pattern-based advisors for the digi path evaluate a return objective over a configurable horizon using the same shared feature vector. Trades are only placed when the advisor confidence exceeds the threshold and the engine’s structural conditions are satisfied.

Overall, EquiPulse DualGate is best described as an adaptive portfolio allocator that “listens” to each pair’s equity behavior, concentrates risk only among locally improving components, and then allows either a trend-filtered momentum entry style or a rhythm-based turning-point style to act—only when an embedded model agrees. The result is a strategy that aims to avoid indiscriminate trading, reduce exposure during fragmented conditions, and emphasize components whose recent behavior is both profitable and supported by the model gates, while remaining robust through walk-forward evaluation and controlled parameterization.

Code
// Evaluation Shell version //////////////////////
#include <evars.h>

// --- PARAMETERS (shell-controlled ONLY; no other vars here!) ---
// Money management / weighting
var Max_Weight;        //= 0.30, 0.05..0.50, 0.05; Max component weight cap
var MarginBasePct;     //= 0.025, 0.005..0.05, 0.005; Base margin factor
var LotsFixed;         //= 0.01, 0.01..0.10, 0.01; Fixed lots

// Equity filter / signals
var EqLP_Period;       //= 100, 30..200, 10; Equity LowPass period
var RSI_Period;        //= 14, 5..30, 1; RSI period
var ATR_Period;        //= 100, 20..200, 10; ATR period for ML signal

// Perceptron margin scaling
var PercepPosDiv;      //= 100, 50..200, 25; Positive perceptron divisor
var PercepNegDiv;      //= 200, 80..400, 40; Negative perceptron divisor
var FactorMin;         //= 0.10, 0.05..1.00, 0.05; Min margin multiplier clamp
var FactorMax;         //= 3.00, 1.00..5.00, 0.50; Max margin multiplier clamp

// RSI algo filter/timeframes
var TF_Filter;         //= 4, 2..12, 1; Filter timeframe multiplier
var TF_Exec;           //= 1, 1..4, 1; Execution timeframe multiplier
var FilterLP;          //= 200, 50..400, 25; LowPass period on filter TF

// Objective horizon used by advise* RETURNS
var ObjBars;           //= 5, 2..20, 1; Objective bars (close[0]-close[ObjBars])

// Digi algo (Roof)  -> ONLY ONE parameter kept (RoofLo). RoofHi is derived.
var RoofLo;            //= 50, 20..100, 5; Roof low cutoff

END_OF_VARS

// --- NOW normal code is allowed (runtime vars, defines, arrays, etc.) ---
#define dist             AlgoVar[0]
#define component_weight AlgoVar[1]

var Total_Dist = 0;
var MLsignals[6];

// --- Shell portfolio definition (NO backslashes / multi-line macros!) ---
#define _ASSETS "EUR/USD","GBP/USD","USD/JPY","USD/CHF","USD/CAD","AUD/USD","NZD/USD","EUR/GBP","EUR/JPY","EUR/CHF","GBP/JPY","GBP/CHF","AUD/JPY","AUD/CHF","NZD/JPY","NZD/CHF","CAD/JPY","CAD/CHF","CHF/JPY","EUR/AUD","EUR/NZD","EUR/CAD","GBP/AUD","GBP/NZD","GBP/CAD","AUD/NZD","NZD/CAD"
#define _ALGOS "rsi","digi"
#define _TIMEFRAMES 60

void updateDist(var eqLP, var rsiP, var atrP)
{
    var old_dist = dist;

    vars EquityCurve = series(EquityLong + EquityShort);
    vars EquityFilt  = series(LowPass(EquityCurve, (int)eqLP));
    dist = (EquityCurve[0] - EquityFilt[0]) * PIP;

    vars rsiSeries = series(RSI(series(price()), (int)rsiP));
    vars atrSeries = series(ATR((int)atrP));

    MLsignals[0] = rsiSeries[0];
    MLsignals[1] = atrSeries[0];
    MLsignals[2] = EquityCurve[0];
    MLsignals[3] = EquityFilt[0];
    MLsignals[4] = dist;
    MLsignals[5] = component_weight;

    // keep your original Total_Dist logic
    if(dist <= 0) {
        if(old_dist > 0) Total_Dist -= old_dist;
    } else {
        if(old_dist <= 0) Total_Dist += dist;
        if(old_dist > 0)  Total_Dist = Total_Dist - old_dist + dist;
    }
    if(Total_Dist < 0) Total_Dist = 0;

    plot("Component_Eq", EquityCurve, NEW, BLUE);
    plot("Filtered_Eq",  EquityFilt,  0,   RED);
}

void componentWeight(var MW, var MBP, var Lot, var divPos, var divNeg, var fMin, var fMax)
{
    if(dist <= 0)
    {
        Lots = Lot;
        Margin = MBP * MW * Capital;
        component_weight = 0;
    }
    else
    {
        component_weight = ifelse(Total_Dist > 0, dist / Total_Dist, 0);
        if(component_weight > MW) component_weight = MW;
        if(component_weight < 0)  component_weight = 0;

        var perceptronOutput = adviseLong(PERCEPTRON + RETURNS, 0, MLsignals, 6);

        var factor;
        if(perceptronOutput > 0)
            factor = 1 + perceptronOutput / divPos;
        else
            factor = 1 + perceptronOutput / divNeg;

        if(factor < fMin) factor = fMin;
        if(factor > fMax) factor = fMax;

        Margin = MBP * component_weight * Capital * factor;
        Lots = Lot;
    }

    plot("dist",       dist,            NEW | BARS, BLUE);
    plot("Total_Dist", Total_Dist,       NEW,        RED);
    plot("wgt",        component_weight, NEW,        BLACK);
}

void tradeRSI(var tfFilter, var tfExec, var filterLP, var rsiP, var objBars)
{
    // Trend filter on higher TF
    TimeFrame = (int)tfFilter;
    vars PriceH4 = series(price());
    vars Filter  = series(LowPass(PriceH4, (int)filterLP));

    // Execute on lower TF
    TimeFrame = (int)tfExec;
    vars PriceH1 = series(price());
    vars rsi     = series(RSI(PriceH1, (int)rsiP));

    var Objective = priceClose(0) - priceClose((int)objBars);

    // use 6 signals (MLsignals[6])
    if(adviseLong(DTREE + RETURNS, Objective, MLsignals, 6) > 50 && PriceH1[0] > Filter[0])
        enterLong();
    if(adviseShort(DTREE + RETURNS, Objective, MLsignals, 6) > 50 && PriceH1[0] < Filter[0])
        enterShort();
}

void tradeDigi(var roofLo, var roofHi, var objBars)
{
    vars Price  = series(price());
    vars filter = series(Roof(Price, (int)roofLo, (int)roofHi));

    var Objective = priceClose(0) - priceClose((int)objBars);

    // use 6 signals (MLsignals[6])
    if(valley(filter) && adviseLong(PATTERN + RETURNS, Objective, MLsignals, 6) > 50)
        enterLong();
    if(peak(filter) && adviseShort(PATTERN + RETURNS, Objective, MLsignals, 6) > 50)
        enterShort();
}

function run()
{
    set(PARAMETERS, FACTORS);
    set(TESTNOW, PLOTNOW, LOGFILE);

    StartDate = 20231231;
    EndDate   = 20250101;
    BarPeriod = 60;
    LookBack  = 300;
    NumWFOCycles = 10;
    NumCores  = -2;
    Capital   = 100000;

    if(is(INITRUN)) {
        Total_Dist = 0;
        dist = 0;
        component_weight = 0;
    }

    if(ReTrain) {
        UpdateDays = -1;
        SelectWFO  = -1;
        set(FACTORS|OFF);
    }

    assetLoop();

    // --- read optimized parameters ONCE (15 total) ---
    var MW      = _optimize(V.Max_Weight);
    var MBP     = _optimize(V.MarginBasePct);
    var Lot     = _optimize(V.LotsFixed);

    var eqLP    = _optimize(V.EqLP_Period);
    var rsiP    = _optimize(V.RSI_Period);
    var atrP    = _optimize(V.ATR_Period);

    var divPos  = _optimize(V.PercepPosDiv);
    var divNeg  = _optimize(V.PercepNegDiv);
    var fMin    = _optimize(V.FactorMin);
    var fMax    = _optimize(V.FactorMax);

    var tfF     = _optimize(V.TF_Filter);
    var tfE     = _optimize(V.TF_Exec);
    var flp     = _optimize(V.FilterLP);

    var objB    = _optimize(V.ObjBars);

    var rLo     = _optimize(V.RoofLo);

    // Derive RoofHi from RoofLo (keeps 15 parameters total)
    var rHi = 2*rLo;
    if(rHi < 50)  rHi = 50;
    if(rHi > 200) rHi = 200;
    // -----------------------------------------------

    updateDist(eqLP, rsiP, atrP);
    componentWeight(MW, MBP, Lot, divPos, divNeg, fMin, fMax);

    if(strstr(Algo,"rsi"))
        tradeRSI(tfF, tfE, flp, rsiP, objB);
    else if(strstr(Algo,"digi"))
        tradeDigi(rLo, rHi, objB);

    return 0;
}

#include <eval.c>