diff --git a/Utils/MarketDataUtils.mqh b/Utils/MarketDataUtils.mqh index c7e8b4e..57f39fb 100644 --- a/Utils/MarketDataUtils.mqh +++ b/Utils/MarketDataUtils.mqh @@ -31,17 +31,6 @@ bool MarketDataUtils::is_new_bar(string symbol, ENUM_TIMEFRAMES time_frame, stri return false; // No new bar } -// Retrieves the latest value from an indicator buffer (shift 0) -double MarketDataUtils::get_latest_buffer_value(int handle) { - double val[]; - ArraySetAsSeries(val, true); // Aligns array with bar indexing (0 = latest) - - if (CopyBuffer(handle, 0, 0, 1, val) == 1) - return val[0]; // Latest value at shift 0 - - return 0.0; -} - // shift = 0 refers to the live candle (still forming) // shift = 1 is the most recently closed candle // shift = 2 is the one before that, etc. diff --git a/Utils/MultiSymbolSignalTracker.mqh b/Utils/MultiSymbolSignalTracker.mqh new file mode 100644 index 0000000..17d35b3 --- /dev/null +++ b/Utils/MultiSymbolSignalTracker.mqh @@ -0,0 +1,86 @@ +#include + +// This class manages a collection of SignalStateTracker instances — one per symbol. +// It allows you to track signals separately for each symbol in a multi-symbol EA. +class MultiSymbolSignalTracker { + private: + // Struct to hold one (symbol → tracker) mapping + struct SymbolTracker { + string symbol; // Symbol name (e.g. "EURUSD", "HSI") + SignalStateTracker* tracker; // Pointer to the signal tracker for that symbol + }; + + SymbolTracker trackers[]; // Dynamic array of symbol-tracker pairs + + // Helper function: Find the index of the given symbol in the array + int find_index(const string& symbol) { + for (int i = 0; i < ArraySize(trackers); i++) { + if (trackers[i].symbol == symbol) return i; // Found symbol, return its index + } + return -1; // Not found + } + + public: + // Destructor: automatically called when this object is destroyed (e.g. at EA shutdown) + ~MultiSymbolSignalTracker() { + clear(); // Clean up memory when done + } + + // Returns the SignalStateTracker for the given symbol. + // If it doesn't exist yet, it creates one and stores it. + SignalStateTracker* get_tracker(const string& symbol) { + int idx = find_index(symbol); + if (idx != -1) return trackers[idx].tracker; // Tracker already exists — return it + + // If not found, create a new tracker for this symbol + SignalStateTracker* tracker = new SignalStateTracker(); + + SymbolTracker item; + item.symbol = symbol; + item.tracker = tracker; + + // Add new item to dynamic array + ArrayResize(trackers, ArraySize(trackers) + 1); + trackers[ArraySize(trackers) - 1] = item; + + return tracker; + } + + // Deletes all trackers and resets the array. + // Should be called in OnDeinit() to free memory. + void clear() { + for (int i = 0; i < ArraySize(trackers); i++) { + delete trackers[i].tracker; // Manually free each dynamically created tracker + } + ArrayResize(trackers, 0); // Reset array to empty + } +}; + + +/* --------------------------------------------------------------------------- +Example usage in an EA: +--------------------------------------------------------------------------- + +// Declare the tracker globally (outside OnTick/OnTimer) +MultiSymbolSignalTracker track_trigger; + +// Inside your strategy() or OnTick(): +void strategy(string symbol, ...) { + bool trig_long = ...; // Your signal logic + bool trig_short = ...; + + // Update the signal tracker for this symbol + track_trigger.get_tracker(symbol).update_signal(trig_long, trig_short); + + // Example: Check if a recent long signal occurred + if (track_trigger.get_tracker(symbol).long_signal_recent(5)) { + // Do something like open a trade + } +} + +// In OnDeinit(): +void OnDeinit(const int reason) { + track_trigger.clear(); // Clean up memory +} + +*/ \ No newline at end of file diff --git a/Utils/SignalStateTracker.mqh b/Utils/SignalStateTracker.mqh new file mode 100644 index 0000000..5f17547 --- /dev/null +++ b/Utils/SignalStateTracker.mqh @@ -0,0 +1,56 @@ +class SignalStateTracker { + private: + int last_signal_long; + int last_signal_short; + + public: + // Constructor initializes both directions to a default "unset" state. + SignalStateTracker() { + reset(); + } + + // Resets the tracked signal bar indexes to an invalid default value (-1000). + void reset() { + last_signal_long = -1000; + last_signal_short = -1000; + } + + // Updates the signal for both long and short directions in one call, if the respective triggers are true. + // curr_bar should refer to the last CLOSED bar (typically bar index 1). + void update_signal(bool trig_long, bool trig_short, int curr_bar=1) { + if (trig_long) last_signal_long = curr_bar; + if (trig_short) last_signal_short = curr_bar; + } + + // Returns true if a long signal occurred within the last `max_bars` bars. + bool long_signal_recent(int max_bars) const { + // Assumes current bar is always bar index 1 (last closed bar). + return has_long_signal() && (1 - last_signal_long <= max_bars); + } + + // Returns true if a short signal occurred within the last `max_bars` bars. + bool short_signal_recent(int max_bars) const { + // Assumes current bar is always bar index 1 (last closed bar). + return has_short_signal() && (1 - last_signal_short <= max_bars); + } + + // Returns true if a signal has been recorded for the long direction. + bool has_long_signal() const { + return last_signal_long != -1000; + } + + // Returns true if a signal has been recorded for the short direction. + bool has_short_signal() const { + return last_signal_short != -1000; + } + + // Returns the last recorded signal bar index for the long direction. + int get_long_signal() const { + return last_signal_long; + } + + // Returns the last recorded signal bar index for the short direction. + int get_short_signal() const { + return last_signal_short; + } +}; diff --git a/indicators/AtrBands.mqh b/indicators/AtrBands.mqh index 5a1dbc7..0463fa4 100644 --- a/indicators/AtrBands.mqh +++ b/indicators/AtrBands.mqh @@ -1,96 +1,139 @@ -// File: MyLibs/Utils/AtrBands.mqh +#include class AtrBands { - protected: - string symbol; - int atr_period; - ENUM_TIMEFRAMES timeframe; - string prefix; - color upper_color; - color lower_color; - color mid_color; - int line_width; - bool plot_bands; - int atr_handle; + private: + MarketDataUtils market_data_utils; public: - AtrBands(string _symbol, int _atr_period = 14, ENUM_TIMEFRAMES _timeframe = PERIOD_CURRENT, - color _upper = clrDodgerBlue, color _lower = clrDodgerBlue, color _mid = clrDodgerBlue, - int _width = 1, bool plot = true); + // Core band accessors + double upper_band(string symbol, double trendline_var, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1, + color clr = clrDodgerBlue, int width = 1, bool plot = true); - double upper_band(double baseline, int shift = 1, double mult = 1.0); - double lower_band(double baseline, int shift = 1, double mult = 1.0); - double middle_band(double baseline, int shift = 1); + double lower_band(string symbol, double trendline_var, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1, + color clr = clrDodgerBlue, int width = 1, bool plot = true); + double middle_band(string symbol, double trendline_var, ENUM_TIMEFRAMES tf, int shift = 1, color clr = clrDodgerBlue, int width = 1, + bool plot = true); + + // Band checkers + bool inside_upper_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1, bool plot = true); + + bool inside_lower_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1, bool plot = true); + + bool crossed_below_upper_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1, + bool plot = true); + + bool crossed_above_lower_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1, + bool plot = true); protected: - double get_atr(int shift); - void draw_line(string name, int shift, double price, color clr); + double get_atr(string symbol, int atr_period, ENUM_TIMEFRAMES tf, int shift); + void draw_line(string name, double price, color clr, int width); }; -// Constructor -AtrBands::AtrBands(string _symbol, int _atr_period, ENUM_TIMEFRAMES _timeframe, - color _upper, color _lower, color _mid, int _width, bool plot) { - symbol = _symbol; - atr_period = _atr_period; - timeframe = _timeframe; - prefix = "ATRBand"; - upper_color = _upper; - lower_color = _lower; - mid_color = _mid; - line_width = _width; - plot_bands = plot; +// ------------------- +// Public Methods +// ------------------- - atr_handle = iATR(symbol, timeframe, atr_period); - if (atr_handle == INVALID_HANDLE) { - Print("Failed to create ATR handle for symbol: ", symbol); - } -} - -// Public methods -double AtrBands::upper_band(double baseline, int shift, double mult) { - double atr = get_atr(shift); - double upper = baseline + atr * mult; - if (plot_bands) draw_line(prefix + "_Upper_" + symbol, shift, upper, upper_color); +double AtrBands::upper_band(string symbol, double trendline_var, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift, color clr, int width, bool plot) { + double atr = get_atr(symbol, atr_period, tf, shift); + double upper = trendline_var + atr * mult; + if (plot && shift == 1) draw_line("ATRBand_Upper_" + symbol, upper, clr, width); // persistent line return upper; } -double AtrBands::lower_band(double baseline, int shift, double mult) { - double atr = get_atr(shift); - double lower = baseline - atr * mult; - if (plot_bands) draw_line(prefix + "_Lower_" + symbol, shift, lower, lower_color); +double AtrBands::lower_band(string symbol, double trendline_var, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift, color clr, int width, bool plot) { + double atr = get_atr(symbol, atr_period, tf, shift); + double lower = trendline_var - atr * mult; + if (plot && shift == 1) draw_line("ATRBand_Lower_" + symbol, lower, clr, width); // persistent line return lower; } -double AtrBands::middle_band(double baseline, int shift) { - if (plot_bands) draw_line(prefix + "_Mid_" + symbol, shift, baseline, mid_color); - return baseline; +double AtrBands::middle_band(string symbol, double trendline_var, ENUM_TIMEFRAMES tf, int shift, color clr, int width, bool plot) { + if (plot && shift == 1) draw_line("ATRBand_Mid_" + symbol, trendline_var, clr, width); // persistent line + return trendline_var; } -// Internal: Get ATR value from buffer -double AtrBands::get_atr(int shift) { - if (atr_handle == INVALID_HANDLE) return 0; +bool AtrBands::inside_upper_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift, bool plot) { + double price = iClose(symbol, tf, shift); + double trendline_var = market_data_utils.get_buffer_value(handle, shift); + double upper = upper_band(symbol, trendline_var, atr_period, tf, mult, shift, clrDodgerBlue, 1, plot); + + if (price == EMPTY_VALUE || trendline_var == EMPTY_VALUE || upper == EMPTY_VALUE) return false; + return (price > trendline_var && price < upper); +} + +bool AtrBands::inside_lower_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift, bool plot) { + double price = iClose(symbol, tf, shift); + double trendline_var = market_data_utils.get_buffer_value(handle, shift); + double lower = lower_band(symbol, trendline_var, atr_period, tf, mult, shift, clrDodgerBlue, 1, plot); + + if (price == EMPTY_VALUE || trendline_var == EMPTY_VALUE || lower == EMPTY_VALUE) return false; + return (price < trendline_var && price > lower); +} + +// pull-back inside upper atr band: +bool AtrBands::crossed_below_upper_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift, bool plot) { + //--- checked bar + double price = iClose(symbol, tf, shift); + double trendline_var = market_data_utils.get_buffer_value(handle, shift); + double atr_upper_band = upper_band(symbol, trendline_var, atr_period, tf, mult, shift, clrDodgerBlue, 1, plot); + + //--- previous bar + double prev_price = iClose(symbol, tf, shift + 1); + double prev_trendline_var = market_data_utils.get_buffer_value(handle, shift + 1); + double prev_atr_upper_band = upper_band(symbol, prev_trendline_var, atr_period, tf, mult, shift + 1, clrDodgerBlue, 1, plot); + + if (price == EMPTY_VALUE || trendline_var == EMPTY_VALUE || atr_upper_band == EMPTY_VALUE || prev_price == EMPTY_VALUE + || prev_trendline_var == EMPTY_VALUE || prev_atr_upper_band == EMPTY_VALUE) return false; + + return (prev_price > prev_atr_upper_band && price < atr_upper_band); +} + +// pull-back inside lower atr band: +bool AtrBands::crossed_above_lower_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift, bool plot) { + //--- checked bar + double price = iClose(symbol, tf, shift); + double trendline_var = market_data_utils.get_buffer_value(handle, shift); + double atr_lower_band = lower_band(symbol, trendline_var, atr_period, tf, mult, shift, clrDodgerBlue, 1, plot); + + //--- previous bar + double prev_price = iClose(symbol, tf, shift + 1); + double prev_trendline_var = market_data_utils.get_buffer_value(handle, shift + 1); + double prev_atr_lower_band = lower_band(symbol, prev_trendline_var, atr_period, tf, mult, shift + 1, clrDodgerBlue, 1, plot); + + if (price == EMPTY_VALUE || trendline_var == EMPTY_VALUE || atr_lower_band == EMPTY_VALUE || prev_price == EMPTY_VALUE + || prev_trendline_var == EMPTY_VALUE || prev_atr_lower_band == EMPTY_VALUE) return false; + + return (prev_price < prev_atr_lower_band && price > atr_lower_band); +} + +// ------------------- +// Internal Helpers +// ------------------- + +double AtrBands::get_atr(string symbol, int atr_period, ENUM_TIMEFRAMES tf, int shift) { + int handle = iATR(symbol, tf, atr_period); + if (handle == INVALID_HANDLE) { + Print("Failed to create ATR handle for ", symbol); + return 0.0; + } double buf[]; ArraySetAsSeries(buf, true); - if (CopyBuffer(atr_handle, 0, shift, 1, buf) == 1 && buf[0] != EMPTY_VALUE) - return buf[0]; + if (CopyBuffer(handle, 0, shift, 1, buf) == 1 && buf[0] != EMPTY_VALUE) return buf[0]; - return 0; + return 0.0; } -// Internal: Draw line for a given price at a bar -void AtrBands::draw_line(string name, int shift, double price, color clr) { - datetime time0 = iTime(symbol, timeframe, shift); - datetime time1 = time0 + PeriodSeconds(timeframe); - +void AtrBands::draw_line(string name, double price, color clr, int width) { if (ObjectFind(0, name) < 0) { - ObjectCreate(0, name, OBJ_TREND, 0, time0, price, time1, price); + ObjectCreate(0, name, OBJ_HLINE, 0, 0, price); } else { - ObjectMove(0, name, 0, time0, price); - ObjectMove(0, name, 1, time1, price); + ObjectSetDouble(0, name, OBJPROP_PRICE, price); } ObjectSetInteger(0, name, OBJPROP_COLOR, clr); - ObjectSetInteger(0, name, OBJPROP_WIDTH, line_width); + ObjectSetInteger(0, name, OBJPROP_WIDTH, width); ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DASH); + ObjectSetInteger(0, name, OBJPROP_BACK, true); } diff --git a/indicators/TrendlineAnalyser.mqh b/indicators/TrendlineAnalyser.mqh new file mode 100644 index 0000000..8d6edc6 --- /dev/null +++ b/indicators/TrendlineAnalyser.mqh @@ -0,0 +1,59 @@ +#include +//+------------------------------------------------------------------+ +//| TrendlineSignal: A utility class to detect trendline crossovers | +//+------------------------------------------------------------------+ +class TrendlineAnalyser { + private: + MarketDataUtils market_data_utils; + + public: + // Main method: detects whether price has crossed the trendline up (long) or down (short) + // Parameters: + // - symbol: the symbol we're evaluating (e.g., "EURUSD") + // - handle: the indicator handle for the trendline (e.g., iMA handle) + // - cross_long: output bool set to true if a bullish cross is detected + // - cross_short: output bool set to true if a bearish cross is detected + void detect_cross(string symbol, int handle, bool& cross_long, bool& cross_short) { + cross_long = false; + cross_short = false; + + // --- Retrieve prices and trendline values from last 2 closed bars + double price = iClose(symbol, PERIOD_CURRENT, 1); // most recent closed bar + double prev_price = iClose(symbol, PERIOD_CURRENT, 2); // bar before that + + double trendline = market_data_utils.get_buffer_value(handle, 1); // trendline now + double prev_trendline = market_data_utils.get_buffer_value(handle, 2); // trendline before + + // --- Exit early if any of the data is missing or invalid + if (price == EMPTY_VALUE || prev_price == EMPTY_VALUE || trendline == EMPTY_VALUE || prev_trendline == EMPTY_VALUE) { + return; + } + + // --- Detect bullish crossover (price moves from below to above the trendline) + cross_long = (prev_price < prev_trendline && price > trendline); + + // --- Detect bearish crossover (price moves from above to below the trendline) + cross_short = (prev_price > prev_trendline && price < trendline); + } + // METHOD: determines if price is currently trending above or below the trendline + // Parameters: + // - symbol: trading symbol (e.g., "EURUSD") + // - handle: trendline indicator handle + // - is_direction_long: output bool, true if price is above trendline + // - is_direction_short: output bool, true if price is below trendline + // - shift: which candle to check (default: 1 = last closed bar) + void trend_direction(string symbol, int handle, bool& direction_long, bool& direction_short, int shift = 1) { + direction_long = false; + direction_short = false; + + double price = iClose(symbol, PERIOD_CURRENT, shift); + double trendline = market_data_utils.get_buffer_value(handle, shift); + + if (price == EMPTY_VALUE || trendline == EMPTY_VALUE) { + return; + } + + direction_long = (price > trendline); + direction_short = (price < trendline); + } +};