Advertisement IC Markets

Post-central bank speech review template for scalpers

Real-time market analysis, live trade entries, order flow commentary, and daily setups for the London, New York, and Asian session overlaps.
PTScalper
Site Admin
Posts: 3921
Joined: Mon Jul 20, 2026 1:28 pm

Re: Post-central bank speech review template for scalpers

Post by PTScalper »

MQL5: Pro News Pullback Scalper

Code: Select all

//+------------------------------------------------------------------+
//|                                     ProNewsPullbackScalper.mq5   |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>

input string   Grp1            = "--- Time & Reaction ---";
input int      NewsStartHour   = 14;
input int      NewsStartMinute = 0;
input int      NewsEndHour     = 14;
input int      NewsEndMinute   = 15;

input string   Grp2            = "--- Trigger & Filters ---";
input int      EmaLength       = 9;
input int      MaxWaitBars     = 30;
input double   MaxSlPips       = 30.0;

input string   Grp3            = "--- Risk Management ---";
input double   RiskPercent     = 1.0;
input double   RrRatio         = 2.0;

CTrade trade;
int emaHandle;

double   initOpen = 0, initHigh = 0, initLow = 0;
int      trendDir = 0, barsSinceNews = 0;
bool     pullbackValid = false, tradeTaken = false, wasInNews = false;
double   pivotExtreme = 0;
datetime lastBarTime = 0, sessionStartTime = 0;
string   currentBox = "";
double   pipSize = 0;

int OnInit() {
    pipSize = (_Digits == 3 || _Digits == 5) ? _Point * 10 : _Point;
    emaHandle = iMA(_Symbol, PERIOD_CURRENT, EmaLength, 0, MODE_EMA, PRICE_CLOSE);
    if(emaHandle == INVALID_HANDLE) return INIT_FAILED;
    
    wasInNews = IsInNewsWindow(TimeCurrent());
    return INIT_SUCCEEDED;
}

void OnDeinit(const int reason) { IndicatorRelease(emaHandle); }

bool IsInNewsWindow(datetime time) {
    MqlDateTime dt; TimeToStruct(time, dt);
    int currentMins = dt.hour * 60 + dt.min;
    int startMins = NewsStartHour * 60 + NewsStartMinute;
    int endMins = NewsEndHour * 60 + NewsEndMinute;
    if(startMins < endMins) return (currentMins >= startMins && currentMins < endMins);
    return (currentMins >= startMins || currentMins < endMins);
}

double CalculateLotSize(double slPrice, ENUM_ORDER_TYPE type) {
    double entryPrice = (type == ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID);
    double riskUsd = AccountInfoDouble(ACCOUNT_BALANCE) * (RiskPercent / 100.0);
    double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
    double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
    
    double ticksAtRisk = MathAbs(entryPrice - slPrice) / tickSize;
    if(ticksAtRisk <= 0) return 0;
    
    double lots = riskUsd / (ticksAtRisk * tickValue);
    double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
    lots = MathFloor(lots / lotStep) * lotStep;
    
    if(lots < SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN)) return 0;
    if(lots > SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX)) lots = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
    return lots;
}

void OnTick() {
    datetime timeArray[]; ArraySetAsSeries(timeArray, true);
    CopyTime(_Symbol, PERIOD_CURRENT, 0, 1, timeArray);
    
    bool isNewBar = false;
    if(timeArray[0] != lastBarTime) {
        isNewBar = true; lastBarTime = timeArray[0];
    }

    bool inNews = IsInNewsWindow(TimeCurrent());

    double openPrices[], closePrices[], lowPrices[], highPrices[];
    ArraySetAsSeries(openPrices, true); ArraySetAsSeries(closePrices, true);
    ArraySetAsSeries(lowPrices, true);  ArraySetAsSeries(highPrices, true);
    CopyOpen(_Symbol, PERIOD_CURRENT, 0, 2, openPrices);
    CopyClose(_Symbol, PERIOD_CURRENT, 0, 2, closePrices);
    CopyLow(_Symbol, PERIOD_CURRENT, 0, 2, lowPrices);
    CopyHigh(_Symbol, PERIOD_CURRENT, 0, 2, highPrices);

    // 1. Session Start
    if(inNews && !wasInNews) {
        initOpen = openPrices[0]; initHigh = highPrices[0]; initLow = lowPrices[0];
        trendDir = 0; pullbackValid = false; tradeTaken = false;
        pivotExtreme = 0; barsSinceNews = 0; sessionStartTime = timeArray[0];
        
        currentBox = "NewsBox_" + TimeToString(TimeCurrent());
        ObjectCreate(0, currentBox, OBJ_RECTANGLE, 0, sessionStartTime, initHigh, timeArray[0], initLow);
        ObjectSetInteger(0, currentBox, OBJPROP_COLOR, clrDarkGray);
        ObjectSetInteger(0, currentBox, OBJPROP_BACK, true);
    }

    // 2. Update Box
    if(inNews) {
        initHigh = MathMax(initHigh, highPrices[0]);
        initLow = MathMin(initLow, lowPrices[0]);
        ObjectMove(0, currentBox, 0, sessionStartTime, initHigh);
        ObjectMove(0, currentBox, 1, timeArray[0], initLow);
    }

    // 3. Session End
    if(!inNews && wasInNews) {
        double initClose = closePrices[1];
        if(initClose > initOpen) { trendDir = 1; ObjectSetInteger(0, currentBox, OBJPROP_COLOR, clrTeal); }
        else if(initClose < initOpen) { trendDir = -1; ObjectSetInteger(0, currentBox, OBJPROP_COLOR, clrMaroon); }
        else trendDir = 0;
        
        pivotExtreme = (trendDir == 1) ? lowPrices[1] : highPrices[1];
    }
    
    wasInNews = inNews;

    // 4. Pullback & Filters
    if(!inNews && trendDir != 0 && !tradeTaken) {
        if(isNewBar) barsSinceNews++;
        
        if(barsSinceNews > MaxWaitBars) {
            trendDir = 0; pullbackValid = false; return;
        }
        
        if(trendDir == 1 && lowPrices[0] < pivotExtreme) pivotExtreme = lowPrices[0];
        if(trendDir == -1 && highPrices[0] > pivotExtreme) pivotExtreme = highPrices[0];

        if(isNewBar) {
            double emaBuffer[]; ArraySetAsSeries(emaBuffer, true);
            if(CopyBuffer(emaHandle, 0, 1, 1, emaBuffer) <= 0) return;
            
            double ema1 = emaBuffer[0];
            double close1 = closePrices[1];
            double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
            double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);

            // LONG SETUP
            if(trendDir == 1) {
                if(close1 < ema1) pullbackValid = true;
                
                if(pullbackValid && close1 > ema1) {
                    double sl = NormalizeDouble(pivotExtreme, _Digits);
                    double slDistance = MathAbs(ask - sl) / pipSize;
                    
                    if(slDistance > 0 && slDistance <= MaxSlPips) {
                        double lots = CalculateLotSize(sl, ORDER_TYPE_BUY);
                        if(lots > 0) {
                            double tp = NormalizeDouble(ask + ((ask - sl) * RrRatio), _Digits);
                            if(trade.Buy(lots, _Symbol, ask, sl, tp, "Pro Scalp")) {
                                tradeTaken = true; pullbackValid = false;
                            }
                        }
                    }
                }
            }
            
            // SHORT SETUP
            if(trend dispersal == -1) { // Typo fix: (trendDir == -1)
            if(trendDir == -1) {
                if(close1 > ema1) pullbackValid = true;
                
                if(pullbackValid && close1 < ema1) {
                    double sl = NormalizeDouble(pivotExtreme, _Digits);
                    double slDistance = MathAbs(sl - bid) / pipSize;
                    
                    if(slDistance > 0 && slDistance <= MaxSlPips) {
                        double lots = CalculateLotSize(sl, ORDER_TYPE_SELL);
                        if(lots > 0) {
                            double tp = NormalizeDouble(bid - ((sl - bid) * RrRatio), _Digits);
                            if(trade.Sell(lots, _Symbol, bid, sl, tp, "Pro Scalp")) {
                                tradeTaken = true; pullbackValid = false;
                            }
                        }
                    }
                }
            }
        }
    }
}
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: 3921
Joined: Mon Jul 20, 2026 1:28 pm

