Advertisement IC Markets

Why most M1 indicators fail after transaction costs

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: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Why most M1 indicators fail after transaction costs

Post by PTScalper »

1. MQL4 Implementation (MetaTrader 4)

Save this file as M1_CostAudit.mq4 in your MQL4\Experts directory.

Code: Select all

//+------------------------------------------------------------------+
//|                                                 M1_CostAudit.mq4 |
//|                                         Quantitative Prototyping |
//+------------------------------------------------------------------+
#property copyright "Quantitative Insights"
#property link      ""
#property version   "1.00"
#property strict

//--- Input Parameters
input string   Grp1 = "--- Primary Trigger Logic ---";
input int      FastEMA = 9;
input int      SlowEMA = 21;

input string   Grp2 = "--- Execution Filters ---";
input bool     UseHTFFilter = true;
input bool     UseATRFilter = true;
input double   MinATRPips = 3.0;

input string   Grp3 = "--- Risk Management ---";
input double   LotSize = 0.1;
input int      MagicNumber = 100100;

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   // Calculate Pip Size based on broker digit format
   double pipSize = Point;
   if(Digits == 3 || Digits == 5) pipSize = Point * 10;

   // 1. Fetch Indicator Data
   double fast0 = iMA(Symbol(), PERIOD_M1, FastEMA, 0, MODE_EMA, PRICE_CLOSE, 0);
   double fast1 = iMA(Symbol(), PERIOD_M1, FastEMA, 0, MODE_EMA, PRICE_CLOSE, 1);
   
   double slow0 = iMA(Symbol(), PERIOD_M1, SlowEMA, 0, MODE_EMA, PRICE_CLOSE, 0);
   double slow1 = iMA(Symbol(), PERIOD_M1, SlowEMA, 0, MODE_EMA, PRICE_CLOSE, 1);
   
   double htfEMA = iMA(Symbol(), PERIOD_M15, 200, 0, MODE_EMA, PRICE_CLOSE, 0);
   double atr    = iATR(Symbol(), PERIOD_M1, 14, 0);
   
   // 2. Logic Compilation
   bool longCross = (fast0 > slow0 && fast1 <= slow1);
   bool shortCross = (fast0 < slow0 && fast1 >= slow1);
   
   bool htfBullish = (Close[0] > htfEMA);
   bool htfBearish = (Close[0] < htfEMA);
   
   double atrInPips = atr / pipSize;
   bool volatilitySufficient = (atrInPips >= MinATRPips);
   
   bool validLongEnv = (!UseHTFFilter || htfBullish) && (!UseATRFilter || volatilitySufficient);
   bool validShortEnv = (!UseHTFFilter || htfBearish) && (!UseATRFilter || volatilitySufficient);

   // 3. Execution & Position Management
   int openPositions = 0;
   int orderType = -1;
   int ticket = -1;
   
   for(int i = OrdersTotal() - 1; i >= 0; i--)
     {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
        {
         if(OrderSymbol() == Symbol() && OrderMagicNumber() == MagicNumber)
           {
            openPositions++;
            orderType = OrderType();
            ticket = OrderTicket();
           }
        }
     }

   // Exit Logic (Reversion Cross)
   if(openPositions > 0)
     {
      if(orderType == OP_BUY && shortCross)
         bool closed = OrderClose(ticket, OrderLots(), Bid, 3, clrRed);
      if(orderType == OP_SELL && longCross)
         bool closed = OrderClose(ticket, OrderLots(), Ask, 3, clrBlue);
     }

   // Entry Logic
   if(openPositions == 0)
     {
      if(longCross && validLongEnv)
         int res = OrderSend(Symbol(), OP_BUY, LotSize, Ask, 3, 0, 0, "M1_Long", MagicNumber, 0, clrBlue);
      if(shortCross && validShortEnv)
         int res = OrderSend(Symbol(), OP_SELL, LotSize, Bid, 3, 0, 0, "M1_Short", MagicNumber, 0, clrRed);
     }
  }
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: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Why most M1 indicators fail after transaction costs

Post by PTScalper »

2. MQL5 Implementation (MetaTrader 5)

MQL5 is architecturally superior for this audit due to its precise tick modeling and integrated commission handling in the strategy tester. Save this as M1_CostAudit.mq5 in your MQL5\Experts directory.

Code: Select all

