VizionAI-Trading-EA/TradeExecution.mqh

478 righe
18 KiB
MQL5

#ifndef TRADE_EXECUTION_MQH
#define TRADE_EXECUTION_MQH
#include "AISignalConfirmation.mqh"
#include "NNDataLogger.mqh"
void UpdateLastTradeInfo(const string direction);
bool IsInCooldown()
{
datetime now = TimeCurrent();
return (now - Last_Trade_Time < Trade_Cooldown_Seconds);
}
int CountOpenTradesForSymbol()
{
int count = 0;
for(int i = PositionsTotal() - 1; i >= 0; --i)
{
ulong ticket = PositionGetTicket(i);
if(ticket == 0 || !PositionSelectByTicket(ticket))
continue;
if(PositionGetString(POSITION_SYMBOL) == _Symbol && PositionGetInteger(POSITION_MAGIC) == MagicNumber)
count++;
}
return count;
}
int GetSLPoints(const bool continuation);
int GetTPPoints(const bool continuation);
int GetEffectiveMaxExposure()
{
int exposure = Max_Open_Trades;
if(g_symbol_policy_ready && g_symbol_policy.max_concurrent_exposure > 0)
exposure = MathMin(exposure, g_symbol_policy.max_concurrent_exposure);
return MathMax(1, exposure);
}
double GetEffectiveMinStopPoints(const bool continuation)
{
int base_points = GetSLPoints(continuation);
double broker_min = GetBrokerMinStopPoints();
if(g_symbol_policy_ready)
base_points = MathMax(base_points, g_symbol_policy.min_stop_points);
base_points = (int)MathMax((double)base_points, broker_min);
return (double)base_points;
}
// ── Base gate: cooldown + position cap ──────────────────────────────────────
// News direction checks live in OpenBuy / OpenSell so each side can be gated
// independently (e.g., news is bullish → OpenBuy passes, OpenSell blocked).
bool CanOpenNewTrade()
{
if(IsInCooldown())
return false;
if(CountOpenTradesForSymbol() >= GetEffectiveMaxExposure())
return false;
return true;
}
int GetSLPoints(const bool continuation)
{
if(Symbol_Profile_Initialized)
{
if(continuation) return Symbol_SL_Points;
return MathMax(20, Symbol_SL_Points / 2);
}
return continuation ? Continuation_SL_Points : Counter_SL_Points;
}
int GetTPPoints(const bool continuation)
{
if(Symbol_Profile_Initialized)
{
if(continuation) return Symbol_TP_Points;
return MathMax(80, (int)((double)Symbol_TP_Points * 0.85));
}
return continuation ? Continuation_TP_Points : Counter_TP_Points;
}
double BuildSignalRiskMultiplier(const bool is_buy, const bool continuation)
{
double mult = 1.0;
if(Active_Praise_Signals >= 4)
mult += 0.15;
else if(Active_Praise_Signals == 3)
mult += 0.10;
if(Active_Warnings >= 3)
mult -= 0.15;
if(Use_Signal_Coordinator)
mult *= ClampDouble(Coordinator_Lot_Multiplier, 0.80, 1.25);
mult *= BuildSymbolPolicyRiskMultiplier(continuation, GW_Session_Name, GW_Session_Phase);
if(News_Bias_Direction != 0)
{
bool aligned = (is_buy && News_Bias_Direction > 0) || (!is_buy && News_Bias_Direction < 0);
double news_factor = 1.0;
if(aligned)
news_factor += MathMin(0.20, (News_Bias_Strength / 100.0) * g_symbol_policy.news_sensitivity * 0.20);
else
news_factor -= MathMin(0.35, (News_Bias_Strength / 100.0) * g_symbol_policy.news_sensitivity * 0.35);
if(News_Trade_Block_Active)
news_factor *= 0.85;
if(News_Trade_Allowed_Direction == 1 && !is_buy)
news_factor *= 0.60;
if(News_Trade_Allowed_Direction == -1 && is_buy)
news_factor *= 0.60;
mult *= news_factor;
}
else if(News_Bias_Strength >= News_Soft_Reduce_Strength)
{
double neutral_factor = 1.0 - MathMin(0.25, (News_Bias_Strength / 100.0) * g_symbol_policy.news_sensitivity * 0.25);
mult *= MathMax(News_Soft_Bias_Floor, neutral_factor);
}
if(NN_IsUsable())
{
if((NN_Bias > 0 && is_buy) || (NN_Bias < 0 && !is_buy))
mult += 0.10;
else if(NN_Bias != 0)
mult -= 0.10;
}
if(Active_Praise_Signals >= 4 && continuation)
mult += 0.08;
if(Active_Warnings >= 4)
mult -= 0.10;
return ClampDouble(mult, Signal_Risk_Down_Floor, Signal_Risk_Up_Cap);
}
double ComputeRiskBasedLots(const bool is_buy,
const bool continuation,
const double entry_price,
const double stop_price,
double &risk_amount,
double &margin_required)
{
risk_amount = 0.0;
margin_required = 0.0;
if(!g_symbol_policy_ready)
ResolveSymbolPolicy();
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
g_symbol_balance = balance;
g_symbol_equity = equity;
g_symbol_free_margin = free_margin;
double base_risk_pct = g_symbol_policy_ready ? g_symbol_policy.base_risk_percent : 0.50;
double max_risk_pct = g_symbol_policy_ready ? g_symbol_policy.max_account_risk_pct : Max_Account_Risk_Pct;
double risk_budget = equity * (MathMin(base_risk_pct, max_risk_pct) / 100.0);
double signal_mult = BuildSignalRiskMultiplier(is_buy, continuation);
risk_budget *= signal_mult;
risk_budget = MathMin(risk_budget, equity * (Max_Account_Risk_Pct / 100.0));
risk_budget = MathMin(risk_budget, equity * (g_symbol_policy.max_account_risk_pct / 100.0));
risk_amount = risk_budget;
double risk_per_lot = EstimateRiskPerLot(is_buy, entry_price, stop_price);
if(risk_per_lot <= 0.0)
return 0.0;
double lots = risk_budget / risk_per_lot;
if(g_symbol_policy_ready)
lots = ClampDouble(lots, g_symbol_policy.lot_floor, g_symbol_policy.lot_ceiling);
lots = ClampLotsToBroker(lots);
if(lots <= 0.0)
return 0.0;
double max_margin_pct = g_symbol_policy_ready ? g_symbol_policy.max_margin_pct : Max_Trade_Margin_Pct;
double min_free_margin_pct = g_symbol_policy_ready ? g_symbol_policy.min_free_margin_pct : Min_Free_Margin_Pct;
double min_free_margin = equity * (min_free_margin_pct / 100.0);
double max_margin_allowed = equity * (max_margin_pct / 100.0);
double lot_step = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP);
double min_lot = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN);
if(lot_step <= 0.0) lot_step = 0.01;
while(lots >= min_lot)
{
if(!EstimateMarginForLots(is_buy, lots, entry_price, margin_required))
return 0.0;
if(margin_required <= max_margin_allowed &&
(free_margin - margin_required) >= min_free_margin)
break;
lots = lots - lot_step;
lots = ClampLotsToBroker(lots);
if(lots < min_lot)
return 0.0;
}
risk_amount = risk_per_lot * lots;
g_symbol_margin_required = margin_required;
g_symbol_risk_amount = risk_amount;
g_symbol_intended_lots = lots;
g_symbol_final_lots = lots;
if(lots < min_lot)
return 0.0;
return lots;
}
void LogRejectedTrade(const bool is_buy,
const string setup,
const string reason,
const double entry_price,
const bool continuation,
const double lots,
const double spread_points,
const double free_margin,
const double equity,
const double balance,
const double margin_required,
const double risk_amount)
{
if(!NN_LogData)
return;
NN_LogRejectedDecision(is_buy, setup, reason, lots, entry_price,
GW_Session_Name, GW_Session_Phase, g_symbol_policy.symbol_class,
Coordinator_Cluster_Strength, Coordinator_Conflict_Score,
continuation ? 0 : 1,
spread_points, free_margin, equity, balance, margin_required, risk_amount);
}
bool OpenBuy(const string reason, double lot_multiplier = 1.0)
{
if(!CanOpenNewTrade())
return false;
if(!g_symbol_policy_ready)
ResolveSymbolPolicy();
if(g_symbol_policy.require_session_alignment &&
!SessionInPreferenceList(GW_Session_Name, g_symbol_policy.preferred_sessions))
{
g_symbol_reject_reason = "session not preferred";
LogRejectedTrade(true, reason, g_symbol_reject_reason, SymbolInfoDouble(_Symbol, SYMBOL_ASK),
(Current_Mode == MODE_TRENDING && Current_State == STATE_CONTINUATION),
0.0, GetCurrentSpreadPoints(), AccountInfoDouble(ACCOUNT_MARGIN_FREE),
AccountInfoDouble(ACCOUNT_EQUITY), AccountInfoDouble(ACCOUNT_BALANCE), 0.0, 0.0);
return false;
}
double ai_mult = 1.0;
bool continuation = (Current_Mode == MODE_TRENDING && Current_State == STATE_CONTINUATION);
double entry = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
double sl_pts = GetEffectiveMinStopPoints(continuation);
int tp_pts = GetTPPoints(continuation);
double sl = NormalizePrice(entry - (sl_pts * _Point));
double tp = NormalizePrice(entry + (tp_pts * _Point));
double risk_amount = 0.0;
double margin_required = 0.0;
double lots = ComputeRiskBasedLots(true, continuation, entry, sl, risk_amount, margin_required);
if(lots <= 0.0)
{
g_symbol_reject_reason = "risk sizing or margin gate";
LogRejectedTrade(true, reason, g_symbol_reject_reason, entry, continuation,
0.0, GetCurrentSpreadPoints(), g_symbol_free_margin,
g_symbol_equity, g_symbol_balance, margin_required, risk_amount);
return false;
}
if(!AIApproveTrade(true, reason, lots, GetCurrentSpreadPoints(), risk_amount, margin_required, g_symbol_free_margin, GW_Session_Phase, ai_mult))
{
g_symbol_reject_reason = "ai rejection";
LogRejectedTrade(true, reason, g_symbol_reject_reason, entry, continuation,
lots, GetCurrentSpreadPoints(), g_symbol_free_margin,
g_symbol_equity, g_symbol_balance, margin_required, risk_amount);
return false;
}
double setup_mult = ClampDouble(lot_multiplier, 0.75, 1.25);
lots = ClampLotsToBroker(lots * setup_mult * ai_mult);
if(g_symbol_policy_ready)
lots = ClampDouble(lots, g_symbol_policy.lot_floor, g_symbol_policy.lot_ceiling);
double post_margin = 0.0;
if(!EstimateMarginForLots(true, lots, entry, post_margin))
{
g_symbol_reject_reason = "margin estimate failed";
LogRejectedTrade(true, reason, g_symbol_reject_reason, entry, continuation,
lots, GetCurrentSpreadPoints(), g_symbol_free_margin,
g_symbol_equity, g_symbol_balance, margin_required, risk_amount);
return false;
}
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
if(post_margin > equity * (g_symbol_policy.max_margin_pct / 100.0) ||
(free_margin - post_margin) < (equity * (g_symbol_policy.min_free_margin_pct / 100.0)))
{
g_symbol_reject_reason = "post-ai margin gate";
LogRejectedTrade(true, reason, g_symbol_reject_reason, entry, continuation,
lots, GetCurrentSpreadPoints(), free_margin,
equity, AccountInfoDouble(ACCOUNT_BALANCE), post_margin, risk_amount);
return false;
}
margin_required = post_margin;
risk_amount = EstimateRiskPerLot(true, entry, sl) * lots;
g_symbol_margin_required = margin_required;
g_symbol_risk_amount = risk_amount;
g_symbol_final_lots = lots;
bool ok = Trade.Buy(lots, _Symbol, 0.0, sl, tp, reason);
if(ok)
{
Last_Trade_Time = TimeCurrent();
UpdateLastTradeInfo("bullish");
TodayTrades++;
BuyTrades++;
// Capture feature snapshot for NN training (labeled when trade closes via NN_LabelFromDeal)
if(NN_LogData)
{
ulong d = Trade.ResultDeal();
if(d > 0 && HistoryDealSelect(d))
{
ulong pos_id = (ulong)HistoryDealGetInteger(d, DEAL_POSITION_ID);
int cat = (Current_State == STATE_CONTINUATION) ? 0 : 1;
NN_CaptureEntry(pos_id, true, reason, entry,
GW_Session_Name, GW_Session_Phase, g_symbol_policy.symbol_class,
Coordinator_Cluster_Strength, Coordinator_Conflict_Score, cat,
GetCurrentSpreadPoints(), g_symbol_free_margin, g_symbol_equity,
g_symbol_balance, margin_required, risk_amount);
}
}
}
else
{
g_symbol_reject_reason = "trade buy failed";
LogRejectedTrade(true, reason, g_symbol_reject_reason, entry, continuation,
lots, GetCurrentSpreadPoints(), g_symbol_free_margin,
g_symbol_equity, g_symbol_balance, margin_required, risk_amount);
}
return ok;
}
bool OpenSell(const string reason, double lot_multiplier = 1.0)
{
if(!CanOpenNewTrade())
return false;
if(!g_symbol_policy_ready)
ResolveSymbolPolicy();
if(g_symbol_policy.require_session_alignment &&
!SessionInPreferenceList(GW_Session_Name, g_symbol_policy.preferred_sessions))
{
g_symbol_reject_reason = "session not preferred";
LogRejectedTrade(false, reason, g_symbol_reject_reason, SymbolInfoDouble(_Symbol, SYMBOL_BID),
(Current_Mode == MODE_TRENDING && Current_State == STATE_CONTINUATION),
0.0, GetCurrentSpreadPoints(), AccountInfoDouble(ACCOUNT_MARGIN_FREE),
AccountInfoDouble(ACCOUNT_EQUITY), AccountInfoDouble(ACCOUNT_BALANCE), 0.0, 0.0);
return false;
}
double ai_mult = 1.0;
bool continuation = (Current_Mode == MODE_TRENDING && Current_State == STATE_CONTINUATION);
double entry = SymbolInfoDouble(_Symbol, SYMBOL_BID);
double sl_pts = GetEffectiveMinStopPoints(continuation);
int tp_pts = GetTPPoints(continuation);
double sl = NormalizePrice(entry + (sl_pts * _Point));
double tp = NormalizePrice(entry - (tp_pts * _Point));
double risk_amount = 0.0;
double margin_required = 0.0;
double lots = ComputeRiskBasedLots(false, continuation, entry, sl, risk_amount, margin_required);
if(lots <= 0.0)
{
g_symbol_reject_reason = "risk sizing or margin gate";
LogRejectedTrade(false, reason, g_symbol_reject_reason, entry, continuation,
0.0, GetCurrentSpreadPoints(), g_symbol_free_margin,
g_symbol_equity, g_symbol_balance, margin_required, risk_amount);
return false;
}
if(!AIApproveTrade(false, reason, lots, GetCurrentSpreadPoints(), risk_amount, margin_required, g_symbol_free_margin, GW_Session_Phase, ai_mult))
{
g_symbol_reject_reason = "ai rejection";
LogRejectedTrade(false, reason, g_symbol_reject_reason, entry, continuation,
lots, GetCurrentSpreadPoints(), g_symbol_free_margin,
g_symbol_equity, g_symbol_balance, margin_required, risk_amount);
return false;
}
double setup_mult = ClampDouble(lot_multiplier, 0.75, 1.25);
lots = ClampLotsToBroker(lots * setup_mult * ai_mult);
if(g_symbol_policy_ready)
lots = ClampDouble(lots, g_symbol_policy.lot_floor, g_symbol_policy.lot_ceiling);
double post_margin = 0.0;
if(!EstimateMarginForLots(false, lots, entry, post_margin))
{
g_symbol_reject_reason = "margin estimate failed";
LogRejectedTrade(false, reason, g_symbol_reject_reason, entry, continuation,
lots, GetCurrentSpreadPoints(), g_symbol_free_margin,
g_symbol_equity, g_symbol_balance, margin_required, risk_amount);
return false;
}
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
if(post_margin > equity * (g_symbol_policy.max_margin_pct / 100.0) ||
(free_margin - post_margin) < (equity * (g_symbol_policy.min_free_margin_pct / 100.0)))
{
g_symbol_reject_reason = "post-ai margin gate";
LogRejectedTrade(false, reason, g_symbol_reject_reason, entry, continuation,
lots, GetCurrentSpreadPoints(), free_margin,
equity, AccountInfoDouble(ACCOUNT_BALANCE), post_margin, risk_amount);
return false;
}
margin_required = post_margin;
risk_amount = EstimateRiskPerLot(false, entry, sl) * lots;
g_symbol_margin_required = margin_required;
g_symbol_risk_amount = risk_amount;
g_symbol_final_lots = lots;
bool ok = Trade.Sell(lots, _Symbol, 0.0, sl, tp, reason);
if(ok)
{
Last_Trade_Time = TimeCurrent();
UpdateLastTradeInfo("bearish");
TodayTrades++;
SellTrades++;
// Capture feature snapshot for NN training (labeled when trade closes via NN_LabelFromDeal)
if(NN_LogData)
{
ulong d = Trade.ResultDeal();
if(d > 0 && HistoryDealSelect(d))
{
ulong pos_id = (ulong)HistoryDealGetInteger(d, DEAL_POSITION_ID);
int cat = (Current_State == STATE_CONTINUATION) ? 0 : 1;
NN_CaptureEntry(pos_id, false, reason, entry,
GW_Session_Name, GW_Session_Phase, g_symbol_policy.symbol_class,
Coordinator_Cluster_Strength, Coordinator_Conflict_Score, cat,
GetCurrentSpreadPoints(), g_symbol_free_margin, g_symbol_equity,
g_symbol_balance, margin_required, risk_amount);
}
}
}
else
{
g_symbol_reject_reason = "trade sell failed";
LogRejectedTrade(false, reason, g_symbol_reject_reason, entry, continuation,
lots, GetCurrentSpreadPoints(), g_symbol_free_margin,
g_symbol_equity, g_symbol_balance, margin_required, risk_amount);
}
return ok;
}
#endif