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