VizionAI-Trading-EA/SymbolPolicy.mqh

504 righe
19 KiB
MQL5

#ifndef SYMBOL_POLICY_MQH
#define SYMBOL_POLICY_MQH
string GWJsonEscape(const string s);
double ClampDouble(const double value, const double min_value, const double max_value)
{
return MathMax(min_value, MathMin(max_value, value));
}
string UpperCopy(const string s)
{
string x = s;
StringToUpper(x);
return x;
}
bool SymbolNameHas(const string symbol, const string token)
{
return (StringFind(UpperCopy(symbol), UpperCopy(token)) >= 0);
}
string BoolJson(const bool value)
{
return value ? "true" : "false";
}
void ResetSymbolPolicy(SymbolPolicy &policy)
{
policy.symbol_name = _Symbol;
policy.symbol_class = "MANUAL";
policy.base_risk_percent = 0.50;
policy.max_spread_points = 60.0;
policy.min_stop_points = 80;
policy.preferred_sessions = "London,NY";
policy.news_sensitivity = 0.50;
policy.lot_floor = 0.01;
policy.lot_ceiling = 1.00;
policy.max_concurrent_exposure = 4;
policy.continuation_differs = true;
policy.continuation_risk_mult = 1.05;
policy.counter_risk_mult = 0.80;
policy.require_session_alignment = false;
policy.min_free_margin_pct = Min_Free_Margin_Pct;
policy.max_account_risk_pct = Max_Account_Risk_Pct;
policy.max_margin_pct = Max_Trade_Margin_Pct;
policy.notes = "fallback profile";
}
void ApplyPolicyForSymbolClass(const string symbol, SymbolPolicy &policy)
{
ResetSymbolPolicy(policy);
policy.symbol_name = symbol;
if(SymbolNameHas(symbol, "XAU"))
{
policy.symbol_class = "XAUUSD";
policy.base_risk_percent = 0.35;
policy.max_spread_points = 350.0;
policy.min_stop_points = 450;
policy.preferred_sessions = "London,NY";
policy.news_sensitivity = 0.95;
policy.lot_ceiling = 2.0;
policy.max_concurrent_exposure = 2;
policy.continuation_risk_mult = 1.10;
policy.counter_risk_mult = 0.70;
policy.require_session_alignment = true;
policy.min_free_margin_pct = 35.0;
policy.max_account_risk_pct = 1.0;
policy.max_margin_pct = 12.0;
policy.notes = "gold: macro sensitive";
}
else if(SymbolNameHas(symbol, "US30") || SymbolNameHas(symbol, "DJI"))
{
policy.symbol_class = "US30";
policy.base_risk_percent = 0.25;
policy.max_spread_points = 120.0;
policy.min_stop_points = 120;
policy.preferred_sessions = "NY";
policy.news_sensitivity = 0.90;
policy.lot_ceiling = 1.0;
policy.max_concurrent_exposure = 2;
policy.continuation_risk_mult = 1.10;
policy.counter_risk_mult = 0.75;
policy.require_session_alignment = true;
policy.min_free_margin_pct = 40.0;
policy.max_account_risk_pct = 0.90;
policy.max_margin_pct = 10.0;
policy.notes = "index: session sensitive";
}
else if(SymbolNameHas(symbol, "NAS100") || SymbolNameHas(symbol, "USTEC"))
{
policy.symbol_class = "NAS100";
policy.base_risk_percent = 0.25;
policy.max_spread_points = 150.0;
policy.min_stop_points = 150;
policy.preferred_sessions = "NY";
policy.news_sensitivity = 0.95;
policy.lot_ceiling = 1.0;
policy.max_concurrent_exposure = 2;
policy.continuation_risk_mult = 1.10;
policy.counter_risk_mult = 0.70;
policy.require_session_alignment = true;
policy.min_free_margin_pct = 40.0;
policy.max_account_risk_pct = 0.90;
policy.max_margin_pct = 10.0;
policy.notes = "index: macro and vol sensitive";
}
else if(SymbolNameHas(symbol, "USOIL") || SymbolNameHas(symbol, "WTI"))
{
policy.symbol_class = "USOIL";
policy.base_risk_percent = 0.40;
policy.max_spread_points = 80.0;
policy.min_stop_points = 100;
policy.preferred_sessions = "London,NY";
policy.news_sensitivity = 0.80;
policy.lot_ceiling = 1.5;
policy.max_concurrent_exposure = 3;
policy.continuation_risk_mult = 1.05;
policy.counter_risk_mult = 0.80;
policy.require_session_alignment = false;
policy.min_free_margin_pct = 30.0;
policy.max_account_risk_pct = 1.25;
policy.max_margin_pct = 15.0;
policy.notes = "oil: event sensitive";
}
else if(SymbolNameHas(symbol, "BTC") || SymbolNameHas(symbol, "ETH"))
{
policy.symbol_class = "CRYPTO";
policy.base_risk_percent = 0.30;
policy.max_spread_points = 2000.0;
policy.min_stop_points = 1500;
policy.preferred_sessions = "Interbank,NY";
policy.news_sensitivity = 0.70;
policy.lot_ceiling = 0.25;
policy.max_concurrent_exposure = 1;
policy.continuation_risk_mult = 1.05;
policy.counter_risk_mult = 0.75;
policy.require_session_alignment = false;
policy.min_free_margin_pct = 45.0;
policy.max_account_risk_pct = 0.75;
policy.max_margin_pct = 8.0;
policy.notes = "crypto: high volatility";
}
else if(SymbolNameHas(symbol, "JPY"))
{
policy.symbol_class = "JPY";
policy.base_risk_percent = 0.45;
policy.max_spread_points = 35.0;
policy.min_stop_points = 60;
policy.preferred_sessions = "Tokyo,London";
policy.news_sensitivity = 0.65;
policy.lot_ceiling = 2.0;
policy.max_concurrent_exposure = 4;
policy.continuation_risk_mult = 1.05;
policy.counter_risk_mult = 0.80;
policy.require_session_alignment = false;
policy.min_free_margin_pct = Min_Free_Margin_Pct;
policy.max_account_risk_pct = Max_Account_Risk_Pct;
policy.max_margin_pct = Max_Trade_Margin_Pct;
policy.notes = "yen-cross family";
}
else if(SymbolNameHas(symbol, "CAD"))
{
policy.symbol_class = "COMMODITY_FX";
policy.base_risk_percent = 0.50;
policy.max_spread_points = 30.0;
policy.min_stop_points = 40;
policy.preferred_sessions = "London,NY";
policy.news_sensitivity = 0.60;
policy.lot_ceiling = 2.0;
policy.max_concurrent_exposure = 4;
policy.continuation_risk_mult = 1.00;
policy.counter_risk_mult = 0.85;
policy.require_session_alignment = false;
policy.min_free_margin_pct = Min_Free_Margin_Pct;
policy.max_account_risk_pct = Max_Account_Risk_Pct;
policy.max_margin_pct = Max_Trade_Margin_Pct;
policy.notes = "commodity fx";
}
else
{
policy.symbol_class = "MAJOR_FX";
policy.base_risk_percent = 0.45;
policy.max_spread_points = 25.0;
policy.min_stop_points = 50;
policy.preferred_sessions = "London,NY";
policy.news_sensitivity = 0.55;
policy.lot_ceiling = 3.0;
policy.max_concurrent_exposure = 4;
policy.continuation_risk_mult = 1.00;
policy.counter_risk_mult = 0.85;
policy.require_session_alignment = false;
policy.min_free_margin_pct = Min_Free_Margin_Pct;
policy.max_account_risk_pct = Max_Account_Risk_Pct;
policy.max_margin_pct = Max_Trade_Margin_Pct;
policy.notes = "major fx fallback";
}
}
void ResolveSymbolPolicy()
{
if(!Use_Auto_Symbol_Config)
{
ResetSymbolPolicy(g_symbol_policy);
g_symbol_policy.symbol_name = _Symbol;
g_symbol_policy.symbol_class = "MANUAL";
g_symbol_policy.min_stop_points = Manual_SL_Points;
g_symbol_policy.lot_ceiling = BaseLotSize * 10.0;
g_symbol_policy.base_risk_percent = 0.50;
g_symbol_policy.notes = "manual fallback";
g_symbol_policy_ready = true;
Symbol_Profile_Name = "MANUAL";
Symbol_SL_Points = Manual_SL_Points;
Symbol_TP_Points = Manual_TP_Points;
return;
}
ApplyPolicyForSymbolClass(_Symbol, g_symbol_policy);
ApplySymbolPolicyFeedback(g_symbol_policy);
g_symbol_policy_ready = true;
Symbol_Profile_Name = g_symbol_policy.symbol_class;
Symbol_SL_Points = g_symbol_policy.min_stop_points;
Symbol_TP_Points = (int)MathMax((double)Symbol_SL_Points * 4.0, (double)Symbol_SL_Points * 2.5);
if(Symbol_Profile_Name == "XAUUSD")
Symbol_TP_Points = 3500;
else if(Symbol_Profile_Name == "US30")
Symbol_TP_Points = 1000;
else if(Symbol_Profile_Name == "NAS100")
Symbol_TP_Points = 1400;
}
bool SessionInPreferenceList(const string session, const string preferred_sessions)
{
if(StringLen(preferred_sessions) == 0)
return true;
string sess = UpperCopy(session);
string prefs = UpperCopy(preferred_sessions);
return (StringFind(prefs, sess) >= 0);
}
int GetSymbolFeedbackIndex(const string symbol_class)
{
string cls = UpperCopy(symbol_class);
if(cls == "XAUUSD") return 0;
if(cls == "US30") return 1;
if(cls == "NAS100") return 2;
if(cls == "USOIL") return 3;
if(cls == "CRYPTO") return 4;
if(cls == "JPY") return 5;
if(cls == "COMMODITY_FX") return 6;
return 7;
}
void InitSymbolPolicyFeedback()
{
if(g_symbol_feedback_ready)
return;
string names[8] = {"XAUUSD","US30","NAS100","USOIL","CRYPTO","JPY","COMMODITY_FX","MAJOR_FX"};
for(int i = 0; i < 8; i++)
{
g_symbol_feedback_states[i].symbol_class = names[i];
g_symbol_feedback_states[i].sample_count = 0;
g_symbol_feedback_states[i].win_count = 0;
g_symbol_feedback_states[i].loss_count = 0;
g_symbol_feedback_states[i].continuation_count = 0;
g_symbol_feedback_states[i].continuation_win_count = 0;
g_symbol_feedback_states[i].counter_count = 0;
g_symbol_feedback_states[i].counter_win_count = 0;
g_symbol_feedback_states[i].ema_profit = 0.0;
g_symbol_feedback_states[i].ema_spread_points = 0.0;
g_symbol_feedback_states[i].ema_margin_headroom_pct = 0.0;
g_symbol_feedback_states[i].ema_risk_amount_pct = 0.0;
g_symbol_feedback_states[i].ema_win_rate = 0.0;
g_symbol_feedback_states[i].last_update = 0;
}
g_symbol_feedback_ready = true;
}
void UpdateSymbolPolicyFeedback(const string symbol_class,
const double profit,
const double spread_points,
const double margin_headroom_pct,
const double risk_amount_pct,
const int category)
{
InitSymbolPolicyFeedback();
int idx = GetSymbolFeedbackIndex(symbol_class);
bool win = (profit > 0.0);
SymbolPolicyFeedbackState state = g_symbol_feedback_states[idx];
state.sample_count++;
if(win) state.win_count++;
else state.loss_count++;
if(category == 0)
{
state.continuation_count++;
if(win) state.continuation_win_count++;
}
else
{
state.counter_count++;
if(win) state.counter_win_count++;
}
double alpha = 0.15;
if(state.sample_count == 1)
{
state.ema_profit = profit;
state.ema_spread_points = spread_points;
state.ema_margin_headroom_pct = margin_headroom_pct;
state.ema_risk_amount_pct = risk_amount_pct;
state.ema_win_rate = win ? 1.0 : 0.0;
}
else
{
state.ema_profit = (alpha * profit) + ((1.0 - alpha) * state.ema_profit);
state.ema_spread_points = (alpha * spread_points) + ((1.0 - alpha) * state.ema_spread_points);
state.ema_margin_headroom_pct = (alpha * margin_headroom_pct) + ((1.0 - alpha) * state.ema_margin_headroom_pct);
state.ema_risk_amount_pct = (alpha * risk_amount_pct) + ((1.0 - alpha) * state.ema_risk_amount_pct);
state.ema_win_rate = (alpha * (win ? 1.0 : 0.0)) + ((1.0 - alpha) * state.ema_win_rate);
}
state.last_update = TimeCurrent();
g_symbol_feedback_states[idx] = state;
}
void ApplySymbolPolicyFeedback(SymbolPolicy &policy)
{
InitSymbolPolicyFeedback();
int idx = GetSymbolFeedbackIndex(policy.symbol_class);
SymbolPolicyFeedbackState state = g_symbol_feedback_states[idx];
if(state.sample_count < 12)
return;
double win_rate = (state.sample_count > 0) ? ((double)state.win_count / (double)state.sample_count) : 0.0;
double cont_wr = (state.continuation_count > 0) ? ((double)state.continuation_win_count / (double)state.continuation_count) : win_rate;
double counter_wr = (state.counter_count > 0) ? ((double)state.counter_win_count / (double)state.counter_count) : win_rate;
if(win_rate >= 0.58 && state.ema_margin_headroom_pct >= policy.min_free_margin_pct * 1.20)
policy.base_risk_percent *= 1.03;
else if(win_rate <= 0.45 || state.ema_margin_headroom_pct < policy.min_free_margin_pct)
policy.base_risk_percent *= 0.92;
if(win_rate >= 0.60 && state.ema_spread_points <= policy.max_spread_points * 0.75)
policy.max_spread_points *= 1.02;
else if(win_rate <= 0.45 && state.ema_spread_points >= policy.max_spread_points * 0.85)
policy.max_spread_points *= 0.95;
if(win_rate >= 0.58 && state.ema_risk_amount_pct <= policy.max_account_risk_pct * 0.75)
policy.min_stop_points = (int)MathMax(20.0, (double)policy.min_stop_points * 0.98);
else if(win_rate <= 0.45)
policy.min_stop_points = (int)MathMin(5000.0, (double)policy.min_stop_points * 1.03);
if(win_rate >= 0.58)
policy.news_sensitivity = ClampDouble(policy.news_sensitivity * 0.98, 0.25, 1.0);
else if(win_rate <= 0.45)
policy.news_sensitivity = ClampDouble(policy.news_sensitivity * 1.05, 0.25, 1.0);
if(state.continuation_count >= 6 && state.counter_count >= 6)
{
if(cont_wr > counter_wr + 0.05)
{
policy.continuation_risk_mult = ClampDouble(policy.continuation_risk_mult * 1.02, 0.75, 1.25);
policy.counter_risk_mult = ClampDouble(policy.counter_risk_mult * 0.98, 0.55, 1.05);
}
else if(counter_wr > cont_wr + 0.05)
{
policy.continuation_risk_mult = ClampDouble(policy.continuation_risk_mult * 0.98, 0.75, 1.25);
policy.counter_risk_mult = ClampDouble(policy.counter_risk_mult * 1.02, 0.55, 1.05);
}
}
policy.base_risk_percent = ClampDouble(policy.base_risk_percent, 0.10, 0.80);
policy.max_spread_points = ClampDouble(policy.max_spread_points, 5.0, 5000.0);
policy.min_stop_points = (int)ClampDouble((double)policy.min_stop_points, 10.0, 10000.0);
}
string SymbolPolicyFeedbackSummary()
{
InitSymbolPolicyFeedback();
int idx = GetSymbolFeedbackIndex(g_symbol_policy.symbol_class);
SymbolPolicyFeedbackState state = g_symbol_feedback_states[idx];
double win_rate = (state.sample_count > 0) ? ((double)state.win_count / (double)state.sample_count) * 100.0 : 0.0;
return "Adapt: " + g_symbol_policy.symbol_class +
" n=" + IntegerToString(state.sample_count) +
" wr=" + DoubleToString(win_rate, 0) + "%" +
" risk=" + DoubleToString(g_symbol_policy.base_risk_percent, 2) + "%" +
" stop=" + IntegerToString(g_symbol_policy.min_stop_points) +
" spr=" + DoubleToString(g_symbol_policy.max_spread_points, 0);
}
color SymbolPolicyFeedbackColor()
{
InitSymbolPolicyFeedback();
int idx = GetSymbolFeedbackIndex(g_symbol_policy.symbol_class);
SymbolPolicyFeedbackState state = g_symbol_feedback_states[idx];
if(state.sample_count < 12)
return clrOrange;
double win_rate = (state.sample_count > 0) ? ((double)state.win_count / (double)state.sample_count) * 100.0 : 0.0;
if(win_rate >= 60.0) return clrGold;
if(win_rate >= 52.0) return clrLime;
if(win_rate >= 45.0) return clrCyan;
return clrRed;
}
double GetBrokerMinStopPoints()
{
long stops = (long)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL);
if(stops < 0) stops = 0;
return (double)stops;
}
double GetCurrentSpreadPoints()
{
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
if(_Point <= 0.0)
return 0.0;
return (ask - bid) / _Point;
}
double ClampLotsToBroker(double lots)
{
double min_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
double max_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX);
double lot_step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
if(lot_step <= 0.0) lot_step = 0.01;
lots = ClampDouble(lots, min_lot, max_lot);
lots = MathFloor(lots / lot_step) * lot_step;
return NormalizeDouble(MathMax(min_lot, lots), 2);
}
double EstimateRiskPerLot(const bool is_buy, const double entry_price, const double stop_price)
{
double profit = 0.0;
ENUM_ORDER_TYPE order_type = is_buy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
if(!OrderCalcProfit(order_type, _Symbol, 1.0, entry_price, stop_price, profit))
return 0.0;
return MathAbs(profit);
}
bool EstimateMarginForLots(const bool is_buy, const double lots, const double entry_price, double &margin_required)
{
margin_required = 0.0;
ENUM_ORDER_TYPE order_type = is_buy ? ORDER_TYPE_BUY : ORDER_TYPE_SELL;
return OrderCalcMargin(order_type, _Symbol, lots, entry_price, margin_required);
}
double BuildSymbolPolicyRiskMultiplier(const bool continuation, const string session_name, const string session_phase)
{
double mult = 1.0;
if(g_symbol_policy.continuation_differs)
mult *= (continuation ? g_symbol_policy.continuation_risk_mult : g_symbol_policy.counter_risk_mult);
if(!SessionInPreferenceList(session_name, g_symbol_policy.preferred_sessions))
mult *= g_symbol_policy.require_session_alignment ? 0.50 : 0.80;
string phase = UpperCopy(session_phase);
if(phase == "M")
mult *= 0.80;
else if(phase == "D" && continuation)
mult *= 1.08;
else if(phase == "A" && !continuation)
mult *= 0.92;
return ClampDouble(mult, Signal_Risk_Down_Floor, Signal_Risk_Up_Cap);
}
string SymbolPolicyToJson()
{
string json = "{";
json += "\"symbol\":\"" + GWJsonEscape(g_symbol_policy.symbol_name) + "\",";
json += "\"class\":\"" + GWJsonEscape(g_symbol_policy.symbol_class) + "\",";
json += "\"base_risk_percent\":" + DoubleToString(g_symbol_policy.base_risk_percent, 2) + ",";
json += "\"max_spread_points\":" + DoubleToString(g_symbol_policy.max_spread_points, 1) + ",";
json += "\"min_stop_points\":" + IntegerToString(g_symbol_policy.min_stop_points) + ",";
json += "\"preferred_sessions\":\"" + GWJsonEscape(g_symbol_policy.preferred_sessions) + "\",";
json += "\"news_sensitivity\":" + DoubleToString(g_symbol_policy.news_sensitivity, 2) + ",";
json += "\"lot_floor\":" + DoubleToString(g_symbol_policy.lot_floor, 2) + ",";
json += "\"lot_ceiling\":" + DoubleToString(g_symbol_policy.lot_ceiling, 2) + ",";
json += "\"max_concurrent_exposure\":" + IntegerToString(g_symbol_policy.max_concurrent_exposure) + ",";
json += "\"continuation_differs\":" + BoolJson(g_symbol_policy.continuation_differs) + ",";
json += "\"continuation_risk_mult\":" + DoubleToString(g_symbol_policy.continuation_risk_mult, 2) + ",";
json += "\"counter_risk_mult\":" + DoubleToString(g_symbol_policy.counter_risk_mult, 2) + ",";
json += "\"require_session_alignment\":" + BoolJson(g_symbol_policy.require_session_alignment) + ",";
json += "\"min_free_margin_pct\":" + DoubleToString(g_symbol_policy.min_free_margin_pct, 1) + ",";
json += "\"max_account_risk_pct\":" + DoubleToString(g_symbol_policy.max_account_risk_pct, 2) + ",";
json += "\"max_margin_pct\":" + DoubleToString(g_symbol_policy.max_margin_pct, 2);
json += "}";
return json;
}
#endif