914 lines
40 KiB
MQL5
914 lines
40 KiB
MQL5
//+------------------------------------------------------------------+
|
|
//| Warrior_EA |
|
|
//| AnimateDread |
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
#include <Expert\ExpertSignal.mqh>
|
|
#include "..\System\NewBar.mqh"
|
|
#include "..\Structures\tradeRecordStructure.mqh"
|
|
#include "..\Structures\signalInfoStructure.mqh"
|
|
//--- Enumerations
|
|
#include "..\Enumerations\GlobalEnums.mqh"
|
|
//
|
|
class CExpertSignalCustom : public CExpertSignal
|
|
{
|
|
private:
|
|
bool FetchTradeRecords(string tableName, TradeRecord &tradeRecords[]);
|
|
bool ShouldDeleteOldestEntry(TradeRecord &tradeRecords[]);
|
|
void DeleteOldestEntry(string tableName);
|
|
bool CheckForDuplicateTrade(string pattern, string direction, double entryPrice, TradeRecord &tradeRecords[]);
|
|
int FindLastTradeIndex(string pattern, string direction, TradeRecord &tradeRecords[], bool &tradeOpen);
|
|
void UpdateTradeStatusAndExit(string tableName, string pattern, string direction, double entryPrice, double exitPrice, TradeRecord &tradeRecord, TradeRecord &tradeRecords[]);
|
|
void UpdateTradeRecordInDatabase(string tableName, TradeRecord &tradeRecord);
|
|
void RegisterSignal(int year, int month, int day, int DOW, int hour, int minutes, string tableName, string pattern, string direction, double entryPrice, double exitPrice, string result);
|
|
void ProcessSignal(SignalInfo &signal);
|
|
void BufferSignal(SignalInfo &signal);
|
|
bool CheckClosePosition(bool isLong, double &price);
|
|
bool CheckOpenPosition(bool isLong, double &price, double &sl, double &tp, datetime &expiration);
|
|
void BufferNewTickSignal(string filterID, string pattern, string bias, const MqlDateTime& gmtTime, double entryPrice);
|
|
SignalInfo signalBuffer[];
|
|
|
|
protected:
|
|
bool m_prohibition_signal;
|
|
bool m_useDatabase;
|
|
CiATR m_ATR; // ATR indicator
|
|
string m_id;
|
|
string m_active_pattern;
|
|
string m_active_direction;
|
|
int m_pattern_count;
|
|
double m_entry_multiplier; // Configurable multiple for ATR entry adjustment
|
|
int m_periods; // ATR periods
|
|
|
|
public:
|
|
CExpertSignalCustom(void);
|
|
~CExpertSignalCustom(void);
|
|
virtual bool AddFilter(CExpertSignal *filter);
|
|
virtual bool CheckOpenLong(double &price, double &sl, double &tp, datetime &expiration) override;
|
|
virtual bool CheckOpenShort(double &price, double &sl, double &tp, datetime &expiration) override;
|
|
virtual bool CheckCloseLong(double &price) override;
|
|
virtual bool CheckCloseShort(double &price) override;
|
|
bool OpenParams(bool isLong, double &price, double &sl, double &tp, datetime &expiration); // Added for generalized parameter calculation
|
|
virtual bool OpenLongParams(double &price, double &sl, double &tp, datetime &expiration) override;
|
|
virtual bool OpenShortParams(double &price, double &sl, double &tp, datetime &expiration) override;
|
|
virtual bool ValidationSettings(void) override;
|
|
virtual bool InitIndicators(CIndicators *indicators) override;
|
|
void Entry_Multiplier(double entry_multiplier) { m_entry_multiplier = entry_multiplier; }
|
|
void Periods(int periods) { m_periods = periods; }
|
|
virtual void ApplyPatternWeight(int patternNumber, int weight) {};
|
|
void ID(string id) { m_id = id; }
|
|
virtual string GetFilterID(void) { return m_id; };
|
|
virtual string GetActivePattern(void);
|
|
virtual string GetActiveDirection(void);
|
|
virtual int GetPatternCount(void) { return m_pattern_count; };
|
|
virtual double Direction(void) override;
|
|
bool UpdateSignalsWeights(void);
|
|
int CalculatePatternWinRate(string pattern, TradeRecord &tr[]);
|
|
int NormalizeWinRate(double winRate);
|
|
void ProcessBufferedSignals(void);
|
|
bool InRange(double value, double min, double max); // Helper function for range checking
|
|
void UseDatabase(bool value) { m_useDatabase = value; };
|
|
//--- event handler
|
|
virtual void OnTickHandler(void);
|
|
virtual void OnChartEventHandler(const int id,
|
|
const long &lparam,
|
|
const double &dparam,
|
|
const string &sparam);
|
|
};
|
|
//+------------------------------------------------------------------+
|
|
//| Constructor |
|
|
//+------------------------------------------------------------------+
|
|
CExpertSignalCustom::CExpertSignalCustom(void) :
|
|
m_id("NULL"),
|
|
m_active_pattern("NULL"),
|
|
m_active_direction("NULL"),
|
|
m_pattern_count(0),
|
|
m_entry_multiplier(0),
|
|
m_prohibition_signal(false),
|
|
m_periods(14),
|
|
m_useDatabase(false)
|
|
{
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Destructor |
|
|
//+------------------------------------------------------------------+
|
|
CExpertSignalCustom::~CExpertSignalCustom(void)
|
|
{
|
|
ArrayFree(signalBuffer);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Helper function to check value ranges |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalCustom::InRange(double value, double min, double max)
|
|
{
|
|
return value >= min && value <= max;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Validation settings protected data |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalCustom::ValidationSettings(void)
|
|
{
|
|
if(!CExpertSignal::ValidationSettings())
|
|
return false;
|
|
// Simplified checks using the InRange helper
|
|
if(!InRange(m_periods, 0, 200))
|
|
{
|
|
printf(__FUNCTION__ ": ATR Periods must be 0-200");
|
|
return false;
|
|
}
|
|
if(!InRange(StartIndex(), 0, 200))
|
|
{
|
|
printf(__FUNCTION__ ": ATR shift must be 0-200");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Create indicators |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalCustom::InitIndicators(CIndicators *indicators)
|
|
{
|
|
//--- check pointer
|
|
if(indicators == NULL)
|
|
return(false);
|
|
//---
|
|
CExpertSignal *filter;
|
|
int total = m_filters.Total();
|
|
//--- gather information about using of timeseries
|
|
for(int i = 0; i < total; i++)
|
|
{
|
|
filter = m_filters.At(i);
|
|
m_used_series |= filter.UsedSeries();
|
|
}
|
|
//--- create required timeseries
|
|
if(!CExpertBase::InitIndicators(indicators))
|
|
return(false);
|
|
//--- initialization of indicators and timeseries in the additional filters
|
|
for(int i = 0; i < total; i++)
|
|
{
|
|
filter = m_filters.At(i);
|
|
filter.SetPriceSeries(m_open, m_high, m_low, m_close);
|
|
filter.SetOtherSeries(m_spread, m_time, m_tick_volume, m_real_volume);
|
|
if(!filter.InitIndicators(indicators))
|
|
return(false);
|
|
}
|
|
if(!indicators.Add(GetPointer(m_ATR)) || !m_ATR.Create(m_symbol.Name(), m_period, m_periods) || !CExpertSignal::InitIndicators(indicators))
|
|
{
|
|
printf(__FUNCTION__ ": error initializing indicators");
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Setting an additional filter |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalCustom::AddFilter(CExpertSignal *filter)
|
|
{
|
|
if(filter == NULL)
|
|
return false;
|
|
if(!filter.Init(m_symbol, m_period, m_adjusted_point))
|
|
return false;
|
|
if(!m_filters.Add(filter))
|
|
return false;
|
|
filter.EveryTick(m_every_tick);
|
|
filter.Magic(m_magic);
|
|
CExpertSignalCustom *customFilter = dynamic_cast<CExpertSignalCustom*>(filter);
|
|
if(customFilter != NULL)
|
|
{
|
|
string filterID = customFilter.GetFilterID();
|
|
if(filterID != "NULL" && m_useDatabase)
|
|
{
|
|
int patternCount = customFilter.GetPatternCount();
|
|
for(int i = 0; i < patternCount; i++)
|
|
{
|
|
string tableNameBuy = filterID + "_" + "Pattern" + "_" + IntegerToString(i) + "_" + "Buy";
|
|
string tableNameSell = filterID + "_" + "Pattern" + "_" + IntegerToString(i) + "_" + "Sell";
|
|
dbm.CreateTable(tableNameBuy, tableschema); // Create table for Buy direction
|
|
dbm.CreateTable(tableNameSell, tableschema); // Create table for Sell direction
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Wrapper functions for buying and selling parameters |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalCustom::OpenParams(bool isLong, double &price, double &sl, double &tp, datetime &expiration)
|
|
{
|
|
int idx = StartIndex();
|
|
double atr = m_ATR.Main(idx);
|
|
if(atr <= 0.0)
|
|
return false; // ATR must be positive
|
|
if(!m_symbol.Name(_Symbol))
|
|
return false; // Symbol information must be accessible
|
|
int lookback_period = m_periods;
|
|
double base_price = (m_base_price == 0.0) ? (isLong ? m_symbol.Ask() : m_symbol.Bid()) : m_base_price;
|
|
price = base_price; // Entry price
|
|
int lowest_index = iLowest(_Symbol, PERIOD_CURRENT, MODE_LOW, lookback_period, idx);
|
|
int highest_index = iHighest(_Symbol, PERIOD_CURRENT, MODE_HIGH, lookback_period, idx);
|
|
double lowest_low = Low(lowest_index);
|
|
double highest_high = High(highest_index);
|
|
if(isLong)
|
|
{
|
|
sl = m_symbol.NormalizePrice(lowest_low - 3 * atr);
|
|
tp = m_symbol.NormalizePrice(highest_high - 0.25 * atr);
|
|
}
|
|
else
|
|
{
|
|
sl = m_symbol.NormalizePrice(highest_high + 3 * atr);
|
|
tp = m_symbol.NormalizePrice(lowest_low + 0.25 * atr);
|
|
}
|
|
// Ensure SL and TP are at least y x ATR from entry price
|
|
if(fabs(price - sl) < (2 * atr))
|
|
{
|
|
if(isLong)
|
|
{
|
|
sl = price - (2 * atr);
|
|
}
|
|
else
|
|
{
|
|
sl = price + (2 * atr);
|
|
}
|
|
}
|
|
if(fabs(tp - price) < (3 * atr))
|
|
{
|
|
if(isLong)
|
|
{
|
|
tp = price + (3 * atr);
|
|
}
|
|
else
|
|
{
|
|
tp = price - (3 * atr);
|
|
}
|
|
}
|
|
// Check if the risk-to-reward ratio is at least 1:2
|
|
double risk = fabs(price - sl);
|
|
double reward = fabs(tp - price);
|
|
if(reward < 2 * risk)
|
|
return false;
|
|
// Adjust expiration time
|
|
expiration += m_expiration * PeriodSeconds(m_period);
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Detecting the levels for buying |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalCustom::OpenLongParams(double &price, double &sl, double &tp, datetime &expiration)
|
|
{
|
|
return OpenParams(true, price, sl, tp, expiration);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Detecting the levels for selling |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalCustom::OpenShortParams(double &price, double &sl, double &tp, datetime &expiration)
|
|
{
|
|
return OpenParams(false, price, sl, tp, expiration);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Common function for closing positions |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalCustom::CheckClosePosition(bool isLong, double &price)
|
|
{
|
|
bool result = false;
|
|
//--- check of exceeding the threshold value, adjusted for long/short
|
|
double directionMultiplier = isLong ? -1 : 1;
|
|
// Allowing position closing without checking the prohibition signal.
|
|
if(directionMultiplier * m_direction >= m_threshold_close)
|
|
{
|
|
//--- there's a signal
|
|
result = true;
|
|
//--- try to get the level of closing, differentiating based on isLong
|
|
if(!(isLong ? CloseLongParams(price) : CloseShortParams(price)))
|
|
result = false;
|
|
}
|
|
//--- zeroize the base price
|
|
m_base_price = 0.0;
|
|
//--- return the result
|
|
return result;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Generating a signal for closing of a long position |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalCustom::CheckCloseLong(double &price)
|
|
{
|
|
return CheckClosePosition(true, price);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Generating a signal for closing a short position |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalCustom::CheckCloseShort(double &price)
|
|
{
|
|
return CheckClosePosition(false, price);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Common function for opening positions |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalCustom::CheckOpenPosition(bool isLong, double &price, double &sl, double &tp, datetime &expiration)
|
|
{
|
|
bool result = false;
|
|
//--- the "prohibition" signal
|
|
if(m_prohibition_signal == true)
|
|
return false;
|
|
//--- check of exceeding the threshold value, adjusted for long/short
|
|
double directionMultiplier = isLong ? 1 : -1;
|
|
if(directionMultiplier * m_direction >= m_threshold_open)
|
|
{
|
|
//--- there's a signal
|
|
result = true;
|
|
//--- try to get the levels of opening, differentiating based on isLong
|
|
if(!(isLong ? OpenLongParams(price, sl, tp, expiration) : OpenShortParams(price, sl, tp, expiration)))
|
|
result = false;
|
|
}
|
|
//--- zeroize the base price
|
|
m_base_price = 0.0;
|
|
//--- return the result
|
|
return result;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Generating a buy signal |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalCustom::CheckOpenLong(double &price, double &sl, double &tp, datetime &expiration)
|
|
{
|
|
// Check if the trading strategy allows opening long positions
|
|
if(tradingdirection == LONG_ONLY || tradingdirection == BOTH)
|
|
{
|
|
return CheckOpenPosition(true, price, sl, tp, expiration);
|
|
}
|
|
// If the strategy is SHORT_ONLY, prevent opening a long position
|
|
return false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Generating a sell signal |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalCustom::CheckOpenShort(double &price, double &sl, double &tp, datetime &expiration)
|
|
{
|
|
// Check if the trading strategy allows opening short positions
|
|
if(tradingdirection == SHORT_ONLY || tradingdirection == BOTH)
|
|
{
|
|
return CheckOpenPosition(false, price, sl, tp, expiration);
|
|
}
|
|
// If the strategy is LONG_ONLY, prevent opening a short position
|
|
return false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return the currently active pattern |
|
|
//+------------------------------------------------------------------+
|
|
string CExpertSignalCustom::GetActivePattern(void)
|
|
{
|
|
string ret = m_active_pattern;
|
|
m_active_pattern = "NULL";
|
|
return ret;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Return the currently active direction |
|
|
//+------------------------------------------------------------------+
|
|
string CExpertSignalCustom::GetActiveDirection(void)
|
|
{
|
|
string ret = m_active_direction;
|
|
m_active_direction = "NULL";
|
|
return ret;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Detecting the "weighted" direction |
|
|
//+------------------------------------------------------------------+
|
|
double CExpertSignalCustom::Direction(void)
|
|
{
|
|
static int currentSecond = -1; // Variable to store the current second
|
|
static double aggregatedResult = 0.0; // Variable to store the aggregated result
|
|
static int count = 0; // Variable to count the number of updates within the current second
|
|
static double lastResult = 0.0; // Variable to store the last result to return
|
|
MqlDateTime gmtTime;
|
|
TimeGMT(gmtTime);
|
|
// Check if the current second has changed
|
|
if(gmtTime.sec != currentSecond)
|
|
{
|
|
// If the second has changed, update the last result
|
|
if(count != 0)
|
|
{
|
|
lastResult = aggregatedResult / count;
|
|
}
|
|
else
|
|
{
|
|
lastResult = 0.0; // Default to 0 if no results were aggregated
|
|
}
|
|
// Reset variables for the new second
|
|
aggregatedResult = 0.0;
|
|
count = 0;
|
|
currentSecond = gmtTime.sec; // Update the current second
|
|
}
|
|
m_prohibition_signal = false;
|
|
double result = m_weight * (LongCondition() - ShortCondition());
|
|
int number = (result == 0.0) ? 0 : 1;
|
|
int total = m_filters.Total();
|
|
PrintVerbose("Starting direction calculation with total filters: " + IntegerToString(total));
|
|
for(int i = 0; i < total; i++)
|
|
{
|
|
long mask = ((long)1) << i;
|
|
if((m_ignore & mask) != 0)
|
|
continue;
|
|
CExpertSignalCustom *filter = m_filters.At(i);
|
|
if(filter == NULL)
|
|
{
|
|
Print("Error: Filter at index " + IntegerToString(i) + " is NULL");
|
|
continue;
|
|
}
|
|
double price = 0.0, sl = 0.0, tp = 0.0;
|
|
datetime expiration = 0;
|
|
string bias = filter.GetActiveDirection();
|
|
string filterID = filter.GetFilterID();
|
|
string pattern = filter.GetActivePattern();
|
|
if(filterID != "NULL" && bias != "NULL" && pattern != "NULL" && m_useDatabase)
|
|
{
|
|
PrintVerbose("Processing filter: " + filterID + ", Bias: " + bias + ", Pattern: " + pattern);
|
|
double newPrice = 0;
|
|
bool signalBuffered = false; // Flag to track if signal was buffered
|
|
if(bias == "Buy")
|
|
{
|
|
if(OpenLongParams(price, sl, tp, expiration))
|
|
{
|
|
newPrice = m_symbol.Ask(); // Adjust price to current ask price
|
|
signalBuffered = true; // Set flag to true as signal will be buffered
|
|
}
|
|
}
|
|
else
|
|
if(bias == "Sell")
|
|
{
|
|
if(OpenShortParams(price, sl, tp, expiration))
|
|
{
|
|
newPrice = m_symbol.Bid(); // Adjust price to current bid price
|
|
signalBuffered = true; // Set flag to true as signal will be buffered
|
|
}
|
|
}
|
|
if(signalBuffered)
|
|
{
|
|
BufferNewTickSignal(filterID, pattern, bias, gmtTime, newPrice);
|
|
}
|
|
}
|
|
double direction = filter.Direction();
|
|
if(direction == EMPTY_VALUE)
|
|
{
|
|
m_prohibition_signal = true;
|
|
continue;
|
|
}
|
|
if(direction != EMPTY_VALUE)
|
|
{
|
|
// Validate the result to be within the range of -100 to 100
|
|
if(direction < -100 || direction > 100)
|
|
{
|
|
PrintVerbose("A filter's direction is invalid. Skipping tick.");
|
|
direction = EMPTY_VALUE;
|
|
result = 0;
|
|
number = 0;
|
|
break;
|
|
}
|
|
if(direction != 0)
|
|
{
|
|
number++; // Only increment `number` if `direction` is not 0 or EMPTY_VALUE
|
|
result += ((m_invert & mask) != 0) ? -direction : direction;
|
|
}
|
|
}
|
|
}
|
|
// Aggregate the result and count
|
|
aggregatedResult += result;
|
|
count++;
|
|
// Validate the aggregated result to be within the range of -100 to 100
|
|
if(lastResult < -100 || lastResult > 100)
|
|
{
|
|
lastResult = 0.0; // Set result to 0 if it's outside the range
|
|
Print("Directional result is out of range. Setting to 0.");
|
|
}
|
|
PrintVerbose("Final directional result: " + DoubleToString(lastResult));
|
|
return lastResult;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| handles the new bar signal buffering |
|
|
//+------------------------------------------------------------------+
|
|
void CExpertSignalCustom::BufferNewTickSignal(string filterID, string pattern, string bias, const MqlDateTime& gmtTime, double entryPrice)
|
|
{
|
|
if(filterID == "NULL" || pattern == "NULL" || bias == "NULL")
|
|
{
|
|
Print("Error buffering new tick signal: Invalid filter parameters - filterID: '" + filterID +
|
|
"', pattern: '" + pattern + "', bias: '" + bias + "'.");
|
|
return;
|
|
}
|
|
string tableName = filterID + "_" + pattern + "_" + bias;
|
|
SignalInfo signal = {gmtTime.year, gmtTime.mon, gmtTime.day, gmtTime.day_of_week, gmtTime.hour, gmtTime.min, tableName, pattern, bias, entryPrice};
|
|
BufferSignal(signal);
|
|
PrintVerbose("New tick signal buffered: " + tableName + ", Pattern: " + pattern + ", Bias: " + bias + ", Entry Price: " + DoubleToString(entryPrice));
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CExpertSignalCustom::BufferSignal(SignalInfo &signal)
|
|
{
|
|
// Check for duplicate signals in the buffer
|
|
for(int i = 0; i < ArraySize(signalBuffer); i++)
|
|
{
|
|
if(signalBuffer[i].tableName == signal.tableName &&
|
|
signalBuffer[i].pattern == signal.pattern &&
|
|
signalBuffer[i].direction == signal.direction)
|
|
{
|
|
PrintVerbose("Duplicate signal detected, not adding to buffer: " + signal.tableName + ", Pattern: " + signal.pattern + ", Direction: " + signal.direction);
|
|
return; // Skip buffering if a duplicate is found
|
|
}
|
|
}
|
|
// Resize the buffer and add the new signal
|
|
ArrayResize(signalBuffer, ArraySize(signalBuffer) + 1);
|
|
signalBuffer[ArraySize(signalBuffer) - 1] = signal;
|
|
PrintVerbose("Signal buffered for: " + signal.tableName + ", Pattern: " + signal.pattern + ", Direction: " + signal.direction);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Process the signal and update trades |
|
|
//+------------------------------------------------------------------+
|
|
void CExpertSignalCustom::ProcessSignal(SignalInfo &signal)
|
|
{
|
|
string currentTableName = signal.tableName;
|
|
string oppositeTableName = currentTableName; // Start with a copy of the current table name
|
|
PrintVerbose("Processing signal for table: " + currentTableName);
|
|
// Swap the direction in the table name to get the opposite table name
|
|
if(signal.direction == "Buy")
|
|
{
|
|
StringReplace(oppositeTableName, "Buy", "Sell");
|
|
PrintVerbose("Swapped to opposite table: " + oppositeTableName + " from Buy to Sell");
|
|
}
|
|
else
|
|
{
|
|
StringReplace(oppositeTableName, "Sell", "Buy");
|
|
PrintVerbose("Swapped to opposite table: " + oppositeTableName + " from Sell to Buy");
|
|
}
|
|
// Fetch trade records for both directions
|
|
TradeRecord tradeRecordsCurrent[], tradeRecordsOpposite[];
|
|
if(!FetchTradeRecords(currentTableName, tradeRecordsCurrent))
|
|
{
|
|
Print("Failed to fetch current direction trades from: " + currentTableName);
|
|
return; // Fail to fetch current direction trades
|
|
}
|
|
if(!FetchTradeRecords(oppositeTableName, tradeRecordsOpposite))
|
|
{
|
|
Print("Failed to fetch opposite direction trades from: " + oppositeTableName);
|
|
return; // Fail to fetch opposite direction trades
|
|
}
|
|
if(ShouldDeleteOldestEntry(tradeRecordsCurrent))
|
|
DeleteOldestEntry(currentTableName);
|
|
if(ShouldDeleteOldestEntry(tradeRecordsOpposite))
|
|
DeleteOldestEntry(oppositeTableName);
|
|
// Process trades in the opposite direction to close them
|
|
bool isTradeOpen = false;
|
|
for(int i = 0; i < ArraySize(tradeRecordsOpposite); i++)
|
|
{
|
|
if(tradeRecordsOpposite[i].pattern == signal.pattern && tradeRecordsOpposite[i].result == "NA")
|
|
{
|
|
// Close the opposite trade
|
|
tradeRecordsOpposite[i].exitPrice = signal.entryPrice;
|
|
double profitLoss = (tradeRecordsOpposite[i].direction == "Buy") ?
|
|
(signal.entryPrice - tradeRecordsOpposite[i].entryPrice) :
|
|
(tradeRecordsOpposite[i].entryPrice - signal.entryPrice);
|
|
tradeRecordsOpposite[i].result = profitLoss >= 0 ? "Profit" : "Loss";
|
|
UpdateTradeRecordInDatabase(oppositeTableName, tradeRecordsOpposite[i]);
|
|
PrintVerbose("Closed opposite trade: " + oppositeTableName + ", Profit/Loss: " + DoubleToString(profitLoss));
|
|
isTradeOpen = true; // Signal that a trade was handled
|
|
break; // Since it's a stop and reverse, handle only one trade at a time
|
|
}
|
|
}
|
|
// Check for open trades or duplicate entries in the current direction
|
|
for(int i = 0; i < ArraySize(tradeRecordsCurrent); i++)
|
|
{
|
|
// Check for exact duplicates first
|
|
if(tradeRecordsCurrent[i].pattern == signal.pattern &&
|
|
tradeRecordsCurrent[i].year == signal.year &&
|
|
tradeRecordsCurrent[i].month == signal.month &&
|
|
tradeRecordsCurrent[i].day == signal.day &&
|
|
tradeRecordsCurrent[i].hour == signal.hour &&
|
|
tradeRecordsCurrent[i].minutes == signal.minutes)
|
|
{
|
|
PrintVerbose("Duplicate trade found, not registering new trade. Table: " + currentTableName);
|
|
return; // Duplicate trade found, exit processing
|
|
}
|
|
// Check for outdated or same time trades
|
|
if((tradeRecordsCurrent[i].year > signal.year) ||
|
|
(tradeRecordsCurrent[i].year == signal.year && tradeRecordsCurrent[i].month > signal.month) ||
|
|
(tradeRecordsCurrent[i].year == signal.year && tradeRecordsCurrent[i].month == signal.month && tradeRecordsCurrent[i].day > signal.day) ||
|
|
(tradeRecordsCurrent[i].year == signal.year && tradeRecordsCurrent[i].month == signal.month && tradeRecordsCurrent[i].day == signal.day && tradeRecordsCurrent[i].hour > signal.hour) ||
|
|
(tradeRecordsCurrent[i].year == signal.year && tradeRecordsCurrent[i].month == signal.month && tradeRecordsCurrent[i].day == signal.day && tradeRecordsCurrent[i].hour == signal.hour && tradeRecordsCurrent[i].minutes >= signal.minutes))
|
|
{
|
|
PrintVerbose("Outdated or same time trade found, not registering new trade. Table: " + currentTableName);
|
|
return; // Outdated or same time trade found, exit processing
|
|
}
|
|
// Check if there's an open trade with the same pattern
|
|
if(tradeRecordsCurrent[i].result == "NA" && tradeRecordsCurrent[i].pattern == signal.pattern)
|
|
{
|
|
PrintVerbose("Open trade found, not registering new trade. Table: " + currentTableName + ", Pattern: " + signal.pattern);
|
|
return; // Open trade found, exit processing
|
|
}
|
|
}
|
|
// Register a new trade if no duplicates, outdated, or open trades are found
|
|
if(!isTradeOpen)
|
|
{
|
|
RegisterSignal(signal.year, signal.month, signal.day, signal.DOW, signal.hour, signal.minutes,
|
|
currentTableName, signal.pattern, signal.direction, signal.entryPrice, 0.0, "NA");
|
|
PrintVerbose("Registered new trade in table: " + currentTableName + ", Pattern: " + signal.pattern + ", Direction: " + signal.direction);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Helper function to compare two datetime values |
|
|
//+------------------------------------------------------------------+
|
|
bool IsEarlier(const SignalInfo& a, const SignalInfo& b)
|
|
{
|
|
datetime dtA = MakeDateTime(a);
|
|
datetime dtB = MakeDateTime(b);
|
|
return dtA < dtB;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Selection sort for sorting SignalInfo array by datetime |
|
|
//+------------------------------------------------------------------+
|
|
void SelectionSort(SignalInfo &signals[], int size)
|
|
{
|
|
for(int i = 0; i < size - 1; i++)
|
|
{
|
|
int min_idx = i;
|
|
for(int j = i + 1; j < size; j++)
|
|
{
|
|
if(IsEarlier(signals[j], signals[min_idx]))
|
|
{
|
|
min_idx = j;
|
|
}
|
|
}
|
|
if(min_idx != i)
|
|
{
|
|
// Swapping the elements
|
|
SignalInfo temp = signals[i];
|
|
signals[i] = signals[min_idx];
|
|
signals[min_idx] = temp;
|
|
}
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Helper function to create a sortable datetime value |
|
|
//+------------------------------------------------------------------+
|
|
datetime MakeDateTime(const SignalInfo &signal)
|
|
{
|
|
return D'1970.01.01' + signal.year * 36524L / 100 + signal.month * 30 + signal.day + signal.hour * 3600 + signal.minutes * 60;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Process the signal and update trades |
|
|
//+------------------------------------------------------------------+
|
|
void CExpertSignalCustom::ProcessBufferedSignals()
|
|
{
|
|
// Sort the signals array by datetime before processing
|
|
SelectionSort(signalBuffer, ArraySize(signalBuffer));
|
|
if(!dbm.OpenDatabase())
|
|
{
|
|
Print("Failed to open database.");
|
|
return;
|
|
}
|
|
if(!dbm.BeginTransaction())
|
|
{
|
|
Print("Failed to begin database transaction.");
|
|
return;
|
|
}
|
|
for(int i = 0; i < ArraySize(signalBuffer); i++)
|
|
{
|
|
PrintVerbose("Processing signal " + IntegerToString(i + 1) + " of " + IntegerToString(ArraySize(signalBuffer)));
|
|
ProcessSignal(signalBuffer[i]);
|
|
}
|
|
if(!dbm.CommitTransaction())
|
|
{
|
|
Print("Failed to commit the transaction to the database.");
|
|
return;
|
|
}
|
|
ArrayResize(signalBuffer, 0);
|
|
PrintVerbose("Signal buffer cleared after processing.");
|
|
if(!IsBacktesting)
|
|
{
|
|
dbm.CloseDatabase();
|
|
PrintVerbose("Database closed successfully.");
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalCustom::FetchTradeRecords(string tableName, TradeRecord &tradeRecords[])
|
|
{
|
|
TradeRecord tradeRecordStruct;
|
|
if(!dbm.FetchTradeRecords(tableName, tradeRecordStruct, tradeRecords))
|
|
{
|
|
Print(__FUNCTION__ + " Failed to fetch trade records from " + tableName);
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalCustom::ShouldDeleteOldestEntry(TradeRecord &tradeRecords[])
|
|
{
|
|
if(ArraySize(tradeRecords) >= 1000)
|
|
{
|
|
//PrintVerbose("Deleting oldest entry from " + tableName);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CExpertSignalCustom::DeleteOldestEntry(string tableName)
|
|
{
|
|
if(!dbm.DeleteOldestEntry(tableName))
|
|
return;
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Register a signal in the database |
|
|
//+------------------------------------------------------------------+
|
|
void CExpertSignalCustom::RegisterSignal(int year, int month, int day, int DOW, int hour, int minutes, string tableName, string pattern, string direction, double entryPrice, double exitPrice, string result)
|
|
{
|
|
string Columns[] = {"year", "month", "day", "dayOfWeek", "hour", "minutes", "pattern", "direction", "entryPrice", "exitPrice", "result"};
|
|
string valArr[] = {IntegerToString(year), IntegerToString(month), IntegerToString(day), IntegerToString(DOW), IntegerToString(hour), IntegerToString(minutes), pattern, direction, DoubleToString(entryPrice, Digits()), DoubleToString(exitPrice, Digits()), result};
|
|
if(dbm.InsertTradeRecord(tableName, Columns, valArr))
|
|
{
|
|
PrintVerbose("Successfully registered signal in table: " + tableName);
|
|
}
|
|
else
|
|
{
|
|
Print("Failed to register signal in table: " + tableName);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Update a trade record in the database |
|
|
//+------------------------------------------------------------------+
|
|
void CExpertSignalCustom::UpdateTradeRecordInDatabase(string tableName, TradeRecord &tradeRecord)
|
|
{
|
|
string columns[] = { "exitPrice", "result" };
|
|
string values[] = { DoubleToString(tradeRecord.exitPrice, Digits()), tradeRecord.result };
|
|
string condition = "pattern='" + tradeRecord.pattern + "' AND direction='" + tradeRecord.direction + "' AND result='NA'";
|
|
if(dbm.UpdateTradeRecord(tableName, columns, values, condition))
|
|
{
|
|
PrintVerbose("Successfully updated trade record in table: " + tableName);
|
|
}
|
|
else
|
|
{
|
|
Print("Failed to update trade record in table: " + tableName + " for pattern " + tradeRecord.pattern + " and direction " + tradeRecord.direction);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
bool CExpertSignalCustom::UpdateSignalsWeights(void)
|
|
{
|
|
if(!dbm.BeginTransaction())
|
|
return(false);
|
|
TradeRecord tradeRecordStruct;
|
|
int total = m_filters.Total();
|
|
for(int i = 0; i < total; i++)
|
|
{
|
|
CExpertSignalCustom *filter = m_filters.At(i);
|
|
//--- check pointer
|
|
if(filter == NULL)
|
|
continue;
|
|
string filterID = filter.GetFilterID();
|
|
if(filterID == "NULL")
|
|
continue;
|
|
int patternCount = filter.GetPatternCount();
|
|
if(patternCount <= 0 || patternCount == NULL)
|
|
continue;
|
|
int totalWinRate = 0;
|
|
int validPatternCount = 0;
|
|
int lowestWinRate = 101; // Initialize to store the lowest win rate
|
|
for(int j = 0; j < patternCount; j++)
|
|
{
|
|
// Fetch trade records for the specified table
|
|
string pattern = "Pattern_" + IntegerToString(j);
|
|
string tableNameBuy = filterID + "_" + pattern + "_" + "Buy";
|
|
string tableNameSell = filterID + "_" + pattern + "_" + "Sell";
|
|
TradeRecord tradeRecordsBuy[], tradeRecordsSell[];
|
|
// Fetch Buy side trade records
|
|
if(!dbm.FetchTradeRecords(tableNameBuy, tradeRecordStruct, tradeRecordsBuy))
|
|
{
|
|
Print(__FUNCTION__ + " Failed to fetch trade records from " + tableNameBuy);
|
|
continue;
|
|
}
|
|
// Fetch Sell side trade records
|
|
if(!dbm.FetchTradeRecords(tableNameSell, tradeRecordStruct, tradeRecordsSell))
|
|
{
|
|
Print(__FUNCTION__ + " Failed to fetch trade records from " + tableNameSell);
|
|
continue;
|
|
}
|
|
int winRateBuy = CalculatePatternWinRate(pattern, tradeRecordsBuy);
|
|
int winRateSell = CalculatePatternWinRate(pattern, tradeRecordsSell);
|
|
int combinedWinRate = (winRateBuy + winRateSell) / 2; // Average the win rates from Buy and Sell sides
|
|
if(combinedWinRate > 0 && combinedWinRate <= 100)
|
|
{
|
|
filter.ApplyPatternWeight(j, combinedWinRate);
|
|
totalWinRate += combinedWinRate;
|
|
validPatternCount++;
|
|
PrintVerbose("Applied " + filterID + " " + pattern + " Weight " + IntegerToString(combinedWinRate));
|
|
}
|
|
}
|
|
// Calculate the average win rate for valid patterns
|
|
double averageWinRate = validPatternCount > 0 ? (totalWinRate) / validPatternCount : 0.0;
|
|
// Normalize the average win rate to the range 0 to 1
|
|
double normalizedWinRate = averageWinRate / 100.0;
|
|
// Round the normalized win rate to the nearest 0.05
|
|
normalizedWinRate = MathRound(normalizedWinRate * 10) / 10.0;
|
|
// Ensure the rounded value is within 0 to 1
|
|
normalizedWinRate = MathMax(0, MathMin(normalizedWinRate, 1));
|
|
// Apply the main weight based on the normalized and rounded win rate
|
|
double moduleWeight = normalizedWinRate;
|
|
if(moduleWeight > 0 && moduleWeight <= 1)
|
|
{
|
|
filter.Weight(moduleWeight);
|
|
PrintVerbose("Applied " + filterID + " Main Weight " + DoubleToString(moduleWeight, 2));
|
|
}
|
|
}
|
|
if(dbm.CommitTransaction())
|
|
return true;
|
|
else
|
|
return(false);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| Calculate the time based win rate for specified pattern |
|
|
//+------------------------------------------------------------------+
|
|
int CExpertSignalCustom::CalculatePatternWinRate(string pattern, TradeRecord & tr[])
|
|
{
|
|
int totalTrades = 0;
|
|
int profitableTrades = 0;
|
|
MqlDateTime gmtTime;
|
|
TimeGMT(gmtTime);
|
|
if(IsBacktesting)
|
|
{
|
|
for(int i = ArraySize(tr) - 1; i >= 0; i--)
|
|
{
|
|
if(tr[i].year >= gmtTime.year && tr[i].month >= gmtTime.mon && tr[i].day >= gmtTime.day && tr[i].hour >= gmtTime.hour && tr[i].minutes >= gmtTime.min)
|
|
ArrayResize(tr, ArraySize(tr) - 1);
|
|
else
|
|
break;
|
|
}
|
|
}
|
|
// Loop through trade records
|
|
for(int i = 0; i < ArraySize(tr); i++)
|
|
{
|
|
if(tr[i].pattern == pattern && tr[i].result != "NA")
|
|
{
|
|
if(tr[i].hour == gmtTime.hour && tr[i].day == gmtTime.day && tr[i].day_of_week == gmtTime.day_of_week && tr[i].month == gmtTime.mon)
|
|
{
|
|
totalTrades++; // Increment total trades
|
|
if(tr[i].result == "Profit")
|
|
profitableTrades++;
|
|
}
|
|
}
|
|
}
|
|
// Check if total trades meet the minimum requirement
|
|
if(totalTrades < 100)
|
|
return -1;
|
|
// Calculate win rate based on the selected trading style
|
|
double winRate = 0.0;
|
|
winRate = (totalTrades > 2) ? (double)profitableTrades / totalTrades * 100.0 : 0.0;
|
|
// Normalize and return win rate
|
|
return NormalizeWinRate(winRate);
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
int CExpertSignalCustom::NormalizeWinRate(double winRate)
|
|
{
|
|
return (int)MathRound(winRate / 10) * 10; // Round to the nearest 10
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CExpertSignalCustom::OnTickHandler(void)
|
|
{
|
|
int total = m_filters.Total();
|
|
for(int i = 0; i < total; i++)
|
|
{
|
|
CExpertSignalCustom *filter = m_filters.At(i);
|
|
//--- check pointer
|
|
if(filter == NULL)
|
|
continue;
|
|
string filterID = filter.GetFilterID();
|
|
if(filterID == "NULL")
|
|
continue;
|
|
filter.OnTickHandler();
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|
|
//| |
|
|
//+------------------------------------------------------------------+
|
|
void CExpertSignalCustom::OnChartEventHandler(const int id,
|
|
const long &lparam,
|
|
const double &dparam,
|
|
const string &sparam)
|
|
{
|
|
int total = m_filters.Total();
|
|
for(int i = 0; i < total; i++)
|
|
{
|
|
CExpertSignalCustom *filter = m_filters.At(i);
|
|
//--- check pointer
|
|
if(filter == NULL)
|
|
continue;
|
|
string filterID = filter.GetFilterID();
|
|
if(filterID == "NULL")
|
|
continue;
|
|
filter.OnChartEventHandler(id, lparam, dparam, sparam);
|
|
}
|
|
}
|
|
//+------------------------------------------------------------------+
|