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
|
|
|
}
|
|
|
|
|
//+------------------------------------------------------------------+
|