#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