340 lines
No EOL
12 KiB
MQL5
340 lines
No EOL
12 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| _Close_at_PRofit.mq5 |
|
|
//| Copyright © 2025 sunokfx |
|
|
//| https://www.mql5.com/en/users/sunokfx |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright © 2025 sunokfx"
|
|
#property link "https://www.mql5.com/en/users/sunokfx"
|
|
#property version "1.10"
|
|
#property strict
|
|
|
|
#include <Trade\Trade.mqh>
|
|
|
|
//--- Input parameters
|
|
input bool useProfitToClose = true; // Close at profit
|
|
input double profitToClose = 200; // Profit threshold ($)
|
|
input bool useLossToClose = false; // Close at loss
|
|
input double lossToClose = 100; // Loss threshold ($)
|
|
input bool AllSymbols = false; // All symbols
|
|
input bool PendingOrders = true; // Include pending orders
|
|
input double MaxSlippage = 3; // Max slippage (pips)
|
|
input bool showMenu = true; // Show info panel
|
|
input color menuColor = clrLightSteelBlue; // Header color
|
|
input color valueColor = clrTeal; // Value color (neutral)
|
|
input color profitColor = clrLimeGreen; // Value color (profit)
|
|
input color lossColor = clrTomato; // Value color (loss)
|
|
input int fontSize = 10; // Font size
|
|
sinput int Magic = -1; // Magic number (-1 = all)
|
|
input int PanelX = 10; // Panel X offset (px)
|
|
input int PanelY = 20; // Panel Y offset (px)
|
|
input ENUM_BASE_CORNER PanelCorner = CORNER_LEFT_UPPER; // Panel corner
|
|
|
|
//--- Panel constants
|
|
#define PANEL_PREFIX "CAP_"
|
|
#define ROW_H 18 // Row height (px)
|
|
#define COL_LABEL_X 0 // Label column X
|
|
#define COL_VALUE_X 185 // Value column X (aligned)
|
|
|
|
//--- Panel object names
|
|
#define OBJ_TITLE PANEL_PREFIX"title"
|
|
#define OBJ_SEP1 PANEL_PREFIX"sep1"
|
|
#define OBJ_LBL_TRADES PANEL_PREFIX"lbl_trades"
|
|
#define OBJ_VAL_TRADES PANEL_PREFIX"val_trades"
|
|
#define OBJ_LBL_LOTS PANEL_PREFIX"lbl_lots"
|
|
#define OBJ_VAL_LOTS PANEL_PREFIX"val_lots"
|
|
#define OBJ_LBL_PROFIT PANEL_PREFIX"lbl_profit"
|
|
#define OBJ_VAL_PROFIT PANEL_PREFIX"val_profit"
|
|
#define OBJ_SEP2 PANEL_PREFIX"sep2"
|
|
#define OBJ_LBL_TARGET PANEL_PREFIX"lbl_target"
|
|
#define OBJ_VAL_TARGET PANEL_PREFIX"val_target"
|
|
#define OBJ_LBL_STOP PANEL_PREFIX"lbl_stop"
|
|
#define OBJ_VAL_STOP PANEL_PREFIX"val_stop"
|
|
|
|
string g_panelObjects[] = {
|
|
OBJ_TITLE, OBJ_SEP1,
|
|
OBJ_LBL_TRADES, OBJ_VAL_TRADES,
|
|
OBJ_LBL_LOTS, OBJ_VAL_LOTS,
|
|
OBJ_LBL_PROFIT, OBJ_VAL_PROFIT,
|
|
OBJ_SEP2,
|
|
OBJ_LBL_TARGET, OBJ_VAL_TARGET,
|
|
OBJ_LBL_STOP, OBJ_VAL_STOP
|
|
};
|
|
|
|
//--- Working variables
|
|
double g_profit = 0;
|
|
bool g_clear = true;
|
|
CTrade g_trade;
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Helper: create or update an OBJ_LABEL object |
|
|
//+------------------------------------------------------------------+
|
|
void SetLabel(const string name,
|
|
const string text,
|
|
const int x,
|
|
const int y,
|
|
const color clr,
|
|
const int fsize = 0,
|
|
const bool bold = false)
|
|
{
|
|
if(ObjectFind(0, name) < 0)
|
|
{
|
|
ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
|
|
ObjectSetInteger(0, name, OBJPROP_CORNER, PanelCorner);
|
|
ObjectSetInteger(0, name, OBJPROP_SELECTABLE, false);
|
|
ObjectSetInteger(0, name, OBJPROP_SELECTED, false);
|
|
ObjectSetInteger(0, name, OBJPROP_HIDDEN, true);
|
|
ObjectSetInteger(0, name, OBJPROP_BACK, false);
|
|
ObjectSetInteger(0, name, OBJPROP_ZORDER, 10);
|
|
}
|
|
int fs = (fsize > 0) ? fsize : fontSize;
|
|
ObjectSetString (0, name, OBJPROP_TEXT, text);
|
|
ObjectSetString (0, name, OBJPROP_FONT, bold ? "Arial Bold" : "Arial");
|
|
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fs);
|
|
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
|
|
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, PanelX + x);
|
|
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, PanelY + y);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Draw static panel skeleton |
|
|
//+------------------------------------------------------------------+
|
|
void DrawPanel()
|
|
{
|
|
int row = 0;
|
|
|
|
// Title
|
|
SetLabel(OBJ_TITLE, "Close at Profit / Loss",
|
|
COL_LABEL_X, row * ROW_H, menuColor, fontSize + 1, true);
|
|
row++;
|
|
|
|
// Separator 1
|
|
SetLabel(OBJ_SEP1, "--------------------------------",
|
|
COL_LABEL_X, row * ROW_H, clrDimGray, fontSize - 2);
|
|
row++;
|
|
|
|
// Row: Positions
|
|
SetLabel(OBJ_LBL_TRADES, "Positions (S / B):",
|
|
COL_LABEL_X, row * ROW_H, menuColor);
|
|
SetLabel(OBJ_VAL_TRADES, "0 / 0",
|
|
COL_VALUE_X, row * ROW_H, valueColor);
|
|
row++;
|
|
|
|
// Row: Lots
|
|
SetLabel(OBJ_LBL_LOTS, "Lots (S / B):",
|
|
COL_LABEL_X, row * ROW_H, menuColor);
|
|
SetLabel(OBJ_VAL_LOTS, "0.00 / 0.00",
|
|
COL_VALUE_X, row * ROW_H, valueColor);
|
|
row++;
|
|
|
|
// Row: Profit
|
|
SetLabel(OBJ_LBL_PROFIT, "Profit (S / B):",
|
|
COL_LABEL_X, row * ROW_H, menuColor);
|
|
SetLabel(OBJ_VAL_PROFIT, "0.00 / 0.00",
|
|
COL_VALUE_X, row * ROW_H, valueColor);
|
|
row++;
|
|
|
|
// Separator 2
|
|
SetLabel(OBJ_SEP2, "--------------------------------",
|
|
COL_LABEL_X, row * ROW_H, clrDimGray, fontSize - 2);
|
|
row++;
|
|
|
|
// Row: Take Profit threshold
|
|
string tpText = useProfitToClose
|
|
? DoubleToString(profitToClose, 2) + " $"
|
|
: "disabled";
|
|
color tpClr = useProfitToClose ? profitColor : clrGray;
|
|
SetLabel(OBJ_LBL_TARGET, "Take Profit:",
|
|
COL_LABEL_X, row * ROW_H, menuColor);
|
|
SetLabel(OBJ_VAL_TARGET, tpText,
|
|
COL_VALUE_X, row * ROW_H, tpClr);
|
|
row++;
|
|
|
|
// Row: Stop Loss threshold
|
|
string slText = useLossToClose
|
|
? DoubleToString(lossToClose, 2) + " $"
|
|
: "disabled";
|
|
color slClr = useLossToClose ? lossColor : clrGray;
|
|
SetLabel(OBJ_LBL_STOP, "Stop Loss:",
|
|
COL_LABEL_X, row * ROW_H, menuColor);
|
|
SetLabel(OBJ_VAL_STOP, slText,
|
|
COL_VALUE_X, row * ROW_H, slClr);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Refresh dynamic panel values only |
|
|
//+------------------------------------------------------------------+
|
|
void UpdatePanel()
|
|
{
|
|
int totalS = 0, totalB = 0;
|
|
double lotsS = 0, lotsB = 0;
|
|
double profS = 0, profB = 0;
|
|
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(ticket == 0) continue;
|
|
if(Magic != -1 && (int)PositionGetInteger(POSITION_MAGIC) != Magic) continue;
|
|
if(!AllSymbols && PositionGetString(POSITION_SYMBOL) != Symbol()) continue;
|
|
|
|
double vol = PositionGetDouble(POSITION_VOLUME);
|
|
double prof = PositionGetDouble(POSITION_PROFIT)
|
|
+ PositionGetDouble(POSITION_SWAP);
|
|
|
|
if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY)
|
|
{ totalB++; lotsB += vol; profB += prof; }
|
|
else
|
|
{ totalS++; lotsS += vol; profS += prof; }
|
|
}
|
|
|
|
// Positions count
|
|
ObjectSetString(0, OBJ_VAL_TRADES, OBJPROP_TEXT,
|
|
IntegerToString(totalS) + " / " + IntegerToString(totalB));
|
|
|
|
// Lots
|
|
ObjectSetString(0, OBJ_VAL_LOTS, OBJPROP_TEXT,
|
|
DoubleToString(lotsS, 2) + " / " + DoubleToString(lotsB, 2));
|
|
|
|
// Profit with dynamic color coding
|
|
double totalProfit = profS + profB;
|
|
color profClr = (totalProfit > 0.0) ? profitColor
|
|
: (totalProfit < 0.0) ? lossColor
|
|
: valueColor;
|
|
ObjectSetString (0, OBJ_VAL_PROFIT, OBJPROP_TEXT,
|
|
DoubleToString(profS, 2) + " / " + DoubleToString(profB, 2));
|
|
ObjectSetInteger(0, OBJ_VAL_PROFIT, OBJPROP_COLOR, profClr);
|
|
|
|
ChartRedraw(0);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Delete all panel objects |
|
|
//+------------------------------------------------------------------+
|
|
void DeletePanel()
|
|
{
|
|
int n = ArraySize(g_panelObjects);
|
|
for(int i = 0; i < n; i++)
|
|
ObjectDelete(0, g_panelObjects[i]);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Close positions and optionally delete pending orders |
|
|
//+------------------------------------------------------------------+
|
|
bool CloseAll(const bool currentSymbolOnly, const bool includePending)
|
|
{
|
|
// Close open positions
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(ticket == 0) continue;
|
|
if(Magic != -1 && (int)PositionGetInteger(POSITION_MAGIC) != Magic) continue;
|
|
if(currentSymbolOnly && PositionGetString(POSITION_SYMBOL) != Symbol()) continue;
|
|
|
|
if(!g_trade.PositionClose(ticket))
|
|
{
|
|
Print("CloseAll: failed to close position #", ticket,
|
|
" err=", GetLastError());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Delete pending orders
|
|
if(includePending)
|
|
{
|
|
for(int i = OrdersTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong ticket = OrderGetTicket(i);
|
|
if(ticket == 0) continue;
|
|
if(Magic != -1 && (int)OrderGetInteger(ORDER_MAGIC) != Magic) continue;
|
|
if(currentSymbolOnly && OrderGetString(ORDER_SYMBOL) != Symbol()) continue;
|
|
|
|
if(!g_trade.OrderDelete(ticket))
|
|
{
|
|
Print("CloseAll: failed to delete order #", ticket,
|
|
" err=", GetLastError());
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate total floating P&L of matching positions |
|
|
//+------------------------------------------------------------------+
|
|
double ProfitCheck()
|
|
{
|
|
double total = 0;
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
ulong ticket = PositionGetTicket(i);
|
|
if(ticket == 0) continue;
|
|
if(Magic != -1 && (int)PositionGetInteger(POSITION_MAGIC) != Magic) continue;
|
|
if(!AllSymbols && PositionGetString(POSITION_SYMBOL) != Symbol()) continue;
|
|
|
|
total += PositionGetDouble(POSITION_PROFIT)
|
|
+ PositionGetDouble(POSITION_SWAP);
|
|
}
|
|
return total;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| OnInit |
|
|
//+------------------------------------------------------------------+
|
|
int OnInit()
|
|
{
|
|
g_trade.SetDeviationInPoints((ulong)(MaxSlippage * 10));
|
|
if(Magic != -1)
|
|
g_trade.SetExpertMagicNumber(Magic);
|
|
|
|
g_clear = true;
|
|
|
|
if(showMenu)
|
|
{
|
|
DrawPanel();
|
|
UpdatePanel();
|
|
}
|
|
return INIT_SUCCEEDED;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| OnDeinit |
|
|
//+------------------------------------------------------------------+
|
|
void OnDeinit(const int reason)
|
|
{
|
|
DeletePanel();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| OnTick |
|
|
//+------------------------------------------------------------------+
|
|
void OnTick()
|
|
{
|
|
if(showMenu)
|
|
UpdatePanel();
|
|
|
|
// Retry if previous close attempt failed
|
|
if(!g_clear)
|
|
{
|
|
if(CloseAll(!AllSymbols, PendingOrders))
|
|
g_clear = true;
|
|
else
|
|
return;
|
|
}
|
|
|
|
g_profit = ProfitCheck();
|
|
|
|
// Take Profit trigger
|
|
if(useProfitToClose && g_profit > profitToClose)
|
|
{
|
|
if(!CloseAll(!AllSymbols, PendingOrders))
|
|
g_clear = false;
|
|
}
|
|
|
|
// Stop Loss trigger
|
|
if(useLossToClose && g_profit < -lossToClose)
|
|
{
|
|
if(!CloseAll(!AllSymbols, PendingOrders))
|
|
g_clear = false;
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+ |