Page 3 of 3

Re: Stop Trading Fixed Lot Sizes: Why Sizing by Pip Distance Saved My Account

Posted: Sat Sep 26, 2026 6:23 pm
by PTScalper
2. MetaTrader 4 Version (MQL4)

Code: Select all

//+------------------------------------------------------------------+
//|                                          DynamicRiskExecutor.mq4 |
//+------------------------------------------------------------------+
#property copyright "Dynamic Risk Sizer"
#property link      ""
#property version   "1.20"
#property strict

enum ENUM_SL_MODE
{
   SL_MANUAL_PIPS,    // Manual Pips
   SL_ATR_VOLATILITY  // ATR Volatility
};

// --- Inputs ---
input string          InpHeader1         = "=== Risk & Stop Loss ===";
input double          InpRiskPct         = 0.5;             // Risk Per Trade (%)
input ENUM_SL_MODE    InpSlMode          = SL_MANUAL_PIPS;  // Stop Loss Mode
input double          InpManualPips      = 10.0;            // Manual SL (Pips)
input int             InpAtrPeriod       = 14;              // ATR Period
input double          InpAtrMultiplier   = 1.5;             // ATR Multiplier
input double          InpRiskRewardRatio = 2.0;             // Risk:Reward Ratio (0 = No TP)

input string          InpHeader2         = "=== Trade Management ===";
input bool            InpUseBreakeven    = true;            // Auto Move SL to Breakeven
input double          InpBreakevenTriggerR = 1.0;           // Breakeven Trigger (Multiple of Risk/R)

input string          InpHeader3         = "=== System ===";
input int             InpMagicNumber     = 223344;          // Magic Number

double currentLots = 0.0;
double currentSlPips = 0.0;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   CreateUI();
   UpdateCalculations();
   return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   ObjectDelete(0, "DRS_BG");
   ObjectDelete(0, "DRS_LABEL");
   ObjectDelete(0, "DRS_BUY_BTN");
   ObjectDelete(0, "DRS_SELL_BTN");
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   UpdateCalculations();
   if(InpUseBreakeven) CheckBreakeven();
}

