575 lines
18 KiB
Plaintext
575 lines
18 KiB
Plaintext
//+------------------------------------------------------------------+
|
|
//| RiskManager.mqh |
|
|
//| Universal Buffer Reader EA v2.0 |
|
|
//+------------------------------------------------------------------+
|
|
#property copyright "Copyright 2025"
|
|
#property link ""
|
|
#property version "1.00"
|
|
#property strict
|
|
|
|
#include <Trade\Trade.mqh>
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| CRiskManager - Manages breakeven and trailing stop |
|
|
//+------------------------------------------------------------------+
|
|
class CRiskManager
|
|
{
|
|
private:
|
|
bool m_use_trailing_stop;
|
|
int m_trailing_stop_pips;
|
|
bool m_use_breakeven;
|
|
int m_breakeven_pips;
|
|
|
|
double m_pip_value;
|
|
int m_digits;
|
|
double m_stop_level_points;
|
|
int m_magic_number;
|
|
string m_symbol;
|
|
|
|
CTrade *m_trade;
|
|
|
|
// Logging
|
|
bool m_enable_debug;
|
|
|
|
// Partial close tracking
|
|
bool m_partial_close_enabled;
|
|
|
|
public:
|
|
//+------------------------------------------------------------------+
|
|
//| Validated SL/TP structure |
|
|
//+------------------------------------------------------------------+
|
|
struct ValidatedSLTP
|
|
{
|
|
double sl_price;
|
|
double tp_prices[];
|
|
int tp_count;
|
|
bool is_valid;
|
|
string error_message;
|
|
};
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CRiskManager()
|
|
{
|
|
m_use_trailing_stop = false;
|
|
m_trailing_stop_pips = 300;
|
|
m_use_breakeven = true;
|
|
m_breakeven_pips = 30;
|
|
|
|
m_pip_value = 0;
|
|
m_digits = 0;
|
|
m_stop_level_points = 0;
|
|
m_magic_number = 0;
|
|
m_symbol = "";
|
|
m_enable_debug = false;
|
|
m_partial_close_enabled = false;
|
|
|
|
m_trade = new CTrade();
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Destructor |
|
|
//+------------------------------------------------------------------+
|
|
~CRiskManager()
|
|
{
|
|
if(m_trade != NULL)
|
|
{
|
|
delete m_trade;
|
|
m_trade = NULL;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set parameters |
|
|
//+------------------------------------------------------------------+
|
|
void SetParameters(
|
|
bool use_trailing_stop,
|
|
int trailing_stop_pips,
|
|
bool use_breakeven,
|
|
int breakeven_pips,
|
|
double pip_value,
|
|
int digits,
|
|
double stop_level_points,
|
|
int magic_number,
|
|
string symbol,
|
|
bool enable_debug = false
|
|
)
|
|
{
|
|
// Validate parameters
|
|
if(trailing_stop_pips <= 0)
|
|
{
|
|
Print("[ERROR] Invalid trailing stop pips: ", trailing_stop_pips, ". Using default 300");
|
|
m_trailing_stop_pips = 300;
|
|
}
|
|
else
|
|
{
|
|
m_trailing_stop_pips = trailing_stop_pips;
|
|
}
|
|
|
|
if(breakeven_pips <= 0)
|
|
{
|
|
Print("[ERROR] Invalid breakeven pips: ", breakeven_pips, ". Using default 30");
|
|
m_breakeven_pips = 30;
|
|
}
|
|
else
|
|
{
|
|
m_breakeven_pips = breakeven_pips;
|
|
}
|
|
|
|
if(pip_value <= 0)
|
|
{
|
|
Print("[ERROR] Invalid pip value: ", pip_value);
|
|
}
|
|
m_pip_value = pip_value;
|
|
|
|
if(digits <= 0)
|
|
{
|
|
Print("[ERROR] Invalid digits: ", digits);
|
|
}
|
|
m_digits = digits;
|
|
|
|
if(stop_level_points < 0)
|
|
{
|
|
Print("[ERROR] Invalid stop level points: ", stop_level_points);
|
|
}
|
|
m_stop_level_points = stop_level_points;
|
|
|
|
m_use_trailing_stop = use_trailing_stop;
|
|
m_use_breakeven = use_breakeven;
|
|
m_magic_number = magic_number;
|
|
m_symbol = symbol;
|
|
m_enable_debug = enable_debug;
|
|
|
|
m_trade.SetExpertMagicNumber(m_magic_number);
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[RiskManager] Parameters set:");
|
|
Print(" Use trailing stop: ", m_use_trailing_stop, " (", m_trailing_stop_pips, " pips)");
|
|
Print(" Use breakeven: ", m_use_breakeven, " (", m_breakeven_pips, " pips)");
|
|
Print(" Pip value: ", m_pip_value, ", Digits: ", m_digits);
|
|
Print(" Stop level: ", m_stop_level_points, " points");
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Set debug mode |
|
|
//+------------------------------------------------------------------+
|
|
void SetDebugMode(bool enable_debug)
|
|
{
|
|
m_enable_debug = enable_debug;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Enable partial close (for TP-based trailing) |
|
|
//+------------------------------------------------------------------+
|
|
void EnablePartialClose(bool enable)
|
|
{
|
|
m_partial_close_enabled = enable;
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[RiskManager] Partial close ", (enable ? "enabled" : "disabled"));
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Manage breakeven and trailing stop |
|
|
//+------------------------------------------------------------------+
|
|
void ManageRiskManagement()
|
|
{
|
|
// Iterate through all positions
|
|
for(int i = PositionsTotal() - 1; i >= 0; i--)
|
|
{
|
|
if(!PositionSelectByTicket(PositionGetTicket(i))) continue;
|
|
if(PositionGetString(POSITION_SYMBOL) != m_symbol) continue;
|
|
if(PositionGetInteger(POSITION_MAGIC) != m_magic_number) continue;
|
|
|
|
ulong ticket = PositionGetInteger(POSITION_TICKET);
|
|
double open_price = PositionGetDouble(POSITION_PRICE_OPEN);
|
|
double current_sl = PositionGetDouble(POSITION_SL);
|
|
ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
|
|
bool is_buy = (pos_type == POSITION_TYPE_BUY);
|
|
double current_price = is_buy ?
|
|
SymbolInfoDouble(m_symbol, SYMBOL_BID) :
|
|
SymbolInfoDouble(m_symbol, SYMBOL_ASK);
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[RiskManager] Checking position #", ticket);
|
|
Print(" Type: ", (is_buy ? "BUY" : "SELL"));
|
|
Print(" Open: ", open_price, ", Current: ", current_price, ", SL: ", current_sl);
|
|
}
|
|
|
|
// Check breakeven
|
|
if(m_use_breakeven && current_sl != open_price)
|
|
{
|
|
if(ShouldMoveToBreakeven(ticket, open_price, current_price))
|
|
{
|
|
TryMoveToBreakeven(ticket, open_price);
|
|
}
|
|
}
|
|
|
|
// Check TP-based trailing
|
|
if(m_partial_close_enabled) // Only if partial close is enabled
|
|
{
|
|
ManageTPBasedTrailing(ticket, is_buy, open_price, current_price, current_sl);
|
|
}
|
|
|
|
// Check standard trailing
|
|
if(m_use_trailing_stop)
|
|
{
|
|
ManageStandardTrailing(ticket, is_buy, open_price, current_price, current_sl);
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Validate SL/TP to meet broker requirements |
|
|
//+------------------------------------------------------------------+
|
|
ValidatedSLTP ValidateSLTP(
|
|
bool is_buy,
|
|
double open_price,
|
|
double sl_price,
|
|
double &tp_prices[],
|
|
int tp_count
|
|
)
|
|
{
|
|
ValidatedSLTP result;
|
|
result.sl_price = sl_price;
|
|
result.tp_count = tp_count;
|
|
result.is_valid = true;
|
|
result.error_message = "";
|
|
|
|
ArrayResize(result.tp_prices, tp_count);
|
|
ArrayCopy(result.tp_prices, tp_prices);
|
|
|
|
// Validate SL
|
|
if(sl_price != 0)
|
|
{
|
|
result.sl_price = AdjustToStopLevel(is_buy, sl_price, open_price);
|
|
}
|
|
|
|
// Validate TPs
|
|
for(int i = 0; i < tp_count; i++)
|
|
{
|
|
if(result.tp_prices[i] != 0)
|
|
{
|
|
result.tp_prices[i] = AdjustToStopLevel(is_buy, result.tp_prices[i], open_price);
|
|
}
|
|
}
|
|
|
|
return result;
|
|
}
|
|
|
|
private:
|
|
//+------------------------------------------------------------------+
|
|
//| Check if SL should move to breakeven |
|
|
//+------------------------------------------------------------------+
|
|
bool ShouldMoveToBreakeven(ulong ticket, double open_price, double current_price)
|
|
{
|
|
double profit_pips = 0;
|
|
|
|
// Get position type
|
|
if(!PositionSelectByTicket(ticket)) return false;
|
|
ENUM_POSITION_TYPE pos_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE);
|
|
|
|
if(pos_type == POSITION_TYPE_BUY)
|
|
{
|
|
profit_pips = (current_price - open_price) / m_pip_value;
|
|
}
|
|
else
|
|
{
|
|
profit_pips = (open_price - current_price) / m_pip_value;
|
|
}
|
|
|
|
return (profit_pips >= m_breakeven_pips);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Try to move SL to breakeven |
|
|
//+------------------------------------------------------------------+
|
|
bool TryMoveToBreakeven(ulong ticket, double open_price)
|
|
{
|
|
if(!PositionSelectByTicket(ticket)) return false;
|
|
|
|
double current_sl = PositionGetDouble(POSITION_SL);
|
|
if(current_sl == open_price) return true; // Already at breakeven
|
|
|
|
string comment = PositionGetString(POSITION_COMMENT);
|
|
|
|
if(m_trade.PositionModify(ticket, open_price, PositionGetDouble(POSITION_TP)))
|
|
{
|
|
Print("[RiskManager] Breakeven set for ticket #", ticket, " at ", open_price);
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
Print("[ERROR] Failed to set breakeven for ticket #", ticket,
|
|
". Error: ", m_trade.ResultRetcodeDescription());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Manage TP-based trailing stop |
|
|
//+------------------------------------------------------------------+
|
|
void ManageTPBasedTrailing(ulong ticket, bool is_buy, double open_price, double current_price, double current_sl)
|
|
{
|
|
// Get TP prices from comment
|
|
double tp_prices[];
|
|
int tp_count = 0;
|
|
if(!GetTPPricesFromComment(ticket, tp_prices, tp_count)) return;
|
|
|
|
if(tp_count == 0) return;
|
|
|
|
// Check which TP level has been reached
|
|
int reached_tp_level = GetReachedTPLevel(is_buy, current_price, tp_prices, tp_count);
|
|
|
|
if(reached_tp_level == 0) return; // No TP reached yet
|
|
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[RiskManager] TP", reached_tp_level, " reached for ticket #", ticket);
|
|
}
|
|
|
|
// Determine new SL based on TP level
|
|
double new_sl = 0;
|
|
|
|
if(reached_tp_level == 1)
|
|
{
|
|
// TP1 reached: Move SL to breakeven
|
|
new_sl = open_price;
|
|
}
|
|
else if(reached_tp_level == 2)
|
|
{
|
|
// TP2 reached: Move SL to TP1
|
|
new_sl = tp_prices[0];
|
|
}
|
|
else if(reached_tp_level >= 3)
|
|
{
|
|
// TP3 reached: Move SL to TP2
|
|
new_sl = tp_prices[1];
|
|
}
|
|
|
|
// Check if SL should be moved
|
|
if(new_sl > 0)
|
|
{
|
|
bool should_move = false;
|
|
if(is_buy)
|
|
{
|
|
should_move = (new_sl > current_sl);
|
|
}
|
|
else
|
|
{
|
|
should_move = (new_sl < current_sl);
|
|
}
|
|
|
|
if(should_move)
|
|
{
|
|
TryMoveSL(ticket, new_sl, "TP" + IntegerToString(reached_tp_level));
|
|
}
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Manage standard trailing stop |
|
|
//+------------------------------------------------------------------+
|
|
void ManageStandardTrailing(ulong ticket, bool is_buy, double open_price, double current_price, double current_sl)
|
|
{
|
|
// Calculate new SL based on trailing distance
|
|
double new_sl = CalculateNewSL(is_buy, current_price);
|
|
|
|
if(new_sl == 0) return;
|
|
|
|
// Ratchet rule: SL must only move in profit direction
|
|
bool should_move = false;
|
|
if(is_buy)
|
|
{
|
|
should_move = (new_sl > current_sl);
|
|
}
|
|
else
|
|
{
|
|
should_move = (new_sl < current_sl);
|
|
}
|
|
|
|
if(should_move)
|
|
{
|
|
if(m_enable_debug)
|
|
{
|
|
Print("[RiskManager] Trailing SL for ticket #", ticket);
|
|
Print(" Current SL: ", current_sl, ", New SL: ", new_sl);
|
|
}
|
|
|
|
TryMoveSL(ticket, new_sl, "TRAIL");
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get TP prices from position comment |
|
|
//+------------------------------------------------------------------+
|
|
bool GetTPPricesFromComment(ulong ticket, double &tp_prices[], int &tp_count)
|
|
{
|
|
if(!PositionSelectByTicket(ticket)) return false;
|
|
|
|
string comment = PositionGetString(POSITION_COMMENT);
|
|
tp_count = 0;
|
|
ArrayResize(tp_prices, 0);
|
|
|
|
// Parse comment for TP values
|
|
// Format: "UnivBufEA_24680_H1;TP1=1.2530;TP2=1.2560;TP3=1.2600;..."
|
|
|
|
int tp_index = 1;
|
|
while(true)
|
|
{
|
|
string search_str = ";TP" + IntegerToString(tp_index) + "=";
|
|
int pos = StringFind(comment, search_str);
|
|
|
|
if(pos == -1) break;
|
|
|
|
// Extract TP value
|
|
int start_pos = pos + StringLen(search_str);
|
|
int end_pos = StringFind(comment, ";", start_pos);
|
|
|
|
if(end_pos == -1) end_pos = StringLen(comment);
|
|
|
|
string tp_str = StringSubstr(comment, start_pos, end_pos - start_pos);
|
|
double tp_value = StringToDouble(tp_str);
|
|
|
|
ArrayResize(tp_prices, tp_count + 1);
|
|
tp_prices[tp_count] = tp_value;
|
|
tp_count++;
|
|
|
|
tp_index++;
|
|
}
|
|
|
|
return (tp_count > 0);
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Get reached TP level |
|
|
//+------------------------------------------------------------------+
|
|
int GetReachedTPLevel(bool is_buy, double current_price, double &tp_prices[], int tp_count)
|
|
{
|
|
for(int i = 0; i < tp_count; i++)
|
|
{
|
|
if(tp_prices[i] == 0) continue;
|
|
|
|
if(is_buy)
|
|
{
|
|
if(current_price >= tp_prices[i]) return (i + 1);
|
|
}
|
|
else
|
|
{
|
|
if(current_price <= tp_prices[i]) return (i + 1);
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate new SL for trailing |
|
|
//+------------------------------------------------------------------+
|
|
double CalculateNewSL(bool is_buy, double current_price)
|
|
{
|
|
if(m_trailing_stop_pips <= 0) return 0;
|
|
|
|
double trailing_distance = m_trailing_stop_pips * m_pip_value;
|
|
|
|
if(is_buy)
|
|
{
|
|
return NormalizeDouble(current_price - trailing_distance, m_digits);
|
|
}
|
|
else
|
|
{
|
|
return NormalizeDouble(current_price + trailing_distance, m_digits);
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Try to move SL |
|
|
//+------------------------------------------------------------------+
|
|
bool TryMoveSL(ulong ticket, double new_sl, string reason)
|
|
{
|
|
if(!PositionSelectByTicket(ticket)) return false;
|
|
|
|
double current_tp = PositionGetDouble(POSITION_TP);
|
|
|
|
if(m_trade.PositionModify(ticket, new_sl, current_tp))
|
|
{
|
|
Print("[RiskManager] SL moved for ticket #", ticket, " to ", new_sl, " (", reason, ")");
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
Print("[ERROR] Failed to move SL for ticket #", ticket,
|
|
". Error: ", m_trade.ResultRetcodeDescription());
|
|
return false;
|
|
}
|
|
}
|
|
|
|
//+------------------------------------------------------------------+
|
|
//| Adjust price to meet stop level requirements |
|
|
//+------------------------------------------------------------------+
|
|
double AdjustToStopLevel(bool is_buy, double price, double open_price)
|
|
{
|
|
double distance = 0;
|
|
|
|
if(is_buy)
|
|
{
|
|
if(price < open_price) // SL
|
|
{
|
|
distance = open_price - price;
|
|
}
|
|
else // TP
|
|
{
|
|
distance = price - open_price;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(price > open_price) // SL
|
|
{
|
|
distance = price - open_price;
|
|
}
|
|
else // TP
|
|
{
|
|
distance = open_price - price;
|
|
}
|
|
}
|
|
|
|
if(distance < m_stop_level_points)
|
|
{
|
|
distance = m_stop_level_points;
|
|
|
|
if(is_buy)
|
|
{
|
|
if(price < open_price) // SL
|
|
{
|
|
price = NormalizeDouble(open_price - distance, m_digits);
|
|
}
|
|
else // TP
|
|
{
|
|
price = NormalizeDouble(open_price + distance, m_digits);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if(price > open_price) // SL
|
|
{
|
|
price = NormalizeDouble(open_price + distance, m_digits);
|
|
}
|
|
else // TP
|
|
{
|
|
price = NormalizeDouble(open_price - distance, m_digits);
|
|
}
|
|
}
|
|
}
|
|
|
|
return price;
|
|
}
|
|
};
|
|
//+------------------------------------------------------------------+ |