//+------------------------------------------------------------------+ //| PositionSizeTool.mq5 | //| Copyright 2026, MetaQuotes Ltd. | //| https://www.mql5.com/en/users/ririeh | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, MetaQuotes Ltd." #property link "https://www.mql5.com/en/users/ririeh" #property version "1.00" //+------------------------------------------------------------------+ //| Position Line Object Names | //+------------------------------------------------------------------+ #define ENTRY_LINE_NAME "PST_Entry_Line" #define SL_LINE_NAME "PST_StopLoss_Line" #define TP_LINE_NAME "PST_TakeProfit_Line" //+------------------------------------------------------------------+ //| Dashboard Object Prefix | //+------------------------------------------------------------------+ #define PANEL_PREFIX "PST_Panel_" //+------------------------------------------------------------------+ //| Dashboard Settings | //+------------------------------------------------------------------+ #define PANEL_BG_NAME "PST_Panel_Background" #define PANEL_X 20 #define PANEL_Y 30 #define PANEL_WIDTH 280 #define PANEL_HEIGHT 285 #define PANEL_LINE_GAP 18 #define PANEL_TEXT clrWhite #define PANEL_TITLE clrGold #define PANEL_BG clrBlack #define PANEL_BORDER clrDimGray //+------------------------------------------------------------------+ //| Position Tool Order Types | //+------------------------------------------------------------------+ enum ENUM_POSITION_TOOL_ORDER_TYPE { PTO_BUY_MARKET = 0, // Buy Market PTO_SELL_MARKET, // Sell Market PTO_BUY_LIMIT, // Buy Limit PTO_SELL_LIMIT, // Sell Limit PTO_BUY_STOP, // Buy Stop PTO_SELL_STOP // Sell Stop }; //+------------------------------------------------------------------+ //| Position Tool Direction | //+------------------------------------------------------------------+ enum ENUM_POSITION_TOOL_DIRECTION { PTD_BUY = 0, // Buy setup PTD_SELL // Sell setup }; //+------------------------------------------------------------------+ //| Returns setup direction from selected order type | //+------------------------------------------------------------------+ ENUM_POSITION_TOOL_DIRECTION GetSetupDirection() { switch(InpOrderType) { case PTO_BUY_MARKET: case PTO_BUY_LIMIT: case PTO_BUY_STOP: return PTD_BUY; case PTO_SELL_MARKET: case PTO_SELL_LIMIT: case PTO_SELL_STOP: return PTD_SELL; } return PTD_BUY; } //+------------------------------------------------------------------+ //| Position Size Tool Inputs | //+------------------------------------------------------------------+ input ENUM_POSITION_TOOL_ORDER_TYPE InpOrderType = PTO_BUY_MARKET; input double InpRiskPercent = 1.0; input color InpEntryLineColor = clrDodgerBlue; input color InpSLLineColor = clrTomato; input color InpTPLineColor = clrLimeGreen; input ENUM_TIMEFRAMES InpATRTimeframe = PERIOD_H1; input int InpATRPeriod = 14; input double InpSLATRFactor = 1.0; input double InpTPATRFactor = 2.0; //+------------------------------------------------------------------+ //| Position Prices | //+------------------------------------------------------------------+ double EntryPrice = 0.0; double StopLossPrice = 0.0; double TakeProfitPrice = 0.0; //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ int OnInit() { ConfigureChartAppearance(); ConfigureChartDisplay(); InitializeLinePrices(); if(!CreatePositionLines()) return INIT_FAILED; CreateDashboard(); UpdateDashboard(); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { DeleteDashboard(); DeletePositionLines(); ChartRedraw(0); } //+------------------------------------------------------------------+ //| Tick event handler | //+------------------------------------------------------------------+ void OnTick() { UpdateMarketEntryPrice(); UpdateDashboard(); } //+------------------------------------------------------------------+ //| Timer function | //+------------------------------------------------------------------+ void OnTimer() { //--- } //+------------------------------------------------------------------+ //| Chart event handler | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { if(id != CHARTEVENT_OBJECT_DRAG) return; if(IsMarketOrderType() && sparam == ENTRY_LINE_NAME) return; if(sparam == ENTRY_LINE_NAME || sparam == SL_LINE_NAME || sparam == TP_LINE_NAME) { UpdateDashboard(); } } //+------------------------------------------------------------------+ //| Initializes default line prices | //+------------------------------------------------------------------+ void InitializeLinePrices() { double bid = SymbolInfoDouble(_Symbol,SYMBOL_BID); double ask = SymbolInfoDouble(_Symbol,SYMBOL_ASK); double atr = GetATRValue(); if(atr <= 0.0) atr = 100 * _Point; double slDistance = atr * InpSLATRFactor; double tpDistance = atr * InpTPATRFactor; switch(InpOrderType) { case PTO_BUY_MARKET: EntryPrice = ask; StopLossPrice = EntryPrice - slDistance; TakeProfitPrice = EntryPrice + tpDistance; break; case PTO_SELL_MARKET: EntryPrice = bid; StopLossPrice = EntryPrice + slDistance; TakeProfitPrice = EntryPrice - tpDistance; break; case PTO_BUY_LIMIT: EntryPrice = bid - slDistance; StopLossPrice = EntryPrice - slDistance; TakeProfitPrice = EntryPrice + tpDistance; break; case PTO_SELL_LIMIT: EntryPrice = ask + slDistance; StopLossPrice = EntryPrice + slDistance; TakeProfitPrice = EntryPrice - tpDistance; break; case PTO_BUY_STOP: EntryPrice = ask + slDistance; StopLossPrice = EntryPrice - slDistance; TakeProfitPrice = EntryPrice + tpDistance; break; case PTO_SELL_STOP: EntryPrice = bid - slDistance; StopLossPrice = EntryPrice + slDistance; TakeProfitPrice = EntryPrice - tpDistance; break; } } //+------------------------------------------------------------------+ //| Creates an adjustable horizontal price line | //+------------------------------------------------------------------+ bool CreatePriceLine(string name,double price,color lineColor,string text) { ObjectDelete(0,name); if(!ObjectCreate(0,name,OBJ_HLINE,0,0,price)) { Print("Failed to create line: ",name," Error: ",GetLastError()); return false; } bool draggable = true; if(name == ENTRY_LINE_NAME && IsMarketOrderType()) draggable = false; ObjectMove(0,name,0,0,price); ObjectSetInteger(0,name,OBJPROP_COLOR,lineColor); ObjectSetInteger(0,name,OBJPROP_WIDTH,2); ObjectSetInteger(0,name,OBJPROP_STYLE,STYLE_SOLID); ObjectSetInteger(0,name,OBJPROP_BACK,false); ObjectSetInteger(0,name,OBJPROP_SELECTABLE,draggable); ObjectSetInteger(0,name,OBJPROP_SELECTED,true); ObjectSetInteger(0,name,OBJPROP_HIDDEN,false); ObjectSetString(0,name,OBJPROP_TEXT,text); return true; } //+------------------------------------------------------------------+ //| Creates all position tool lines | //+------------------------------------------------------------------+ bool CreatePositionLines() { if(!CreatePriceLine(ENTRY_LINE_NAME,EntryPrice,InpEntryLineColor,"Entry")) return false; if(!CreatePriceLine(SL_LINE_NAME,StopLossPrice,InpSLLineColor,"Stop Loss")) return false; if(!CreatePriceLine(TP_LINE_NAME,TakeProfitPrice,InpTPLineColor,"Take Profit")) return false; ChartRedraw(0); return true; } //+------------------------------------------------------------------+ //| Returns the current price of a horizontal line | //+------------------------------------------------------------------+ double GetLinePrice(string name) { if(ObjectFind(0,name) < 0) return 0.0; return ObjectGetDouble(0,name,OBJPROP_PRICE,0); } //+------------------------------------------------------------------+ //| Updates current line prices | //+------------------------------------------------------------------+ void UpdateLinePrices() { EntryPrice = GetLinePrice(ENTRY_LINE_NAME); StopLossPrice = GetLinePrice(SL_LINE_NAME); TakeProfitPrice = GetLinePrice(TP_LINE_NAME); } //+------------------------------------------------------------------+ //| Returns direction text | //+------------------------------------------------------------------+ string GetDirectionText() { ENUM_POSITION_TOOL_DIRECTION direction = GetSetupDirection(); if(direction == PTD_BUY) return "Buy"; return "Sell"; } //+------------------------------------------------------------------+ //| Calculates stop loss distance in price and points | //+------------------------------------------------------------------+ bool CalculateStopLossDistance(double &stopDistance, double &stopPoints, string &status) { stopDistance = MathAbs(EntryPrice - StopLossPrice); stopPoints = stopDistance / _Point; if(stopDistance <= 0.0) { status = "Invalid: Stop Loss distance must be greater than zero."; return false; } status = "Valid stop loss distance."; return true; } //+------------------------------------------------------------------+ //| Calculates risk amount from account balance | //+------------------------------------------------------------------+ double CalculateRiskAmount() { double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE); return accountBalance * InpRiskPercent / 100.0; } //+------------------------------------------------------------------+ //| Normalizes volume according to symbol trading rules | //+------------------------------------------------------------------+ double NormalizeVolume(double volume) { double minVolume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MIN); double maxVolume = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_MAX); double volumeStep = SymbolInfoDouble(_Symbol,SYMBOL_VOLUME_STEP); if(volume < minVolume) volume = minVolume; if(volume > maxVolume) volume = maxVolume; volume = MathFloor(volume / volumeStep) * volumeStep; return NormalizeDouble(volume,2); } //+------------------------------------------------------------------+ //| Calculates lot size from risk amount and stop loss distance | //+------------------------------------------------------------------+ bool CalculateLotSize(double stopDistance, double riskMoney, double &lotSize, string &status) { double tickValue = SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_VALUE); double tickSize = SymbolInfoDouble(_Symbol,SYMBOL_TRADE_TICK_SIZE); if(tickValue <= 0.0 || tickSize <= 0.0) { status = "Invalid: Tick value or tick size is unavailable."; return false; } if(stopDistance <= 0.0) { status = "Invalid: Stop distance must be greater than zero."; return false; } double moneyPerLot = stopDistance / tickSize * tickValue; if(moneyPerLot <= 0.0) { status = "Invalid: Money risk per lot is zero."; return false; } double rawLotSize = riskMoney / moneyPerLot; lotSize = NormalizeVolume(rawLotSize); status = "Lot size calculated."; return true; } //+------------------------------------------------------------------+ //| Calculates reward distance, RR, and estimated reward | //+------------------------------------------------------------------+ bool CalculateRewardMetrics(double stopPoints, double riskMoney, double &rewardPoints, double &rr, double &rewardMoney, string &status) { double rewardDistance = MathAbs(TakeProfitPrice - EntryPrice); rewardPoints = rewardDistance / _Point; if(stopPoints <= 0.0) { status = "Invalid: Stop points must be greater than zero."; return false; } if(rewardPoints <= 0.0) { status = "Invalid: Take Profit distance must be greater than zero."; return false; } rr = rewardPoints / stopPoints; rewardMoney = riskMoney * rr; status = "Reward metrics calculated."; return true; } //+------------------------------------------------------------------+ //| Validates entry, stop loss, and take profit structure | //+------------------------------------------------------------------+ bool ValidateLineStructure(string &status) { ENUM_POSITION_TOOL_DIRECTION direction = GetSetupDirection(); if(EntryPrice == StopLossPrice) { status = "Invalid: Entry and Stop Loss cannot be equal."; return false; } if(direction == PTD_BUY) { if(StopLossPrice >= EntryPrice) { status = "Invalid: Buy setup requires Stop Loss below Entry."; return false; } if(TakeProfitPrice <= EntryPrice) { status = "Warning: Buy setup requires Take Profit above Entry."; return false; } } if(direction == PTD_SELL) { if(StopLossPrice <= EntryPrice) { status = "Invalid: Sell setup requires Stop Loss above Entry."; return false; } if(TakeProfitPrice >= EntryPrice) { status = "Warning: Sell setup requires Take Profit below Entry."; return false; } } status = "Valid setup."; return true; } //+------------------------------------------------------------------+ //| Creates dashboard background panel | //+------------------------------------------------------------------+ void CreateDashboardBackground() { ObjectDelete(0,PANEL_BG_NAME); if(!ObjectCreate(0,PANEL_BG_NAME,OBJ_RECTANGLE_LABEL,0,0,0)) return; ObjectSetInteger(0,PANEL_BG_NAME,OBJPROP_CORNER,CORNER_LEFT_UPPER); ObjectSetInteger(0,PANEL_BG_NAME,OBJPROP_XDISTANCE,PANEL_X - 10); ObjectSetInteger(0,PANEL_BG_NAME,OBJPROP_YDISTANCE,PANEL_Y - 10); ObjectSetInteger(0,PANEL_BG_NAME,OBJPROP_XSIZE,PANEL_WIDTH); ObjectSetInteger(0,PANEL_BG_NAME,OBJPROP_YSIZE,PANEL_HEIGHT); ObjectSetInteger(0,PANEL_BG_NAME,OBJPROP_BGCOLOR,clrBlack); ObjectSetInteger(0,PANEL_BG_NAME,OBJPROP_COLOR,clrDimGray); ObjectSetInteger(0,PANEL_BG_NAME,OBJPROP_BORDER_TYPE,BORDER_FLAT); ObjectSetInteger(0,PANEL_BG_NAME,OBJPROP_BACK,false); ObjectSetInteger(0,PANEL_BG_NAME,OBJPROP_ZORDER,100); ObjectSetInteger(0,PANEL_BG_NAME,OBJPROP_SELECTABLE,false); ObjectSetInteger(0,PANEL_BG_NAME,OBJPROP_SELECTED,false); ObjectSetInteger(0,PANEL_BG_NAME,OBJPROP_HIDDEN,true); } //+------------------------------------------------------------------+ //| Creates or updates a dashboard label | //+------------------------------------------------------------------+ void SetPanelText(string name,string text,int x,int y,color textColor) { if(ObjectFind(0,name) < 0) { if(!ObjectCreate(0,name,OBJ_LABEL,0,0,0)) return; ObjectSetInteger(0,name,OBJPROP_CORNER,CORNER_LEFT_UPPER); ObjectSetInteger(0,name,OBJPROP_ANCHOR,ANCHOR_LEFT_UPPER); ObjectSetInteger(0,name,OBJPROP_FONTSIZE,9); ObjectSetString(0,name,OBJPROP_FONT,"Tahoma"); ObjectSetInteger(0,name,OBJPROP_BACK,false); ObjectSetInteger(0,name,OBJPROP_SELECTABLE,false); ObjectSetInteger(0,name,OBJPROP_SELECTED,false); ObjectSetInteger(0,name,OBJPROP_HIDDEN,true); ObjectSetInteger(0,name,OBJPROP_ZORDER,101); } ObjectSetInteger(0,name,OBJPROP_XDISTANCE,x); ObjectSetInteger(0,name,OBJPROP_YDISTANCE,y); ObjectSetInteger(0,name,OBJPROP_COLOR,textColor); ObjectSetString(0,name,OBJPROP_TEXT,text); } void CreateDashboard() { CreateDashboardBackground(); for(int i = 0; i < 15; i++) { string name = PANEL_PREFIX + IntegerToString(i); SetPanelText(name,"",PANEL_X,PANEL_Y + i * PANEL_LINE_GAP,PANEL_TEXT); } ChartRedraw(0); } //+------------------------------------------------------------------+ //| Returns selected order type text | //+------------------------------------------------------------------+ string GetOrderTypeText() { switch(InpOrderType) { case PTO_BUY_MARKET: return "Buy Market"; case PTO_SELL_MARKET: return "Sell Market"; case PTO_BUY_LIMIT: return "Buy Limit"; case PTO_SELL_LIMIT: return "Sell Limit"; case PTO_BUY_STOP: return "Buy Stop"; case PTO_SELL_STOP: return "Sell Stop"; } return "Unknown"; } //+------------------------------------------------------------------+ //| Updates dashboard values | //+------------------------------------------------------------------+ void UpdateDashboard() { UpdateLinePrices(); string status = ""; double stopDistance = 0.0; double stopPoints = 0.0; double riskMoney = 0.0; double lotSize = 0.0; double rewardPoints = 0.0; double rr = 0.0; double rewardMoney = 0.0; bool valid = ValidateLineStructure(status); if(valid) valid = CalculateStopLossDistance(stopDistance,stopPoints,status); if(valid) { riskMoney = CalculateRiskAmount(); valid = CalculateLotSize(stopDistance,riskMoney,lotSize,status); } if(valid) valid = CalculateRewardMetrics(stopPoints,riskMoney,rewardPoints,rr,rewardMoney,status); SetPanelText(PANEL_PREFIX + "0","Position Size Tool",PANEL_X,PANEL_Y + 0 * PANEL_LINE_GAP,PANEL_TITLE); SetPanelText(PANEL_PREFIX + "1","Symbol: " + _Symbol,PANEL_X,PANEL_Y + 1 * PANEL_LINE_GAP,PANEL_TEXT); SetPanelText(PANEL_PREFIX + "2","Order Type: " + GetOrderTypeText(),PANEL_X,PANEL_Y + 2 * PANEL_LINE_GAP,PANEL_TEXT); SetPanelText(PANEL_PREFIX + "3","Direction: " + GetDirectionText(),PANEL_X,PANEL_Y + 3 * PANEL_LINE_GAP,PANEL_TEXT); SetPanelText(PANEL_PREFIX + "4","Entry: " + DoubleToString(EntryPrice,_Digits),PANEL_X,PANEL_Y + 4 * PANEL_LINE_GAP,PANEL_TEXT); SetPanelText(PANEL_PREFIX + "5","Stop Loss: " + DoubleToString(StopLossPrice,_Digits),PANEL_X,PANEL_Y + 5 * PANEL_LINE_GAP,PANEL_TEXT); SetPanelText(PANEL_PREFIX + "6","Take Profit: " + DoubleToString(TakeProfitPrice,_Digits),PANEL_X,PANEL_Y + 6 * PANEL_LINE_GAP,PANEL_TEXT); SetPanelText(PANEL_PREFIX + "7","SL Points: " + DoubleToString(stopPoints,1),PANEL_X,PANEL_Y + 7 * PANEL_LINE_GAP,PANEL_TEXT); SetPanelText(PANEL_PREFIX + "8","TP Points: " + DoubleToString(rewardPoints,1),PANEL_X,PANEL_Y + 8 * PANEL_LINE_GAP,PANEL_TEXT); SetPanelText(PANEL_PREFIX + "9","Risk: " + DoubleToString(InpRiskPercent,2) + "%",PANEL_X,PANEL_Y + 9 * PANEL_LINE_GAP,PANEL_TEXT); SetPanelText(PANEL_PREFIX + "10","Risk Money: " + DoubleToString(riskMoney,2),PANEL_X,PANEL_Y + 10 * PANEL_LINE_GAP,PANEL_TEXT); SetPanelText(PANEL_PREFIX + "11","Reward Money: " + DoubleToString(rewardMoney,2),PANEL_X,PANEL_Y + 11 * PANEL_LINE_GAP,PANEL_TEXT); SetPanelText(PANEL_PREFIX + "12","Lot Size: " + DoubleToString(lotSize,2),PANEL_X,PANEL_Y + 12 * PANEL_LINE_GAP,PANEL_TEXT); SetPanelText(PANEL_PREFIX + "13","RR: 1:" + DoubleToString(rr,2),PANEL_X,PANEL_Y + 13 * PANEL_LINE_GAP,PANEL_TEXT); SetPanelText(PANEL_PREFIX + "14","Status: " + status,PANEL_X,PANEL_Y + 14 * PANEL_LINE_GAP,valid ? clrLimeGreen : clrTomato); ChartRedraw(0); } //+------------------------------------------------------------------+ //| Deletes dashboard objects | //+------------------------------------------------------------------+ void DeleteDashboard() { ObjectDelete(0,PANEL_BG_NAME); for(int i = 0; i < 15; i++) { string name = PANEL_PREFIX + IntegerToString(i); ObjectDelete(0,name); } } //+------------------------------------------------------------------+ //| Deletes position lines | //+------------------------------------------------------------------+ void DeletePositionLines() { ObjectDelete(0,ENTRY_LINE_NAME); ObjectDelete(0,SL_LINE_NAME); ObjectDelete(0,TP_LINE_NAME); } //+------------------------------------------------------------------+ //| Returns current ATR value | //+------------------------------------------------------------------+ double GetATRValue() { int handle = iATR(_Symbol,InpATRTimeframe,InpATRPeriod); if(handle == INVALID_HANDLE) return 0.0; double buffer[]; if(CopyBuffer(handle,0,0,1,buffer) < 1) { IndicatorRelease(handle); return 0.0; } IndicatorRelease(handle); return buffer[0]; } //+------------------------------------------------------------------+ //| Checks whether selected order type is market execution | //+------------------------------------------------------------------+ bool IsMarketOrderType() { return InpOrderType == PTO_BUY_MARKET || InpOrderType == PTO_SELL_MARKET; } //+------------------------------------------------------------------+ //| Updates market order entry line from Bid/Ask | //+------------------------------------------------------------------+ void UpdateMarketEntryPrice() { if(!IsMarketOrderType()) return; if(InpOrderType == PTO_BUY_MARKET) EntryPrice = SymbolInfoDouble(_Symbol,SYMBOL_ASK); else if(InpOrderType == PTO_SELL_MARKET) EntryPrice = SymbolInfoDouble(_Symbol,SYMBOL_BID); ObjectMove(0,ENTRY_LINE_NAME,0,0,EntryPrice); } //+------------------------------------------------------------------+ //| Configures chart display settings | //+------------------------------------------------------------------+ void ConfigureChartDisplay() { ChartSetInteger(0,CHART_SHOW_ASK_LINE,false); ChartSetInteger(0,CHART_SHOW_BID_LINE,false); ChartSetInteger(0,CHART_FOREGROUND,false); ChartSetInteger(0,CHART_SHOW_OBJECT_DESCR,true); ChartRedraw(0); } //+------------------------------------------------------------------+ //| Applies Position Size Tool chart appearance | //+------------------------------------------------------------------+ void ConfigureChartAppearance() { ChartSetInteger(0,CHART_COLOR_BACKGROUND,clrWhite); ChartSetInteger(0,CHART_SHOW_GRID,false); ChartSetInteger(0,CHART_MODE,CHART_CANDLES); ChartSetInteger(0,CHART_COLOR_FOREGROUND,clrBlack); ChartSetInteger(0,CHART_COLOR_CANDLE_BULL,clrLimeGreen); ChartSetInteger(0,CHART_COLOR_CANDLE_BEAR,clrTomato); ChartSetInteger(0,CHART_COLOR_CHART_UP,clrLimeGreen); ChartSetInteger(0,CHART_COLOR_CHART_DOWN,clrTomato); ChartSetInteger(0,CHART_COLOR_BID,clrDodgerBlue); ChartSetInteger(0,CHART_COLOR_ASK,clrOrange); ChartSetInteger(0,CHART_SHOW_ASK_LINE,false); ChartSetInteger(0,CHART_SHOW_BID_LINE,false); ChartRedraw(0); } //+------------------------------------------------------------------+