Advertisement IC Markets

The 1% Rule Isn't Optional

Discuss 1-minute to 15-minute price action setups, fading intraday momentum, key support/resistance zones, and proven short-term trading methodologies.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: The 1% Rule Isn't Optional

Post by PTScalper »

2. Update the Symbol Engine (Execution Gate)

Inside the SymbolEngine class, modify the Evaluate() method. Place the global capacity check after the moving average crossover logic, but before you calculate volume or send the order to the exchange.

Code: Select all

public void Evaluate()
{
    if (_algo.SystemHalted) return;

    ManageOpenPositions();

    var openPositions = _algo.Positions.FindAll(_label, _symbol.Name);
    if (openPositions.Length == 0)
    {
        _tp1Hit = false;

        bool isLongTrigger = _fastSma.Result.Last(1) <= _slowSma.Result.Last(1) && _fastSma.Result.Last(0) > _slowSma.Result.Last(0);
        bool isShortTrigger = _fastSma.Result.Last(1) >= _slowSma.Result.Last(1) && _fastSma.Result.Last(0) < _slowSma.Result.Last(0);

        if (isLongTrigger || isShortTrigger)
        {
            // =================================================================
            // GLOBAL PORTFOLIO LIMIT CHECK
            // =================================================================
            if (_algo.TotalOpenPositions >= _algo.InpMaxGlobalPositions)
            {
                // The engine detects an edge, but the master portfolio 
                // is at maximum capacity. Abort execution.
                return;
            }

            double stopDistPips = (_atr.Result.Last(0) * _algo.InpAtrMultiplier) / _symbol.PipSize;
            double volume = CalculatePositionVolume(stopDistPips);

            if (volume > 0)
            {
                var tradeType = isLongTrigger ? TradeType.Buy : TradeType.Sell;
                ExecuteTrade(tradeType, volume, stopDistPips);
            }
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Recommended broker for automated trading & scalping IC Markets
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: The 1% Rule Isn't Optional

Post by PTScalper »

Advanced: Currency-Specific Exposure Tracking

While capping total trades at 2 achieves your goal if you are only trading EURUSD and GBPUSD, it becomes insufficient if you scale the bot to 15 currency pairs. You might end up with EURUSD and USDJPY open (neutralizing USD exposure), or EURUSD and GBPUSD open (doubling USD short exposure).

To build a true institutional exposure matrix, you would replace TotalOpenPositions with a LINQ query that parses the symbol strings for specific currencies:

Code: Select all

// Inside the Master Robot
public double GetNetCurrencyExposure(string currency)
{
    double netExposure = 0;
    var allPositions = Positions.FindAll("InstKellyMulti");
    
    foreach (var pos in allPositions)
    {
        // Example: If Long EURUSD, we are Short USD
        if (pos.SymbolName.EndsWith(currency)) 
            netExposure += pos.TradeType == TradeType.Buy ? -pos.VolumeInUnits : pos.VolumeInUnits;
            
        // Example: If Long USDJPY, we are Long USD
        else if (pos.SymbolName.StartsWith(currency))
            netExposure += pos.TradeType == TradeType.Buy ? pos.VolumeInUnits : -pos.VolumeInUnits;
    }
    
    return netExposure;
}
The engine would then query _algo.GetNetCurrencyExposure("USD") and block the trade if taking the new position would push your net aggregate USD volume past a defined threshold.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: The 1% Rule Isn't Optional

Post by PTScalper »

To build a dynamic correlation matrix, you must calculate the Pearson Correlation Coefficient between the two assets at the exact moment your algorithm generates an entry signal.

Because you are scalping 1-minute or 5-minute price action, calculating correlation on raw closing prices is mathematically flawed (financial time series are non-stationary). You must calculate the correlation of their percentage returns over a rolling window.

Here is the architectural update to implement the Correlation Matrix into your cTrader engine.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: The 1% Rule Isn't Optional

Post by PTScalper »

1. Update the Master Robot (Inputs & Pearson Math)

Add the correlation constraints to your InstKellyMultiScalper parameters, and include a highly optimized, single-pass Pearson calculation method.

Code: Select all

// =========================================================================
// CORRELATION LIMIT PARAMETERS
// =========================================================================
[Parameter("Max Allowed Correlation", DefaultValue = 0.75, MinValue = 0.1, MaxValue = 1.0, Group = "Portfolio Risk Limits", 
    Tooltip = "0.75 means reject if 75% correlated.")]
public double InpMaxCorrelation { get; set; }

[Parameter("Correlation Lookback (Bars)", DefaultValue = 50, MinValue = 10, Group = "Portfolio Risk Limits")]
public int InpCorrLookback { get; set; }

// =========================================================================
// PEARSON MATH UTILITY (Place inside InstKellyMultiScalper)
// =========================================================================
public double GetReturnCorrelation(string symbolA, string symbolB, int periods)
{
    var barsA = MarketData.GetBars(TimeFrame, symbolA).ClosePrices;
    var barsB = MarketData.GetBars(TimeFrame, symbolB).ClosePrices;

    // Ensure we have enough data
    if (barsA.Count <= periods || barsB.Count <= periods) return 0;

    double sumX = 0, sumY = 0, sumX2 = 0, sumY2 = 0, sumXY = 0;
    int n = periods;

    // Loop through the requested window (skipping the active unclosed bar at index 0)
    for (int i = 1; i <= n; i++)
    {
        // Calculate percentage returns: (Current - Previous) / Previous
        double returnA = (barsA.Last(i) - barsA.Last(i + 1)) / barsA.Last(i + 1);
        double returnB = (barsB.Last(i) - barsB.Last(i + 1)) / barsB.Last(i + 1);

        sumX += returnA;
        sumY += returnB;
        sumX2 += returnA * returnA;
        sumY2 += returnB * returnB;
        sumXY += returnA * returnB;
    }

    double numerator = (n * sumXY) - (sumX * sumY);
    double denominator = Math.Sqrt(((n * sumX2) - (sumX * sumX)) * ((n * sumY2) - (sumY * sumY)));

    return denominator == 0 ? 0 : numerator / denominator;
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: The 1% Rule Isn't Optional

Post by PTScalper »

2. Update the Symbol Engine (The Directional Filter)

In your SymbolEngine class, before executing the trade, you must iterate over all currently open positions in the portfolio.

The logic requires a Directional Check. If you are buying EURUSD, and you are already long GBPUSD, a high positive correlation ($+0.85$) is dangerous. However, if you are buying EURUSD and you are already short USDCHF, a high negative correlation ($-0.90$) is equally dangerous, because buying EUR and shorting USDCHF is fundamentally the same directional USD exposure.

Update the Evaluate() method in the SymbolEngine class:

Code: Select all

public void Evaluate()
{
    if (_algo.SystemHalted) return;

    ManageOpenPositions();

    var openPositions = _algo.Positions.FindAll("InstKellyMulti", _symbol.Name);
    if (openPositions.Length == 0)
    {
        _tp1Hit = false;

        bool isLongTrigger = _fastSma.Result.Last(1) <= _slowSma.Result.Last(1) && _fastSma.Result.Last(0) > _slowSma.Result.Last(0);
        bool isShortTrigger = _fastSma.Result.Last(1) >= _slowSma.Result.Last(1) && _fastSma.Result.Last(0) < _slowSma.Result.Last(0);

        if (isLongTrigger || isShortTrigger)
        {
            var triggerDirection = isLongTrigger ? TradeType.Buy : TradeType.Sell;

            // =================================================================
            // DYNAMIC CORRELATION MATRIX CHECK
            // =================================================================
            var globalPositions = _algo.Positions.FindAll("InstKellyMulti");
            foreach (var openPos in globalPositions)
            {
                // Calculate dynamic Pearson correlation between the trigger symbol and the open position symbol
                double correlation = _algo.GetReturnCorrelation(_symbol.Name, openPos.SymbolName, _algo.InpCorrLookback);

                bool sameDirection = triggerDirection == openPos.TradeType;
                
                // Risk Case 1: Strong Positive Correlation AND Same Direction (e.g., Long EURUSD, Long GBPUSD)
                if (correlation > _algo.InpMaxCorrelation && sameDirection)
                {
                    _algo.Print($"[Rejected] {_symbol.Name} {triggerDirection} is {Math.Round(correlation*100, 1)}% positively correlated with open {openPos.SymbolName} {openPos.TradeType}.");
                    return; 
                }

                // Risk Case 2: Strong Negative Correlation AND Opposite Direction (e.g., Long EURUSD, Short USDCHF)
                if (correlation < -_algo.InpMaxCorrelation && !sameDirection)
                {
                    _algo.Print($"[Rejected] {_symbol.Name} {triggerDirection} is {Math.Round(correlation*100, 1)}% negatively correlated with open {openPos.SymbolName} {openPos.TradeType}.");
                    return; 
                }
            }

            // Passed all correlation checks. Proceed to sizing and execution.
            double stopDistPips = (_atr.Result.Last(0) * _algo.InpAtrMultiplier) / _symbol.PipSize;
            double volume = CalculatePositionVolume(stopDistPips);

            if (volume > 0)
            {
                ExecuteTrade(triggerDirection, volume, stopDistPips);
            }
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: The 1% Rule Isn't Optional

Post by PTScalper »

Architectural Efficiencies

On-Demand Processing: The Pearson math uses a standard for loop instead of LINQ for speed. More importantly, it only fires when a trigger condition is met, saving massive CPU cycles compared to continuously updating a background correlation grid on every OnTick event.

Cross-Timeframe Portability: By passing TimeFrame dynamically into MarketData.GetBars(TimeFrame, symbolA), the correlation window perfectly matches your scalping horizon whether you attach the bot to a 1-minute or 15-minute chart.

Self-Exclusion Safe: The openPositions.Length == 0 check ensures that the engine only calculates correlation against other symbols' open trades, preventing the algorithm from accidentally blocking itself.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: The 1% Rule Isn't Optional

Post by PTScalper »

To pivot from directional scalping to Statistical Arbitrage (Stat Arb), you fundamentally change the engine's objective. Instead of rejecting a trade because of correlation, you actively hunt the correlation matrix for a "partner" asset to absorb your systemic risk (e.g., delta-neutralizing your USD exposure).

If your algorithm signals a Long on EURUSD, and you find a highly negatively correlated asset (e.g., USDCHF at $r = -0.92$), you execute a Long/Long basket. Because they move inversely, going Long on both neutralizes the USD denominator. Your trade is no longer a bet on the US Dollar; it is a relative value bet that the Euro will outperform the Swiss Franc.

To execute this, we must introduce Volatility Parity. You cannot simply buy 1 Lot of A and 1 Lot of B. If USDCHF is currently twice as volatile as EURUSD, a 1:1 lot size leaves you severely exposed to CHF.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: The 1% Rule Isn't Optional

Post by PTScalper »

1. The Volatility Parity Equation

To achieve a true delta-neutral hedge, the fiat value of Leg B's daily movement must perfectly match Leg A's movement.

2. The Master Robot: Hedging Scanner

Add the hedging parameters to InstKellyMultiScalper and a method that scans the global array for the optimal negative correlation partner.

Code: Select all

[Parameter("Min Negative Correlation", DefaultValue = -0.80, MaxValue = -0.50, Group = "Stat Arb Settings")]
public double InpMinNegativeCorr { get; set; }

// Scans the active symbol list to find the strongest inverse pair
public string FindOptimalHedge(string primarySymbol, out double correlation)
{
    string bestHedge = null;
    double lowestCorr = 1.0; 

    string[] allSymbols = InpSymbols.Split(',').Select(s => s.Trim()).ToArray();

    foreach (string sym in allSymbols)
    {
        if (sym == primarySymbol) continue;

        double currentCorr = GetReturnCorrelation(primarySymbol, sym, InpCorrLookback);
        
        if (currentCorr < lowestCorr)
        {
            lowestCorr = currentCorr;
            bestHedge = sym;
        }
    }

    correlation = lowestCorr;
    return bestHedge;
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: The 1% Rule Isn't Optional

Post by PTScalper »

3. The Symbol Engine: Basket Execution

We rewrite the Evaluate() method inside the SymbolEngine. When the SMA crossover fires, it pauses, scans for a hedge, calculates Volatility Parity, and fires a synchronized basket.

Code: Select all

public void Evaluate()
{
    if (_algo.SystemHalted) return;

    ManageOpenPositions(); // Manages Leg A partials

    var openPositions = _algo.Positions.FindAll("InstKellyMulti", _symbol.Name);
    if (openPositions.Length == 0)
    {
        _tp1Hit = false;

        bool isLongTrigger = _fastSma.Result.Last(1) <= _slowSma.Result.Last(1) && _fastSma.Result.Last(0) > _slowSma.Result.Last(0);
        bool isShortTrigger = _fastSma.Result.Last(1) >= _slowSma.Result.Last(1) && _fastSma.Result.Last(0) < _slowSma.Result.Last(0);

        if (isLongTrigger || isShortTrigger)
        {
            var triggerDirection = isLongTrigger ? TradeType.Buy : TradeType.Sell;

            // =================================================================
            // 1. SEEK STATISTICAL ARBITRAGE PARTNER
            // =================================================================
            string hedgeSymbolName = _algo.FindOptimalHedge(_symbol.Name, out double correlation);

            if (hedgeSymbolName != null && correlation <= _algo.InpMinNegativeCorr)
            {
                _algo.Print($"[Stat Arb] {_symbol.Name} paired with {hedgeSymbolName}. Correlation: {Math.Round(correlation, 2)}");

                // =================================================================
                // 2. CALCULATE LEG A (Primary Kelly Volume)
                // =================================================================
                double stopDistPipsA = (_atr.Result.Last(0) * _algo.InpAtrMultiplier) / _symbol.PipSize;
                double volumeA = CalculatePositionVolume(stopDistPipsA);
                if (volumeA == 0) return;

                // =================================================================
                // 3. CALCULATE LEG B (Volatility Parity Volume)
                // =================================================================
                Symbol hedgeSymbol = _algo.Symbols.GetSymbol(hedgeSymbolName);
                var hedgeBars = _algo.MarketData.GetBars(_algo.TimeFrame, hedgeSymbolName);
                var hedgeAtr = _algo.Indicators.AverageTrueRange(hedgeBars, _algo.InpAtrPeriod, MovingAverageType.Simple);
                
                double atrA = _atr.Result.Last(0) / _symbol.PipSize;     // Convert to pips
                double atrB = hedgeAtr.Result.Last(0) / hedgeSymbol.PipSize; 

                // Parity Math: Match the fiat dollar variance of both assets
                double rawVolumeB = volumeA * ((atrA * _symbol.PipValue) / (atrB * hedgeSymbol.PipValue));
                double volumeB = hedgeSymbol.NormalizeVolumeInUnits(rawVolumeB, RoundingMode.Down);

                // =================================================================
                // 4. BASKET EXECUTION
                // =================================================================
                // Because they are negatively correlated, going the SAME direction makes us Delta-Neutral.
                ExecuteTrade(triggerDirection, volumeA, stopDistPipsA); // Leg A Execution
                
                // Leg B Execution (We use a different label to manage it as a hedge)
                double stopDistPipsB = (_algo.InpAtrMultiplier * atrB);
                _algo.ExecuteMarketOrder(triggerDirection, hedgeSymbolName, volumeB, "StatArb_Hedge", stopDistPipsB, null);
            }
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

Re: The 1% Rule Isn't Optional

Post by PTScalper »

The Trade Management Reality

When executing a delta-neutral basket, managing the exit becomes the primary engineering challenge.

Because Leg B was executed with the label "StatArb_Hedge", your existing ManageOpenPositions() method will ignore it. This is intentional. In pairs trading, you usually do not scale out of legs independently based on static TP targets. If Leg A hits TP1 and you scale out half, but Leg B is left intact, you instantly lose delta-neutrality and are suddenly directionally exposed to the market on the remaining Leg B volume.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Post Reply