Page 2 of 3

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

Posted: Sat Sep 26, 2026 6:13 pm
by PTScalper
1-Click Dynamic Execution cBot (C#)

Code: Select all

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

namespace cAlgo.Robots
{
    public enum SlCalculationMode
    {
        ManualPips,
        ATR_Volatility
    }

    [Robot(TimeZone = TimeZones.UTC, AccessRights = AccessRights.None)]
    public class DynamicRiskExecutor : Robot
    {
        [Parameter("Risk Per Trade (%)", DefaultValue = 0.5, MinValue = 0.01, Step = 0.1)]
        public double RiskPct { get; set; }

        [Parameter("Stop Loss Mode", DefaultValue = SlCalculationMode.ManualPips)]
        public SlCalculationMode SlMode { get; set; }

        [Parameter("Manual SL (Pips)", DefaultValue = 10.0, MinValue = 0.1)]
        public double ManualPips { get; set; }

        [Parameter("ATR Period", DefaultValue = 14, MinValue = 1)]
        public int AtrPeriod { get; set; }

        [Parameter("ATR Multiplier", DefaultValue = 1.5, MinValue = 0.1, Step = 0.1)]
        public double AtrMultiplier { get; set; }

        private AverageTrueRange _atr;
        private TextBlock _uiRisk;
        private TextBlock _uiSl;
        private TextBlock _uiLots;

        // State variables to hold the exact size and SL when a button is clicked
        private double _currentVolumeInUnits;
        private double _currentSlPips;

        protected override void OnStart()
        {
            _atr = Indicators.AverageTrueRange(AtrPeriod, MovingAverageType.Simple);
            BuildUI();
            
            // Run initial calculation so the dashboard populates immediately
            UpdateCalculations();
        }

        protected override void OnTick()
        {
            // Updates continuously as price and volatility shift
            UpdateCalculations();
        }

        private void UpdateCalculations()
        {
            double riskDollars = Account.Balance * (RiskPct / 100.0);
            _currentSlPips = ManualPips;

            if (SlMode == SlCalculationMode.ATR_Volatility)
            {
                _currentSlPips = (_atr.Result.LastValue / Symbol.PipSize) * AtrMultiplier;
            }

            if (_currentSlPips <= 0 || Symbol.PipValue == 0) return;

            // Calculate raw units and normalize for broker requirements
            double rawUnits = riskDollars / (_currentSlPips * Symbol.PipValue);
            _currentVolumeInUnits = Symbol.NormalizeVolumeInUnits(rawUnits, RoundingMode.Down);
            
            double lots = Symbol.VolumeInUnitsToQuantity(_currentVolumeInUnits);

            // Update UI 
            _uiRisk.Text = $"Risk: {Account.Currency} {Math.Round(riskDollars, 2)}";
            string modeLabel = SlMode == SlCalculationMode.ManualPips ? "Pips" : "Pips (ATR)";
            _uiSl.Text = $"SL: {Math.Round(_currentSlPips, 1)} {modeLabel}";
            _uiLots.Text = $"Execute: {Math.Round(lots, 2)} Lots";
        }

        private void BuildUI()
        {
            var mainPanel = new StackPanel
            {
                Orientation = Orientation.Vertical,
                BackgroundColor = Color.FromArgb(220, 15, 15, 15),
                HorizontalAlignment = HorizontalAlignment.Right,
                VerticalAlignment = VerticalAlignment.Bottom,
                Margin = new Thickness(20),
                Width = 180
            };

            mainPanel.AddChild(new TextBlock 
            { 
                Text = "1-CLICK SIZER", 
                ForegroundColor = Color.White, 
                BackgroundColor = Color.DodgerBlue,
                FontWeight = FontWeight.ExtraBold, 
                Margin = new Thickness(0, 0, 0, 5),
                Padding = new Thickness(5),
                TextAlignment = TextAlignment.Center
            });

            _uiRisk = CreateTextRow(mainPanel, Color.Tomato);
            _uiSl = CreateTextRow(mainPanel, Color.LightGray);
            _uiLots = CreateTextRow(mainPanel, Color.LimeGreen, true);

            // Create horizontal panel for the execution buttons
            var buttonPanel = new StackPanel
            {
                Orientation = Orientation.Horizontal,
                Margin = new Thickness(0, 10, 0, 0),
                HorizontalAlignment = HorizontalAlignment.Center
            };

            var buyBtn = new Button 
            { 
                Text = "BUY", 
                BackgroundColor = Color.SeaGreen, 
                ForegroundColor = Color.White, 
                Width = 75, 
                Margin = new Thickness(0, 0, 5, 0) 
            };
            buyBtn.Click += args => ExecuteTrade(TradeType.Buy);

            var sellBtn = new Button 
            { 
                Text = "SELL", 
                BackgroundColor = Color.Firebrick, 
                ForegroundColor = Color.White, 
                Width = 75 
            };
            sellBtn.Click += args => ExecuteTrade(TradeType.Sell);

            buttonPanel.AddChild(buyBtn);
            buttonPanel.AddChild(sellBtn);
            mainPanel.AddChild(buttonPanel);

            Chart.AddControl(mainPanel);
        }

        private TextBlock CreateTextRow(StackPanel parent, Color color, bool isBold = false)
        {
            var textBlock = new TextBlock
            {
                ForegroundColor = color,
                FontWeight = isBold ? FontWeight.ExtraBold : FontWeight.Normal,
                Margin = new Thickness(5, 2, 5, 2)
            };
            parent.AddChild(textBlock);
            return textBlock;
        }

        private void ExecuteTrade(TradeType tradeType)
        {
            if (_currentVolumeInUnits <= 0)
            {
                Print("Error: Calculated volume is invalid.");
                return;
            }
            
            // ExecuteMarketOrderAsync prevents the UI from freezing while the broker processes the trade
            ExecuteMarketOrderAsync(tradeType, SymbolName, _currentVolumeInUnits, "DynamicSizer", _currentSlPips, null);
        }
    }
}

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

Posted: Sat Sep 26, 2026 6:14 pm
by PTScalper
How to Install

Because this contains execution logic, it goes in a different folder than the previous indicator:

1.) Open the Automate tab on the left sidebar of cTrader.