Re: Post-central bank speech review template for scalpers

Post by PTScalper »

Porting the "Ultra-Pro" mechanics—specifically partial profit-taking (scaling out) and auto-breakeven—requires a structural shift in how MetaTrader handles orders compared to Pine Script.

In Pine, you just call strategy.exit("TP1", qty_percent=50). In MT4/MT5, performing a partial close on a live ticket destroys the original ticket and creates a new one, which creates a nightmare for EA tracking.

The Institutional Solution: We split the initial risk into two separate orders at execution (e.g., 50% volume to TP1, 50% volume to TP2). We assign them different Magic Numbers. The EA simply watches the TP1 Magic Number; the moment it disappears (hits TP1), the EA moves the Stop Loss of the TP2 Magic Number to its exact Open Price.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3921
Joined: Mon Jul 20, 2026 1:28 pm

Re: Post-central bank speech review template for scalpers

Post by PTScalper »

Here is the complete Ultra-Pro system for both platforms, including the SL wick buffer, split-order execution, auto-breakeven manager, and an on-chart HUD using Comment().

MQL4: Ultra-Pro News Pullback Scalper

Code: Select all

//+------------------------------------------------------------------+
//|                                UltraProNewsPullbackScalper.mq4   |
//+------------------------------------------------------------------+
#property strict

input string   Grp1            = "--- Time & Reaction ---";
input int      NewsStartHour   = 14;
input int      NewsStartMinute = 0;
input int      NewsEndHour     = 14;
input int      NewsEndMinute   = 15;

input string   Grp2            = "--- Trigger & Filters ---";
input int      EmaLength       = 9;
input int      MaxWaitBars     = 30;
input double   MaxSlPips       = 30.0;
input double   SlBufferPips    = 1.5;  // Buffer to survive wick sweeps

input string   Grp3            = "--- Risk & Management ---";
input double   RiskPercent     = 1.0;
input double   Tp1Rr           = 1.0;
input int      Tp1QtyPct       = 50;   // % of total lot size for TP1
input double   Tp2Rr           = 2.5;
input bool     AutoBreakeven   = true;

int MagicTP1 = 80001;
int MagicTP2 = 80002;

double   initOpen = 0, initHigh = 0, initLow = 0;
int      trendDir = 0, barsSinceNews = 0; 
bool     pullbackValid = false, tradeTaken = false, wasInNews = false;
double   pivotExtreme = 0;
datetime lastBarTime = 0, sessionStartTime = 0;
string   currentBox = "";
double   pipSize = 0;

int OnInit() {
    pipSize = (Digits == 3 || Digits == 5) ? Point * 10 : Point;
    wasInNews = IsInNewsWindow(TimeCurrent());
    return INIT_SUCCEEDED;
}

bool IsInNewsWindow(datetime time) {
    MqlDateTime dt; TimeToStruct(time, dt);
    int currentMins = dt.hour * 60 + dt.min;
    int startMins = NewsStartHour * 60 + NewsStartMinute;
    int endMins = NewsEndHour * 60 + NewsEndMinute;
    if(startMins < endMins) return (currentMins >= startMins && currentMins < endMins);
    return (currentMins >= startMins || currentMins < endMins);
}

double CalculateTotalLots(double slPrice, int type) {
    double entryPrice = (type == OP_BUY) ? Ask : Bid;
    double riskUsd = AccountBalance() * (RiskPercent / 100.0);
    double tickSize = MarketInfo(Symbol(), MODE_TICKSIZE);
    double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
    
    double ticksAtRisk = MathAbs(entryPrice - slPrice) / tickSize;
    if(ticksAtRisk <= 0) return 0;
    
    return riskUsd / (ticksAtRisk * tickValue);
}

