Advertisement IC Markets

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

Master exponential money management, position sizing calculators, strict daily stop-loss limits, and overcoming FOMO on micro-timeframes.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

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

Post 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);
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Recommended broker for automated trading & scalping IC Markets
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

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

Post 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.
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

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

Post 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);
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
PTScalper
Site Admin
Posts: 3349
Joined: Mon Jul 20, 2026 1:28 pm

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

Post 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);
}
Preserve your own money. Scale with the market's money. Exponential growth is the ultimate key.
Post Reply