//+------------------------------------------------------------------+
//|                                                 M1_CostAudit.mq5 |
//|                                         Quantitative Prototyping |
//+------------------------------------------------------------------+
#property copyright "Quantitative Insights"
#property link      ""
#property version   "1.00"

#include <Trade\Trade.mqh>
CTrade trade;

//--- Input Parameters
input group "--- Primary Trigger Logic ---"
input int      FastEMA = 9;
input int      SlowEMA = 21;

input group "--- Execution Filters ---"
input bool     UseHTFFilter = true;
input bool     UseATRFilter = true;
input double   MinATRPips = 3.0;

input group "--- Risk Management ---"
input double   LotSize = 0.1;
input ulong    MagicNumber = 100100;

//--- Indicator Handles
int handleFast, handleSlow, handleHTF, handleATR;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
  {
   trade.SetExpertMagicNumber(MagicNumber);
   
   handleFast = iMA(_Symbol, PERIOD_M1, FastEMA, 0, MODE_EMA, PRICE_CLOSE);
   handleSlow = iMA(_Symbol, PERIOD_M1, SlowEMA, 0, MODE_EMA, PRICE_CLOSE);
   handleHTF  = iMA(_Symbol, PERIOD_M15, 200, 0, MODE_EMA, PRICE_CLOSE);
   handleATR  = iATR(_Symbol, PERIOD_M1, 14);
   
   if(handleFast == INVALID_HANDLE || handleSlow == INVALID_HANDLE || handleHTF == INVALID_HANDLE || handleATR == INVALID_HANDLE)
     {
      Print("Error initializing indicator handles.");
      return(INIT_FAILED);
     }
     
   return(INIT_SUCCEEDED);
  }

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
  {
   double pipSize = _Point;
   if(_Digits == 3 || _Digits == 5) pipSize = _Point * 10;

   // 1. Fetch Indicator Data Arrays
   double fast[], slow[], htf[], atr[], close[];
   ArraySetAsSeries(fast, true); ArraySetAsSeries(slow, true); 
   ArraySetAsSeries(htf, true); ArraySetAsSeries(atr, true); ArraySetAsSeries(close, true);
   
   if(CopyBuffer(handleFast, 0, 0, 2, fast) <= 0) return;
   if(CopyBuffer(handleSlow, 0, 0, 2, slow) <= 0) return;
   if(CopyBuffer(handleHTF, 0, 0, 1, htf) <= 0) return;
   if(CopyBuffer(handleATR, 0, 0, 1, atr) <= 0) return;
   if(CopyRates(_Symbol, PERIOD_M1, 0, 1, close) <= 0) return; // Note: CopyRates requires MqlRates, simplified to CopyClose here
   
   double currentClose[];
   ArraySetAsSeries(currentClose, true);
   CopyClose(_Symbol, PERIOD_M1, 0, 1, currentClose);

   // 2. Logic Compilation
   bool longCross = (fast[0] > slow[0] && fast[1] <= slow[1]);
   bool shortCross = (fast[0] < slow[0] && fast[1] >= slow[1]);
   
   bool htfBullish = (currentClose[0] > htf[0]);
   bool htfBearish = (currentClose[0] < htf[0]);
   
   double atrInPips = atr[0] / pipSize;
   bool volatilitySufficient = (atrInPips >= MinATRPips);
   
   bool validLongEnv = (!UseHTFFilter || htfBullish) && (!UseATRFilter || volatilitySufficient);
   bool validShortEnv = (!UseHTFFilter || htfBearish) && (!UseATRFilter || volatilitySufficient);

   // 3. Execution & Position Management
   bool hasOpenPosition = PositionSelect(_Symbol);
   long posType = PositionGetInteger(POSITION_TYPE);
   
   // Exit Logic (Reversion Cross)
   if(hasOpenPosition)
     {
      if(posType == POSITION_TYPE_BUY && shortCross)
         trade.PositionClose(_Symbol);
      if(posType == POSITION_TYPE_SELL && longCross)
         trade.PositionClose(_Symbol);
     }

   // Entry Logic
   if(!PositionSelect(_Symbol))
     {
      if(longCross && validLongEnv)
         trade.Buy(LotSize, _Symbol);
      if(shortCross && validShortEnv)
         trade.Sell(LotSize, _Symbol);
     }
  }
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Why most M1 indicators fail after transaction costs

Post by PTScalper »

How to Conduct the Quantitative Audit in MT5

To accurately replicate the cost degradation observed in the theory:

