Files
mt5-quant-lib/trend_following_ea.mq5
2025-07-12 17:06:33 +02:00

373 lines
20 KiB
Plaintext

//+------------------------------------------------------------------+
//| Multi-Symbol Strategy EA using MyLibs Framework |
//+------------------------------------------------------------------+
// Description:
// - This Expert Advisor (EA) implements a modular, multi-symbol trading
// strategy using the MyLibs library for MetaTrader 5.
// - The strategy combines RSI triggers, trendline analysis, ATR bands,
// confirmation and volume indicators, and manages both TP and runner
// trades with risk control.
//
// Features:
// - Modular logic for entry/exit using signal structs
// - Trailing stop and breakeven logic for runners
// - Custom fitness criteria for optimization (via OnTester())
// - Support for training/testing data splits
// - Time filtering for bar-based updates
//
// Usage:
// - Place the 'MyLibs' folder in MQL5/Include/
// - Compile this EA and attach it to any chart
// - Strategy supports multiple symbols simply modify the SymbolsArray
// - Adjust inputs for lot size, SL/TP modes, ATR periods, etc. as needed
//
// Notes:
// - All indicator handles are registered and released via ResourceManager
// - Trade logic only executes once per bar per symbol
// - Signals are tracked and time-decayed using MultiSymbolSignalTracker
//+------------------------------------------------------------------+
#include <MyLibs/BacktestUtils/CustomMax.mqh>
#include <MyLibs/BacktestUtils/TestDataSplit.mqh>
#include <MyLibs/Indicators/AtrBands.mqh>
#include <MyLibs/Indicators/TrendlineAnalyser.mqh>
#include <MyLibs/Orders/AdjustPosition.mqh>
#include <MyLibs/Orders/EntryOrders.mqh>
#include <MyLibs/Orders/ExitOrders.mqh>
#include <MyLibs/Utils/Enums.mqh>
#include <MyLibs/Utils/MarketDataUtils.mqh>
#include <MyLibs/Utils/MultiSymbolSignalTracker.mqh>
#include <MyLibs/Utils/ResourceManager.mqh>
//--- classes
AdjustPosition adjust_pos;
AtrBands atr_bands;
CustomMax c_max;
EntryOrders entry_orders;
ExitOrders exit_orders;
MarketDataUtils m_utils;
MultiSymbolSignalTracker track_trigger_signal;
ResourceManager resource_manager;
TestDataSplit data_split;
TrendlineAnalyser tl_tools;
//--- Inputs
//+------------------------------------------------------------------+
//| RISK AND POSITION SETTINGS |
//+------------------------------------------------------------------+
input LOT_MODE inp_lot_mode = LOT_MODE_PCT_RISK; // Method used for position sizing (e.g., fixed lot, or % risk)
input double inp_lot_var = 2; // Lot value or risk percentage (depending on selected lot mode)
input SL_MODE inp_sl_mode = SL_ATR_MULTIPLE; // Stop Loss method (e.g., ATR-based, fixed value, etc.)
input double inp_sl_var = 1.5; // SL parameter (e.g., ATR multiplier or fixed points)
input TP_MODE inp_tp_mode = TP_ATR_MULTIPLE; // Take Profit method (e.g., ATR-based, fixed value, etc.)
input double inp_tp_var = 1; // TP parameter (e.g., ATR multiplier or fixed points)
string lot_mode = EnumToString(inp_lot_mode); // convert to string
string sl_mode = EnumToString(inp_sl_mode); // convert to string
string tp_mode = EnumToString(inp_tp_mode); // convert to string
//+------------------------------------------------------------------+
//| BACKTESTING & OPTIMIZATION SETTINGS |
//+------------------------------------------------------------------+
input CUSTOM_MAX_TYPE inp_custom_criteria = CM_WIN_PERCENT; // Custom performance metric to use in OnTester()
input int inp_opt_min_trades = 0; // Minimum trades required to validate backtest result
input MODE_SPLIT_DATA inp_data_split_method = NO_SPLIT; // Method used to split data (e.g., train/test split)
input int inp_force_opt = 1; // Forces optimizer to call OnTester() even if not in training set
//+------------------------------------------------------------------+
//| INDICATOR PERIODS |
//+------------------------------------------------------------------+
input int InpTrendlinePeriod = 100; // Lookback period for moving average used in trendline
input int InpTriggerPeriod = 14; // RSI period used to trigger entries
input int InpConformationPeriod = 50; // CCI period used for confirming trend direction
input int InpExitPeriod = 5; // Stochastic period used to signal exits
//--- Global vars/arrays
int magic_number = 1000;
int runner_magic = magic_number + 1;
// string SymbolsArray[] = {_Symbol}; // Chart symbol only
string SymbolsArray[] = {"AUDNZD", "EURGBP", "AUDCAD", "CHFJPY", "EURUSD"}; // Multi symbol trading
int tl_handle[], trig_handle[], conf_handle[], vol_handle[], exit_handle[];
int OnInit() {
ArrayResize(tl_handle, ArraySize(SymbolsArray));
ArrayResize(trig_handle, ArraySize(SymbolsArray));
ArrayResize(conf_handle, ArraySize(SymbolsArray));
ArrayResize(vol_handle, ArraySize(SymbolsArray));
ArrayResize(exit_handle, ArraySize(SymbolsArray));
for (int i = 0; i < ArraySize(SymbolsArray); i++) {
// --- Create and register all indicator handles for this symbol
tl_handle[i] = iMA(SymbolsArray[i], PERIOD_CURRENT, InpTrendlinePeriod, 0, MODE_SMA, PRICE_CLOSE);
trig_handle[i] = iRSI(SymbolsArray[i], PERIOD_CURRENT, InpTriggerPeriod, PRICE_CLOSE);
conf_handle[i] = iCCI(SymbolsArray[i], PERIOD_CURRENT, InpConformationPeriod, PRICE_TYPICAL);
vol_handle[i] = iOBV(SymbolsArray[i], PERIOD_CURRENT, VOLUME_TICK);
exit_handle[i] = iStochastic(SymbolsArray[i], PERIOD_CURRENT, InpExitPeriod, 3, 3, MODE_EMA, STO_CLOSECLOSE);
resource_manager.register_handle(tl_handle[i]);
resource_manager.register_handle(trig_handle[i]);
resource_manager.register_handle(conf_handle[i]);
resource_manager.register_handle(vol_handle[i]);
resource_manager.register_handle(exit_handle[i]);
}
EventSetTimer(60);
return INIT_SUCCEEDED;
}
void OnDeinit(const int reason) {
EventKillTimer();
resource_manager.release_all_handles();
track_trigger_signal.clear();
}
double OnTester() {
return c_max.calculate_custom_criteria(inp_custom_criteria, inp_opt_min_trades);
}
void OnTimer() {
for (int i = 0; i < ArraySize(SymbolsArray); i++) {
string symbol = SymbolsArray[i];
run_trade_logic(symbol, tl_handle[i], trig_handle[i], conf_handle[i], vol_handle[i], exit_handle[i]);
}
}
void run_trade_logic(string symbol, int tl_hand, int trig_hand, int conf_hand, int vol_hand, int exit_hand) {
// --- Breakeven logic: if a profit target was reached, move SL to breakeven for runner trades (execution every time step)
adjust_pos.set_breakeven_if_profit_target_hit(symbol, runner_magic);
// --- Only run strategy on a new bar to avoid repeated execution within the same candle
if (!m_utils.is_new_bar(symbol, PERIOD_CURRENT, "00:05")) return;
// --- ATR-based trailing stop for runner trades only.
// Updated each bar, calculation uses previous bar close. Trail by 1.5x ATR after 2x ATR in profit
adjust_pos.trailing_stop_atr(symbol, runner_magic, PERIOD_CURRENT, 2.0, 1.5, 14, true);
// --- Construct signal data structures for long and short directions
trading_signals long_signals = build_trading_signals(true, symbol, tl_hand, trig_hand, conf_hand, vol_hand, exit_hand);
trading_signals short_signals = build_trading_signals(false, symbol, tl_hand, trig_hand, conf_hand, vol_hand, exit_hand);
// --- Determine whether to enter long:
bool entry_long =
standard_entry(long_signals)
|| pullback_entry(long_signals)
|| trendline_cross_entry(long_signals);
// --- Determine whether to enter short:
bool entry_short =
standard_entry(short_signals)
|| pullback_entry(short_signals)
|| trendline_cross_entry(short_signals);
// --- Determine whether exit long:
bool exit_long =
long_signals.exit_indi_sig
|| long_signals.tl_cross_exit;
// --- Determine whether exit short:
bool exit_short =
short_signals.exit_indi_sig
|| short_signals.tl_cross_exit;
// --- Centralised trade management:
manage_orders(symbol, entry_long, entry_short, exit_long, exit_short);
}
struct trading_signals {
bool trigger_sig; int last_trigger_sig; bool trigger_agrees; bool trigger_agrees_prv; // Trigger signals
bool conformation; // Comformation indicator signal
bool tl_cross; bool tl_cross_prev; bool tl_agrees; bool tl_agrees_prev; // Trendline signals
bool atr_b; bool atr_b_prev; bool atr_pullback; // Atr band signals
bool volume; // Volume indicator signal
bool exit_indi_sig; bool tl_cross_exit; // Exit signals
};
trading_signals build_trading_signals(bool is_long, string symbol, int tl_hand, int trig_hand, int conf_hand, int vol_hand, int exit_hand) {
trading_signals long_sigs;
trading_signals short_sigs;
// --- Trigger signals
bool dummy1 = false, dummy2 = false;
trigger(trig_hand, long_sigs.trigger_sig, short_sigs.trigger_sig, long_sigs.trigger_agrees, short_sigs.trigger_agrees, 1);
trigger(trig_hand, dummy1, dummy2, long_sigs.trigger_agrees_prv, short_sigs.trigger_agrees_prv, 2);
track_trigger_signal.get_tracker(symbol).update_signal_tracker(long_sigs.trigger_sig, short_sigs.trigger_sig);
long_sigs.last_trigger_sig = track_trigger_signal.get_tracker(symbol).get_long_signal();
short_sigs.last_trigger_sig = track_trigger_signal.get_tracker(symbol).get_short_signal();
// --- Trendline crossover and direction signals
tl_tools.detect_cross(symbol, tl_hand, long_sigs.tl_cross, short_sigs.tl_cross, 1);
tl_tools.detect_cross(symbol, tl_hand, long_sigs.tl_cross_prev, short_sigs.tl_cross_prev, 2);
tl_tools.trend_direction(symbol, tl_hand, long_sigs.tl_agrees, short_sigs.tl_agrees, 1);
tl_tools.trend_direction(symbol, tl_hand, long_sigs.tl_agrees_prev, short_sigs.tl_agrees_prev, 2);
// --- Confirmation indicator signals
conformation(conf_hand, long_sigs.conformation, short_sigs.conformation);
// --- Volume indicator signals
volume_filter(vol_hand, long_sigs.volume, short_sigs.volume);
// --- ATR band signals
// atr_bands.plot_bands(symbol, tl_hand, 14, PERIOD_CURRENT, 1.0, 1); // prit bands (for debugging)
long_sigs.atr_b = atr_bands.inside_upper_band(symbol, tl_hand, 14, PERIOD_CURRENT, 1.0, 1);
short_sigs.atr_b = atr_bands.inside_lower_band(symbol, tl_hand, 14, PERIOD_CURRENT, 1.0, 1);
long_sigs.atr_b_prev = atr_bands.inside_upper_band(symbol, tl_hand, 14, PERIOD_CURRENT, 1.0, 2);
short_sigs.atr_b_prev = atr_bands.inside_lower_band(symbol, tl_hand, 14, PERIOD_CURRENT, 1.0, 2);
long_sigs.atr_pullback = atr_bands.crossed_below_upper_band(symbol, tl_hand, 14, PERIOD_CURRENT, 1.0, 1);
short_sigs.atr_pullback = atr_bands.crossed_above_lower_band(symbol, tl_hand, 14, PERIOD_CURRENT, 1.0, 1);
// --- Exit signals
exit_signal(exit_hand, long_sigs.exit_indi_sig, short_sigs.exit_indi_sig);
long_sigs.tl_cross_exit = short_sigs.tl_cross; // Long exits when price crosses below the trendline
short_sigs.tl_cross_exit = long_sigs.tl_cross; // Short exits when price crosses above the trendline
// --- Return side-specific struct
return is_long ? long_sigs : short_sigs;
}
// A "standard" entry requires key conditions to be true: trigger, trendline agreement, ATR band position, confirmation, and volume.
// A delayed entry is allowed one bar after the trigger, if confirmation and volume were not ready at the time.
bool standard_entry(const trading_signals& data) {
bool trigger_sig = data.trigger_sig; // Trigger signal occurred on the current candle
bool trigger_sig_prv = (data.last_trigger_sig == 2); // Trigger signal occurred on the previous candle
bool trigger_agrees = data.trigger_agrees; // Trigger direction still aligns on current candle
bool tl_agree = data.tl_agrees; // Current price is on the correct side of the trendline
bool tl_agree_prev = data.tl_agrees_prev; // Previous candle also agreed with trendline direction
bool inside_atr_band = data.atr_b; // Price is within ATR band on current candle
bool inside_atr_band_prev = data.atr_b_prev; // Price was within ATR band on previous candle
bool confirmation = data.conformation; // Confirmation indicator aligns with trade direction
bool volume = data.volume; // Volume supports entry
// --- Standard entry: all conditions met on the current candle
bool current_entry_valid = trigger_sig && tl_agree && inside_atr_band && confirmation && volume;
// --- Delayed entry: previous candle had trigger + setup, current candle confirms with vol & confluence
bool delayed_entry_valid = trigger_sig_prv && tl_agree_prev && inside_atr_band_prev && // Previous candle
trigger_agrees && tl_agree && confirmation && volume; // Current candle
return current_entry_valid || delayed_entry_valid;
}
// A "trendline cross" entry occurs after price crosses the trendline, provided other conditions (ATR band, confirmation, volume)
// are met. Allows 1-bar delay if confirmation and volume were not ready at the time.
bool trendline_cross_entry(const trading_signals& data) {
bool tl_cross = data.tl_cross; // Trendline cross occurred on current candle **WORKS
bool tl_cross_prev = data.tl_cross_prev; // Trendline cross occurred on previous candle **WORKS
bool tl_agrees = data.tl_agrees; // Current price is on the correct side of the trendline
bool trigger_agrees = data.trigger_agrees; // Trigger aligns with direction on current candle
bool trigger_agrees_prev = data.trigger_agrees_prv; // Trigger aligned on previous candle
bool trigger_sig_recent = (data.last_trigger_sig > 0 // Ensure the signal was actually triggered (not 0 or negative)
&& data.last_trigger_sig <= 7 // Only consider signals within the last 7 bars
&& data.last_trigger_sig != EMPTY_VALUE); // Avoid using uninitialized or invalid values
bool inside_atr_band = data.atr_b; // Price is inside ATR band on current candle
bool inside_atr_band_prev = data.atr_b_prev; // Price was inside ATR band on previous candle
bool confirmation = data.conformation; // Confirmation indicator agrees with trade direction
bool volume = data.volume; // Volume supports entry
// --- Entry on current candle: trendline cross with trigger agreement, ATR, confirmation and volume
bool current_entry_valid =
tl_cross && trigger_agrees && trigger_sig_recent && inside_atr_band && confirmation && volume;
// --- Delayed entry: trendline cross and alignment happened previously, confirmation and volume caught up now
bool delayed_entry_valid =
tl_cross_prev && trigger_agrees_prev && inside_atr_band_prev && // Previous candle
trigger_sig_recent && trigger_agrees && tl_agrees && confirmation && volume; // Current candle
return current_entry_valid || delayed_entry_valid;
}
// A "Pullback" entry expects price to pull back inside the ATR band 1 bar after a trendline cross.
// The setup must have occurred on the previous candle, and all confirmations must align on the current candle.
bool pullback_entry(const trading_signals& data) {
bool tl_cross_prev = data.tl_cross_prev; // Trendline cross occurred on the previous candle
bool trigger_agrees_prev = data.trigger_agrees_prv; // Trigger aligned on previous candle
bool atr_pullback = data.atr_pullback; // Price has pulled back into ATR band on current candle
bool tl_agree = data.tl_agrees; // Current price is on the correct side of the trendline
bool trigger_agrees = data.trigger_agrees; // Trigger aligns with direction on current candle
bool confirmation = data.conformation; // Confirmation indicator agrees with trade direction
bool volume = data.volume; // Volume supports entry
// --- Pullback entry: setup on previous candle, pullback + confirmation on current
return tl_cross_prev && trigger_agrees_prev && // Previous candle
atr_pullback && tl_agree && trigger_agrees && confirmation && volume; // Current candle
}
void manage_orders(string symbol, bool long_in, bool short_in, bool exit_long, bool exit_short) {
// Split risk between TP and runner
double lot_var = inp_lot_var / 2;
// --- Always manage open positions regardless of test window ---
// Close TP positions if exit conditions met
exit_orders.close_buy_orders(symbol, exit_long, 0, PERIOD_CURRENT, magic_number);
exit_orders.close_sell_orders(symbol, exit_short, 0, PERIOD_CURRENT, magic_number);
// Close runner positions if exit triggered
exit_orders.close_buy_orders(symbol, exit_long, 0, PERIOD_CURRENT, runner_magic);
exit_orders.close_sell_orders(symbol, exit_short, 0, PERIOD_CURRENT, runner_magic);
// --- Only open new positions if in test window ---
if (!data_split.in_test_period(inp_data_split_method)) return;
// --- Only open new positions if there are no open positions for symbol (regardless of side) ---
if (entry_orders.count_open_positions(symbol, 0, magic_number) > 0) return;
if (entry_orders.count_open_positions(symbol, 0, runner_magic) > 0) return;
// TP entries
entry_orders.open_buy_orders(symbol, long_in, PERIOD_CURRENT, sl_mode, inp_sl_var, tp_mode, inp_tp_var, lot_mode, lot_var, magic_number);
entry_orders.open_sell_orders(symbol, short_in, PERIOD_CURRENT, sl_mode, inp_sl_var, tp_mode, inp_tp_var, lot_mode, lot_var, magic_number);
// Runner entries
entry_orders.open_runner_buy_order_with_virtual_tp(symbol, long_in, PERIOD_CURRENT, sl_mode, inp_sl_var, tp_mode, inp_tp_var, lot_mode, lot_var, runner_magic);
entry_orders.open_runner_sell_order_with_virtual_tp(symbol, short_in, PERIOD_CURRENT, sl_mode, inp_sl_var, tp_mode, inp_tp_var, lot_mode, lot_var, runner_magic);
}
// Trigger logic
void trigger(int handle, bool& long_sig, bool& short_sig, bool& long_agree, bool& short_agree, int shift = 1) {
long_sig = false; short_sig = false; long_agree = false; short_agree = false;
double curr = m_utils.get_buffer_value(handle, shift);
double prev = m_utils.get_buffer_value(handle, shift + 1);
if (curr == EMPTY_VALUE || prev == EMPTY_VALUE) return;
long_sig = (prev < 50 && curr > 50);
short_sig = (prev > 50 && curr < 50);
long_agree = (curr > 50);
short_agree = (curr < 50);
}
// Confirmation logic
void conformation(int handle, bool& long_ok, bool& short_ok) {
long_ok = false; short_ok = false;
double value = m_utils.get_buffer_value(handle, 1);
if (value == EMPTY_VALUE) return;
long_ok = (value > 0);
short_ok = (value < 0);
}
// Volume filter
void volume_filter(int handle, bool& long_ok, bool& short_ok) {
long_ok = false; short_ok = false;
double curr = m_utils.get_buffer_value(handle, 1);
double prev = m_utils.get_buffer_value(handle, 2);
if (curr == EMPTY_VALUE || prev == EMPTY_VALUE) return;
long_ok = (curr > prev);
short_ok = (curr < prev);
}
// Exit signal
void exit_signal(int handle, bool& exit_long, bool& exit_short) {
exit_long = false; exit_short = false;
double curr = m_utils.get_buffer_value(handle, 1);
double prev = m_utils.get_buffer_value(handle, 2);
if (curr == EMPTY_VALUE || prev == EMPTY_VALUE) return;
exit_long = (prev > 50 && curr < 50);
exit_short = (prev < 50 && curr > 50);
}