PositionPlanningTool/PositionPlanningTool.mq5

1400 lines
48 KiB
MQL5
Raw Permalink Normal View History

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