1.) Open the MT5 Strategy Tester (Ctrl + R).

2.) Select the M1_CostAudit.ex5 Expert Advisor.

3.) Select an instrument with high liquidity (e.g., EURUSD).

4.) Critical Step: In the tester settings, set Delay to Random or 50-100ms (to simulate execution slippage). Set Modeling to Every tick based on real ticks.

5.) Under the symbol properties or execution settings in MT5, ensure your broker's actual commission structure is applied (usually $3-$4 per side per lot).

6.) Run the test with all filters false. Observe the gross vs. net discrepancy.

7.) Run the test with filters true. You will observe execution frequency drop, but the average EV per trade will stabilize.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Why most M1 indicators fail after transaction costs

Post by PTScalper »

Porting this to cTrader’s C# API (cAlgo) is where the cost-audit becomes highly precise. Because cTrader natively supports C# and object-oriented architecture, we can handle the multi-timeframe (MTF) logic much more cleanly than MQL's buffer arrays.

To address the "repaint / recalc that vanishes live" issue you mentioned in the original autopsy, this cBot uses OnBar() rather than OnTick() and evaluates the EMA crosses on the first closed index (Last(1)). This ensures the backtest strictly reflects confirmed signals, preventing the illusion of intra-bar profitability.

cTrader Implementation (cAlgo)

Save this as a new cBot in the cTrader Automate tab.

Code: Select all

using System;
using System.Linq;
using cAlgo.API;
using cAlgo.API.Indicators;
using cAlgo.API.Internals;
using cAlgo.Indicators;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class M1CostAudit : Robot
    {
        [Parameter("Fast EMA", Group = "Primary Trigger", DefaultValue = 9)]
        public int FastEmaLength { get; set; }

        [Parameter("Slow EMA", Group = "Primary Trigger", DefaultValue = 21)]
        public int SlowEmaLength { get; set; }

        [Parameter("Use 15m Trend Filter", Group = "Execution Filters", DefaultValue = true)]
        public bool UseHtfFilter { get; set; }

        [Parameter("Use ATR Volatility Floor", Group = "Execution Filters", DefaultValue = true)]
        public bool UseAtrFilter { get; set; }

        [Parameter("Min ATR (Pips)", Group = "Execution Filters", DefaultValue = 3.0)]
        public double MinAtrPips { get; set; }

        [Parameter("Volume (Units)", Group = "Risk Management", DefaultValue = 100000)]
        public double VolumeInUnits { get; set; }

        private ExponentialMovingAverage _fastEma;
        private ExponentialMovingAverage _slowEma;
        private ExponentialMovingAverage _htfEma;
        private AverageTrueRange _atr;
        private Bars _m15Bars;

        protected override void OnStart()
        {
            // Initialize M1 Indicators
            _fastEma = Indicators.ExponentialMovingAverage(Bars.ClosePrices, FastEmaLength);
            _slowEma = Indicators.ExponentialMovingAverage(Bars.ClosePrices, SlowEmaLength);
            
            // ATR configured for Simple MA type as is standard
            _atr = Indicators.AverageTrueRange(Bars, 14, MovingAverageType.Simple);

            // Initialize MTF (15-Minute) Indicators
            _m15Bars = MarketData.GetBars(TimeFrame.Minute15);
            _htfEma = Indicators.ExponentialMovingAverage(_m15Bars.ClosePrices, 200);
        }

        protected override void OnBar()
        {
            // 1. Trigger Logic: Evaluated on closed bars (index 1) to prevent live repainting
            bool isLongCross = _fastEma.Result.HasCrossedAbove(_slowEma.Result, 1);
            bool isShortCross = _fastEma.Result.HasCrossedBelow(_slowEma.Result, 1);

            if (!isLongCross && !isShortCross) return;

            // 2. Filter Evaluation
            bool validLongEnv = true;
            bool validShortEnv = true;

            if (UseHtfFilter)
            {
                // Retrieve the most recently closed 15m bar to avoid look-ahead bias
                double currentHtfClose = _m15Bars.ClosePrices.Last(1);
                double currentHtfEma = _htfEma.Result.Last(1);

                bool htfBullish = currentHtfClose > currentHtfEma;
                bool htfBearish = currentHtfClose < currentHtfEma;

                validLongEnv &= htfBullish;
                validShortEnv &= htfBearish;
            }

            if (UseAtrFilter)
            {
                double atrInPips = _atr.Result.Last(1) / Symbol.PipSize;
                bool volatilitySufficient = atrInPips >= MinAtrPips;

                validLongEnv &= volatilitySufficient;
                validShortEnv &= volatilitySufficient;
            }

            // 3. Execution & Position Management
            var activePosition = Positions.Find("M1_Audit", SymbolName);

            // Reversion Exit Logic
            if (activePosition != null)
            {
                if (activePosition.TradeType == TradeType.Buy && isShortCross)
                    ClosePosition(activePosition);
                else if (activePosition.TradeType == TradeType.Sell && isLongCross)
                    ClosePosition(activePosition);
            }

            // Entry Logic
            if (Positions.Find("M1_Audit", SymbolName) == null)
            {
                if (isLongCross && validLongEnv)
                    ExecuteMarketOrder(TradeType.Buy, SymbolName, VolumeInUnits, "M1_Audit");
                else if (isShortCross && validShortEnv)
                    ExecuteMarketOrder(TradeType.Sell, SymbolName, VolumeInUnits, "M1_Audit");
            }
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Why most M1 indicators fail after transaction costs

Post by PTScalper »

Running the Quantitative Audit in cTrader

1.) Open the Automate application in cTrader.

2.) Select the M1CostAudit cBot and add an M1 instance for a major pair (e.g., EURUSD).