//+------------------------------------------------------------------+
//| Auto Breakeven Logic                                             |
//+------------------------------------------------------------------+
void CheckBreakeven()
{
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
         if(OrderSymbol() == Symbol() && OrderMagicNumber() == InpMagicNumber)
         {
            double openPrice = OrderOpenPrice();
            double currentSL = OrderStopLoss();
            double currentTP = OrderTakeProfit();
            int type = OrderType();
            
            double normOpenPrice = NormalizeDouble(openPrice, Digits);
            
            if(type == OP_BUY)
            {
               if(currentSL < normOpenPrice) // SL is still below entry price
               {
                  double slDistance = openPrice - currentSL;
                  double triggerPrice = openPrice + (slDistance * InpBreakevenTriggerR);
                  double currentBid = MarketInfo(Symbol(), MODE_BID);
                  
                  if(currentBid >= triggerPrice)
                  {
                     bool res = OrderModify(OrderTicket(), normOpenPrice, normOpenPrice, currentTP, 0, clrBlue);
                     if(res) Print("Buy trade moved to breakeven.");
                  }
               }
            }
            else if(type == OP_SELL)
            {
               if(currentSL > normOpenPrice || currentSL == 0) // SL is still above entry price
               {
                  double slDistance = currentSL - openPrice;
                  double triggerPrice = openPrice - (slDistance * InpBreakevenTriggerR);
                  double currentAsk = MarketInfo(Symbol(), MODE_ASK);
                  
                  if(currentAsk <= triggerPrice && slDistance > 0)
                  {
                     bool res = OrderModify(OrderTicket(), normOpenPrice, normOpenPrice, currentTP, 0, clrRed);
                     if(res) Print("Sell trade moved to breakeven.");
                  }
               }
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Core Calculation Logic                                           |
//+------------------------------------------------------------------+
void UpdateCalculations()
{
   double balance = AccountBalance();
   double riskDollars = balance * (InpRiskPct / 100.0);
   
   double pipSize = (Digits == 3 || Digits == 5) ? Point * 10 : Point;

   if(InpSlMode == SL_MANUAL_PIPS)
   {
      currentSlPips = InpManualPips;
   }
   else
   {
      double atrVal = iATR(Symbol(), 0, InpAtrPeriod, 1);
      currentSlPips = (atrVal / pipSize) * InpAtrMultiplier;
   }

   if(currentSlPips <= 0) return;

   double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
   double tickSize  = MarketInfo(Symbol(), MODE_TICKSIZE);
   if(tickSize == 0 || tickValue == 0) return;

   double slDistancePrice = currentSlPips * pipSize;
   double riskPerLot = (slDistancePrice / tickSize) * tickValue;
   if(riskPerLot <= 0) return;

   double rawLots = riskDollars / riskPerLot;
   double stepLot = MarketInfo(Symbol(), MODE_LOTSTEP);

   currentLots = MathFloor(rawLots / stepLot) * stepLot;
   if(currentLots < MarketInfo(Symbol(), MODE_MINLOT)) currentLots = MarketInfo(Symbol(), MODE_MINLOT);
   if(currentLots > MarketInfo(Symbol(), MODE_MAXLOT)) currentLots = MarketInfo(Symbol(), MODE_MAXLOT);

   string mode = (InpSlMode == SL_MANUAL_PIPS) ? "Pips" : "Pips (ATR)";
   string tpMode = (InpRiskRewardRatio > 0) ? StringFormat("\nTP: 1:%.1f (%.1f Pips)", InpRiskRewardRatio, currentSlPips * InpRiskRewardRatio) : "\nTP: None";
   string info = StringFormat("Risk: $%.2f (%.1f%%)\nSL: %.1f %s%s\nLots: %.2f", 
                              riskDollars, InpRiskPct, currentSlPips, mode, tpMode, currentLots);
   ObjectSetString(0, "DRS_LABEL", OBJPROP_TEXT, info);
}

//+------------------------------------------------------------------+
//| Chart Event Handler (Clicks)                                     |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   if(id != CHARTEVENT_OBJECT_CLICK) return;

   double pipSize = (Digits == 3 || Digits == 5) ? Point * 10 : Point;
   double slOffset = currentSlPips * pipSize;

   if(sparam == "DRS_BUY_BTN")
   {
      RefreshRates();
      double ask = MarketInfo(Symbol(), MODE_ASK);
      double sl = NormalizeDouble(ask - slOffset, Digits);
      double tp = (InpRiskRewardRatio > 0) ? NormalizeDouble(ask + (slOffset * InpRiskRewardRatio), Digits) : 0;
      
      int ticket = OrderSend(Symbol(), OP_BUY, currentLots, ask, 3, sl, tp, "DynamicRisk", InpMagicNumber, 0, clrSeaGreen);
      if(ticket < 0) Print("Buy order failed. Error: ", GetLastError());
      ObjectSetInteger(0, "DRS_BUY_BTN", OBJPROP_STATE, false);
   }
   else if(sparam == "DRS_SELL_BTN")
   {
      RefreshRates();
      double bid = MarketInfo(Symbol(), MODE_BID);
      double sl = NormalizeDouble(bid + slOffset, Digits);
      double tp = (InpRiskRewardRatio > 0) ? NormalizeDouble(bid - (slOffset * InpRiskRewardRatio), Digits) : 0;
      
      int ticket = OrderSend(Symbol(), OP_SELL, currentLots, bid, 3, sl, tp, "DynamicRisk", InpMagicNumber, 0, clrFireBrick);
      if(ticket < 0) Print("Sell order failed. Error: ", GetLastError());
      ObjectSetInteger(0, "DRS_SELL_BTN", OBJPROP_STATE, false);
   }
}

//+------------------------------------------------------------------+
//| UI Builder                                                       |
//+------------------------------------------------------------------+
void CreateUI()
{
   int x = 20; int y = 40;

   ObjectCreate(0, "DRS_BG", OBJ_RECTANGLE_LABEL, 0, 0, 0);
   ObjectSetInteger(0, "DRS_BG", OBJPROP_XDISTANCE, x);
   ObjectSetInteger(0, "DRS_BG", OBJPROP_YDISTANCE, y);
   ObjectSetInteger(0, "DRS_BG", OBJPROP_XSIZE, 150);
   ObjectSetInteger(0, "DRS_BG", OBJPROP_YSIZE, 135);
   ObjectSetInteger(0, "DRS_BG", OBJPROP_BGCOLOR, clrBlack);
   ObjectSetInteger(0, "DRS_BG", OBJPROP_BORDER_COLOR, clrDodgerBlue);
   ObjectSetInteger(0, "DRS_BG", OBJPROP_CORNER, CORNER_LEFT_UPPER);

   ObjectCreate(0, "DRS_LABEL", OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, "DRS_LABEL", OBJPROP_XDISTANCE, x + 10);
   ObjectSetInteger(0, "DRS_LABEL", OBJPROP_YDISTANCE, y + 10);
   ObjectSetInteger(0, "DRS_LABEL", OBJPROP_COLOR, clrWhite);
   ObjectSetInteger(0, "DRS_LABEL", OBJPROP_FONTSIZE, 9);
   ObjectSetInteger(0, "DRS_LABEL", OBJPROP_CORNER, CORNER_LEFT_UPPER);

   ObjectCreate(0, "DRS_BUY_BTN", OBJ_BUTTON, 0, 0, 0);
   ObjectSetInteger(0, "DRS_BUY_BTN", OBJPROP_XDISTANCE, x + 10);
   ObjectSetInteger(0, "DRS_BUY_BTN", OBJPROP_YDISTANCE, y + 85);
   ObjectSetInteger(0, "DRS_BUY_BTN", OBJPROP_XSIZE, 60);
   ObjectSetInteger(0, "DRS_BUY_BTN", OBJPROP_YSIZE, 35);
   ObjectSetString(0, "DRS_BUY_BTN", OBJPROP_TEXT, "BUY");
   ObjectSetInteger(0, "DRS_BUY_BTN", OBJPROP_BGCOLOR, clrSeaGreen);
   ObjectSetInteger(0, "DRS_BUY_BTN", OBJPROP_COLOR, clrWhite);
   ObjectSetInteger(0, "DRS_BUY_BTN", OBJPROP_CORNER, CORNER_LEFT_UPPER);

   ObjectCreate(0, "DRS_SELL_BTN", OBJ_BUTTON, 0, 0, 0);
   ObjectSetInteger(0, "DRS_SELL_BTN", OBJPROP_XDISTANCE, x + 80);
   ObjectSetInteger(0, "DRS_SELL_BTN", OBJPROP_YDISTANCE, y + 85);
   ObjectSetInteger(0, "DRS_SELL_BTN", OBJPROP_XSIZE, 60);
   ObjectSetInteger(0, "DRS_SELL_BTN", OBJPROP_YSIZE, 35);
   ObjectSetString(0, "DRS_SELL_BTN", OBJPROP_TEXT, "SELL");
   ObjectSetInteger(0, "DRS_SELL_BTN", OBJPROP_BGCOLOR, clrFireBrick);
   ObjectSetInteger(0, "DRS_SELL_BTN", OBJPROP_COLOR, clrWhite);
   ObjectSetInteger(0, "DRS_SELL_BTN", OBJPROP_CORNER, CORNER_LEFT_UPPER);
}

Re: Stop Trading Fixed Lot Sizes: Why Sizing by Pip Distance Saved My Account

Posted: Sat Sep 26, 2026 6:26 pm
by PTScalper
To upgrade this into a professional-grade scalping tool, we need to move beyond basic math and add the protections and management features that proprietary trading firms require.

I have rewritten both EAs to include four major professional upgrades:

Market Structure Stop Loss: Added a Swing High / Swing Low mode. Instead of arbitrary pips, it scans the last N bars and places your stop exactly behind the structural liquidity sweep (plus a small buffer), dynamically sizing the lot to fit that exact structure.

Spread Filter & Slippage Control: As a 1M scalper, spread widening can destroy your risk model. The UI now displays the real-time spread. If it exceeds your Max Spread input, the dashboard turns red and execution is blocked.

Advanced "Scale Out" Management: A stateless partial-close engine. When price hits a specified target (e.g., 1.5R), it simultaneously moves your SL to Breakeven and closes a specific percentage of your position (e.g., 50%) to secure realized profit while leaving runners.

Professional HUD: The UI is rebuilt into a dark-mode dashboard displaying exact dollar amounts for Risk, Reward, and live spread conditions.

Re: Stop Trading Fixed Lot Sizes: Why Sizing by Pip Distance Saved My Account

Posted: Sat Sep 26, 2026 6:27 pm
by PTScalper
1. MetaTrader 5 Pro Edition (MQL5)

Code: Select all

//+------------------------------------------------------------------+
//|                                     DynamicRiskExecutor_PRO.mq5  |
//+------------------------------------------------------------------+
#property copyright "Pro Dynamic Risk Sizer"
#property version   "2.00"
#include <Trade\Trade.mqh>

enum ENUM_SL_MODE {
   SL_MANUAL_PIPS,    // Manual Pips
   SL_ATR_VOLATILITY, // ATR Volatility
   SL_SWING_HIGHLOW   // Recent Swing High/Low
};

// --- Inputs ---
input group "=== Risk & Execution ==="
input double          InpRiskPct         = 0.5;             // Risk Per Trade (%)
input double          InpMaxSpreadPips   = 2.0;             // Max Allowed Spread (Pips)
input ulong           InpMaxSlippage     = 3;               // Max Slippage (Points)

input group "=== Stop Loss Logic ==="
input ENUM_SL_MODE    InpSlMode          = SL_SWING_HIGHLOW;// Stop Loss Logic
input double          InpManualPips      = 10.0;            // Manual SL (Pips)
input int             InpAtrOrSwingBars  = 14;              // ATR Period / Swing Bars Back
input double          InpBufferPips      = 1.0;             // ATR Mult / Swing Buffer Pips

input group "=== Take Profit & Management ==="
input double          InpRiskRewardRatio = 2.0;             // Initial TP (R:R Ratio, 0 = None)
input double          InpScaleOutR       = 1.0;             // Scale Out & BE Trigger (R-Multiple)
input double          InpScaleOutPct     = 50.0;            // % of Position to Close at Trigger

input group "=== System ==="
input ulong           InpMagicNumber     = 998877;

CTrade trade;
int    atrHandle;
double currentLots = 0.0;
double currentSlPips = 0.0;
bool   spreadOk = true;

int OnInit() {
   trade.SetExpertMagicNumber(InpMagicNumber);
   trade.SetDeviationInPoints(InpMaxSlippage);
   
   if(InpSlMode == SL_ATR_VOLATILITY) {
      atrHandle = iATR(_Symbol, _Period, InpAtrOrSwingBars);
   }
   CreateUI();
   return INIT_SUCCEEDED;
}

void OnDeinit(const int reason) {
   if(atrHandle != INVALID_HANDLE) IndicatorRelease(atrHandle);
   ObjectsDeleteAll(0, "DRS_");
}

void OnTick() {
   UpdateCalculations();
   ManageOpenTrades();
}

void UpdateCalculations() {
   double balance = AccountInfoDouble(ACCOUNT_BALANCE);
   double riskDollars = balance * (InpRiskPct / 100.0);
   double pipSize = (_Digits == 3 || _Digits == 5) ? _Point * 10 : _Point;
   
   // Real-time Spread Check
   double currentSpread = SymbolInfoInteger(_Symbol, SYMBOL_SPREAD) * _Point / pipSize;
   spreadOk = (currentSpread <= InpMaxSpreadPips);

   // SL Calculation
   if(InpSlMode == SL_MANUAL_PIPS) {
      currentSlPips = InpManualPips;
   } 
   else if(InpSlMode == SL_ATR_VOLATILITY) {
      double atrVal[]; ArraySetAsSeries(atrVal, true);
      if(CopyBuffer(atrHandle, 0, 0, 1, atrVal) > 0)
         currentSlPips = (atrVal[0] / pipSize) * InpBufferPips;
   }
   else if(InpSlMode == SL_SWING_HIGHLOW) {
      double high[], low[]; 
      ArraySetAsSeries(high, true); ArraySetAsSeries(low, true);
      CopyHigh(_Symbol, _Period, 1, InpAtrOrSwingBars, high);
      CopyLow(_Symbol, _Period, 1, InpAtrOrSwingBars, low);
      
      double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      double highest = high[ArrayMaximum(high)];
      double lowest = low[ArrayMinimum(low)];
      
      // Assumes worst-case distance between current price and recent structure
      double distBuy = ask - lowest;
      double distSell = highest - SymbolInfoDouble(_Symbol, SYMBOL_BID);
      currentSlPips = (MathMax(distBuy, distSell) / pipSize) + InpBufferPips;
   }

   if(currentSlPips <= 0) return;

   // Lot Sizing
   double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
   double tickSize  = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   double riskPerLot = ((currentSlPips * pipSize) / tickSize) * tickValue;
   
   double rawLots = riskDollars / (riskPerLot > 0 ? riskPerLot : 1);
   double stepLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
   currentLots = MathFloor(rawLots / stepLot) * stepLot;
   
   double minLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   if(currentLots < minLot) currentLots = minLot;
   if(currentLots > maxLot) currentLots = maxLot;

   UpdateUI(riskDollars, currentSpread);
}

void ManageOpenTrades() {
   if(InpScaleOutR <= 0) return;

   for(int i = PositionsTotal() - 1; i >= 0; i--) {
      ulong ticket = PositionGetTicket(i);
      if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == InpMagicNumber) {
         
         double openPrice = PositionGetDouble(POSITION_PRICE_OPEN);
         double slPrice = PositionGetDouble(POSITION_SL);
         double tpPrice = PositionGetDouble(POSITION_TP);
         double volume = PositionGetDouble(POSITION_VOLUME);
         long type = PositionGetInteger(POSITION_TYPE);
         
         double normOpen = NormalizeDouble(openPrice, _Digits);
         double normSL = NormalizeDouble(slPrice, _Digits);
         
         // Stateless check: If SL != Entry, trade hasn't been managed yet
         if(normSL != normOpen && normSL != 0) {
            double riskDist = (type == POSITION_TYPE_BUY) ? (openPrice - slPrice) : (slPrice - openPrice);
            double triggerPrice = (type == POSITION_TYPE_BUY) ? openPrice + (riskDist * InpScaleOutR) : openPrice - (riskDist * InpScaleOutR);
            double currentPrice = (type == POSITION_TYPE_BUY) ? SymbolInfoDouble(_Symbol, SYMBOL_BID) : SymbolInfoDouble(_Symbol, SYMBOL_ASK);
            
            bool triggered = (type == POSITION_TYPE_BUY) ? (currentPrice >= triggerPrice) : (currentPrice <= triggerPrice);
            
            if(triggered) {
               // 1. Partial Close
               double closeVol = 0;
               if(InpScaleOutPct > 0) {
                  double stepLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
                  closeVol = MathFloor((volume * (InpScaleOutPct / 100.0)) / stepLot) * stepLot;
                  if(closeVol >= SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN)) {
                     trade.PositionClosePartial(ticket, closeVol);
                  }
               }
               // 2. Move to Breakeven
               trade.PositionModify(ticket, normOpen, tpPrice);
               Print("Pro Manager: Scaled out and moved to Breakeven.");
            }
         }
      }
   }
}