void ManageBreakeven() {
    if(!AutoBreakeven) return;
    
    bool tp1Exists = false;
    for(int i = OrdersTotal() - 1; i >= 0; i--) {
        if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES) && OrderSymbol() == Symbol() && OrderMagicNumber() == MagicTP1) {
            tp1Exists = true; break;
        }
    }
    
    if(!tp1Exists) { // TP1 hit or closed
        for(int i = OrdersTotal() - 1; i >= 0; i--) {
            if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES) && OrderSymbol() == Symbol() && OrderMagicNumber() == MagicTP2) {
                double openPrice = OrderOpenPrice();
                double currentSl = OrderStopLoss();
                
                // Move SL to BE if not already there
                if((OrderType() == OP_BUY && currentSl < openPrice) || (OrderType() == OP_SELL && currentSl > openPrice)) {
                    OrderModify(OrderTicket(), openPrice, openPrice, OrderTakeProfit(), 0, clrBlue);
                }
            }
        }
    }
}

void UpdateHUD(bool inNews) {
    string bias = inNews ? "Awaiting News..." : (trendDir == 1 ? "Bullish Pullback" : (trendDir == -1 ? "Bearish Pullback" : "Flat / No Setup"));
    string pos = "WAITING";
    if(OrdersTotal() > 0) pos = "ACTIVE"; // Simplified for HUD
    
    string hud = "\n=== PRO SCALPER HUD ===\n" +
                 "Status: " + bias + "\n" +
                 "Position: " + pos;
    Comment(hud);
}