2.) Under cBots (not Indicators), click the + icon to create a new cBot and name it DynamicRiskExecutor.

3.) Paste the C# code above, overwriting the default template, and press F8 to build.

4.) Back on your chart, click the cBots icon in the top toolbar (or right-click the chart -> cBots), and add the DynamicRiskExecutor.

5.) Start the cBot by clicking the Play button on its parameter window. The execution panel will appear in the bottom right corner.

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

Posted: Sat Sep 26, 2026 6:15 pm
by PTScalper
Here are the complete, ready-to-run Expert Advisors (EAs) for both MetaTrader 5 (MQL5) and MetaTrader 4 (MQL4).

Both scripts create a floating HUD with live risk updates and 1-click BUY and SELL buttons that calculate the required lot size from your current balance, broker tick values, and dynamic stop loss before sending the order.

1. MetaTrader 5 Version (MQL5)

Save this file in MetaEditor under Experts/DynamicRiskExecutor.mq5.

Code: Select all

//+------------------------------------------------------------------+
//|                                          DynamicRiskExecutor.mq5 |
//+------------------------------------------------------------------+
#property copyright "Dynamic Risk Sizer"
#property link      ""
#property version   "1.00"
#include <Trade\Trade.mqh>

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

// --- Inputs ---
input group "=== Risk & Stop Loss Settings ==="
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 ulong           InpMagicNumber   = 223344;          // Magic Number

CTrade trade;
int    atrHandle;
double currentLots = 0.0;
double currentSlPips = 0.0;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   trade.SetExpertMagicNumber(InpMagicNumber);
   
   if(InpSlMode == SL_ATR_VOLATILITY)
   {
      atrHandle = iATR(_Symbol, _Period, InpAtrPeriod);
      if(atrHandle == INVALID_HANDLE)
      {
         Print("Error creating ATR handle.");
         return INIT_FAILED;
      }
   }
   
   CreateUI();
   UpdateCalculations();
   return INIT_SUCCEEDED;
}

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

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