void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) {
   if(id != CHARTEVENT_OBJECT_CLICK) return;

   double pipSize = (_Digits == 3 || _Digits == 5) ? _Point * 10 : _Point;
   double slOffset = currentSlPips * pipSize;

   if(sparam == "DRS_BUY_BTN" && spreadOk) {
      double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      double sl = NormalizeDouble(ask - slOffset, _Digits);
      double tp = (InpRiskRewardRatio > 0) ? NormalizeDouble(ask + (slOffset * InpRiskRewardRatio), _Digits) : 0;
      trade.Buy(currentLots, _Symbol, ask, sl, tp, "Pro_Buy");
   }
   else if(sparam == "DRS_SELL_BTN" && spreadOk) {
      double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      double sl = NormalizeDouble(bid + slOffset, _Digits);
      double tp = (InpRiskRewardRatio > 0) ? NormalizeDouble(bid - (slOffset * InpRiskRewardRatio), _Digits) : 0;
      trade.Sell(currentLots, _Symbol, bid, sl, tp, "Pro_Sell");
   }
   ObjectSetInteger(0, sparam, OBJPROP_STATE, false);
}

void CreateUI() {
   int x = 20, y = 30;
   
   ObjectCreate(0, "DRS_BG", OBJ_RECTANGLE_LABEL, 0, 0, 0);
   ObjectSetInteger(0, "DRS_BG", OBJPROP_XDISTANCE, x);
   ObjectSetInteger(0, "DRS_BG", OBJPROP_YDISTANCE, y);
   ObjectSetInteger(0, "DRS_BG", OBJPROP_XSIZE, 170);
   ObjectSetInteger(0, "DRS_BG", OBJPROP_YSIZE, 160);
   ObjectSetInteger(0, "DRS_BG", OBJPROP_BGCOLOR, 0x2D2D2D); // Dark gray
   ObjectSetInteger(0, "DRS_BG", OBJPROP_BORDER_COLOR, 0xCC7A00); // Blue Accent
   
   ObjectCreate(0, "DRS_TITLE", OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, "DRS_TITLE", OBJPROP_XDISTANCE, x + 10);
   ObjectSetInteger(0, "DRS_TITLE", OBJPROP_YDISTANCE, y + 10);
   ObjectSetString(0, "DRS_TITLE", OBJPROP_TEXT, "PRO SIZER");
   ObjectSetInteger(0, "DRS_TITLE", OBJPROP_COLOR, clrWhite);
   ObjectSetString(0, "DRS_TITLE", OBJPROP_FONT, "Trebuchet MS");
   
   ObjectCreate(0, "DRS_SPREAD", OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, "DRS_SPREAD", OBJPROP_XDISTANCE, x + 10);
   ObjectSetInteger(0, "DRS_SPREAD", OBJPROP_YDISTANCE, y + 35);
   ObjectSetString(0, "DRS_SPREAD", OBJPROP_FONT, "Trebuchet MS");
   
   ObjectCreate(0, "DRS_INFO", OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, "DRS_INFO", OBJPROP_XDISTANCE, x + 10);
   ObjectSetInteger(0, "DRS_INFO", OBJPROP_YDISTANCE, y + 55);
   ObjectSetInteger(0, "DRS_INFO", OBJPROP_COLOR, clrLightGray);
   ObjectSetString(0, "DRS_INFO", OBJPROP_FONT, "Consolas");
   
   ObjectCreate(0, "DRS_BUY_BTN", OBJ_BUTTON, 0, 0, 0);
   ObjectSetInteger(0, "DRS_BUY_BTN", OBJPROP_XDISTANCE, x + 10);
   ObjectSetInteger(0, "DRS_BUY_BTN", OBJPROP_YDISTANCE, y + 115);
   ObjectSetInteger(0, "DRS_BUY_BTN", OBJPROP_XSIZE, 70);
   ObjectSetInteger(0, "DRS_BUY_BTN", OBJPROP_YSIZE, 35);
   ObjectSetString(0, "DRS_BUY_BTN", OBJPROP_TEXT, "BUY");
   ObjectSetInteger(0, "DRS_BUY_BTN", OBJPROP_BGCOLOR, clrSeaGreen);
   ObjectSetInteger(0, "DRS_BUY_BTN", OBJPROP_COLOR, clrWhite);
   
   ObjectCreate(0, "DRS_SELL_BTN", OBJ_BUTTON, 0, 0, 0);
   ObjectSetInteger(0, "DRS_SELL_BTN", OBJPROP_XDISTANCE, x + 90);
   ObjectSetInteger(0, "DRS_SELL_BTN", OBJPROP_YDISTANCE, y + 115);
   ObjectSetInteger(0, "DRS_SELL_BTN", OBJPROP_XSIZE, 70);
   ObjectSetInteger(0, "DRS_SELL_BTN", OBJPROP_YSIZE, 35);
   ObjectSetString(0, "DRS_SELL_BTN", OBJPROP_TEXT, "SELL");
   ObjectSetInteger(0, "DRS_SELL_BTN", OBJPROP_BGCOLOR, clrFireBrick);
   ObjectSetInteger(0, "DRS_SELL_BTN", OBJPROP_COLOR, clrWhite);
}