3.) In the Backtesting tab, ensure Data is set to Tick data from Server (accurate).

4.) The Friction Test: First, uncheck the Execution Filters and run it. The gross pipeline will look active, but the net equity curve will decay predictably.

5.) Enable the filters. The trade frequency will bottleneck, but the execution quality survives the fixed spread/commission requirements set in your cTrader account properties.

Demonstrating this math via code perfectly illustrates why shifting focus away from lagging M1 indicators toward raw price action and candlestick structure on the 15-minute and Daily timeframes provides a much thicker margin of error. The larger structural moves inherently absorb the frictional costs that completely break high-frequency retail scalping.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Why most M1 indicators fail after transaction costs

Post by PTScalper »

This is the necessary evolution. Shifting entirely away from lagging mathematical averages toward raw price action on the Daily and 15-minute charts is where you actually find structural edge. Indicators tell you what happened; liquidity sweeps tell you what the larger participants are doing right now.

If we strip out the EMAs, we can build a cBot that relies purely on market microstructure. The logic below replaces the moving averages with a 15-minute Liquidity Sweep detector and a Daily Price Action filter.

Here is the structural logic:

The Daily Filter: Instead of a 15m EMA, we look at the Daily chart. If yesterday closed higher than the day before, we are structurally bullish.

The 15m Setup (The Sweep): We define a "Swing Point" by finding the highest high (or lowest low) of the last 20 bars. A sweep occurs when the current 15m bar pierces that structural high to grab buy-side liquidity (triggering retail breakout traders' long entries and early shorters' stop losses), but aggressively rejects and closes back below that high.

The M1 Execution: We run the cBot on the M1 chart. The exact minute the 15m sweep bar closes, the logic executes a market order in the opposite direction of the sweep.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Why most M1 indicators fail after transaction costs

Post by PTScalper »

The Raw Price Action cBot (cAlgo)

Code: Select all