//+------------------------------------------------------------------+
//| Core Calculation Logic                                           |
//+------------------------------------------------------------------+
void UpdateCalculations()
{
   double balance = AccountInfoDouble(ACCOUNT_BALANCE);
   double riskDollars = balance * (InpRiskPct / 100.0);
   
   // Pip Size calculation (handles 3/5 decimal brokers)
   double pipSize = (_Digits == 3 || _Digits == 5) ? _Point * 10 : _Point;
   
   if(InpSlMode == SL_MANUAL_PIPS)
   {
      currentSlPips = InpManualPips;
   }
   else
   {
      double atrVal[];
      ArraySetAsSeries(atrVal, true);
      if(CopyBuffer(atrHandle, 0, 0, 1, atrVal) > 0)
         currentSlPips = (atrVal[0] / pipSize) * InpAtrMultiplier;
      else
         currentSlPips = InpManualPips;
   }

   if(currentSlPips <= 0) return;

   // Tick value & sizing
   double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
   double tickSize  = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   if(tickSize == 0 || tickValue == 0) return;

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

   double rawLots = riskDollars / riskPerLot;

   // Broker lot constraints
   double minLot  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   double stepLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);

   currentLots = MathFloor(rawLots / stepLot) * stepLot;
   if(currentLots < minLot) currentLots = minLot;
   if(currentLots > maxLot) currentLots = maxLot;

   // Update HUD Text
   string mode = (InpSlMode == SL_MANUAL_PIPS) ? "Pips" : "Pips (ATR)";
   string info = StringFormat("Risk: $%.2f (%.1f%%)\nSL: %.1f %s\nLots: %.2f", 
                              riskDollars, InpRiskPct, currentSlPips, mode, 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")
   {
      double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
      double sl = ask - slOffset;
      trade.Buy(currentLots, _Symbol, ask, sl, 0, "DynamicRisk_Buy");
      ObjectSetInteger(0, "DRS_BUY_BTN", OBJPROP_STATE, false);
   }
   else if(sparam == "DRS_SELL_BTN")
   {
      double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
      double sl = bid + slOffset;
      trade.Sell(currentLots, _Symbol, bid, sl, 0, "DynamicRisk_Sell");
      ObjectSetInteger(0, "DRS_SELL_BTN", OBJPROP_STATE, false);
   }
}

//+------------------------------------------------------------------+
//| UI Builder                                                       |
//+------------------------------------------------------------------+
void CreateUI()
{
   int x = 20; int y = 40;
   
   // Background
   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, 120);
   ObjectSetInteger(0, "DRS_BG", OBJPROP_BGCOLOR, clrBlack);
   ObjectSetInteger(0, "DRS_BG", OBJPROP_BORDER_COLOR, clrDodgerBlue);
   ObjectSetInteger(0, "DRS_BG", OBJPROP_CORNER, CORNER_LEFT_UPPER);

   // Stats Label
   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);

   // Buy Button
   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 + 70);
   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);

   // Sell Button
   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 + 70);
   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:16 pm
by PTScalper
2. MetaTrader 4 Version (MQL4)

Save this file in MetaEditor under Experts/DynamicRiskExecutor.mq4.

Code: Select all

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

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

// --- Inputs ---
input string          InpHeader        = "=== 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 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();
}

//+------------------------------------------------------------------+
//| 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 minLot  = MarketInfo(Symbol(), MODE_MINLOT);
   double maxLot  = MarketInfo(Symbol(), MODE_MAXLOT);
   double stepLot = MarketInfo(Symbol(), MODE_LOTSTEP);

   currentLots = MathFloor(rawLots / stepLot) * stepLot;
   if(currentLots < minLot) currentLots = minLot;
   if(currentLots > maxLot) currentLots = maxLot;

   string mode = (InpSlMode == SL_MANUAL_PIPS) ? "Pips" : "Pips (ATR)";
   string info = StringFormat("Risk: $%.2f (%.1f%%)\nSL: %.1f %s\nLots: %.2f", 
                              riskDollars, InpRiskPct, currentSlPips, mode, 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 = ask - slOffset;
      int ticket = OrderSend(Symbol(), OP_BUY, currentLots, ask, 3, sl, 0, "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 = bid + slOffset;
      int ticket = OrderSend(Symbol(), OP_SELL, currentLots, bid, 3, sl, 0, "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, 120);
   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 + 70);
   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 + 70);
   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:16 pm
by PTScalper
Setup Instructions

1.) Press F4 in your terminal to open MetaEditor.

2.) In the Navigator on the left, right-click Experts -> New File -> Expert Advisor (template).

3.) Name it DynamicRiskExecutor, paste the respective code, and click Compile (or press F7).