void UpdateUI(double riskDollars, double spread) {
   color spreadColor = spreadOk ? clrLimeGreen : clrRed;
   string spreadTxt = StringFormat("Spread: %.1f pips", spread);
   ObjectSetString(0, "DRS_SPREAD", OBJPROP_TEXT, spreadTxt);
   ObjectSetInteger(0, "DRS_SPREAD", OBJPROP_COLOR, spreadColor);
   
   double estReward = riskDollars * InpRiskRewardRatio;
   string info = StringFormat("Risk:  $%.2f\nRewd:  $%.2f\nSL:    %.1f pips\nSize:  %.2f Lots", 
                              riskDollars, estReward, currentSlPips, currentLots);
   ObjectSetString(0, "DRS_INFO", OBJPROP_TEXT, info);
}

Re: Stop Trading Fixed Lot Sizes: Why Sizing by Pip Distance Saved My Account

Posted: Sat Sep 26, 2026 6:27 pm
by PTScalper
2. MetaTrader 4 Version (MQL4)

Code: Select all

//+------------------------------------------------------------------+
//|                                          DynamicRiskExecutor.mq4 |
//+------------------------------------------------------------------+
#property copyright "Dynamic Risk Sizer"
#property link      ""
#property version   "1.20"
#property strict

enum ENUM_SL_MODE
{
   SL_MANUAL_PIPS,    // Manual Pips
   SL_ATR_VOLATILITY  // ATR Volatility
};

