Vizion-Trading-EA/SignalCoordinator.mqh

404 lines
13 KiB
MQL5
Raw Permalink Normal View History

#ifndef SIGNAL_COORDINATOR_MQH
#define SIGNAL_COORDINATOR_MQH
#include "CandlePatterns.mqh"
#include "NNDataLogger.mqh"
struct CoordinatedSignal
{
string source;
string action;
string direction;
string signal_type;
double strength;
string location;
datetime timestamp;
double price;
bool at_key_level;
};
struct SignalCluster
{
CoordinatedSignal signals[20];
int count;
string consensus_direction;
string consensus_type;
double combined_strength;
double conflict_score;
datetime cluster_time;
};
CoordinatedSignal SignalHistory[100];
int SignalHistoryCount = 0;
SignalCluster CurrentCluster;
void InitializeSignalCoordinator()
{
SignalHistoryCount = 0;
CurrentCluster.count = 0;
CurrentCluster.consensus_direction = "neutral";
CurrentCluster.consensus_type = "";
CurrentCluster.combined_strength = 0.0;
CurrentCluster.conflict_score = 100.0;
BullWarnCount = 0;
BullPraiseCount = 0;
BearWarnCount = 0;
BearPraiseCount = 0;
Coordinator_Bias_Direction = 0;
Coordinator_Cluster_Strength = 0.0;
Coordinator_Conflict_Score = 100.0;
Coordinator_Lot_Multiplier = 1.0;
Coordinator_AllowTrade = false;
}
void ResetSignalCluster()
{
CurrentCluster.count = 0;
CurrentCluster.consensus_direction = "neutral";
CurrentCluster.consensus_type = "";
CurrentCluster.combined_strength = 0.0;
CurrentCluster.conflict_score = 0.0;
}
bool HasConflict(CoordinatedSignal &new_signal)
{
datetime now = TimeCurrent();
int lookback = MathMin(SignalHistoryCount, 20);
for(int i = SignalHistoryCount - 1; i >= SignalHistoryCount - lookback && i >= 0; --i)
{
if(now - SignalHistory[i].timestamp > Conflict_Prevention_Seconds)
break;
if(SignalHistory[i].source == new_signal.source && now - SignalHistory[i].timestamp < 60)
return true;
if(SignalHistory[i].direction != new_signal.direction &&
SignalHistory[i].strength >= 70.0 && new_signal.strength >= 70.0)
return true;
}
return false;
}
void UpdateBalanceCounters(const CoordinatedSignal &signal)
{
if(signal.direction == "bullish" && signal.signal_type == "WARN") BullWarnCount++;
if(signal.direction == "bullish" && signal.signal_type == "PRAISE") BullPraiseCount++;
if(signal.direction == "bearish" && signal.signal_type == "WARN") BearWarnCount++;
if(signal.direction == "bearish" && signal.signal_type == "PRAISE") BearPraiseCount++;
}
bool AddCoordinatedSignal(const string source,
const string action,
const string direction,
const string signal_type,
const double strength,
const string location,
const double price,
const bool at_key_level)
{
CoordinatedSignal signal;
signal.source = source;
signal.action = action;
signal.direction = direction;
signal.signal_type = signal_type;
signal.strength = strength;
signal.location = location;
signal.timestamp = TimeCurrent();
signal.price = price;
signal.at_key_level = at_key_level;
if(HasConflict(signal))
return false;
if(SignalHistoryCount < ArraySize(SignalHistory))
SignalHistory[SignalHistoryCount++] = signal;
if(CurrentCluster.count < ArraySize(CurrentCluster.signals))
CurrentCluster.signals[CurrentCluster.count++] = signal;
UpdateBalanceCounters(signal);
return true;
}
void GenerateMASignals()
{
double p = MidPrice();
if(p > MA_Current[2]) AddCoordinatedSignal("ma", "reclaim", "bullish", "PRAISE", 65, "ma21", p, false);
if(p < MA_Current[2]) AddCoordinatedSignal("ma", "break", "bearish", "PRAISE", 65, "ma21", p, false);
if(NearLevel(p, MA_Current[3], MA_Touch_Buffer))
{
if(iClose(_Symbol, PERIOD_CURRENT, 0) > MA_Current[3])
AddCoordinatedSignal("ma", "reclaim", "bullish", "WARN", 70, "ma50", MA_Current[3], true);
else
AddCoordinatedSignal("ma", "reject", "bearish", "WARN", 70, "ma50", MA_Current[3], true);
}
if(p > MA_Current[4]) AddCoordinatedSignal("ma", "reclaim", "bullish", "PRAISE", 75, "ma140", p, true);
if(p < MA_Current[4]) AddCoordinatedSignal("ma", "break", "bearish", "PRAISE", 75, "ma140", p, true);
}
void GenerateMFIBSignals()
{
double p = MidPrice();
static double prev = 0.0;
if(prev == 0.0) prev = p;
double lv[4] = {MFIB_Level_236, MFIB_Level_382, MFIB_Level_618, MFIB_Level_786};
string nm[4] = {"mfib236","mfib382","mfib618","mfib786"};
for(int i = 0; i < 4; ++i)
{
if(!NearLevel(p, lv[i], 50)) continue;
bool support = (i < 2);
if(support)
{
if(prev < lv[i] && p >= lv[i]) AddCoordinatedSignal("mfib", "reclaim", "bullish", "PRAISE", 85, nm[i], lv[i], true);
else AddCoordinatedSignal("mfib", "reject", "bullish", "WARN", 90, nm[i], lv[i], true);
}
else
{
if(prev > lv[i] && p <= lv[i]) AddCoordinatedSignal("mfib", "break", "bearish", "PRAISE", 85, nm[i], lv[i], true);
else AddCoordinatedSignal("mfib", "reject", "bearish", "WARN", 90, nm[i], lv[i], true);
}
}
prev = p;
}
void GenerateFIBSignals()
{
double p = MidPrice();
static double prev = 0.0;
if(prev == 0.0) prev = p;
if(NearLevel(p, PriceLevels[1], 50))
{
if(prev < PriceLevels[1] && p >= PriceLevels[1])
AddCoordinatedSignal("fib", "reclaim", "bullish", "PRAISE", 75, "fib236", PriceLevels[1], true);
else
AddCoordinatedSignal("fib", "reject", "bullish", "WARN", 80, "fib236", PriceLevels[1], true);
}
if(NearLevel(p, PriceLevels[4], 50))
{
if(prev > PriceLevels[4] && p <= PriceLevels[4])
AddCoordinatedSignal("fib", "break", "bearish", "PRAISE", 75, "fib618", PriceLevels[4], true);
else
AddCoordinatedSignal("fib", "reject", "bearish", "WARN", 80, "fib618", PriceLevels[4], true);
}
prev = p;
}
void GenerateStochSignals()
{
if(Stoch_K_Current < Stoch_Oversold)
AddCoordinatedSignal("stoch", "reject", "bullish", "WARN", 70, "oversold", Stoch_K_Current, false);
if(Stoch_K_Current > Stoch_Overbought)
AddCoordinatedSignal("stoch", "reject", "bearish", "WARN", 70, "overbought", Stoch_K_Current, false);
if(Stoch_K_Current > Stoch_D_Current && Stoch_K_Current > 30 && Stoch_K_Current < 50)
AddCoordinatedSignal("stoch", "reclaim", "bullish", "PRAISE", 65, "mid", Stoch_K_Current, false);
if(Stoch_K_Current < Stoch_D_Current && Stoch_K_Current < 70 && Stoch_K_Current > 50)
AddCoordinatedSignal("stoch", "break", "bearish", "PRAISE", 65, "mid", Stoch_K_Current, false);
}
void GenerateCandleSignals()
{
for(int i = 0; i < g_PatternCount; ++i)
{
string direction = "";
string action = "";
string type = "WARN";
if(g_ActivePatterns[i].type == PATTERN_REVERSAL_BULLISH) { direction = "bullish"; action = "reject"; type = "WARN"; }
if(g_ActivePatterns[i].type == PATTERN_REVERSAL_BEARISH) { direction = "bearish"; action = "reject"; type = "WARN"; }
if(g_ActivePatterns[i].type == PATTERN_CONTINUATION_BULL) { direction = "bullish"; action = "reclaim"; type = "PRAISE"; }
if(g_ActivePatterns[i].type == PATTERN_CONTINUATION_BEAR) { direction = "bearish"; action = "break"; type = "PRAISE"; }
if(direction != "")
AddCoordinatedSignal("candle", action, direction, type,
g_ActivePatterns[i].adjusted_strength,
g_ActivePatterns[i].name_str,
g_ActivePatterns[i].pattern_price,
false);
}
}
void BuildSignalCluster()
{
if(CurrentCluster.count == 0)
{
CurrentCluster.consensus_direction = "neutral";
CurrentCluster.consensus_type = "";
CurrentCluster.combined_strength = 0;
CurrentCluster.conflict_score = 100;
return;
}
int bull = 0, bear = 0, warn = 0, praise = 0;
double total = 0.0;
for(int i = 0; i < CurrentCluster.count; ++i)
{
if(CurrentCluster.signals[i].direction == "bullish") bull++;
if(CurrentCluster.signals[i].direction == "bearish") bear++;
if(CurrentCluster.signals[i].signal_type == "WARN") warn++;
if(CurrentCluster.signals[i].signal_type == "PRAISE") praise++;
total += CurrentCluster.signals[i].strength;
}
double bull_pct = (double)bull / (double)CurrentCluster.count;
double bear_pct = (double)bear / (double)CurrentCluster.count;
if(bull_pct >= Signal_Consensus_Threshold) CurrentCluster.consensus_direction = "bullish";
else if(bear_pct >= Signal_Consensus_Threshold) CurrentCluster.consensus_direction = "bearish";
else CurrentCluster.consensus_direction = "neutral";
CurrentCluster.consensus_type = (warn > praise) ? "WARN" : "PRAISE";
CurrentCluster.combined_strength = total / (double)CurrentCluster.count;
int min_dir = MathMin(bull, bear);
int min_type = MathMin(warn, praise);
double dir_conflict = (double)min_dir / (double)CurrentCluster.count;
double type_conflict = (double)min_type / (double)CurrentCluster.count;
CurrentCluster.conflict_score = ((dir_conflict * 0.7) + (type_conflict * 0.3)) * 100.0;
CurrentCluster.cluster_time = TimeCurrent();
}
bool ShouldTradeFromCluster(SignalCluster &cluster, double &multiplier)
{
if(News_Trade_Block_Active)
return false;
if(cluster.conflict_score > Max_Cluster_Conflict)
return false;
if(cluster.consensus_direction == "neutral")
return false;
if(cluster.combined_strength < Min_Cluster_Strength)
return false;
if(TimeCurrent() - Last_Trade_Time < Conflict_Prevention_Seconds &&
LastTradeDirection == cluster.consensus_direction)
return false;
int state_dir = Current_Bias_Direction;
if(state_dir > 0 && cluster.consensus_direction == "bearish") return false;
if(state_dir < 0 && cluster.consensus_direction == "bullish") return false;
if(News_Bias_Direction > 0 && cluster.consensus_direction == "bearish")
return false;
if(News_Bias_Direction < 0 && cluster.consensus_direction == "bullish")
return false;
multiplier = 1.0;
if(cluster.combined_strength >= 85.0) multiplier += 0.3;
else if(cluster.combined_strength >= 75.0) multiplier += 0.2;
else if(cluster.combined_strength >= 65.0) multiplier += 0.1;
if(cluster.conflict_score > 30.0) multiplier -= 0.2;
else if(cluster.conflict_score > 20.0) multiplier -= 0.1;
bool nn_usable = Use_NN_In_Coordinator && NN_Initialized && NN_Confidence >= NN_MinConfidenceToUse && NN_RiskScore <= NN_MaxRiskToUse;
if(nn_usable)
{
if((NN_Bias > 0 && cluster.consensus_direction == "bullish") || (NN_Bias < 0 && cluster.consensus_direction == "bearish"))
multiplier += 0.15;
else if(NN_Bias != 0)
multiplier -= 0.20;
}
multiplier = MathMax(0.5, MathMin(2.0, multiplier));
return true;
}
void CleanupOldSignals()
{
datetime cutoff = TimeCurrent() - (Conflict_Prevention_Seconds * 2);
int new_count = 0;
for(int i = 0; i < SignalHistoryCount; ++i)
{
if(SignalHistory[i].timestamp > cutoff)
{
if(new_count != i)
SignalHistory[new_count] = SignalHistory[i];
new_count++;
}
}
SignalHistoryCount = new_count;
}
void UpdateLastTradeInfo(const string direction)
{
Last_Trade_Time = TimeCurrent();
LastTradeDirection = direction;
}
void CoordinateAllSignals()
{
if(!Use_Signal_Coordinator)
{
Coordinator_AllowTrade = true;
Coordinator_Bias_Direction = Current_Bias_Direction;
Coordinator_Cluster_Strength = 65.0;
Coordinator_Conflict_Score = 0.0;
Coordinator_Lot_Multiplier = 1.0;
return;
}
ResetSignalCluster();
GenerateMASignals();
GenerateMFIBSignals();
GenerateFIBSignals();
GenerateStochSignals();
GenerateCandleSignals();
BuildSignalCluster();
double mult = 1.0;
Coordinator_AllowTrade = ShouldTradeFromCluster(CurrentCluster, mult);
Coordinator_Cluster_Strength = CurrentCluster.combined_strength;
Coordinator_Conflict_Score = CurrentCluster.conflict_score;
Coordinator_Lot_Multiplier = mult;
if(CurrentCluster.consensus_direction == "bullish") Coordinator_Bias_Direction = 1;
else if(CurrentCluster.consensus_direction == "bearish") Coordinator_Bias_Direction = -1;
else Coordinator_Bias_Direction = 0;
// If the cluster is trade-ready, log a training sample for the NN dataset.
if(Coordinator_AllowTrade && NN_LogData)
{
bool is_buy = (Coordinator_Bias_Direction > 0);
// Category heuristic for labeling: continuation when trending+continuation else counter.
int category = 1;
if(Current_Mode == MODE_TRENDING && Current_State == STATE_CONTINUATION)
category = 0;
NN_LogSignalReadySample(is_buy, category, Coordinator_Cluster_Strength, Coordinator_Conflict_Score);
}
static int cleanup_counter = 0;
cleanup_counter++;
if(cleanup_counter >= 100)
{
CleanupOldSignals();
cleanup_counter = 0;
}
}
#endif