using System;
using cAlgo.API;
using cAlgo.API.Internals;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class LiquiditySweepExecution : Robot
    {
        [Parameter("Swing Lookback (15m Bars)", Group = "Structure", DefaultValue = 20)]
        public int SwingLookback { get; set; }

        [Parameter("Align with Daily Trend", Group = "Filters", DefaultValue = true)]
        public bool UseDailyBias { get; set; }

        [Parameter("Volume (Units)", Group = "Risk", DefaultValue = 100000)]
        public double Volume { get; set; }

        [Parameter("Stop Loss (Pips)", Group = "Risk", DefaultValue = 10)]
        public double StopLoss { get; set; }

        [Parameter("Take Profit (Pips)", Group = "Risk", DefaultValue = 20)]
        public double TakeProfit { get; set; }

        private Bars _m15Bars;
        private Bars _dailyBars;

        protected override void OnStart()
        {
            // Load the necessary higher timeframe data series
            _m15Bars = MarketData.GetBars(TimeFrame.Minute15);
            _dailyBars = MarketData.GetBars(TimeFrame.Daily);
        }

        protected override void OnBar()
        {
            // Ensure we only process the logic at the exact close of a 15-minute cycle
            if (Bars.OpenTimes.Last(0).Minute % 15 != 0)
                return;

            // 1. Daily Bias (Raw PA: Are we structurally up or down based on yesterday's close?)
            bool dailyBullish = _dailyBars.ClosePrices.Last(1) > _dailyBars.ClosePrices.Last(2);
            bool dailyBearish = _dailyBars.ClosePrices.Last(1) < _dailyBars.ClosePrices.Last(2);

            // 2. Identify 15m Structural Highs & Lows (Excluding the bar that just closed)
            double localHigh = double.MinValue;
            double localLow = double.MaxValue;

            // Iterate backward to find the strict structural high/low
            for (int i = 2; i <= SwingLookback + 1; i++)
            {
                if (_m15Bars.HighPrices.Last(i) > localHigh)
                    localHigh = _m15Bars.HighPrices.Last(i);
                    
                if (_m15Bars.LowPrices.Last(i) < localLow)
                    localLow = _m15Bars.LowPrices.Last(i);
            }

            // 3. Detect the Liquidity Sweep on the recently closed 15m bar
            double sweepBarHigh = _m15Bars.HighPrices.Last(1);
            double sweepBarLow = _m15Bars.LowPrices.Last(1);
            double sweepBarClose = _m15Bars.ClosePrices.Last(1);

            // Buy-side liquidity swept: Pierced the high, but closed back below it (Pin bar / Rejection)
            bool buySideSwept = (sweepBarHigh > localHigh) && (sweepBarClose < localHigh);
            
            // Sell-side liquidity swept: Pierced the low, but closed back above it
            bool sellSideSwept = (sweepBarLow < localLow) && (sweepBarClose > localLow);

            // 4. Logic Compilation
            bool validLongEntry = sellSideSwept && (!UseDailyBias || dailyBullish);
            bool validShortEntry = buySideSwept && (!UseDailyBias || dailyBearish);

            // 5. Execution 
            if (validLongEntry)
            {
                CloseOppositePositions(TradeType.Sell);
                if (Positions.Find("PA_Sweep", SymbolName) == null)
                {
                    ExecuteMarketOrder(TradeType.Buy, SymbolName, Volume, "PA_Sweep", StopLoss, TakeProfit);
                }
            }
            else if (validShortEntry)
            {
                CloseOppositePositions(TradeType.Buy);
                if (Positions.Find("PA_Sweep", SymbolName) == null)
                {
                    ExecuteMarketOrder(TradeType.Sell, SymbolName, Volume, "PA_Sweep", StopLoss, TakeProfit);
                }
            }
        }

        private void CloseOppositePositions(TradeType typeToClose)
        {
            foreach (var position in Positions.FindAll("PA_Sweep", SymbolName))
            {
                if (position.TradeType == typeToClose)
                    ClosePosition(position);
            }
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Why most M1 indicators fail after transaction costs

Post by PTScalper »

Notice how clean the C# is when you aren't managing indicator buffers. Because cTrader isolates the MarketData.GetBars() collections so effectively, pulling structural data from higher timeframes takes a fraction of the compute power compared to MT4.

When you run this on a raw dataset, it completely bypasses the spread-drain issue of the previous EMA model. Instead of entering late after a move has started, you are entering exactly when the smart money is absorbing the retail stop-loss liquidity.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Why most M1 indicators fail after transaction costs

Post by PTScalper »

To implement partial profit taking and a breakeven stop loss in cTrader, we need to introduce an OnTick() override. While entries can wait for the 15-minute bar to close, trade management must monitor real-time tick data to trigger exactly when the 1:1 risk/reward target is hit.

From a C# architecture perspective, the cleanest way to track whether a position has been scaled out is to keep it stateless. Rather than storing position IDs in memory (which would reset if the cBot restarts or you lose connection), we simply check if the current position.VolumeInUnits is less than the initial Volume parameter.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3027
Joined: Mon Jul 20, 2026 1:28 pm

Re: Why most M1 indicators fail after transaction costs

Post by PTScalper »

Here is the updated cBot with the ManageOpenPositions() logic injected into the tick cycle:

Code: Select all

using System;
using cAlgo.API;
using cAlgo.API.Internals;

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class LiquiditySweepExecution : Robot
    {
        [Parameter("Swing Lookback (15m Bars)", Group = "Structure", DefaultValue = 20)]
        public int SwingLookback { get; set; }

        [Parameter("Align with Daily Trend", Group = "Filters", DefaultValue = true)]
        public bool UseDailyBias { get; set; }

        [Parameter("Initial Volume (Units)", Group = "Risk", DefaultValue = 100000)]
        public double Volume { get; set; }

        [Parameter("Stop Loss (Pips)", Group = "Risk", DefaultValue = 10)]
        public double StopLoss { get; set; }

        [Parameter("Take Profit (Pips)", Group = "Risk", DefaultValue = 20)] // Final Target (e.g., 1:2 RR)
        public double TakeProfit { get; set; }

        private Bars _m15Bars;
        private Bars _dailyBars;

        protected override void OnStart()
        {
            _m15Bars = MarketData.GetBars(TimeFrame.Minute15);
            _dailyBars = MarketData.GetBars(TimeFrame.Daily);
        }

        protected override void OnTick()
        {
            // Real-time management for scaling out and trailing stops
            ManageOpenPositions();
        }

        protected override void OnBar()
        {
            // Ensure we only process the entry logic at the exact close of a 15-minute cycle
            if (Bars.OpenTimes.Last(0).Minute % 15 != 0)
                return;

            // 1. Daily Bias
            bool dailyBullish = _dailyBars.ClosePrices.Last(1) > _dailyBars.ClosePrices.Last(2);
            bool dailyBearish = _dailyBars.ClosePrices.Last(1) < _dailyBars.ClosePrices.Last(2);

            // 2. Identify 15m Structural Highs & Lows
            double localHigh = double.MinValue;
            double localLow = double.MaxValue;

            for (int i = 2; i <= SwingLookback + 1; i++)
            {
                if (_m15Bars.HighPrices.Last(i) > localHigh)
                    localHigh = _m15Bars.HighPrices.Last(i);
                    
                if (_m15Bars.LowPrices.Last(i) < localLow)
                    localLow = _m15Bars.LowPrices.Last(i);
            }

            // 3. Detect the Liquidity Sweep
            double sweepBarHigh = _m15Bars.HighPrices.Last(1);
            double sweepBarLow = _m15Bars.LowPrices.Last(1);
            double sweepBarClose = _m15Bars.ClosePrices.Last(1);

            bool buySideSwept = (sweepBarHigh > localHigh) && (sweepBarClose < localHigh);
            bool sellSideSwept = (sweepBarLow < localLow) && (sweepBarClose > localLow);

            // 4. Logic Compilation
            bool validLongEntry = sellSideSwept && (!UseDailyBias || dailyBullish);
            bool validShortEntry = buySideSwept && (!UseDailyBias || dailyBearish);

            // 5. Execution 
            if (validLongEntry)
            {
                CloseOppositePositions(TradeType.Sell);
                if (Positions.Find("PA_Sweep", SymbolName) == null)
                {
                    ExecuteMarketOrder(TradeType.Buy, SymbolName, Volume, "PA_Sweep", StopLoss, TakeProfit);
                }
            }
            else if (validShortEntry)
            {
                CloseOppositePositions(TradeType.Buy);
                if (Positions.Find("PA_Sweep", SymbolName) == null)
                {
                    ExecuteMarketOrder(TradeType.Sell, SymbolName, Volume, "PA_Sweep", StopLoss, TakeProfit);
                }
            }
        }

        private void ManageOpenPositions()
        {
            foreach (var position in Positions.FindAll("PA_Sweep", SymbolName))
            {
                // Stateless check: If the volume is lower than the initial parameter, it's already scaled.
                if (position.VolumeInUnits < Volume)
                    continue;

                // cAlgo's native .Pips property automatically accounts for the live bid/ask spread
                if (position.Pips >= StopLoss)
                {
                    // 1. Calculate 50% volume and snap it to the broker's allowed step size
                    double halfVolume = Symbol.NormalizeVolumeInUnits(position.VolumeInUnits / 2, RoundingMode.Down);

                    // 2. Scale out
                    ClosePosition(position, halfVolume);

                    // 3. Move stop to Breakeven
                    ModifyPosition(position, position.EntryPrice, position.TakeProfit);
                }
            }
        }

        private void CloseOppositePositions(TradeType typeToClose)
        {
            foreach (var position in Positions.FindAll("PA_Sweep", SymbolName))
            {
                if (position.TradeType == typeToClose)
                    ClosePosition(position);
            }
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Post Reply