// --- Inputs ---
input string          InpHeader1         = "=== Risk & Stop Loss ===";
input double          InpRiskPct         = 0.5;             // Risk Per Trade (%)
input ENUM_SL_MODE    InpSlMode          = SL_MANUAL_PIPS;  // Stop Loss Mode
input double          InpManualPips      = 10.0;            // Manual SL (Pips)
input int             InpAtrPeriod       = 14;              // ATR Period
input double          InpAtrMultiplier   = 1.5;             // ATR Multiplier
input double          InpRiskRewardRatio = 2.0;             // Risk:Reward Ratio (0 = No TP)

input string          InpHeader2         = "=== Trade Management ===";
input bool            InpUseBreakeven    = true;            // Auto Move SL to Breakeven
input double          InpBreakevenTriggerR = 1.0;           // Breakeven Trigger (Multiple of Risk/R)

input string          InpHeader3         = "=== System ===";
input int             InpMagicNumber     = 223344;          // Magic Number

double currentLots = 0.0;
double currentSlPips = 0.0;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   CreateUI();
   UpdateCalculations();
   return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   ObjectDelete(0, "DRS_BG");
   ObjectDelete(0, "DRS_LABEL");
   ObjectDelete(0, "DRS_BUY_BTN");
   ObjectDelete(0, "DRS_SELL_BTN");
}

