373 lines
20 KiB
Plaintext
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);
|
|
}
|
|
|