4.) Drag it from the Navigator onto your chart.

5.) In the EA parameters window under the Common tab, ensure Allow Algo Trading / Live Trading is checked.

6.) Make sure the master Algo Trading (or AutoTrading) toggle button in the top toolbar of MetaTrader is switched on (green icon).

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

Posted: Sat Sep 26, 2026 6:21 pm
by PTScalper
By adding a Risk-to-Reward multiplier, the scripts now calculate your Take Profit distance relative to your dynamic Stop Loss and send both parameters to the broker simultaneously.

I've added the InpRiskRewardRatio setting. If you set it to 2.0, it automatically places your TP twice as far as your SL. Set it to 0 to disable the automated Take Profit entirely. The on-chart HUD has also been expanded to display your exact TP pip distance before you click.

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

Posted: Sat Sep 26, 2026 6:21 pm
by PTScalper
1. MetaTrader 5 Version (MQL5)

Code: Select all

//+------------------------------------------------------------------+
//|                                          DynamicRiskExecutor.mq5 |
//+------------------------------------------------------------------+
#property copyright "Dynamic Risk Sizer"
#property link      ""
#property version   "1.10"
#include <Trade\Trade.mqh>

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

// --- Inputs ---
input group "=== Risk & Stop Loss Settings ==="
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 ulong           InpMagicNumber     = 223344;          // Magic Number

CTrade trade;
int    atrHandle;
double currentLots = 0.0;
double currentSlPips = 0.0;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   trade.SetExpertMagicNumber(InpMagicNumber);
   
   if(InpSlMode == SL_ATR_VOLATILITY)
   {
      atrHandle = iATR(_Symbol, _Period, InpAtrPeriod);
      if(atrHandle == INVALID_HANDLE)
      {
         Print("Error creating ATR handle.");
         return INIT_FAILED;
      }
   }
   
   CreateUI();
   UpdateCalculations();
   return INIT_SUCCEEDED;
}

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

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

//+------------------------------------------------------------------+
//| Core Calculation Logic                                           |
//+------------------------------------------------------------------+
void UpdateCalculations()
{
   double balance = AccountInfoDouble(ACCOUNT_BALANCE);
   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[];
      ArraySetAsSeries(atrVal, true);
      if(CopyBuffer(atrHandle, 0, 0, 1, atrVal) > 0)
         currentSlPips = (atrVal[0] / pipSize) * InpAtrMultiplier;
      else
         currentSlPips = InpManualPips;
   }

   if(currentSlPips <= 0) return;

   double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
   double tickSize  = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   if(tickSize == 0 || tickValue == 0) return;

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

   double rawLots = riskDollars / riskPerLot;

   double minLot  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   double maxLot  = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
   double stepLot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);

   currentLots = MathFloor(rawLots / stepLot) * stepLot;
   if(currentLots < minLot) currentLots = minLot;
   if(currentLots > maxLot) currentLots = 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")
   {
      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, "DynamicRisk_Buy");
      ObjectSetInteger(0, "DRS_BUY_BTN", OBJPROP_STATE, false);
   }
   else if(sparam == "DRS_SELL_BTN")
   {
      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, "DynamicRisk_Sell");
      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); // Expanded for TP info
   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); // Shifted down
   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); // Shifted down
   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:21 pm
by PTScalper
2. MetaTrader 4 Version (MQL4)

Code: Select all

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

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

// --- Inputs ---
input string          InpHeader          = "=== 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 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();
}

//+------------------------------------------------------------------+
//| 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 minLot  = MarketInfo(Symbol(), MODE_MINLOT);
   double maxLot  = MarketInfo(Symbol(), MODE_MAXLOT);
   double stepLot = MarketInfo(Symbol(), MODE_LOTSTEP);

   currentLots = MathFloor(rawLots / stepLot) * stepLot;
   if(currentLots < minLot) currentLots = minLot;
   if(currentLots > maxLot) currentLots = 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); // Expanded for TP info
   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); // Shifted down
   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); // Shifted down
   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:22 pm
by PTScalper
Adding an automated breakeven feature is an excellent way to secure trades on fast-moving charts without having to manually drag lines.

