//+------------------------------------------------------------------+ //| Warrior_EA | //| AnimateDread | //| | //+------------------------------------------------------------------+ #include #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(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); } } //+------------------------------------------------------------------+