void OnTick() {
    bool isNewBar = false;
    if(Time[0] != lastBarTime) { isNewBar = true; lastBarTime = Time[0]; }
    bool inNews = IsInNewsWindow(TimeCurrent());

    ManageBreakeven();
    UpdateHUD(inNews);

    // 1. Session Start (Box Setup)
    if(inNews && !wasInNews) {
        initOpen = Open[0]; initHigh = High[0]; initLow = Low[0];
        trendDir = 0; pullbackValid = false; tradeTaken = false;
        pivotExtreme = 0; barsSinceNews = 0; sessionStartTime = Time[0];
        
        currentBox = "NewsBox_" + TimeToStr(TimeCurrent());
        ObjectCreate(0, currentBox, OBJ_RECTANGLE, 0, sessionStartTime, initHigh, Time[0], initLow);
        ObjectSetInteger(0, currentBox, OBJPROP_COLOR, clrDarkGray); ObjectSetInteger(0, currentBox, OBJPROP_BACK, true);
    }
    if(inNews) {
        initHigh = MathMax(initHigh, High[0]); initLow = MathMin(initLow, Low[0]);
        ObjectMove(0, currentBox, 0, sessionStartTime, initHigh); ObjectMove(0, currentBox, 1, Time[0], initLow);
    }
    // 2. Session End
    if(!inNews && wasInNews) {
        double initClose = Close[1];
        if(initClose > initOpen) { trendDir = 1; ObjectSetInteger(0, currentBox, OBJPROP_COLOR, clrTeal); }
        else if(initClose < initOpen) { trendDir = -1; ObjectSetInteger(0, currentBox, OBJPROP_COLOR, clrMaroon); }
        else trendDir = 0;
        pivotExtreme = (trendDir == 1) ? Low[1] : High[1];
    }
    wasInNews = inNews;

    // 3. Pullback Logic
    if(!inNews && trendDir != 0 && !tradeTaken) {
        if(isNewBar) barsSinceNews++;
        if(barsSinceNews > MaxWaitBars) { trendDir = 0; pullbackValid = false; return; }
        
        if(trendDir == 1 && Low[0] < pivotExtreme) pivotExtreme = Low[0];
        if(trendDir == -1 && High[0] > pivotExtreme) pivotExtreme = High[0];

        if(isNewBar) {
            double ema1 = iMA(Symbol(), 0, EmaLength, 0, MODE_EMA, PRICE_CLOSE, 1);
            double close1 = Close[1];
            double buffer = SlBufferPips * pipSize;

            if(trendDir == 1) { // LONG
                if(close1 < ema1) pullbackValid = true;
                if(pullbackValid && close1 > ema1) {
                    double sl = NormalizeDouble(pivotExtreme - buffer, Digits);
                    double slDistance = MathAbs(Ask - sl) / pipSize;
                    
                    if(slDistance > 0 && slDistance <= MaxSlPips) {
                        double totalLots = CalculateTotalLots(sl, OP_BUY);
                        double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
                        double lot1 = MathFloor((totalLots * (Tp1QtyPct / 100.0)) / lotStep) * lotStep;
                        double lot2 = MathFloor((totalLots - lot1) / lotStep) * lotStep;
                        
                        if(lot1 > 0 && lot2 > 0) {
                            double tp1 = NormalizeDouble(Ask + ((Ask - sl) * Tp1Rr), Digits);
                            double tp2 = NormalizeDouble(Ask + ((Ask - sl) * Tp2Rr), Digits);
                            
                            OrderSend(Symbol(), OP_BUY, lot1, Ask, 3, sl, tp1, "TP1 Scalp", MagicTP1, 0, clrGreen);
                            OrderSend(Symbol(), OP_BUY, lot2, Ask, 3, sl, tp2, "TP2 Runner", MagicTP2, 0, clrLime);
                            tradeTaken = true; pullbackValid = false;
                        }
                    }
                }
            }
            if(trendDir == -1) { // SHORT
                if(close1 > ema1) pullbackValid = true;
                if(pullbackValid && close1 < ema1) {
                    double sl = NormalizeDouble(pivotExtreme + buffer, Digits);
                    double slDistance = MathAbs(sl - Bid) / pipSize;
                    
                    if(slDistance > 0 && slDistance <= MaxSlPips) {
                        double totalLots = CalculateTotalLots(sl, OP_SELL);
                        double lotStep = MarketInfo(Symbol(), MODE_LOTSTEP);
                        double lot1 = MathFloor((totalLots * (Tp1QtyPct / 100.0)) / lotStep) * lotStep;
                        double lot2 = MathFloor((totalLots - lot1) / lotStep) * lotStep;
                        
                        if(lot1 > 0 && lot2 > 0) {
                            double tp1 = NormalizeDouble(Bid - ((sl - Bid) * Tp1Rr), Digits);
                            double tp2 = NormalizeDouble(Bid - ((sl - Bid) * Tp2Rr), Digits);
                            
                            OrderSend(Symbol(), OP_SELL, lot1, Bid, 3, sl, tp1, "TP1 Scalp", MagicTP1, 0, clrRed);
                            OrderSend(Symbol(), OP_SELL, lot2, Bid, 3, sl, tp2, "TP2 Runner", MagicTP2, 0, clrOrange);
                            tradeTaken = true; pullbackValid = false;
                        }
                    }
                }
            }
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3921
Joined: Mon Jul 20, 2026 1:28 pm

Re: Post-central bank speech review template for scalpers

Post by PTScalper »

MQL5: Ultra-Pro News Pullback Scalper

Note: This relies on the terminal being set to a Hedging account type to allow two distinct positions on the same symbol simultaneously.

Code: Select all

//+------------------------------------------------------------------+
//|                                UltraProNewsPullbackScalper.mq5   |
//+------------------------------------------------------------------+
#include <Trade\Trade.mqh>
#include <Trade\PositionInfo.mqh>

input string   Grp1            = "--- Time & Reaction ---";
input int      NewsStartHour   = 14;
input int      NewsStartMinute = 0;
input int      NewsEndHour     = 14;
input int      NewsEndMinute   = 15;

input string   Grp2            = "--- Trigger & Filters ---";
input int      EmaLength       = 9;
input int      MaxWaitBars     = 30;
input double   MaxSlPips       = 30.0;
input double   SlBufferPips    = 1.5;

input string   Grp3            = "--- Risk & Management ---";
input double   RiskPercent     = 1.0;
input double   Tp1Rr           = 1.0;
input int      Tp1QtyPct       = 50;
input double   Tp2Rr           = 2.5;
input bool     AutoBreakeven   = true;

ulong MagicTP1 = 80001;
ulong MagicTP2 = 80002;

CTrade trade1;
CTrade trade2;
CPositionInfo posInfo;
int emaHandle;

double   initOpen = 0, initHigh = 0, initLow = 0;
int      trendDir = 0, barsSinceNews = 0;
bool     pullbackValid = false, tradeTaken = false, wasInNews = false;
double   pivotExtreme = 0;
datetime lastBarTime = 0, sessionStartTime = 0;
string   currentBox = "";
double   pipSize = 0;

int OnInit() {
    pipSize = (_Digits == 3 || _Digits == 5) ? _Point * 10 : _Point;
    emaHandle = iMA(_Symbol, PERIOD_CURRENT, EmaLength, 0, MODE_EMA, PRICE_CLOSE);
    if(emaHandle == INVALID_HANDLE) return INIT_FAILED;
    
    trade1.SetExpertMagicNumber(MagicTP1);
    trade2.SetExpertMagicNumber(MagicTP2);
    
    wasInNews = IsInNewsWindow(TimeCurrent());
    return INIT_SUCCEEDED;
}
void OnDeinit(const int reason) { IndicatorRelease(emaHandle); }

bool IsInNewsWindow(datetime time) {
    MqlDateTime dt; TimeToStruct(time, dt);
    int currentMins = dt.hour * 60 + dt.min;
    int startMins = NewsStartHour * 60 + NewsStartMinute;
    int endMins = NewsEndHour * 60 + NewsEndMinute;
    if(startMins < endMins) return (currentMins >= startMins && currentMins < endMins);
    return (currentMins >= startMins || currentMins < endMins);
}

double CalculateTotalLots(double slPrice, ENUM_ORDER_TYPE type) {
    double entryPrice = (type == ORDER_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_ASK) : SymbolInfoDouble(_Symbol, SYMBOL_BID);
    double riskUsd = AccountInfoDouble(ACCOUNT_BALANCE) * (RiskPercent / 100.0);
    double tickSize = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
    double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
    double ticksAtRisk = MathAbs(entryPrice - slPrice) / tickSize;
    if(ticksAtRisk <= 0) return 0;
    return riskUsd / (ticksAtRisk * tickValue);
}

void ManageBreakeven() {
    if(!AutoBreakeven) return;
    
    bool tp1Exists = false;
    for(int i = PositionsTotal() - 1; i >= 0; i--) {
        if(posInfo.SelectByIndex(i) && posInfo.Symbol() == _Symbol && posInfo.Magic() == MagicTP1) {
            tp1Exists = true; break;
        }
    }
    
    if(!tp1Exists) { // TP1 hit
        for(int i = PositionsTotal() - 1; i >= 0; i--) {
            if(posInfo.SelectByIndex(i) && posInfo.Symbol() == _Symbol && posInfo.Magic() == MagicTP2) {
                double openPrice = posInfo.PriceOpen();
                double currentSl = posInfo.StopLoss();
                
                if((posInfo.PositionType() == POSITION_TYPE_BUY && currentSl < openPrice) || 
                   (posInfo.PositionType() == POSITION_TYPE_SELL && currentSl > openPrice)) {
                    trade2.PositionModify(posInfo.Ticket(), openPrice, posInfo.TakeProfit());
                }
            }
        }
    }
}

void OnTick() {
    datetime timeArray[]; ArraySetAsSeries(timeArray, true);
    CopyTime(_Symbol, PERIOD_CURRENT, 0, 1, timeArray);
    bool isNewBar = false;
    if(timeArray[0] != lastBarTime) { isNewBar = true; lastBarTime = timeArray[0]; }

    bool inNews = IsInNewsWindow(TimeCurrent());
    ManageBreakeven();
    
    // HUD
    string bias = inNews ? "Awaiting News..." : (trendDir == 1 ? "Bullish Pullback" : (trendDir == -1 ? "Bearish Pullback" : "Flat"));
    Comment("\n=== PRO SCALPER HUD ===\nStatus: ", bias, "\nPositions: ", PositionsTotal());

    double openPrices[], closePrices[], lowPrices[], highPrices[];
    ArraySetAsSeries(openPrices, true); ArraySetAsSeries(closePrices, true);
    ArraySetAsSeries(lowPrices, true);  ArraySetAsSeries(highPrices, true);
    CopyOpen(_Symbol, PERIOD_CURRENT, 0, 2, openPrices); CopyClose(_Symbol, PERIOD_CURRENT, 0, 2, closePrices);
    CopyLow(_Symbol, PERIOD_CURRENT, 0, 2, lowPrices);   CopyHigh(_Symbol, PERIOD_CURRENT, 0, 2, highPrices);

    // 1. Session Start (Box)
    if(inNews && !wasInNews) {
        initOpen = openPrices[0]; initHigh = highPrices[0]; initLow = lowPrices[0];
        trendDir = 0; pullbackValid = false; tradeTaken = false;
        pivotExtreme = 0; barsSinceNews = 0; sessionStartTime = timeArray[0];
        
        currentBox = "NewsBox_" + TimeToString(TimeCurrent());
        ObjectCreate(0, currentBox, OBJ_RECTANGLE, 0, sessionStartTime, initHigh, timeArray[0], initLow);
        ObjectSetInteger(0, currentBox, OBJPROP_COLOR, clrDarkGray); ObjectSetInteger(0, currentBox, OBJPROP_BACK, true);
    }
    if(inNews) {
        initHigh = MathMax(initHigh, highPrices[0]); initLow = MathMin(initLow, lowPrices[0]);
        ObjectMove(0, currentBox, 0, sessionStartTime, initHigh); ObjectMove(0, currentBox, 1, timeArray[0], initLow);
    }
    
    // 2. Session End
    if(!inNews && wasInNews) {
        double initClose = closePrices[1];
        if(initClose > initOpen) { trendDir = 1; ObjectSetInteger(0, currentBox, OBJPROP_COLOR, clrTeal); }
        else if(initClose < initOpen) { trendDir = -1; ObjectSetInteger(0, currentBox, OBJPROP_COLOR, clrMaroon); }
        else trendDir = 0;
        pivotExtreme = (trendDir == 1) ? lowPrices[1] : highPrices[1];
    }
    wasInNews = inNews;

    // 3. Pullback Logic
    if(!inNews && trendDir != 0 && !tradeTaken) {
        if(isNewBar) barsSinceNews++;
        if(barsSinceNews > MaxWaitBars) { trendDir = 0; pullbackValid = false; return; }
        
        if(trendDir == 1 && lowPrices[0] < pivotExtreme) pivotExtreme = lowPrices[0];
        if(trendDir == -1 && highPrices[0] > pivotExtreme) pivotExtreme = highPrices[0];

        if(isNewBar) {
            double emaBuffer[]; ArraySetAsSeries(emaBuffer, true);
            if(CopyBuffer(emaHandle, 0, 1, 1, emaBuffer) <= 0) return;
            
            double ema1 = emaBuffer[0];
            double close1 = closePrices[1];
            double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
            double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
            double buffer = SlBufferPips * pipSize;

            if(trendDir == 1) { // LONG
                if(close1 < ema1) pullbackValid = true;
                if(pullbackValid && close1 > ema1) {
                    double sl = NormalizeDouble(pivotExtreme - buffer, _Digits);
                    double slDistance = MathAbs(ask - sl) / pipSize;
                    
                    if(slDistance > 0 && slDistance <= MaxSlPips) {
                        double totalLots = CalculateTotalLots(sl, ORDER_TYPE_BUY);
                        double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
                        double lot1 = MathFloor((totalLots * (Tp1QtyPct / 100.0)) / lotStep) * lotStep;
                        double lot2 = MathFloor((totalLots - lot1) / lotStep) * lotStep;
                        
                        if(lot1 > 0 && lot2 > 0) {
                            double tp1 = NormalizeDouble(ask + ((ask - sl) * Tp1Rr), _Digits);
                            double tp2 = NormalizeDouble(ask + ((ask - sl) * Tp2Rr), _Digits);
                            trade1.Buy(lot1, _Symbol, ask, sl, tp1, "TP1 Scalp");
                            trade2.Buy(lot2, _Symbol, ask, sl, tp2, "TP2 Runner");
                            tradeTaken = true; pullbackValid = false;
                        }
                    }
                }
            }
            if(trendDir == -1) { // SHORT
                if(close1 > ema1) pullbackValid = true;
                if(pullbackValid && close1 < ema1) {
                    double sl = NormalizeDouble(pivotExtreme + buffer, _Digits);
                    double slDistance = MathAbs(sl - bid) / pipSize;
                    
                    if(slDistance > 0 && slDistance <= MaxSlPips) {
                        double totalLots = CalculateTotalLots(sl, ORDER_TYPE_SELL);
                        double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
                        double lot1 = MathFloor((totalLots * (Tp1QtyPct / 100.0)) / lotStep) * lotStep;
                        double lot2 = MathFloor((totalLots - lot1) / lotStep) * lotStep;
                        
                        if(lot1 > 0 && lot2 > 0) {
                            double tp1 = NormalizeDouble(bid - ((sl - bid) * Tp1Rr), _Digits);
                            double tp2 = NormalizeDouble(bid - ((sl - bid) * Tp2Rr), _Digits);
                            trade1.Sell(lot1, _Symbol, bid, sl, tp1, "TP1 Scalp");
                            trade2.Sell(lot2, _Symbol, bid, sl, tp2, "TP2 Runner");
                            tradeTaken = true; pullbackValid = false;
                        }
                    }
                }
            }
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3921
Joined: Mon Jul 20, 2026 1:28 pm

Re: Post-central bank speech review template for scalpers

Post by PTScalper »

Adding a trailing stop to a split-ticket setup is a classic prop firm technique. Because we separated the trade into two distinct Magic Numbers (MagicTP1 and MagicTP2), managing the runner becomes incredibly clean.

We don't need to track partial close histories or split tickets. The logic is simply: If MagicTP1 no longer exists, breakeven is secured. From that moment on, if price moves X pips in our favor, drag the MagicTP2 Stop Loss behind it.

Here are the components to add to both the MQL4 and MQL5 versions.

1. Add the New Inputs (Top of the File)

Add these variables to the Grp3 (Risk & Management) section of your EA to control the trailing behavior. A "Step" is crucial to prevent your EA from spamming the broker's server with a modify request on every single tick.

Code: Select all

input bool   UseTrailingStop   = true;
input double TrailingStopPips  = 15.0; // Distance to trail behind price
input double TrailingStepPips  = 2.0;  // Minimum move required before updating SL again
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3921
Joined: Mon Jul 20, 2026 1:28 pm

Re: Post-central bank speech review template for scalpers

Post by PTScalper »

2. MQL4 Implementation

Add this standalone function anywhere above your OnTick() function.

Code: Select all

void ManageTrailingStop() {
    if(!UseTrailingStop) return;
    
    // First, verify TP1 has been hit (MagicTP1 order no longer exists)
    bool tp1Exists = false;
    for(int i = OrdersTotal() - 1; i >= 0; i--) {
        if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES) && OrderSymbol() == Symbol() && OrderMagicNumber() == MagicTP1) {
            tp1Exists = true; break;
        }
    }
    
    // If TP1 is still active, we do not trail. (Let the AutoBreakeven function handle the first move).
    if(tp1Exists) return;

    // Manage the TP2 Runner
    for(int i = OrdersTotal() - 1; i >= 0; i--) {
        if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES) && OrderSymbol() == Symbol() && OrderMagicNumber() == MagicTP2) {
            
            double trailPoints = TrailingStopPips * pipSize;
            double stepPoints = TrailingStepPips * pipSize;
            double currentSl = OrderStopLoss();
            double openPrice = OrderOpenPrice();

            if(OrderType() == OP_BUY) {
                // Ensure we are far enough in profit to begin trailing
                if(Bid - openPrice > trailPoints) {
                    double newSl = NormalizeDouble(Bid - trailPoints, Digits);
                    // Only modify if the new SL is better than the current SL + the Step threshold
                    if(currentSl < newSl - stepPoints || currentSl == 0) {
                        OrderModify(OrderTicket(), openPrice, newSl, OrderTakeProfit(), 0, clrBlue);
                    }
                }
            } 
            else if(OrderType() == OP_SELL) {
                if(openPrice - Ask > trailPoints) {
                    double newSl = NormalizeDouble(Ask + trailPoints, Digits);
                    if(currentSl > newSl + stepPoints || currentSl == 0) {
                        OrderModify(OrderTicket(), openPrice, newSl, OrderTakeProfit(), 0, clrRed);
                    }
                }
            }
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3921
Joined: Mon Jul 20, 2026 1:28 pm

Re: Post-central bank speech review template for scalpers

Post by PTScalper »

3. MQL5 Implementation

Add this standalone function above your OnTick() function in the MQL5 file. It uses the exact same logic but leverages the CPositionInfo and CTrade classes.

Code: Select all

void ManageTrailingStop() {
    if(!UseTrailingStop) return;

    bool tp1Exists = false;
    for(int i = PositionsTotal() - 1; i >= 0; i--) {
        if(posInfo.SelectByIndex(i) && posInfo.Symbol() == _Symbol && posInfo.Magic() == MagicTP1) {
            tp1Exists = true; break;
        }
    }
    
    if(tp1Exists) return;

    for(int i = PositionsTotal() - 1; i >= 0; i--) {
        if(posInfo.SelectByIndex(i) && posInfo.Symbol() == _Symbol && posInfo.Magic() == MagicTP2) {
            
            double trailPoints = TrailingStopPips * pipSize;
            double stepPoints = TrailingStepPips * pipSize;
            double currentSl = posInfo.StopLoss();
            double openPrice = posInfo.PriceOpen();

            if(posInfo.PositionType() == POSITION_TYPE_BUY) {
                double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
                if(bid - openPrice > trailPoints) {
                    double newSl = NormalizeDouble(bid - trailPoints, _Digits);
                    if(currentSl < newSl - stepPoints || currentSl == 0) {
                        trade2.PositionModify(posInfo.Ticket(), newSl, posInfo.TakeProfit());
                    }
                }
            } 
            else if(posInfo.PositionType() == POSITION_TYPE_SELL) {
                double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
                if(openPrice - ask > trailPoints) {
                    double newSl = NormalizeDouble(ask + trailPoints, _Digits);
                    if(currentSl > newSl + stepPoints || currentSl == 0) {
                        trade2.PositionModify(posInfo.Ticket(), newSl, posInfo.TakeProfit());
                    }
                }
            }
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3921
Joined: Mon Jul 20, 2026 1:28 pm

Re: Post-central bank speech review template for scalpers

Post by PTScalper »

4. Integration into OnTick()

Finally, call the function inside your OnTick() sequence in both platforms. Place it directly below ManageBreakeven().

Code: Select all

void OnTick() {
    // ... existing top-of-tick code ...

    bool inNews = IsInNewsWindow(TimeCurrent());
    
    // Execute Trade Management First
    ManageBreakeven();
    ManageTrailingStop();  // <--- ADD THIS LINE HERE

    // Update HUD
    // ... rest of the logic ...
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3921
Joined: Mon Jul 20, 2026 1:28 pm

Re: Post-central bank speech review template for scalpers

Post by PTScalper »

Moving this strategy into cTrader (cAlgo) is going to feel like a massive upgrade. MQL4/5 requires clunky workarounds for bar states and array copying, but cTrader’s API provides a modern, event-driven C# environment where we can use LINQ for order management and built-in normalization for volume.

In this cBot, we will take advantage of cTrader’s native OnBar() handler to evaluate our triggers cleanly, while keeping OnTick() strictly for trade management (Breakeven and Trailing Stop) and real-time box drawing. We will also use string Labels instead of Magic Numbers to manage the split-ticket TP1 and TP2 positions.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3921
Joined: Mon Jul 20, 2026 1:28 pm

Re: Post-central bank speech review template for scalpers

Post by PTScalper »

Here is the complete Ultra-Pro News Scalper written in C# for cTrader.

The cTrader cBot (C#)

Code: Select all

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

namespace cAlgo.Robots
{
    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class UltraProNewsScalper : Robot
    {
        // =========================================================================
        // 1. SESSION & REACTION
        // =========================================================================
        [Parameter("News Start Time (HH:mm)", Group = "1. Session", DefaultValue = "14:00")]
        public string NewsStartTimeStr { get; set; }

        [Parameter("News End Time (HH:mm)", Group = "1. Session", DefaultValue = "14:15")]
        public string NewsEndTimeStr { get; set; }

        // =========================================================================
        // 2. TRIGGER & FILTERS
        // =========================================================================
        [Parameter("Pullback EMA", Group = "2. Triggers & Filters", DefaultValue = 9)]
        public int EmaLength { get; set; }

        [Parameter("Max Bars to Wait", Group = "2. Triggers & Filters", DefaultValue = 30)]
        public int MaxWaitBars { get; set; }

        [Parameter("Max Allowed SL (Pips)", Group = "2. Triggers & Filters", DefaultValue = 30.0)]
        public double MaxSlPips { get; set; }

        [Parameter("SL Wick Buffer (Pips)", Group = "2. Triggers & Filters", DefaultValue = 1.5)]
        public double SlBufferPips { get; set; }

        // =========================================================================
        // 3. RISK & MANAGEMENT
        // =========================================================================
        [Parameter("Total Risk Per Trade (%)", Group = "3. Risk & Management", DefaultValue = 1.0)]
        public double RiskPercent { get; set; }

        [Parameter("Target 1 (R:R)", Group = "3. Risk & Management", DefaultValue = 1.0)]
        public double Tp1Rr { get; set; }

        [Parameter("Target 1 Exit Size (%)", Group = "3. Risk & Management", DefaultValue = 50, MinValue = 1, MaxValue = 99)]
        public int Tp1QtyPct { get; set; }

        [Parameter("Target 2 / Runner (R:R)", Group = "3. Risk & Management", DefaultValue = 2.5)]
        public double Tp2Rr { get; set; }

        [Parameter("Auto-Breakeven Runner?", Group = "3. Risk & Management", DefaultValue = true)]
        public bool AutoBreakeven { get; set; }

        [Parameter("Trail Runner?", Group = "3. Risk & Management", DefaultValue = true)]
        public bool UseTrailingStop { get; set; }

        [Parameter("Trailing Stop (Pips)", Group = "3. Risk & Management", DefaultValue = 15.0)]
        public double TrailingStopPips { get; set; }

        [Parameter("Trailing Step (Pips)", Group = "3. Risk & Management", DefaultValue = 2.0)]
        public double TrailingStepPips { get; set; }

        // State Variables
        private TimeSpan _startTime;
        private TimeSpan _endTime;
        private ExponentialMovingAverage _ema;
        
        private double _initOpen, _initHigh, _initLow;
        private int _trendDir = 0;
        private int _barsSinceNews = 0;
        private bool _pullbackValid = false;
        private double _pivotExtreme = double.NaN;
        private bool _wasInNews = false;
        private DateTime _sessionStartDateTime;
        
        private const string Tp1Label = "NewsScalp_TP1";
        private const string Tp2Label = "NewsScalp_TP2";
        private const string BoxName = "NewsBox";

        protected override void OnStart()
        {
            if (!TimeSpan.TryParse(NewsStartTimeStr, out _startTime) || !TimeSpan.TryParse(NewsEndTimeStr, out _endTime))
            {
                Print("Invalid Time Format. Please use HH:mm");
                Stop();
            }

            _ema = Indicators.ExponentialMovingAverage(Bars.ClosePrices, EmaLength);
            _wasInNews = IsInNewsWindow(Server.Time.TimeOfDay);
        }

        private bool IsInNewsWindow(TimeSpan currentTime)
        {
            if (_startTime < _endTime)
                return currentTime >= _startTime && currentTime < _endTime;
            return currentTime >= _startTime || currentTime < _endTime;
        }

        protected override void OnTick()
        {
            bool inNews = IsInNewsWindow(Server.Time.TimeOfDay);

            ManageBreakevenAndTrailing();
            UpdateHUD(inNews);

            // 1. Session Start (Box Setup)
            if (inNews && !_wasInNews)
            {
                _initOpen = Bars.OpenPrices.LastValue;
                _initHigh = Bars.HighPrices.LastValue;
                _initLow = Bars.LowPrices.LastValue;
                _trendDir = 0;
                _pullbackValid = false;
                _barsSinceNews = 0;
                _sessionStartDateTime = Bars.OpenTimes.LastValue;

                Chart.DrawRectangle(BoxName, _sessionStartDateTime, _initHigh, Bars.OpenTimes.LastValue, _initLow, Color.FromArgb(50, Color.DarkGray));
            }

            // 2. Session Update (Expand Box vertically/horizontally)
            if (inNews)
            {
                _initHigh = Math.Max(_initHigh, Bars.HighPrices.LastValue);
                _initLow = Math.Min(_initLow, Bars.LowPrices.LastValue);
                Chart.DrawRectangle(BoxName, _sessionStartDateTime, _initHigh, Bars.OpenTimes.LastValue, _initLow, Color.FromArgb(50, Color.DarkGray));
            }

            // 3. Session End (Determine Trend & Color Box)
            if (!inNews && _wasInNews)
            {
                double initClose = Bars.ClosePrices.Last(1); // Close of the previous bar
                
                if (initClose > _initOpen)
                {
                    _trendDir = 1;
                    Chart.DrawRectangle(BoxName, _sessionStartDateTime, _initHigh, Bars.OpenTimes.LastValue, _initLow, Color.FromArgb(50, Color.Teal));
                }
                else if (initClose < _initOpen)
                {
                    _trendDir = -1;
                    Chart.DrawRectangle(BoxName, _sessionStartDateTime, _initHigh, Bars.OpenTimes.LastValue, _initLow, Color.FromArgb(50, Color.Maroon));
                }
                else
                {
                    _trendDir = 0;
                }

                _pivotExtreme = _trendDir == 1 ? Bars.LowPrices.Last(1) : Bars.HighPrices.Last(1);
            }

            // Continuous Stop-Loss Pivot Tracking for active setups
            if (!inNews && _trendDir != 0 && !HasActivePosition())
            {
                if (_trendDir == 1 && Bars.LowPrices.LastValue < _pivotExtreme) _pivotExtreme = Bars.LowPrices.LastValue;
                if (_trendDir == -1 && Bars.HighPrices.LastValue > _pivotExtreme) _pivotExtreme = Bars.HighPrices.LastValue;
            }

            _wasInNews = inNews;
        }

        protected override void OnBar()
        {
            if (IsInNewsWindow(Server.Time.TimeOfDay) || _trendDir == 0 || HasActivePosition()) 
                return;

            _barsSinceNews++;
            
            // Expiration Filter
            if (_barsSinceNews > MaxWaitBars)
            {
                _trendDir = 0;
                _pullbackValid = false;
                return;
            }

            double close1 = Bars.ClosePrices.Last(1);
            double ema1 = _ema.Result.Last(1);
            double slBuffer = Symbol.PipSize * SlBufferPips;

            // LONG SETUP
            if (_trendDir == 1)
            {
                if (close1 < ema1) _pullbackValid = true;

                if (_pullbackValid && close1 > ema1) // Trigger
                {
                    double sl = Math.Round(_pivotExtreme - slBuffer, Symbol.Digits);
                    ExecuteSplitOrder(TradeType.Buy, sl);
                }
            }

            // SHORT SETUP
            if (_trendDir == -1)
            {
                if (close1 > ema1) _pullbackValid = true;

                if (_pullbackValid && close1 < ema1) // Trigger
                {
                    double sl = Math.Round(_pivotExtreme + slBuffer, Symbol.Digits);
                    ExecuteSplitOrder(TradeType.Sell, sl);
                }
            }
        }

        private void ExecuteSplitOrder(TradeType tradeType, double sl)
        {
            double entryPrice = tradeType == TradeType.Buy ? Symbol.Ask : Symbol.Bid;
            double slPips = Math.Abs(entryPrice - sl) / Symbol.PipSize;

            if (slPips <= 0 || slPips > MaxSlPips) return; // Volatility Filter

            // cTrader perfect risk calculation
            double riskAmount = Account.Balance * (RiskPercent / 100.0);
            double exactVolume = riskAmount / (slPips * Symbol.PipValue);
            double totalVolume = Symbol.NormalizeVolumeInUnits(exactVolume, RoundingMode.Down);

            if (totalVolume < Symbol.VolumeInUnitsMin) return;

            double vol1 = Symbol.NormalizeVolumeInUnits(totalVolume * (Tp1QtyPct / 100.0), RoundingMode.Down);
            double vol2 = Symbol.NormalizeVolumeInUnits(totalVolume - vol1, RoundingMode.Down);

            if (vol1 > 0 && vol2 > 0)
            {
                double tp1Offset = slPips * Tp1Rr;
                double tp2Offset = slPips * Tp2Rr;

                ExecuteMarketOrder(tradeType, SymbolName, vol1, Tp1Label, slPips, tp1Offset);
                ExecuteMarketOrder(tradeType, SymbolName, vol2, Tp2Label, slPips, tp2Offset);

                _trendDir = 0; // Reset setup
                _pullbackValid = false;
            }
        }

        private void ManageBreakevenAndTrailing()
        {
            var tp1Exists = Positions.FindAll(Tp1Label, SymbolName).Any();
            var runners = Positions.FindAll(Tp2Label, SymbolName);

            foreach (var runner in runners)
            {
                // 1. Auto-Breakeven logic (If TP1 is hit/closed)
                if (AutoBreakeven && !tp1Exists)
                {
                    if (runner.TradeType == TradeType.Buy && runner.StopLoss < runner.EntryPrice)
                    {
                        ModifyPosition(runner, runner.EntryPrice, runner.TakeProfit);
                    }
                    else if (runner.TradeType == TradeType.Sell && runner.StopLoss > runner.EntryPrice)
                    {
                        ModifyPosition(runner, runner.EntryPrice, runner.TakeProfit);
                    }
                }

                // 2. Trailing Stop Logic for Runner (Only activates after TP1 is hit)
                if (UseTrailingStop && !tp1Exists)
                {
                    double trailPips = TrailingStopPips * Symbol.PipSize;
                    double stepPips = TrailingStepPips * Symbol.PipSize;

                    if (runner.TradeType == TradeType.Buy)
                    {
                        if (Symbol.Bid - runner.EntryPrice > trailPips)
                        {
                            double newSl = Math.Round(Symbol.Bid - trailPips, Symbol.Digits);
                            if (runner.StopLoss == null || runner.StopLoss < newSl - stepPips)
                            {
                                ModifyPosition(runner, newSl, runner.TakeProfit);
                            }
                        }
                    }
                    else if (runner.TradeType == TradeType.Sell)
                    {
                        if (runner.EntryPrice - Symbol.Ask > trailPips)
                        {
                            double newSl = Math.Round(Symbol.Ask + trailPips, Symbol.Digits);
                            if (runner.StopLoss == null || runner.StopLoss > newSl + stepPips)
                            {
                                ModifyPosition(runner, newSl, runner.TakeProfit);
                            }
                        }
                    }
                }
            }
        }

        private bool HasActivePosition()
        {
            return Positions.FindAll(Tp1Label, SymbolName).Any() || Positions.FindAll(Tp2Label, SymbolName).Any();
        }

        private void UpdateHUD(bool inNews)
        {
            string bias = inNews ? "Awaiting News..." : (_trendDir == 1 ? "Bullish Pullback" : (_trendDir == -1 ? "Bearish Pullback" : "Flat / No Setup"));
            string pos = HasActivePosition() ? "ACTIVE" : "WAITING";

            string hudText = $"=== PRO SCALPER HUD ===\nStatus: {bias}\nPosition: {pos}";
            Chart.DrawStaticText("HUD", hudText, VerticalAlignment.Top, HorizontalAlignment.Right, Color.White);
        }
    }
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Post Reply