To achieve this, I've added a Trade Management section to both EAs. The script will now continuously monitor all open trades placed by the panel. Because we dynamically calculate the original Stop Loss distance (OpenPrice - SL), the script knows exactly what "1R" means for each individual trade, even if one had a 5-pip SL and another had a 12-pip SL.

When the current price hits your InpBreakevenTriggerR (e.g., 1.0 for 1R), it will instantly modify the trade and move the Stop Loss exactly to your entry price.

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

Posted: Sat Sep 26, 2026 6:23 pm
by PTScalper
1. MetaTrader 5 Version (MQL5)

Code: Select all

//+------------------------------------------------------------------+
//|                                          DynamicRiskExecutor.mq5 |
//+------------------------------------------------------------------+
#property copyright "Dynamic Risk Sizer"
#property link      ""
#property version   "1.20"
#include <Trade\Trade.mqh>

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

// --- Inputs ---
input group "=== Risk & Stop Loss Settings ==="
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 group "=== Trade Management ==="
input bool            InpUseBreakeven    = true;            // Auto Move SL to Breakeven
input double          InpBreakevenTriggerR = 1.0;           // Breakeven Trigger (Multiple of Risk/R)

input group "=== System ==="
input ulong           InpMagicNumber     = 223344;          // Magic Number

CTrade trade;
int    atrHandle;
double currentLots = 0.0;
double currentSlPips = 0.0;

//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
int OnInit()
{
   trade.SetExpertMagicNumber(InpMagicNumber);
   
   if(InpSlMode == SL_ATR_VOLATILITY)
   {
      atrHandle = iATR(_Symbol, _Period, InpAtrPeriod);
      if(atrHandle == INVALID_HANDLE)
      {
         Print("Error creating ATR handle.");
         return INIT_FAILED;
      }
   }
   
   CreateUI();
   UpdateCalculations();
   return INIT_SUCCEEDED;
}

//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
   if(atrHandle != INVALID_HANDLE)
      IndicatorRelease(atrHandle);
      
   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 = 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 currentSL = PositionGetDouble(POSITION_SL);
         double currentTP = PositionGetDouble(POSITION_TP);
         long type = PositionGetInteger(POSITION_TYPE);
         
         double normOpenPrice = NormalizeDouble(openPrice, _Digits);
         
         if(type == POSITION_TYPE_BUY)
         {
            if(currentSL < normOpenPrice) // SL is still below entry price
            {
               double slDistance = openPrice - currentSL;
               double triggerPrice = openPrice + (slDistance * InpBreakevenTriggerR);
               double currentBid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
               
               if(currentBid >= triggerPrice)
               {
                  trade.PositionModify(ticket, normOpenPrice, currentTP);
                  Print("Buy trade moved to breakeven.");
               }
            }
         }
         else if(type == POSITION_TYPE_SELL)
         {
            if(currentSL > normOpenPrice || currentSL == 0) // SL is still above entry price
            {
               double slDistance = currentSL - openPrice;
               double triggerPrice = openPrice - (slDistance * InpBreakevenTriggerR);
               double currentAsk = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
               
               if(currentAsk <= triggerPrice && slDistance > 0)
               {
                  trade.PositionModify(ticket, normOpenPrice, currentTP);
                  Print("Sell trade moved to breakeven.");
               }
            }
         }
      }
   }
}

//+------------------------------------------------------------------+
//| Core Calculation Logic                                           |
//+------------------------------------------------------------------+
void UpdateCalculations()
{
   double balance = AccountInfoDouble(ACCOUNT_BALANCE);
   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[];
      ArraySetAsSeries(atrVal, true);
      if(CopyBuffer(atrHandle, 0, 0, 1, atrVal) > 0)
         currentSlPips = (atrVal[0] / pipSize) * InpAtrMultiplier;
      else
         currentSlPips = InpManualPips;
   }

   if(currentSlPips <= 0) return;

   double tickValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE);
   double tickSize  = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE);
   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 = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);

   currentLots = MathFloor(rawLots / stepLot) * stepLot;
   if(currentLots < SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN)) currentLots = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
   if(currentLots > SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX)) currentLots = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);

   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")
   {
      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, "DynamicRisk_Buy");
      ObjectSetInteger(0, "DRS_BUY_BTN", OBJPROP_STATE, false);
   }
   else if(sparam == "DRS_SELL_BTN")
   {
      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, "DynamicRisk_Sell");
      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);
}