//+------------------------------------------------------------------+ //| PositionPlanningTool.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 | //+------------------------------------------------------------------+ //--- Use unique names so each planning line can be created, found, and updated. #define ENTRY_LINE_NAME "PPT_Entry_Line" #define SL_LINE_NAME "PPT_StopLoss_Line" #define TP_LINE_NAME "PPT_TakeProfit_Line" //+------------------------------------------------------------------+ //| Dashboard settings | //+------------------------------------------------------------------+ //--- Prefix all dashboard labels so they can be identified as one group. #define PANEL_PREFIX "PPT_Panel_" //--- Use a dedicated object name for the dashboard background panel. #define PANEL_BG_NAME "PPT_Panel_Background" //--- Define the dashboard position and dimensions on the chart. #define PANEL_X 20 #define PANEL_Y 30 #define PANEL_WIDTH 280 #define PANEL_HEIGHT 285 #define PANEL_LINE_GAP 18 //--- Define the colors used for dashboard text, title, background, and border. #define PANEL_TEXT clrWhite #define PANEL_TITLE clrGold #define PANEL_BG clrBlack #define PANEL_BORDER clrDimGray //+------------------------------------------------------------------+ //| Supported planning 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 planning direction | //+------------------------------------------------------------------+ enum ENUM_POSITION_TOOL_DIRECTION { PTD_BUY = 0, // Buy setup PTD_SELL // Sell setup }; //+------------------------------------------------------------------+ //| Input parameters | //+------------------------------------------------------------------+ 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; //+------------------------------------------------------------------+ //| Current planning prices | //+------------------------------------------------------------------+ //--- Store the active Entry, Stop-Loss, and Take-Profit values used by the tool. double EntryPrice = 0.0; double StopLossPrice = 0.0; double TakeProfitPrice = 0.0; //+------------------------------------------------------------------+ //| Returns setup direction from selected order type | //+------------------------------------------------------------------+ ENUM_POSITION_TOOL_DIRECTION GetSetupDirection() { //--- All BUY order variants share the same planning direction. switch(InpOrderType) { case PTO_BUY_MARKET: case PTO_BUY_LIMIT: case PTO_BUY_STOP: return PTD_BUY; //--- All SELL order variants share the opposite planning direction. case PTO_SELL_MARKET: case PTO_SELL_LIMIT: case PTO_SELL_STOP: return PTD_SELL; } //--- Use BUY as a safe fallback if the input value is unexpected. return PTD_BUY; } //+------------------------------------------------------------------+ //| Expert initialization function | //+------------------------------------------------------------------+ int OnInit() { //--- Apply the visual style used by the Position Planning Tool. if(!ConfigureChartAppearance()) return INIT_FAILED; //--- Configure chart display settings required by the planning interface. if(!ConfigureChartDisplay()) return INIT_FAILED; //--- Calculate the initial Entry, Stop-Loss, and Take-Profit prices. if(!InitializeLinePrices()) return INIT_FAILED; //--- Create the interactive planning lines at the initialized prices. if(!CreatePositionLines()) return INIT_FAILED; //--- Create the dashboard objects used to display the position plan. if(!CreateDashboard()) return INIT_FAILED; //--- Populate the dashboard with the first complete set of calculations. if(!UpdateDashboard()) return INIT_FAILED; //--- Redraw the chart after all interface components are initialized. ChartRedraw(0); return INIT_SUCCEEDED; } //+------------------------------------------------------------------+ //| Expert deinitialization function | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { //--- Remove all dashboard objects created by the EA. if(!DeleteDashboard()) Print("Warning: One or more dashboard objects could not be deleted."); //--- Remove the Entry, Stop-Loss, and Take-Profit planning lines. if(!DeletePositionLines()) Print("Warning: One or more planning lines could not be deleted."); //--- Refresh the chart after cleanup so removed objects disappear immediately. ChartRedraw(0); } //+------------------------------------------------------------------+ //| Tick event handler | //+------------------------------------------------------------------+ void OnTick() { //--- Keep market Entry synchronized with the current Bid or Ask price. if(!UpdateMarketEntryPrice()) return; //--- Recalculate and refresh the dashboard using the latest market data. if(!UpdateDashboard()) Print("Failed to update the dashboard."); } //+------------------------------------------------------------------+ //| Chart event handler | //+------------------------------------------------------------------+ void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) { //--- Ignore chart events that are unrelated to dragging objects. if(id != CHARTEVENT_OBJECT_DRAG) return; //--- Market Entry follows Bid or Ask and should not be handled as a manual drag. if(IsMarketOrderType() && sparam == ENTRY_LINE_NAME) return; //--- Recalculate the plan when any adjustable planning line is moved. if(sparam == ENTRY_LINE_NAME || sparam == SL_LINE_NAME || sparam == TP_LINE_NAME) { //--- Refresh all calculations and dashboard values after the drag ends. if(!UpdateDashboard()) Print("Failed to update the dashboard after line movement."); } } //+------------------------------------------------------------------+ //| Prints a standard API error message | //+------------------------------------------------------------------+ void PrintApiError(const string functionName,const string context) { //--- Combine the failed function, operation context, and terminal error //--- code so API failures can be traced consistently from the log. Print(functionName," failed while ",context,". Error: ",GetLastError()); } //+------------------------------------------------------------------+ //| Returns current ATR value | //+------------------------------------------------------------------+ bool GetATRValue(double &atr) { //--- Reset the output value before attempting to read the indicator. atr = 0.0; ResetLastError(); //--- Create the ATR indicator handle using the configured timeframe and period. int handle = iATR(_Symbol,InpATRTimeframe,InpATRPeriod); if(handle == INVALID_HANDLE) { //--- Stop immediately if the indicator handle could not be created. PrintApiError("iATR()","creating the ATR indicator handle"); return false; } double buffer[]; ResetLastError(); //--- Copy the latest ATR value from the indicator buffer. int copied = CopyBuffer(handle,0,0,1,buffer); if(copied < 1) { //--- Report the read failure and release the handle before returning. PrintApiError("CopyBuffer()","reading the ATR value"); ResetLastError(); if(!IndicatorRelease(handle)) PrintApiError("IndicatorRelease()", "releasing the ATR handle after CopyBuffer() failure"); return false; } //--- Store the successfully copied ATR value. atr = buffer[0]; ResetLastError(); //--- Release the temporary indicator handle after the value is obtained. if(!IndicatorRelease(handle)) { PrintApiError("IndicatorRelease()", "releasing the ATR indicator handle"); return false; } return true; } //+------------------------------------------------------------------+ //| Reads a symbol double property | //+------------------------------------------------------------------+ bool GetSymbolDoubleProperty(const ENUM_SYMBOL_INFO_DOUBLE property, double &value) { ResetLastError(); //--- Read the requested double property for the current chart symbol. if(!SymbolInfoDouble(_Symbol,property,value)) { //--- Report the failure so missing or unavailable symbol data is visible. PrintApiError("SymbolInfoDouble()", "reading symbol data for " + _Symbol); return false; } return true; } //+------------------------------------------------------------------+ //| Initializes default line prices | //+------------------------------------------------------------------+ bool InitializeLinePrices() { double bid = 0.0; double ask = 0.0; //--- Read the current Bid and Ask prices used to anchor the initial setup. if(!GetSymbolDoubleProperty(SYMBOL_BID,bid)) return false; if(!GetSymbolDoubleProperty(SYMBOL_ASK,ask)) return false; double atr = 0.0; //--- Use ATR to scale the initial line spacing to current market volatility. if(!GetATRValue(atr) || atr <= 0.0) { //--- Fall back to a fixed 100-point distance if ATR cannot be obtained. atr = 100 * _Point; Print("ATR unavailable. Using a fallback distance of 100 points."); } //--- Convert the ATR value into the initial Stop-Loss and Take-Profit distances. double slDistance = atr * InpSLATRFactor; double tpDistance = atr * InpTPATRFactor; //--- Position the three planning levels according to the selected order type. switch(InpOrderType) { case PTO_BUY_MARKET: //--- A BUY market plan starts from the current Ask price. EntryPrice = ask; StopLossPrice = EntryPrice - slDistance; TakeProfitPrice = EntryPrice + tpDistance; break; case PTO_SELL_MARKET: //--- A SELL market plan starts from the current Bid price. EntryPrice = bid; StopLossPrice = EntryPrice + slDistance; TakeProfitPrice = EntryPrice - tpDistance; break; case PTO_BUY_LIMIT: //--- Place a BUY limit entry below the current Bid price. EntryPrice = bid - slDistance; StopLossPrice = EntryPrice - slDistance; TakeProfitPrice = EntryPrice + tpDistance; break; case PTO_SELL_LIMIT: //--- Place a SELL limit entry above the current Ask price. EntryPrice = ask + slDistance; StopLossPrice = EntryPrice + slDistance; TakeProfitPrice = EntryPrice - tpDistance; break; case PTO_BUY_STOP: //--- Place a BUY stop entry above the current Ask price. EntryPrice = ask + slDistance; StopLossPrice = EntryPrice - slDistance; TakeProfitPrice = EntryPrice + tpDistance; break; case PTO_SELL_STOP: //--- Place a SELL stop entry below the current Bid price. EntryPrice = bid - slDistance; StopLossPrice = EntryPrice + slDistance; TakeProfitPrice = EntryPrice - tpDistance; break; } return true; } //+------------------------------------------------------------------+ //| Deletes a chart object if it exists | //+------------------------------------------------------------------+ bool DeleteObjectIfExists(const string name) { //--- Skip the deletion call when the requested object is not present. if(ObjectFind(0,name) < 0) return true; ResetLastError(); //--- Delete the existing object and report any failure to the terminal log. if(!ObjectDelete(0,name)) { PrintApiError("ObjectDelete()","deleting object '" + name + "'"); return false; } return true; } //+------------------------------------------------------------------+ //| Sets an integer property on a chart object | //+------------------------------------------------------------------+ bool SetObjectIntegerProperty(const string name, const ENUM_OBJECT_PROPERTY_INTEGER property, const long value) { ResetLastError(); //--- Apply the requested integer property to the specified chart object. if(!ObjectSetInteger(0,name,property,value)) { //--- Report the object name so configuration failures are easy to trace. PrintApiError("ObjectSetInteger()", "setting a property on '" + name + "'"); return false; } return true; } //+------------------------------------------------------------------+ //| Sets a string property on a chart object | //+------------------------------------------------------------------+ bool SetObjectStringProperty(const string name, const ENUM_OBJECT_PROPERTY_STRING property, const string value) { ResetLastError(); //--- Apply the requested string property to the specified chart object. if(!ObjectSetString(0,name,property,value)) { //--- Report the object name so text-property failures are easy to trace. PrintApiError("ObjectSetString()", "setting a property on '" + name + "'"); return false; } return true; } //+------------------------------------------------------------------+ //| Checks whether selected order type is market execution | //+------------------------------------------------------------------+ bool IsMarketOrderType() { //--- Market plans are the only cases where Entry follows live Bid/Ask prices. return InpOrderType == PTO_BUY_MARKET || InpOrderType == PTO_SELL_MARKET; } //+------------------------------------------------------------------+ //| Creates an adjustable horizontal price line | //+------------------------------------------------------------------+ bool CreatePriceLine(const string name, const double price, const color lineColor, const string text) { //--- Remove any previous instance so the line can be recreated cleanly. if(!DeleteObjectIfExists(name)) return false; ResetLastError(); //--- Create the horizontal line at the requested planning price. if(!ObjectCreate(0,name,OBJ_HLINE,0,0,price)) { PrintApiError("ObjectCreate()","creating line '" + name + "'"); return false; } bool draggable = true; //--- Market Entry follows Bid or Ask and therefore remains fixed to live price. if(name == ENTRY_LINE_NAME && IsMarketOrderType()) draggable = false; ResetLastError(); //--- Position the line explicitly at its initialized price. if(!ObjectMove(0,name,0,0,price)) { PrintApiError("ObjectMove()","positioning line '" + name + "'"); return false; } //--- Apply the visual and interaction properties used by all planning lines. if(!SetObjectIntegerProperty(name,OBJPROP_COLOR,lineColor)) return false; if(!SetObjectIntegerProperty(name,OBJPROP_WIDTH,2)) return false; if(!SetObjectIntegerProperty(name,OBJPROP_STYLE,STYLE_SOLID)) return false; if(!SetObjectIntegerProperty(name,OBJPROP_BACK,false)) return false; if(!SetObjectIntegerProperty(name,OBJPROP_SELECTABLE,draggable)) return false; if(!SetObjectIntegerProperty(name,OBJPROP_SELECTED,draggable)) return false; if(!SetObjectIntegerProperty(name,OBJPROP_HIDDEN,false)) return false; //--- Assign a readable description to identify the line on the chart. if(!SetObjectStringProperty(name,OBJPROP_TEXT,text)) return false; return true; } //+------------------------------------------------------------------+ //| Creates all position planning lines | //+------------------------------------------------------------------+ bool CreatePositionLines() { //--- Create the Entry line using the initialized planning price. if(!CreatePriceLine(ENTRY_LINE_NAME,EntryPrice, InpEntryLineColor,"Entry")) return false; //--- Create the Stop-Loss line used to define the planned risk distance. if(!CreatePriceLine(SL_LINE_NAME,StopLossPrice, InpSLLineColor,"Stop Loss")) return false; //--- Create the Take-Profit line used to define the planned reward target. if(!CreatePriceLine(TP_LINE_NAME,TakeProfitPrice, InpTPLineColor,"Take Profit")) return false; //--- Refresh the chart so all newly created planning lines are visible. ChartRedraw(0); return true; } //+------------------------------------------------------------------+ //| Returns the current price of a horizontal line | //+------------------------------------------------------------------+ bool GetLinePrice(const string name,double &price) { //--- Confirm that the requested planning line exists before reading it. if(ObjectFind(0,name) < 0) { Print("Object not found: ",name); return false; } ResetLastError(); //--- Read the current price stored in the horizontal line object. if(!ObjectGetDouble(0,name,OBJPROP_PRICE,0,price)) { //--- Report the object name if its price cannot be retrieved. PrintApiError("ObjectGetDouble()", "reading price from '" + name + "'"); return false; } return true; } //+------------------------------------------------------------------+ //| Updates current line prices | //+------------------------------------------------------------------+ bool UpdateLinePrices() { //--- Synchronize the stored Entry price with the current chart line. if(!GetLinePrice(ENTRY_LINE_NAME,EntryPrice)) return false; //--- Synchronize the stored Stop-Loss price with its chart line. if(!GetLinePrice(SL_LINE_NAME,StopLossPrice)) return false; //--- Synchronize the stored Take-Profit price with its chart line. if(!GetLinePrice(TP_LINE_NAME,TakeProfitPrice)) return false; return true; } //+------------------------------------------------------------------+ //| Updates market order entry line from Bid/Ask | //+------------------------------------------------------------------+ bool UpdateMarketEntryPrice() { //--- Pending-order plans keep a manually positioned Entry line. if(!IsMarketOrderType()) return true; //--- BUY market plans use the current Ask price as the live Entry. if(InpOrderType == PTO_BUY_MARKET) { if(!GetSymbolDoubleProperty(SYMBOL_ASK,EntryPrice)) return false; } else //--- SELL market plans use the current Bid price as the live Entry. if(InpOrderType == PTO_SELL_MARKET) { if(!GetSymbolDoubleProperty(SYMBOL_BID,EntryPrice)) return false; } ResetLastError(); //--- Move the Entry line so its chart position follows the latest market price. if(!ObjectMove(0,ENTRY_LINE_NAME,0,0,EntryPrice)) { PrintApiError("ObjectMove()", "updating the market Entry line"); return false; } return true; } //+------------------------------------------------------------------+ //| Validates Entry, Stop-Loss, and Take-Profit structure | //+------------------------------------------------------------------+ bool ValidateLineStructure(string &status) { //--- Determine whether the selected order type represents a BUY or SELL plan. ENUM_POSITION_TOOL_DIRECTION direction = GetSetupDirection(); //--- Entry and Stop-Loss must define a measurable risk distance. if(EntryPrice == StopLossPrice) { status = "Invalid: Entry and Stop-Loss cannot be equal."; return false; } //--- BUY setups require Stop-Loss below Entry and Take-Profit above Entry. if(direction == PTD_BUY) { if(StopLossPrice >= EntryPrice) { status = "Invalid: Buy setup requires Stop-Loss below Entry."; return false; } if(TakeProfitPrice <= EntryPrice) { status = "Invalid: Buy setup requires Take-Profit above Entry."; return false; } } //--- SELL setups require Stop-Loss above Entry and Take-Profit below Entry. if(direction == PTD_SELL) { if(StopLossPrice <= EntryPrice) { status = "Invalid: Sell setup requires Stop-Loss above Entry."; return false; } if(TakeProfitPrice >= EntryPrice) { status = "Invalid: Sell setup requires Take-Profit below Entry."; return false; } } //--- Reaching this point means the three levels form a valid setup. status = "Valid setup."; return true; } //+------------------------------------------------------------------+ //| Calculates Stop-Loss distance in price and points | //+------------------------------------------------------------------+ bool CalculateStopLossDistance(double &stopDistance, double &stopPoints, string &status) { //--- Measure the absolute distance between Entry and Stop-Loss prices. stopDistance = MathAbs(EntryPrice - StopLossPrice); //--- Convert the price distance into symbol points for display and RR use. stopPoints = stopDistance / _Point; //--- Reject a setup that does not define a positive Stop-Loss distance. if(stopDistance <= 0.0) { status = "Invalid: Stop-Loss distance must be greater than zero."; return false; } //--- Confirm that the Stop-Loss distance is suitable for further calculations. status = "Valid Stop-Loss distance."; return true; } //+------------------------------------------------------------------+ //| Calculates risk amount from account balance | //+------------------------------------------------------------------+ bool CalculateRiskAmount(double &riskMoney,string &status) { //--- Read the current account balance as the base for percentage risk. double accountBalance = AccountInfoDouble(ACCOUNT_BALANCE); //--- A positive account balance is required before monetary risk can be calculated. if(accountBalance <= 0.0) { status = "Invalid: Account balance must be greater than zero."; return false; } //--- Reject zero or negative risk percentages from the input settings. if(InpRiskPercent <= 0.0) { status = "Invalid: Risk percentage must be greater than zero."; return false; } //--- Convert the configured percentage of account balance into monetary risk. riskMoney = accountBalance * InpRiskPercent / 100.0; //--- Confirm that the resulting risk amount is valid for later calculations. if(riskMoney <= 0.0) { status = "Invalid: Calculated risk amount must be greater than zero."; return false; } status = "Risk amount calculated."; return true; } //+------------------------------------------------------------------+ //| Returns decimal precision required by the volume step | //+------------------------------------------------------------------+ int GetVolumeDigits(const double volumeStep) { int digits = 0; double step = volumeStep; //--- Increase decimal precision until the volume step becomes an integer. while(digits < 8 && MathAbs(step - MathRound(step)) > 1e-8) { step *= 10.0; digits++; } //--- Return the number of decimal places required for valid volume values. return digits; } //+------------------------------------------------------------------+ //| Normalizes volume according to symbol trading rules | //+------------------------------------------------------------------+ bool NormalizeVolume(const double volume, double &normalizedVolume, string &status) { double minVolume = 0.0; double maxVolume = 0.0; double volumeStep = 0.0; //--- Read the broker-defined minimum, maximum, and step values for volume. if(!GetSymbolDoubleProperty(SYMBOL_VOLUME_MIN,minVolume)) { status = "Invalid: Minimum volume is unavailable."; return false; } if(!GetSymbolDoubleProperty(SYMBOL_VOLUME_MAX,maxVolume)) { status = "Invalid: Maximum volume is unavailable."; return false; } if(!GetSymbolDoubleProperty(SYMBOL_VOLUME_STEP,volumeStep)) { status = "Invalid: Volume step is unavailable."; return false; } //--- Reject incomplete or invalid symbol volume settings. if(minVolume <= 0.0 || maxVolume <= 0.0 || volumeStep <= 0.0) { status = "Invalid: Symbol volume settings are unavailable."; return false; } //--- Start with the calculated volume before applying symbol constraints. normalizedVolume = volume; //--- Clamp the volume to the allowed minimum and maximum range. if(normalizedVolume < minVolume) normalizedVolume = minVolume; if(normalizedVolume > maxVolume) normalizedVolume = maxVolume; //--- Align the volume to the broker-defined increment. normalizedVolume = MathFloor(normalizedVolume / volumeStep) * volumeStep; //--- Match the decimal precision required by the symbol volume step. int volumeDigits = GetVolumeDigits(volumeStep); normalizedVolume = NormalizeDouble(normalizedVolume,volumeDigits); status = "Volume normalized."; return true; } //+------------------------------------------------------------------+ //| Calculates lot size from risk amount and Stop-Loss distance | //+------------------------------------------------------------------+ bool CalculateLotSize(const double stopDistance, const double riskMoney, double &lotSize, string &status) { double tickValue = 0.0; double tickSize = 0.0; //--- Read the symbol values required to convert price movement into money. if(!GetSymbolDoubleProperty(SYMBOL_TRADE_TICK_VALUE,tickValue)) { status = "Invalid: Tick value is unavailable."; return false; } if(!GetSymbolDoubleProperty(SYMBOL_TRADE_TICK_SIZE,tickSize)) { status = "Invalid: Tick size is unavailable."; return false; } //--- Reject invalid tick data before using it in the risk calculation. if(tickValue <= 0.0 || tickSize <= 0.0) { status = "Invalid: Tick value or tick size is unavailable."; return false; } //--- A positive Stop-Loss distance is required to estimate position size. if(stopDistance <= 0.0) { status = "Invalid: Stop distance must be greater than zero."; return false; } //--- Calculate how much one lot would lose over the planned Stop-Loss distance. double moneyPerLot = stopDistance / tickSize * tickValue; if(moneyPerLot <= 0.0) { status = "Invalid: Money risk per lot is zero."; return false; } //--- Divide the allowed monetary risk by the risk carried by one lot. double rawLotSize = riskMoney / moneyPerLot; //--- Adjust the result to the symbol's minimum, maximum, and volume step. if(!NormalizeVolume(rawLotSize,lotSize,status)) return false; status = "Lot size calculated."; return true; } //+------------------------------------------------------------------+ //| Calculates reward distance, RR, and estimated reward | //+------------------------------------------------------------------+ bool CalculateRewardMetrics(const double stopPoints, const double riskMoney, double &rewardPoints, double &rr, double &rewardMoney, string &status) { //--- Measure the absolute distance between Entry and Take-Profit. double rewardDistance = MathAbs(TakeProfitPrice - EntryPrice); //--- Convert the reward distance into symbol points. rewardPoints = rewardDistance / _Point; //--- A positive Stop-Loss distance is required to calculate RR. if(stopPoints <= 0.0) { status = "Invalid: Stop points must be greater than zero."; return false; } //--- Take-Profit must define a positive reward distance from Entry. if(rewardPoints <= 0.0) { status = "Invalid: Take-Profit distance must be greater than zero."; return false; } //--- Compare potential reward with planned risk to obtain the RR value. rr = rewardPoints / stopPoints; //--- Apply the RR value to monetary risk to estimate potential reward. rewardMoney = riskMoney * rr; status = "Reward metrics calculated."; return true; } //+------------------------------------------------------------------+ //| Creates dashboard background panel | //+------------------------------------------------------------------+ bool CreateDashboardBackground() { //--- Remove any previous panel instance before creating a fresh background. if(!DeleteObjectIfExists(PANEL_BG_NAME)) return false; ResetLastError(); //--- Create a rectangle label that acts as the dashboard container. if(!ObjectCreate(0,PANEL_BG_NAME,OBJ_RECTANGLE_LABEL,0,0,0)) { PrintApiError("ObjectCreate()", "creating the dashboard background"); return false; } //--- Anchor the panel to the upper-left corner of the chart. if(!SetObjectIntegerProperty(PANEL_BG_NAME, OBJPROP_CORNER,CORNER_LEFT_UPPER)) return false; //--- Position the panel slightly outside the text origin to create padding. if(!SetObjectIntegerProperty(PANEL_BG_NAME, OBJPROP_XDISTANCE,PANEL_X - 10)) return false; if(!SetObjectIntegerProperty(PANEL_BG_NAME, OBJPROP_YDISTANCE,PANEL_Y - 10)) return false; //--- Apply the fixed dimensions used by the compact dashboard layout. if(!SetObjectIntegerProperty(PANEL_BG_NAME, OBJPROP_XSIZE,PANEL_WIDTH)) return false; if(!SetObjectIntegerProperty(PANEL_BG_NAME, OBJPROP_YSIZE,PANEL_HEIGHT)) return false; //--- Apply the dashboard background and border appearance. if(!SetObjectIntegerProperty(PANEL_BG_NAME, OBJPROP_BGCOLOR,PANEL_BG)) return false; if(!SetObjectIntegerProperty(PANEL_BG_NAME, OBJPROP_COLOR,PANEL_BORDER)) return false; if(!SetObjectIntegerProperty(PANEL_BG_NAME, OBJPROP_BORDER_TYPE,BORDER_FLAT)) return false; //--- Keep the background fixed so it does not interfere with chart interaction. if(!SetObjectIntegerProperty(PANEL_BG_NAME, OBJPROP_SELECTABLE,false)) return false; if(!SetObjectIntegerProperty(PANEL_BG_NAME, OBJPROP_HIDDEN,true)) return false; return true; } //+------------------------------------------------------------------+ //| Creates or updates a dashboard label | //+------------------------------------------------------------------+ bool SetPanelText(const string name, const string text, const int x, const int y, const color textColor) { //--- Create the label only when it does not already exist on the chart. if(ObjectFind(0,name) < 0) { ResetLastError(); //--- Create a label object that will display one dashboard value. if(!ObjectCreate(0,name,OBJ_LABEL,0,0,0)) { PrintApiError("ObjectCreate()", "creating dashboard label '" + name + "'"); return false; } //--- Anchor the label to the upper-left corner for fixed panel layout. if(!SetObjectIntegerProperty(name, OBJPROP_CORNER,CORNER_LEFT_UPPER)) return false; if(!SetObjectIntegerProperty(name, OBJPROP_ANCHOR,ANCHOR_LEFT_UPPER)) return false; //--- Apply the common font settings used by all dashboard labels. if(!SetObjectIntegerProperty(name,OBJPROP_FONTSIZE,9)) return false; if(!SetObjectStringProperty(name,OBJPROP_FONT,"Tahoma")) return false; //--- Prevent dashboard text from interfering with chart interaction. if(!SetObjectIntegerProperty(name,OBJPROP_SELECTABLE,false)) return false; if(!SetObjectIntegerProperty(name,OBJPROP_HIDDEN,true)) return false; } //--- Update the label position, color, and displayed text. if(!SetObjectIntegerProperty(name,OBJPROP_XDISTANCE,x)) return false; if(!SetObjectIntegerProperty(name,OBJPROP_YDISTANCE,y)) return false; if(!SetObjectIntegerProperty(name,OBJPROP_COLOR,textColor)) return false; if(!SetObjectStringProperty(name,OBJPROP_TEXT,text)) return false; return true; } //+------------------------------------------------------------------+ //| Sets an integer chart property | //+------------------------------------------------------------------+ bool SetChartIntegerProperty(const ENUM_CHART_PROPERTY_INTEGER property, const long value) { ResetLastError(); //--- Apply the requested integer setting to the current chart. if(!ChartSetInteger(0,property,value)) { //--- Report the failure so chart configuration problems are traceable. PrintApiError("ChartSetInteger()", "configuring the current chart"); return false; } return true; } //+------------------------------------------------------------------+ //| Creates dashboard objects | //+------------------------------------------------------------------+ bool CreateDashboard() { //--- Create the background container before adding dashboard labels. if(!CreateDashboardBackground()) return false; //--- Create the fixed set of labels used to display planning information. for(int i = 0; i < 15; i++) { string name = PANEL_PREFIX + IntegerToString(i); //--- Position each label on its own row using the common line spacing. if(!SetPanelText(name,"", PANEL_X, PANEL_Y + i * PANEL_LINE_GAP, PANEL_TEXT)) return false; } //--- Redraw the chart so the completed dashboard becomes visible immediately. ChartRedraw(0); return true; } //+------------------------------------------------------------------+ //| Returns selected order type text | //+------------------------------------------------------------------+ string GetOrderTypeText() { //--- Convert the selected order type into readable dashboard text. 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 a fallback label if the selected value is not recognized. return "Unknown"; } //+------------------------------------------------------------------+ //| Returns direction text | //+------------------------------------------------------------------+ string GetDirectionText() { //--- Convert the internal setup direction into readable dashboard text. if(GetSetupDirection() == PTD_BUY) return "Buy"; //--- Any non-BUY setup is presented as a SELL direction. return "Sell"; } //+------------------------------------------------------------------+ //| Updates dashboard values | //+------------------------------------------------------------------+ bool UpdateDashboard() { //--- Read the latest Entry, Stop-Loss, and Take-Profit line positions. if(!UpdateLinePrices()) return false; 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; //--- Validate the price structure before performing any calculations. bool valid = ValidateLineStructure(status); //--- Run each calculation only if the preceding stage completed successfully. if(valid) valid = CalculateStopLossDistance(stopDistance, stopPoints, status); if(valid) valid = CalculateRiskAmount(riskMoney,status); if(valid) valid = CalculateLotSize(stopDistance, riskMoney, lotSize, status); if(valid) valid = CalculateRewardMetrics(stopPoints, riskMoney, rewardPoints, rr, rewardMoney, status); //--- Populate the dashboard with the latest setup and calculation results. if(!SetPanelText(PANEL_PREFIX + "0", "Position Planning Tool", PANEL_X, PANEL_Y, PANEL_TITLE)) return false; if(!SetPanelText(PANEL_PREFIX + "1", "Symbol: " + _Symbol, PANEL_X, PANEL_Y + 1 * PANEL_LINE_GAP, PANEL_TEXT)) return false; if(!SetPanelText(PANEL_PREFIX + "2", "Order Type: " + GetOrderTypeText(), PANEL_X, PANEL_Y + 2 * PANEL_LINE_GAP, PANEL_TEXT)) return false; if(!SetPanelText(PANEL_PREFIX + "3", "Direction: " + GetDirectionText(), PANEL_X, PANEL_Y + 3 * PANEL_LINE_GAP, PANEL_TEXT)) return false; if(!SetPanelText(PANEL_PREFIX + "4", "Entry: " + DoubleToString(EntryPrice,_Digits), PANEL_X, PANEL_Y + 4 * PANEL_LINE_GAP, PANEL_TEXT)) return false; if(!SetPanelText(PANEL_PREFIX + "5", "Stop-Loss: " + DoubleToString(StopLossPrice,_Digits), PANEL_X, PANEL_Y + 5 * PANEL_LINE_GAP, PANEL_TEXT)) return false; if(!SetPanelText(PANEL_PREFIX + "6", "Take-Profit: " + DoubleToString(TakeProfitPrice,_Digits), PANEL_X, PANEL_Y + 6 * PANEL_LINE_GAP, PANEL_TEXT)) return false; if(!SetPanelText(PANEL_PREFIX + "7", "SL Points: " + DoubleToString(stopPoints,1), PANEL_X, PANEL_Y + 7 * PANEL_LINE_GAP, PANEL_TEXT)) return false; if(!SetPanelText(PANEL_PREFIX + "8", "TP Points: " + DoubleToString(rewardPoints,1), PANEL_X, PANEL_Y + 8 * PANEL_LINE_GAP, PANEL_TEXT)) return false; if(!SetPanelText(PANEL_PREFIX + "9", "Risk: " + DoubleToString(InpRiskPercent,2) + "%", PANEL_X, PANEL_Y + 9 * PANEL_LINE_GAP, PANEL_TEXT)) return false; if(!SetPanelText(PANEL_PREFIX + "10", "Risk Money: " + DoubleToString(riskMoney,2), PANEL_X, PANEL_Y + 10 * PANEL_LINE_GAP, PANEL_TEXT)) return false; if(!SetPanelText(PANEL_PREFIX + "11", "Estimated Reward: " + DoubleToString(rewardMoney,2), PANEL_X, PANEL_Y + 11 * PANEL_LINE_GAP, PANEL_TEXT)) return false; if(!SetPanelText(PANEL_PREFIX + "12", "Position Size: " + DoubleToString(lotSize,2), PANEL_X, PANEL_Y + 12 * PANEL_LINE_GAP, PANEL_TEXT)) return false; if(!SetPanelText(PANEL_PREFIX + "13", "RR: 1:" + DoubleToString(rr,2), PANEL_X, PANEL_Y + 13 * PANEL_LINE_GAP, PANEL_TEXT)) return false; //--- Use the final validation state to color the dashboard status message. if(!SetPanelText(PANEL_PREFIX + "14", "Status: " + status, PANEL_X, PANEL_Y + 14 * PANEL_LINE_GAP, valid ? clrLimeGreen : clrTomato)) return false; //--- Redraw the chart so all updated dashboard values appear immediately. ChartRedraw(0); return true; } //+------------------------------------------------------------------+ //| Configures chart display settings | //+------------------------------------------------------------------+ bool ConfigureChartDisplay() { //--- Hide the platform Bid and Ask lines to keep the planning view uncluttered. if(!SetChartIntegerProperty(CHART_SHOW_ASK_LINE,false)) return false; if(!SetChartIntegerProperty(CHART_SHOW_BID_LINE,false)) return false; //--- Keep chart objects visible above the price display. if(!SetChartIntegerProperty(CHART_FOREGROUND,false)) return false; //--- Show object descriptions so the planning lines remain identifiable. if(!SetChartIntegerProperty(CHART_SHOW_OBJECT_DESCR,true)) return false; //--- Apply the updated display settings immediately. ChartRedraw(0); return true; } //+------------------------------------------------------------------+ //| Applies Position Planning Tool chart appearance | //+------------------------------------------------------------------+ bool ConfigureChartAppearance() { //--- Use a clean white background and remove the default chart grid. if(!SetChartIntegerProperty(CHART_COLOR_BACKGROUND,clrWhite)) return false; if(!SetChartIntegerProperty(CHART_SHOW_GRID,false)) return false; //--- Display price action as candlesticks for a clear planning view. if(!SetChartIntegerProperty(CHART_MODE,CHART_CANDLES)) return false; //--- Apply contrasting foreground and candle colors for readability. if(!SetChartIntegerProperty(CHART_COLOR_FOREGROUND,clrBlack)) return false; if(!SetChartIntegerProperty(CHART_COLOR_CANDLE_BULL,clrLimeGreen)) return false; if(!SetChartIntegerProperty(CHART_COLOR_CANDLE_BEAR,clrTomato)) return false; if(!SetChartIntegerProperty(CHART_COLOR_CHART_UP,clrLimeGreen)) return false; if(!SetChartIntegerProperty(CHART_COLOR_CHART_DOWN,clrTomato)) return false; //--- Redraw the chart so the new appearance is applied immediately. ChartRedraw(0); return true; } //+------------------------------------------------------------------+ //| Deletes dashboard objects | //+------------------------------------------------------------------+ bool DeleteDashboard() { bool success = true; //--- Remove the dashboard background while preserving the overall cleanup state. if(!DeleteObjectIfExists(PANEL_BG_NAME)) success = false; //--- Remove every dashboard label created with the common panel prefix. for(int i = 0; i < 15; i++) { string name = PANEL_PREFIX + IntegerToString(i); //--- Continue deleting remaining labels even if one deletion fails. if(!DeleteObjectIfExists(name)) success = false; } //--- Report whether all dashboard objects were removed successfully. return success; } //+------------------------------------------------------------------+ //| Deletes position planning lines | //+------------------------------------------------------------------+ bool DeletePositionLines() { bool success = true; //--- Remove each planning line while preserving the overall cleanup result. if(!DeleteObjectIfExists(ENTRY_LINE_NAME)) success = false; if(!DeleteObjectIfExists(SL_LINE_NAME)) success = false; if(!DeleteObjectIfExists(TP_LINE_NAME)) success = false; //--- Report whether all planning lines were removed successfully. return success; } //+------------------------------------------------------------------+