//+------------------------------------------------------------------+
//| Expert tick function                                             |
//+------------------------------------------------------------------+
void OnTick()
{
   UpdateCalculations();
   if(InpUseBreakeven) CheckBreakeven();
}

//+------------------------------------------------------------------+
//| Auto Breakeven Logic                                             |
//+------------------------------------------------------------------+
void CheckBreakeven()
{
   for(int i = OrdersTotal() - 1; i >= 0; i--)
   {
      if(OrderSelect(i, SELECT_BY_POS, MODE_TRADES))
      {
         if(OrderSymbol() == Symbol() && OrderMagicNumber() == InpMagicNumber)
         {
            double openPrice = OrderOpenPrice();
            double currentSL = OrderStopLoss();
            double currentTP = OrderTakeProfit();
            int type = OrderType();
            
            double normOpenPrice = NormalizeDouble(openPrice, Digits);
            
            if(type == OP_BUY)
            {
               if(currentSL < normOpenPrice) // SL is still below entry price
               {
                  double slDistance = openPrice - currentSL;
                  double triggerPrice = openPrice + (slDistance * InpBreakevenTriggerR);
                  double currentBid = MarketInfo(Symbol(), MODE_BID);
                  
                  if(currentBid >= triggerPrice)
                  {
                     bool res = OrderModify(OrderTicket(), normOpenPrice, normOpenPrice, currentTP, 0, clrBlue);
                     if(res) Print("Buy trade moved to breakeven.");
                  }
               }
            }
            else if(type == OP_SELL)
            {
               if(currentSL > normOpenPrice || currentSL == 0) // SL is still above entry price
               {
                  double slDistance = currentSL - openPrice;
                  double triggerPrice = openPrice - (slDistance * InpBreakevenTriggerR);
                  double currentAsk = MarketInfo(Symbol(), MODE_ASK);
                  
                  if(currentAsk <= triggerPrice && slDistance > 0)
                  {
                     bool res = OrderModify(OrderTicket(), normOpenPrice, normOpenPrice, currentTP, 0, clrRed);
                     if(res) Print("Sell trade moved to breakeven.");
                  }
               }
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Core Calculation Logic                                           |
//+------------------------------------------------------------------+
void UpdateCalculations()
{
   double balance = AccountBalance();
   double riskDollars = balance * (InpRiskPct / 100.0);
   
   double pipSize = (Digits == 3 || Digits == 5) ? Point * 10 : Point;

   if(InpSlMode == SL_MANUAL_PIPS)
   {
      currentSlPips = InpManualPips;
   }
   else
   {
      double atrVal = iATR(Symbol(), 0, InpAtrPeriod, 1);
      currentSlPips = (atrVal / pipSize) * InpAtrMultiplier;
   }

   if(currentSlPips <= 0) return;

   double tickValue = MarketInfo(Symbol(), MODE_TICKVALUE);
   double tickSize  = MarketInfo(Symbol(), MODE_TICKSIZE);
   if(tickSize == 0 || tickValue == 0) return;

   double slDistancePrice = currentSlPips * pipSize;
   double riskPerLot = (slDistancePrice / tickSize) * tickValue;
   if(riskPerLot <= 0) return;

   double rawLots = riskDollars / riskPerLot;
   double stepLot = MarketInfo(Symbol(), MODE_LOTSTEP);

   currentLots = MathFloor(rawLots / stepLot) * stepLot;
   if(currentLots < MarketInfo(Symbol(), MODE_MINLOT)) currentLots = MarketInfo(Symbol(), MODE_MINLOT);
   if(currentLots > MarketInfo(Symbol(), MODE_MAXLOT)) currentLots = MarketInfo(Symbol(), MODE_MAXLOT);

   string mode = (InpSlMode == SL_MANUAL_PIPS) ? "Pips" : "Pips (ATR)";
   string tpMode = (InpRiskRewardRatio > 0) ? StringFormat("\nTP: 1:%.1f (%.1f Pips)", InpRiskRewardRatio, currentSlPips * InpRiskRewardRatio) : "\nTP: None";
   string info = StringFormat("Risk: $%.2f (%.1f%%)\nSL: %.1f %s%s\nLots: %.2f", 
                              riskDollars, InpRiskPct, currentSlPips, mode, tpMode, currentLots);
   ObjectSetString(0, "DRS_LABEL", OBJPROP_TEXT, info);
}

//+------------------------------------------------------------------+
//| Chart Event Handler (Clicks)                                     |
//+------------------------------------------------------------------+
void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam)
{
   if(id != CHARTEVENT_OBJECT_CLICK) return;

   double pipSize = (Digits == 3 || Digits == 5) ? Point * 10 : Point;
   double slOffset = currentSlPips * pipSize;

   if(sparam == "DRS_BUY_BTN")
   {
      RefreshRates();
      double ask = MarketInfo(Symbol(), MODE_ASK);
      double sl = NormalizeDouble(ask - slOffset, Digits);
      double tp = (InpRiskRewardRatio > 0) ? NormalizeDouble(ask + (slOffset * InpRiskRewardRatio), Digits) : 0;
      
      int ticket = OrderSend(Symbol(), OP_BUY, currentLots, ask, 3, sl, tp, "DynamicRisk", InpMagicNumber, 0, clrSeaGreen);
      if(ticket < 0) Print("Buy order failed. Error: ", GetLastError());
      ObjectSetInteger(0, "DRS_BUY_BTN", OBJPROP_STATE, false);
   }
   else if(sparam == "DRS_SELL_BTN")
   {
      RefreshRates();
      double bid = MarketInfo(Symbol(), MODE_BID);
      double sl = NormalizeDouble(bid + slOffset, Digits);
      double tp = (InpRiskRewardRatio > 0) ? NormalizeDouble(bid - (slOffset * InpRiskRewardRatio), Digits) : 0;
      
      int ticket = OrderSend(Symbol(), OP_SELL, currentLots, bid, 3, sl, tp, "DynamicRisk", InpMagicNumber, 0, clrFireBrick);
      if(ticket < 0) Print("Sell order failed. Error: ", GetLastError());
      ObjectSetInteger(0, "DRS_SELL_BTN", OBJPROP_STATE, false);
   }
}

//+------------------------------------------------------------------+
//| UI Builder                                                       |
//+------------------------------------------------------------------+
void CreateUI()
{
   int x = 20; int y = 40;

   ObjectCreate(0, "DRS_BG", OBJ_RECTANGLE_LABEL, 0, 0, 0);
   ObjectSetInteger(0, "DRS_BG", OBJPROP_XDISTANCE, x);
   ObjectSetInteger(0, "DRS_BG", OBJPROP_YDISTANCE, y);
   ObjectSetInteger(0, "DRS_BG", OBJPROP_XSIZE, 150);
   ObjectSetInteger(0, "DRS_BG", OBJPROP_YSIZE, 135);
   ObjectSetInteger(0, "DRS_BG", OBJPROP_BGCOLOR, clrBlack);
   ObjectSetInteger(0, "DRS_BG", OBJPROP_BORDER_COLOR, clrDodgerBlue);
   ObjectSetInteger(0, "DRS_BG", OBJPROP_CORNER, CORNER_LEFT_UPPER);

   ObjectCreate(0, "DRS_LABEL", OBJ_LABEL, 0, 0, 0);
   ObjectSetInteger(0, "DRS_LABEL", OBJPROP_XDISTANCE, x + 10);
   ObjectSetInteger(0, "DRS_LABEL", OBJPROP_YDISTANCE, y + 10);
   ObjectSetInteger(0, "DRS_LABEL", OBJPROP_COLOR, clrWhite);
   ObjectSetInteger(0, "DRS_LABEL", OBJPROP_FONTSIZE, 9);
   ObjectSetInteger(0, "DRS_LABEL", OBJPROP_CORNER, CORNER_LEFT_UPPER);

   ObjectCreate(0, "DRS_BUY_BTN", OBJ_BUTTON, 0, 0, 0);
   ObjectSetInteger(0, "DRS_BUY_BTN", OBJPROP_XDISTANCE, x + 10);
   ObjectSetInteger(0, "DRS_BUY_BTN", OBJPROP_YDISTANCE, y + 85);
   ObjectSetInteger(0, "DRS_BUY_BTN", OBJPROP_XSIZE, 60);
   ObjectSetInteger(0, "DRS_BUY_BTN", OBJPROP_YSIZE, 35);
   ObjectSetString(0, "DRS_BUY_BTN", OBJPROP_TEXT, "BUY");
   ObjectSetInteger(0, "DRS_BUY_BTN", OBJPROP_BGCOLOR, clrSeaGreen);
   ObjectSetInteger(0, "DRS_BUY_BTN", OBJPROP_COLOR, clrWhite);
   ObjectSetInteger(0, "DRS_BUY_BTN", OBJPROP_CORNER, CORNER_LEFT_UPPER);

   ObjectCreate(0, "DRS_SELL_BTN", OBJ_BUTTON, 0, 0, 0);
   ObjectSetInteger(0, "DRS_SELL_BTN", OBJPROP_XDISTANCE, x + 80);
   ObjectSetInteger(0, "DRS_SELL_BTN", OBJPROP_YDISTANCE, y + 85);
   ObjectSetInteger(0, "DRS_SELL_BTN", OBJPROP_XSIZE, 60);
   ObjectSetInteger(0, "DRS_SELL_BTN", OBJPROP_YSIZE, 35);
   ObjectSetString(0, "DRS_SELL_BTN", OBJPROP_TEXT, "SELL");
   ObjectSetInteger(0, "DRS_SELL_BTN", OBJPROP_BGCOLOR, clrFireBrick);
   ObjectSetInteger(0, "DRS_SELL_BTN", OBJPROP_COLOR, clrWhite);
   ObjectSetInteger(0, "DRS_SELL_BTN", OBJPROP_CORNER, CORNER_LEFT_UPPER);
}