From 136522ef1c7867db369dd2ce2773bb065273d002 Mon Sep 17 00:00:00 2001 From: Matt Corcoran Date: Sat, 12 Jul 2025 15:29:30 +0200 Subject: [PATCH] update to my mt5 libs to include better comments --- BacktestUtils/CustomMax.mqh | 67 ++- BacktestUtils/TestDataSplit.mqh | 92 ++-- Orders/AdjustPosition.mqh | 269 ++++++----- Orders/CalculatePositionData.mqh | 93 +++- Orders/EntryOrders.mqh | 124 ++++- Orders/ExitOrders.mqh | 70 ++- Orders/OrderTracker.mqh | 33 ++ Orders/StopLogic.mqh | 26 +- Orders/TrailingLogic.mqh | 81 ---- RiskManagement/DrawdownControl.mqh | 50 +- Strategy/EntryLogic_nnfx.mqh | 105 ----- Strategy/{Range => }/RangeCalculator.mqh | 103 ++++- Utils/AtrHandleManager.mqh | 36 +- Utils/ChartUtils.mqh | 50 +- Utils/ExpertFileHeader.mqh | 28 -- Utils/MarketDataUtils.mqh | 186 +++++--- Utils/MultiSymbolSignalTracker.mqh | 94 ++-- Utils/ResourceManager.mqh | 54 ++- Utils/SignalStateTracker.mqh | 91 +++- Utils/TimeZones.mqh | 268 ++++++----- Utils/TradeWindow.mqh | 17 + _old/BarUtils.mqh | 35 -- _old/BufferUtils.mqh | 43 -- _old/CalculatePositionData.mqh | 278 ------------ _old/MyFunctions.mqh | 211 --------- _old/OrderManagement.mqh | 556 ----------------------- _old/SymbolUtils.mqh | 35 -- _old/TradingWindow.mqh | 65 --- _old/_MyLibs_old.zip | Bin 26431 -> 0 bytes indicators/AtrBands.mqh | 128 ++++-- indicators/TrendlineAnalyser.mqh | 125 ++--- 31 files changed, 1389 insertions(+), 2024 deletions(-) delete mode 100644 Orders/TrailingLogic.mqh delete mode 100644 Strategy/EntryLogic_nnfx.mqh rename Strategy/{Range => }/RangeCalculator.mqh (77%) delete mode 100644 Utils/ExpertFileHeader.mqh delete mode 100644 _old/BarUtils.mqh delete mode 100644 _old/BufferUtils.mqh delete mode 100644 _old/CalculatePositionData.mqh delete mode 100644 _old/MyFunctions.mqh delete mode 100644 _old/OrderManagement.mqh delete mode 100644 _old/SymbolUtils.mqh delete mode 100644 _old/TradingWindow.mqh delete mode 100644 _old/_MyLibs_old.zip diff --git a/BacktestUtils/CustomMax.mqh b/BacktestUtils/CustomMax.mqh index 2d411dd..3544ae9 100644 --- a/BacktestUtils/CustomMax.mqh +++ b/BacktestUtils/CustomMax.mqh @@ -1,10 +1,25 @@ #include +// --------------------------------------------------------------------- +// ENUM: CUSTOM_MAX_TYPE +// --------------------------------------------------------------------- +// Defines the types of custom performance criteria that can be used +// for calculating max optimization targets. +// +// Values: +// - CM_WIN_LOSS_RATIO : Use win/loss ratio. +// - CM_WIN_PERCENT : Use win percentage. +// --------------------------------------------------------------------- enum CUSTOM_MAX_TYPE { CM_WIN_LOSS_RATIO, CM_WIN_PERCENT }; +// --------------------------------------------------------------------- +// CLASS: CustomMax +// --------------------------------------------------------------------- +// Calculates custom performance criteria for use in optimizations. +// --------------------------------------------------------------------- class CustomMax : public CObject { protected: double custom_criteria; @@ -16,6 +31,17 @@ public: double calculate_custom_criteria(CUSTOM_MAX_TYPE cm_type, int min_trades = 0); }; +// --------------------------------------------------------------------- +// Calculates the selected custom criteria metric. +// +// Parameters: +// - cm_type : The selected criteria type to calculate. +// - min_trades : Optional minimum number of trades (default 0). +// +// Logic: +// - Calls the appropriate private method based on cm_type. +// - Returns the resulting metric (or 0 if invalid). +// --------------------------------------------------------------------- double CustomMax::calculate_custom_criteria(CUSTOM_MAX_TYPE cm_type, int min_trades) { switch(cm_type) { case CM_WIN_LOSS_RATIO: @@ -31,22 +57,49 @@ double CustomMax::calculate_custom_criteria(CUSTOM_MAX_TYPE cm_type, int min_tra return custom_criteria; } -// Returns the win/loss ratio, with min trades check +// --------------------------------------------------------------------- +// Calculates win/loss ratio with a minimum trade count check. +// +// Parameters: +// - min_required_trades : Minimum number of trades required. +// +// Logic: +// - Returns wins / losses if minimum is met. +// - Prevents division by zero. +// --------------------------------------------------------------------- double CustomMax::win_loss_ratio(int min_required_trades) { double wins = TesterStatistics(STAT_PROFIT_TRADES); double losses = TesterStatistics(STAT_LOSS_TRADES); double total_trades = TesterStatistics(STAT_TRADES); - if((min_required_trades > 0 && total_trades < min_required_trades) || total_trades == 0) return 0; - if(losses == 0) return 0; // Prevent division by zero + + if ((min_required_trades > 0 && total_trades < min_required_trades) || total_trades == 0) + return 0; + if (losses == 0) + return 0; + return wins / losses; } -// Returns the win percentage, with min trades check +// --------------------------------------------------------------------- +// Calculates win percentage with a minimum trade count check. +// +// Parameters: +// - min_required_trades : Minimum number of trades required. +// +// Logic: +// - Returns (wins / total) * 100 if minimum is met. +// - Filters out invalid math results. +// --------------------------------------------------------------------- double CustomMax::win_percent_min_trades(int min_required_trades) { double wins = TesterStatistics(STAT_PROFIT_TRADES); double total_trades = TesterStatistics(STAT_TRADES); - if((min_required_trades > 0 && total_trades < min_required_trades) || total_trades == 0) return 0; - double result = wins / total_trades * 100; - if(!MathIsValidNumber(result)) return 0; + + if ((min_required_trades > 0 && total_trades < min_required_trades) || total_trades == 0) + return 0; + + double result = (wins / total_trades) * 100; + if (!MathIsValidNumber(result)) + return 0; + return result; } diff --git a/BacktestUtils/TestDataSplit.mqh b/BacktestUtils/TestDataSplit.mqh index 9458589..6efa791 100644 --- a/BacktestUtils/TestDataSplit.mqh +++ b/BacktestUtils/TestDataSplit.mqh @@ -1,4 +1,18 @@ -enum MODE_SPLIT_DATA{ +// --------------------------------------------------------------------- +// ENUM: MODE_SPLIT_DATA +// --------------------------------------------------------------------- +// Defines how to split data during testing based on time attributes. +// +// Values: +// - NO_SPLIT : Do not split, always return true. +// - ODD_YEARS : Include only odd-numbered years. +// - EVEN_YEARS : Include only even-numbered years. +// - ODD_MONTHS : Include only odd-numbered months. +// - EVEN_MONTHS : Include only even-numbered months. +// - ODD_WEEKS : Include only odd-numbered weeks. +// - EVEN_WEEKS : Include only even-numbered weeks. +// --------------------------------------------------------------------- +enum MODE_SPLIT_DATA { NO_SPLIT, ODD_YEARS, EVEN_YEARS, @@ -8,44 +22,60 @@ enum MODE_SPLIT_DATA{ EVEN_WEEKS }; +// --------------------------------------------------------------------- +// CLASS: TestDataSplit +// --------------------------------------------------------------------- +// Provides logic to determine whether the current date falls within +// a selected split group for testing or optimization purposes. +// --------------------------------------------------------------------- class TestDataSplit { public: - bool in_test_period(MODE_SPLIT_DATA data_split_method); + bool in_test_period(MODE_SPLIT_DATA data_split_method); }; +// --------------------------------------------------------------------- +// Determines whether the current time falls in the selected group. +// +// Parameters: +// - data_split_method : Enum value defining the split strategy. +// +// Logic: +// - Extracts current date components (year, month, ISO week). +// - Returns true if current time matches the given split rule. +// --------------------------------------------------------------------- bool TestDataSplit::in_test_period(MODE_SPLIT_DATA data_split_method) { - string result[]; - string string_tc = TimeToString(TimeCurrent()); + string result[]; + string string_tc = TimeToString(TimeCurrent()); - // Extract components from datetime string (assumes YYYY.MM.DD format) - ushort u_sep = StringGetCharacter(".", 0); - StringSplit(string_tc, u_sep, result); + // Extract components from datetime string (assumes YYYY.MM.DD format) + ushort u_sep = StringGetCharacter(".", 0); + StringSplit(string_tc, u_sep, result); - bool odd_year = int(result[0]) % 2; - bool odd_month = int(result[1]) % 2; + bool odd_year = int(result[0]) % 2; + bool odd_month = int(result[1]) % 2; - // Calculate week of the year (basic approximation) - MqlDateTime dt; - TimeToStruct(TimeCurrent(), dt); - int iDay = (dt.day_of_week + 6) % 7 + 1; // Convert to 1=Mon,...,7=Sun - int iWeek = (dt.day_of_year - iDay + 10) / 7; // Estimate ISO week number - bool odd_week = iWeek % 2; + // Calculate week of the year (approximate ISO week) + MqlDateTime dt; + TimeToStruct(TimeCurrent(), dt); + int i_day = (dt.day_of_week + 6) % 7 + 1; // Convert to 1=Mon,...,7=Sun + int i_week = (dt.day_of_year - i_day + 10) / 7; // Approximate ISO week number + bool odd_week = i_week % 2; - // Split logic depending on mode - if (data_split_method == NO_SPLIT) - return true; - if (data_split_method == ODD_YEARS && odd_year) - return true; - if (data_split_method == EVEN_YEARS && !odd_year) - return true; - if (data_split_method == ODD_MONTHS && odd_month) - return true; - if (data_split_method == EVEN_MONTHS && !odd_month) - return true; - if (data_split_method == ODD_WEEKS && odd_week) - return true; - if (data_split_method == EVEN_WEEKS && !odd_week) - return true; + // Split logic depending on mode + if (data_split_method == NO_SPLIT) + return true; + if (data_split_method == ODD_YEARS && odd_year) + return true; + if (data_split_method == EVEN_YEARS && !odd_year) + return true; + if (data_split_method == ODD_MONTHS && odd_month) + return true; + if (data_split_method == EVEN_MONTHS && !odd_month) + return true; + if (data_split_method == ODD_WEEKS && odd_week) + return true; + if (data_split_method == EVEN_WEEKS && !odd_week) + return true; - return false; + return false; } diff --git a/Orders/AdjustPosition.mqh b/Orders/AdjustPosition.mqh index 1209aea..b2d9520 100644 --- a/Orders/AdjustPosition.mqh +++ b/Orders/AdjustPosition.mqh @@ -1,24 +1,35 @@ #include #include +// --------------------------------------------------------------------- +// GLOBALS +// --------------------------------------------------------------------- CTrade trade; AtrHandleManager atr_manager; +// --------------------------------------------------------------------- +// CLASS: AdjustPosition +// --------------------------------------------------------------------- +// Provides methods to manage stop-loss logic for runner trades. +// Includes breakeven, trailing stop (fixed and ATR), and virtual TP SLs. +// --------------------------------------------------------------------- class AdjustPosition { - public: +public: void set_breakeven_sl(string symbol, int runner_magic_no, double buffer_points = 5); void set_breakeven_if_profit_target_hit(string symbol, int runner_magic_no, double buffer_points = 5); void set_fixed_sl(string symbol, int runner_magic_no, double fixed_sl_price); void set_trailing_sl(string symbol, int runner_magic_no, double sl_offset_points = 5); - void trailing_stop_atr(string symbol, int magic_number, ENUM_TIMEFRAMES tf = PERIOD_CURRENT, double activation_mult = 1.0, - double trail_mult = 1.0, int atr_period = 14, bool use_bar_close = false); + void trailing_stop_atr(string symbol, int magic_number, ENUM_TIMEFRAMES tf = PERIOD_CURRENT, double activation_mult = 1.0, + double trail_mult = 1.0, int atr_period = 14, bool use_bar_close = false); - private: - void set_breakeven_sl_for_ticket(string symbol, ulong ticket, long order_type, double entry_price, double current_sl, double current_tp, int digits, double buffer_price, bool remove_tp); +private: + void set_breakeven_sl_for_ticket(string symbol, ulong ticket, long order_type, double entry_price, + double current_sl, double current_tp, int digits, double buffer_price, bool remove_tp); }; -// --------------------------------------------------------- - +// --------------------------------------------------------------------- +// Sets SL to breakeven for all matching runner trades. +// --------------------------------------------------------------------- void AdjustPosition::set_breakeven_sl(string symbol, int runner_magic_no, double buffer_points) { int digits = (int) SymbolInfoInteger(symbol, SYMBOL_DIGITS); double buffer_price = buffer_points * _Point; @@ -38,37 +49,33 @@ void AdjustPosition::set_breakeven_sl(string symbol, int runner_magic_no, double } } -// --------------------------------------------------------- -// Check runner trades for virtual TP hits and set SL to breakeven if crossed. -// Optimized to avoid unnecessary processing on every OnTimer()/OnTick() call. +// --------------------------------------------------------------------- +// Sets SL to breakeven if virtual TP (in comment) was hit. +// --------------------------------------------------------------------- void AdjustPosition::set_breakeven_if_profit_target_hit(string symbol, int runner_magic_no, double buffer_points) { - - // --- Get current bid/ask and symbol precision double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); double bid = SymbolInfoDouble(symbol, SYMBOL_BID); - int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); + int digits = (int) SymbolInfoInteger(symbol, SYMBOL_DIGITS); double buffer_price = buffer_points * _Point; - double price_margin = 50 * _Point; // avoid premature checking if far from TP + double price_margin = 50 * _Point; - // --- Fast check: skip if no runner trades exist for this symbol bool has_runner = false; for (int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if (!PositionSelectByTicket(ticket)) continue; if (PositionGetString(POSITION_SYMBOL) != symbol) continue; - if ((int)PositionGetInteger(POSITION_MAGIC) == runner_magic_no) { + if ((int) PositionGetInteger(POSITION_MAGIC) == runner_magic_no) { has_runner = true; break; } } if (!has_runner) return; - // --- Loop through positions for breakeven SL for (int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if (!PositionSelectByTicket(ticket)) continue; if (PositionGetString(POSITION_SYMBOL) != symbol) continue; - if ((int)PositionGetInteger(POSITION_MAGIC) != runner_magic_no) continue; + if ((int) PositionGetInteger(POSITION_MAGIC) != runner_magic_no) continue; long order_type = PositionGetInteger(POSITION_TYPE); double entry = PositionGetDouble(POSITION_PRICE_OPEN); @@ -76,51 +83,135 @@ void AdjustPosition::set_breakeven_if_profit_target_hit(string symbol, int runne double tp = PositionGetDouble(POSITION_TP); string comment = PositionGetString(POSITION_COMMENT); - - // --- Parse virtual TP from comment: expected format "runner_tp:1.10500" double virtual_tp = 0.0; if (StringFind(comment, "runner_tp:") == 0) { string tp_str = StringSubstr(comment, StringLen("runner_tp:")); virtual_tp = StringToDouble(tp_str); } - if (virtual_tp <= 0.0) continue; // no valid virtual TP - if (tp > 0.0) { + if (virtual_tp <= 0.0) continue; + if (tp > 0.0) PrintFormat("Warning: Runner trade on %s (ticket %d) has TP set: %.5f", symbol, ticket, tp); - } - // --- Skip early if price not near virtual TP if (order_type == POSITION_TYPE_BUY && bid < virtual_tp - price_margin) continue; if (order_type == POSITION_TYPE_SELL && ask > virtual_tp + price_margin) continue; - // --- Check if virtual TP was hit - bool tp_hit = false; - if (order_type == POSITION_TYPE_BUY && bid >= virtual_tp) tp_hit = true; - if (order_type == POSITION_TYPE_SELL && ask <= virtual_tp) tp_hit = true; + bool tp_hit = (order_type == POSITION_TYPE_BUY && bid >= virtual_tp) || + (order_type == POSITION_TYPE_SELL && ask <= virtual_tp); if (!tp_hit) continue; - // Calculate expected breakeven SL double expected_sl = (order_type == POSITION_TYPE_BUY) ? entry + buffer_price : entry - buffer_price; - // Skip if SL already set to breakeven if (NormalizeDouble(sl, digits) == NormalizeDouble(expected_sl, digits)) continue; - - // --- Set breakeven SL set_breakeven_sl_for_ticket(symbol, ticket, order_type, entry, sl, tp, digits, buffer_price, true); } } // --------------------------------------------------------------------- -// TRAILING STOP ATR LOGIC +// Sets SL for a single trade to breakeven, optionally removes TP. // --------------------------------------------------------------------- -// This function updates the stop-loss of runner trades based on ATR. -// It only applies to trades with the given magic number and symbol. +void AdjustPosition::set_breakeven_sl_for_ticket(string symbol, ulong ticket, long order_type, double entry_price, + double current_sl, double current_tp, int digits, + double buffer_price, bool remove_tp) { + double breakeven_sl = (order_type == POSITION_TYPE_BUY) + ? entry_price + buffer_price + : entry_price - buffer_price; + + if ((order_type == POSITION_TYPE_BUY && current_sl >= breakeven_sl) || + (order_type == POSITION_TYPE_SELL && current_sl <= breakeven_sl)) return; + + MqlTradeRequest request = {}; + MqlTradeResult result; + + request.action = TRADE_ACTION_SLTP; + request.symbol = symbol; + request.position = ticket; + request.sl = NormalizeDouble(breakeven_sl, digits); + request.tp = remove_tp ? 0.0 : current_tp; + request.magic = (int) PositionGetInteger(POSITION_MAGIC); + + if (!OrderSend(request, result)) + Print("Failed to adjust runner: ", symbol, ". Error: ", result.retcode); + else if (remove_tp) + Print("Runner upgraded to trailing: SL at breakeven, TP removed for ", symbol); +} + +// --------------------------------------------------------------------- +// Sets a fixed SL price for all runner trades. +// --------------------------------------------------------------------- +void AdjustPosition::set_fixed_sl(string symbol, int runner_magic_no, double fixed_sl_price) { + for (int i = PositionsTotal() - 1; i >= 0; i--) { + ulong ticket = PositionGetTicket(i); + if (!PositionSelectByTicket(ticket)) continue; + if (PositionGetString(POSITION_SYMBOL) != symbol) continue; + if ((int) PositionGetInteger(POSITION_MAGIC) != runner_magic_no) continue; + + double current_sl = PositionGetDouble(POSITION_SL); + double current_tp = PositionGetDouble(POSITION_TP); + if (current_sl == fixed_sl_price) continue; + + MqlTradeRequest request = {}; + MqlTradeResult result; + + request.action = TRADE_ACTION_SLTP; + request.symbol = symbol; + request.position = ticket; + request.sl = fixed_sl_price; + request.tp = current_tp; + request.magic = runner_magic_no; + + if (!OrderSend(request, result)) + Print("Failed to set fixed SL for runner on ", symbol, ". Error: ", result.retcode); + } +} + +// --------------------------------------------------------------------- +// Applies a fixed-point trailing stop to runner trades. +// --------------------------------------------------------------------- +void AdjustPosition::set_trailing_sl(string symbol, int runner_magic_no, double sl_offset_points) { + int digits = (int) SymbolInfoInteger(symbol, SYMBOL_DIGITS); + double offset = sl_offset_points * _Point; + + for (int i = PositionsTotal() - 1; i >= 0; i--) { + ulong ticket = PositionGetTicket(i); + if (!PositionSelectByTicket(ticket)) continue; + if (PositionGetString(POSITION_SYMBOL) != symbol) continue; + if ((int) PositionGetInteger(POSITION_MAGIC) != runner_magic_no) continue; + + long type = PositionGetInteger(POSITION_TYPE); + double current_sl = PositionGetDouble(POSITION_SL); + double current_tp = PositionGetDouble(POSITION_TP); + double price = (type == POSITION_TYPE_BUY) + ? SymbolInfoDouble(symbol, SYMBOL_BID) + : SymbolInfoDouble(symbol, SYMBOL_ASK); + + double sl = (type == POSITION_TYPE_BUY) ? price - offset : price + offset; + if ((type == POSITION_TYPE_BUY && sl <= current_sl) || + (type == POSITION_TYPE_SELL && sl >= current_sl)) continue; + + MqlTradeRequest request = {}; + MqlTradeResult result; + + request.action = TRADE_ACTION_SLTP; + request.symbol = symbol; + request.position = ticket; + request.sl = NormalizeDouble(sl, digits); + request.tp = current_tp; + request.magic = runner_magic_no; + + if (!OrderSend(request, result)) + Print("Failed to update trailing SL for runner on ", symbol, ". Error: ", result.retcode); + } +} + +// --------------------------------------------------------------------- +// Applies an ATR-based trailing stop to runner trades. // // Parameters: -// - symbol : The trading symbol. +// - symbol : Trading symbol. // - _magic_number : Magic number to identify trades. // - tf : Timeframe used for ATR calculation. // - activation_mult: Multiplier to determine when to activate trailing. @@ -132,8 +223,8 @@ void AdjustPosition::set_breakeven_if_profit_target_hit(string symbol, int runne // - Trailing starts only after activation distance is reached. // - SL is only updated if it moves closer to price (i.e., improves). // --------------------------------------------------------------------- -void AdjustPosition::trailing_stop_atr(string symbol, int _magic_number, ENUM_TIMEFRAMES tf, double activation_mult, double trail_mult, int atr_period, bool use_bar_close) { - +void AdjustPosition::trailing_stop_atr(string symbol, int _magic_number, ENUM_TIMEFRAMES tf, double activation_mult, + double trail_mult, int atr_period, bool use_bar_close) { double atr = atr_manager.get_atr_value(symbol, tf, atr_period); if (atr == EMPTY_VALUE) return; @@ -151,16 +242,19 @@ void AdjustPosition::trailing_stop_atr(string symbol, int _magic_number, ENUM_TI double entry = PositionGetDouble(POSITION_PRICE_OPEN); double sl = PositionGetDouble(POSITION_SL); double price = use_bar_close ? iClose(symbol, tf, 1) : (type == POSITION_TYPE_BUY ? bid : ask); + double trail_distance = atr * trail_mult; double activation_distance = atr * activation_mult; - bool should_trail = (type == POSITION_TYPE_BUY && price >= entry + activation_distance) || (type == POSITION_TYPE_SELL && price <= entry - activation_distance); + bool should_trail = (type == POSITION_TYPE_BUY && price >= entry + activation_distance) || + (type == POSITION_TYPE_SELL && price <= entry - activation_distance); if (!should_trail) continue; double new_sl = (type == POSITION_TYPE_BUY) ? price - trail_distance : price + trail_distance; new_sl = NormalizeDouble(new_sl, digits); - if ((type == POSITION_TYPE_BUY && sl >= new_sl) || (type == POSITION_TYPE_SELL && sl <= new_sl)) continue; + if ((type == POSITION_TYPE_BUY && sl >= new_sl) || + (type == POSITION_TYPE_SELL && sl <= new_sl)) continue; if (!trade.PositionModify(ticket, new_sl, PositionGetDouble(POSITION_TP))) PrintFormat("Trailing SL update failed for %s ticket=%d", symbol, ticket); @@ -168,98 +262,3 @@ void AdjustPosition::trailing_stop_atr(string symbol, int _magic_number, ENUM_TI PrintFormat("Trailing SL updated: %s ticket=%d new SL=%.5f", symbol, ticket, new_sl); } } - -// --------------------------------------------------------- -void AdjustPosition::set_breakeven_sl_for_ticket(string symbol, ulong ticket, long order_type, double entry_price, double current_sl, - double current_tp, int digits, double buffer_price, bool remove_tp) { - double breakeven_sl = (order_type == POSITION_TYPE_BUY) ? entry_price + buffer_price : entry_price - buffer_price; - - if ((order_type == POSITION_TYPE_BUY && current_sl >= breakeven_sl) || (order_type == POSITION_TYPE_SELL && current_sl <= breakeven_sl)) - return; - - MqlTradeRequest request = {}; - MqlTradeResult result; - - request.action = TRADE_ACTION_SLTP; - request.symbol = symbol; - request.position = ticket; - request.sl = NormalizeDouble(breakeven_sl, digits); - request.tp = remove_tp ? 0.0 : current_tp; - request.magic = (int) PositionGetInteger(POSITION_MAGIC); - - if (!OrderSend(request, result)) { - Print("Failed to adjust runner: ", symbol, ". Error: ", result.retcode); - } else if (remove_tp) { - Print("Runner upgraded to trailing: SL at breakeven, TP removed for ", symbol); - } -} - -// --------------------------------------------------------- - -void AdjustPosition::set_fixed_sl(string symbol, int runner_magic_no, double fixed_sl_price) { - for (int i = PositionsTotal() - 1; i >= 0; i--) { - ulong ticket = PositionGetTicket(i); - if (!PositionSelectByTicket(ticket)) continue; - if (PositionGetString(POSITION_SYMBOL) != symbol) continue; - if ((int) PositionGetInteger(POSITION_MAGIC) != runner_magic_no) continue; - - double current_sl = PositionGetDouble(POSITION_SL); - double current_tp = PositionGetDouble(POSITION_TP); - - if (current_sl == fixed_sl_price) continue; - - MqlTradeRequest request = {}; - MqlTradeResult result; - - request.action = TRADE_ACTION_SLTP; - request.symbol = symbol; - request.position = ticket; - request.sl = fixed_sl_price; - request.tp = current_tp; - request.magic = runner_magic_no; - - if (!OrderSend(request, result)) { - Print("Failed to set fixed SL for runner on ", symbol, ". Error: ", result.retcode); - } - } -} - -// --------------------------------------------------------- - -void AdjustPosition::set_trailing_sl(string symbol, int runner_magic_no, double sl_offset_points) { - int digits = (int) SymbolInfoInteger(symbol, SYMBOL_DIGITS); - double price = 0; - double sl = 0; - double offset = sl_offset_points * _Point; - - for (int i = PositionsTotal() - 1; i >= 0; i--) { - ulong ticket = PositionGetTicket(i); - if (!PositionSelectByTicket(ticket)) continue; - if (PositionGetString(POSITION_SYMBOL) != symbol) continue; - if ((int) PositionGetInteger(POSITION_MAGIC) != runner_magic_no) continue; - - long type = PositionGetInteger(POSITION_TYPE); - double current_sl = PositionGetDouble(POSITION_SL); - double current_tp = PositionGetDouble(POSITION_TP); - - price = (type == POSITION_TYPE_BUY) ? SymbolInfoDouble(symbol, SYMBOL_BID) : SymbolInfoDouble(symbol, SYMBOL_ASK); - - sl = (type == POSITION_TYPE_BUY) ? price - offset : price + offset; - - if ((type == POSITION_TYPE_BUY && sl <= current_sl) || (type == POSITION_TYPE_SELL && sl >= current_sl)) continue; - - MqlTradeRequest request = {}; - MqlTradeResult result; - - request.action = TRADE_ACTION_SLTP; - request.symbol = symbol; - request.position = ticket; - request.sl = NormalizeDouble(sl, digits); - request.tp = current_tp; - request.magic = runner_magic_no; - - if (!OrderSend(request, result)) { - Print("Failed to update trailing SL for runner on ", symbol, ". Error: ", result.retcode); - } - } -} diff --git a/Orders/CalculatePositionData.mqh b/Orders/CalculatePositionData.mqh index 8e6bc67..cca904d 100644 --- a/Orders/CalculatePositionData.mqh +++ b/Orders/CalculatePositionData.mqh @@ -3,6 +3,12 @@ #include #include +// --------------------------------------------------------------------- +// CLASS: CalculatePositionData +// --------------------------------------------------------------------- +// Provides core logic for computing stop loss, take profit, lot size, +// and trading costs based on symbol, price, and risk parameters. +// --------------------------------------------------------------------- class CalculatePositionData : public CObject { protected: CTrade trade; @@ -20,8 +26,20 @@ public: double calculate_trading_cost(string symbol, ulong ticket); }; -//+------------------------------------------------------------------+ - +// --------------------------------------------------------------------- +// Calculates stop loss based on selected method. +// +// Parameters: +// - symbol : Symbol for the trade. +// - price : Entry price. +// - order_side: 1 = Buy, 2 = Sell. +// - mode_sl : SL method ("NO_STOPLOSS", "SL_FIXED_PIPS", etc). +// - sl_var : SL parameter (pips, %, ATR multiplier, or absolute). +// - atr_tf : Timeframe for ATR. +// +// Returns: +// - Calculated SL price, or 0 if invalid. +// --------------------------------------------------------------------- double CalculatePositionData::calculate_stoploss(string symbol, double price, int order_side, string mode_sl, double sl_var, ENUM_TIMEFRAMES atr_tf) { double sl = 0; @@ -55,8 +73,21 @@ double CalculatePositionData::calculate_stoploss(string symbol, double price, in return sl; } -//+------------------------------------------------------------------+ - +// --------------------------------------------------------------------- +// Calculates take profit based on selected method. +// +// Parameters: +// - symbol : Symbol for the trade. +// - price : Entry price. +// - stoploss : SL value (used in TP/SL ratio mode). +// - order_side: 1 = Buy, -1 = Sell. +// - mode_tp : TP method ("NO_TAKE_PROFIT", "TP_FIXED_PIPS", etc). +// - tp_var : TP parameter (pips, %, ATR multiplier, SL multiple). +// - atr_tf : Timeframe for ATR. +// +// Returns: +// - Calculated TP price, or 0 if invalid. +// --------------------------------------------------------------------- double CalculatePositionData::calculate_take_profit(string symbol, double price, double stoploss, int order_side, string mode_tp, double tp_var, ENUM_TIMEFRAMES atr_tf) { double tp = 0; @@ -96,8 +127,19 @@ double CalculatePositionData::calculate_take_profit(string symbol, double price, return tp; } -//+------------------------------------------------------------------+ - +// --------------------------------------------------------------------- +// Calculates lot size based on selected lot mode. +// +// Parameters: +// - symbol : Symbol for the trade. +// - sl_distance: SL distance in points. +// - price : Current price. +// - mode_lot : Lot mode ("LOT_MODE_FIXED", "LOT_MODE_PCT_RISK", etc). +// - lot_var : Value for lot calculation. +// +// Returns: +// - Computed lot size (rounded and validated). +// --------------------------------------------------------------------- double CalculatePositionData::calculate_lots(string symbol, double sl_distance, double price, string mode_lot, double lot_var) { double lots = 0; double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE); @@ -125,8 +167,16 @@ double CalculatePositionData::calculate_lots(string symbol, double sl_distance, return lots; } -//+------------------------------------------------------------------+ - +// --------------------------------------------------------------------- +// Validates and adjusts lot size to symbol constraints. +// +// Parameters: +// - lots : Input/output lot size. +// - symbol : Trading symbol. +// +// Returns: +// - true if lots are valid after correction. +// --------------------------------------------------------------------- bool CalculatePositionData::check_lots(double& lots, string symbol) { double min = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); double max = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); @@ -146,8 +196,17 @@ bool CalculatePositionData::check_lots(double& lots, string symbol) { return true; } -//+------------------------------------------------------------------+ - +// --------------------------------------------------------------------- +// Normalizes price to the nearest valid tick size. +// +// Parameters: +// - price : Raw price. +// - normalizedPrice: Output normalized price. +// - symbol : Trading symbol. +// +// Returns: +// - true if successful, false if tick size lookup failed. +// --------------------------------------------------------------------- bool CalculatePositionData::normalise_price(double price, double& normalizedPrice, string symbol) { double tick_size; if (!SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE, tick_size)) { @@ -159,17 +218,3 @@ bool CalculatePositionData::normalise_price(double price, double& normalizedPric normalizedPrice = NormalizeDouble(MathRound(price / tick_size) * tick_size, digits); return true; } - -//+------------------------------------------------------------------+ - -// double CalculatePositionData::calculate_trading_cost(string symbol, ulong ticket) { -// position.SelectByTicket(ticket); - -// double swap = PositionGetDouble(POSITION_SWAP); -// double commission = PositionGetDouble(POSITION_COMMISSION); -// double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE); -// double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE); -// double lots = PositionGetDouble(POSITION_VOLUME); - -// return -1.0 * ((commission + swap) / tick_value * tick_size / lots); -// } diff --git a/Orders/EntryOrders.mqh b/Orders/EntryOrders.mqh index e877b40..742fe49 100644 --- a/Orders/EntryOrders.mqh +++ b/Orders/EntryOrders.mqh @@ -28,9 +28,19 @@ public: bool open_runner_sell_order_with_virtual_tp(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var, long _magic_number); - }; +// --------------------------------------------------------------------- +// Counts open positions by symbol, side, and magic number. +// +// Parameters: +// - symbol : Symbol to check. +// - order_side : 1 = Buy, 2 = Sell, 0 = Any. +// - _magic_number : Magic number to filter. +// +// Returns: +// - Number of matching open positions. +// --------------------------------------------------------------------- int EntryOrders::count_open_positions(string symbol, int order_side, long _magic_number) { int count = 0; for (int i = PositionsTotal() - 1; i >= 0; i--) { @@ -45,6 +55,24 @@ int EntryOrders::count_open_positions(string symbol, int order_side, long _magic return count; } +// --------------------------------------------------------------------- +// Opens a market BUY position. +// +// Parameters: +// - symbol : Symbol to trade. +// - condition : If false, trade will not execute. +// - atr_period : Timeframe for ATR-based SL/TP. +// - _sl_mode : SL calculation method. +// - sl_var : SL variable (e.g., pips or ATR multiplier). +// - _tp_mode : TP calculation method. +// - tp_var : TP variable. +// - _lot_mode : Lot calculation method. +// - lot_var : Lot sizing variable. +// - _magic_number : Magic number for trade. +// +// Returns: +// - True if trade was placed successfully. +// --------------------------------------------------------------------- bool EntryOrders::open_buy_orders(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var, long _magic_number) { if (!condition) return false; @@ -68,6 +96,24 @@ bool EntryOrders::open_buy_orders(string symbol, bool condition, ENUM_TIMEFRAMES return result; } +// --------------------------------------------------------------------- +// Opens a market SELL position. +// +// Parameters: +// - symbol : Symbol to trade. +// - condition : If false, trade will not execute. +// - atr_period : Timeframe for ATR-based SL/TP. +// - _sl_mode : SL calculation method. +// - sl_var : SL variable (e.g., pips or ATR multiplier). +// - _tp_mode : TP calculation method. +// - tp_var : TP variable. +// - _lot_mode : Lot calculation method. +// - lot_var : Lot sizing variable. +// - _magic_number : Magic number for trade. +// +// Returns: +// - True if trade was placed successfully. +// --------------------------------------------------------------------- bool EntryOrders::open_sell_orders(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var, long _magic_number) { if (!condition) return false; @@ -91,6 +137,26 @@ bool EntryOrders::open_sell_orders(string symbol, bool condition, ENUM_TIMEFRAME return result; } +// --------------------------------------------------------------------- +// Opens a pending BUY STOP order. +// +// Parameters: +// - symbol : Symbol to trade. +// - condition : If false, order will not be placed. +// - entry_price : Trigger price for Buy Stop. +// - expiration : Expiration time for pending order. +// - atr_period : Timeframe for ATR-based SL/TP. +// - _sl_mode : SL calculation method. +// - sl_var : SL variable. +// - _tp_mode : TP calculation method. +// - tp_var : TP variable. +// - _lot_mode : Lot calculation method. +// - lot_var : Lot sizing variable. +// - _magic_number : Magic number for order. +// +// Returns: +// - True if order was placed successfully. +// --------------------------------------------------------------------- bool EntryOrders::open_buy_stop_order(string symbol, bool condition, double entry_price, datetime expiration, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var, long _magic_number) { @@ -114,6 +180,26 @@ bool EntryOrders::open_buy_stop_order(string symbol, bool condition, double entr return result; } +// --------------------------------------------------------------------- +// Opens a pending SELL STOP order. +// +// Parameters: +// - symbol : Symbol to trade. +// - condition : If false, order will not be placed. +// - entry_price : Trigger price for Sell Stop. +// - expiration : Expiration time for pending order. +// - atr_period : Timeframe for ATR-based SL/TP. +// - _sl_mode : SL calculation method. +// - sl_var : SL variable. +// - _tp_mode : TP calculation method. +// - tp_var : TP variable. +// - _lot_mode : Lot calculation method. +// - lot_var : Lot sizing variable. +// - _magic_number : Magic number for order. +// +// Returns: +// - True if order was placed successfully. +// --------------------------------------------------------------------- bool EntryOrders::open_sell_stop_order(string symbol, bool condition, double entry_price, datetime expiration, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var, long _magic_number) { @@ -137,6 +223,24 @@ bool EntryOrders::open_sell_stop_order(string symbol, bool condition, double ent return result; } +// --------------------------------------------------------------------- +// Opens a market BUY runner with virtual TP in comment. +// +// Parameters: +// - symbol : Symbol to trade. +// - condition : If false, trade will not execute. +// - atr_period : Timeframe for ATR-based SL. +// - _sl_mode : SL calculation method. +// - sl_var : SL variable. +// - _tp_mode : TP calculation method. +// - tp_var : TP variable (used for virtual TP). +// - _lot_mode : Lot calculation method. +// - lot_var : Lot sizing variable. +// - _magic_number : Magic number for trade. +// +// Returns: +// - True if trade was placed successfully. +// --------------------------------------------------------------------- bool EntryOrders::open_runner_buy_order_with_virtual_tp(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var, long _magic_number) { @@ -161,6 +265,24 @@ bool EntryOrders::open_runner_buy_order_with_virtual_tp(string symbol, bool cond return result; } +// --------------------------------------------------------------------- +// Opens a market SELL runner with virtual TP in comment. +// +// Parameters: +// - symbol : Symbol to trade. +// - condition : If false, trade will not execute. +// - atr_period : Timeframe for ATR-based SL. +// - _sl_mode : SL calculation method. +// - sl_var : SL variable. +// - _tp_mode : TP calculation method. +// - tp_var : TP variable (used for virtual TP). +// - _lot_mode : Lot calculation method. +// - lot_var : Lot sizing variable. +// - _magic_number : Magic number for trade. +// +// Returns: +// - True if trade was placed successfully. +// --------------------------------------------------------------------- bool EntryOrders::open_runner_sell_order_with_virtual_tp(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var, long _magic_number) { diff --git a/Orders/ExitOrders.mqh b/Orders/ExitOrders.mqh index f0ed9f1..e994738 100644 --- a/Orders/ExitOrders.mqh +++ b/Orders/ExitOrders.mqh @@ -3,7 +3,7 @@ #include class ExitOrders { - protected: +protected: CTrade trade; TimeZones tz; CalculatePositionData calc; @@ -12,15 +12,27 @@ class ExitOrders { long position_open_time; long first_allowed_close_time; - public: +public: bool close_buy_orders(string symbol, bool condition, int close_bars, ENUM_TIMEFRAMES close_bar_period, long _magic_number); bool close_sell_orders(string symbol, bool condition, int close_bars, ENUM_TIMEFRAMES close_bar_period, long _magic_number); bool daily_timed_exit(string symbol, datetime exit_time, int delay_days, long _magic_number); - bool daily_timed_profit_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, string exit_time, string cw_tzone, int delay_days, - long _magic_number); + bool daily_timed_profit_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, string exit_time, string cw_tzone, int delay_days, long _magic_number); bool first_profitable_close_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, long _magic_number); }; +// --------------------------------------------------------------------- +// Closes BUY positions on condition + after a number of bars (if non 0) . +// +// Parameters: +// - symbol : Symbol to evaluate positions for. +// - condition : If true, closes position immediately. +// - close_bars : Minimum number of bars before auto close. +// - close_bar_period : Timeframe to count bars on. +// - _magic_number : Magic number to identify the trade group. +// +// Returns: +// - True after evaluation and any attempted closes. +// --------------------------------------------------------------------- bool ExitOrders::close_buy_orders(string symbol, bool condition, int close_bars, ENUM_TIMEFRAMES close_bar_period, long _magic_number) { for (int i = PositionsTotal() - 1; i >= 0; i--) { posTicket = PositionGetTicket(i); @@ -38,6 +50,19 @@ bool ExitOrders::close_buy_orders(string symbol, bool condition, int close_bars, return true; } +// --------------------------------------------------------------------- +// Closes SELL positions on condition + after a number of bars (if non 0) . +// +// Parameters: +// - symbol : Symbol to evaluate positions for. +// - condition : If true, closes position immediately. +// - close_bars : Minimum number of bars before auto close. +// - close_bar_period : Timeframe to count bars on. +// - _magic_number : Magic number to identify the trade group. +// +// Returns: +// - True after evaluation and any attempted closes. +// --------------------------------------------------------------------- bool ExitOrders::close_sell_orders(string symbol, bool condition, int close_bars, ENUM_TIMEFRAMES close_bar_period, long _magic_number) { for (int i = PositionsTotal() - 1; i >= 0; i--) { posTicket = PositionGetTicket(i); @@ -55,6 +80,18 @@ bool ExitOrders::close_sell_orders(string symbol, bool condition, int close_bars return true; } +// --------------------------------------------------------------------- +// Closes position after a fixed exit time and delay in days. +// +// Parameters: +// - symbol : Symbol to evaluate. +// - exit_time : Time of day when exit is permitted. +// - delay_days : Number of full days before close allowed. +// - _magic_number : Magic number to identify the trade group. +// +// Returns: +// - True after evaluation and any attempted closes. +// --------------------------------------------------------------------- bool ExitOrders::daily_timed_exit(string symbol, datetime exit_time, int delay_days, long _magic_number) { for (int i = PositionsTotal() - 1; i >= 0; i--) { posTicket = PositionGetTicket(i); @@ -73,6 +110,20 @@ bool ExitOrders::daily_timed_exit(string symbol, datetime exit_time, int delay_d return true; } +// --------------------------------------------------------------------- +// Closes a position only if it's profitable after a given time. +// +// Parameters: +// - symbol : Symbol to evaluate. +// - close_bar_period : Bar timeframe for bar-close evaluation. +// - exit_time : Time of day when profit exit is checked. +// - cw_tzone : Clockwork time zone for exit conversion. +// - delay_days : Minimum days to wait before closing. +// - _magic_number : Magic number to identify the trade group. +// +// Returns: +// - True after evaluation and any attempted closes. +// --------------------------------------------------------------------- bool ExitOrders::daily_timed_profit_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, string exit_time, string cw_tzone, int delay_days, long _magic_number) { for (int i = PositionsTotal() - 1; i >= 0; i--) { @@ -108,6 +159,17 @@ bool ExitOrders::daily_timed_profit_exit(string symbol, ENUM_TIMEFRAMES close_ba return true; } +// --------------------------------------------------------------------- +// Closes position on first profitable bar after one bar completes. +// +// Parameters: +// - symbol : Symbol to evaluate. +// - close_bar_period : Timeframe for bar-close evaluation. +// - _magic_number : Magic number to identify the trade group. +// +// Returns: +// - True after evaluation and any attempted closes. +// --------------------------------------------------------------------- bool ExitOrders::first_profitable_close_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, long _magic_number) { position_open_time = PositionGetInteger(POSITION_TIME); first_allowed_close_time = position_open_time + PeriodSeconds(close_bar_period); diff --git a/Orders/OrderTracker.mqh b/Orders/OrderTracker.mqh index 7e198c3..cc75fbd 100644 --- a/Orders/OrderTracker.mqh +++ b/Orders/OrderTracker.mqh @@ -12,6 +12,18 @@ class OrderTracker { int count_pending_orders(string symbol, ENUM_ORDER_TYPE order_type, long magic); }; + +// --------------------------------------------------------------------- +// Counts the number of open BUY or SELL positions for a given symbol. +// +// Parameters: +// - symbol : Trading symbol (e.g., "EURUSD"). +// - order_side : 1 = BUY, 2 = SELL. +// - magic_number : Magic number identifying strategy group. +// +// Returns: +// - Number of matching open positions. +// --------------------------------------------------------------------- int OrderTracker::count_open_positions(string symbol, int order_side, long magic_number) { int count = 0; @@ -32,6 +44,16 @@ int OrderTracker::count_open_positions(string symbol, int order_side, long magic return count; } +// --------------------------------------------------------------------- +// Counts all open positions for a symbol regardless of direction. +// +// Parameters: +// - symbol : Trading symbol. +// - magic_number : Magic number identifying strategy group. +// +// Returns: +// - Total number of matching positions. +// --------------------------------------------------------------------- int OrderTracker::count_all_positions(string symbol, long magic_number) { int count = 0; @@ -46,6 +68,17 @@ int OrderTracker::count_all_positions(string symbol, long magic_number) { return count; } +// --------------------------------------------------------------------- +// Counts pending orders of a specific type for a symbol and magic number. +// +// Parameters: +// - symbol : Trading symbol. +// - order_type : Type of pending order (e.g., ORDER_TYPE_BUY_STOP). +// - magic : Magic number identifying strategy group. +// +// Returns: +// - Number of matching pending orders. +// --------------------------------------------------------------------- int OrderTracker::count_pending_orders(string symbol, ENUM_ORDER_TYPE order_type, long magic) { int count = 0; diff --git a/Orders/StopLogic.mqh b/Orders/StopLogic.mqh index 4be92fb..4225f0b 100644 --- a/Orders/StopLogic.mqh +++ b/Orders/StopLogic.mqh @@ -1,9 +1,20 @@ class StopLogic { - public: +public: double sl_specified_value_switch(string sl_mode, double inp_sl_var, double value); double tp_specified_value_switch(string tp_mode, double inp_tp_var, double value); }; +// --------------------------------------------------------------------- +// Selects stop loss value based on SL mode. +// +// Parameters: +// - sl_mode : Stop loss mode ("SL_SPECIFIED_VALUE", etc). +// - inp_sl_var : User-input SL value (pips, percent, etc). +// - value : Directly specified SL value. +// +// Returns: +// - `value` if SL mode is "SL_SPECIFIED_VALUE", otherwise `inp_sl_var`. +// --------------------------------------------------------------------- double StopLogic::sl_specified_value_switch(string sl_mode, double inp_sl_var, double value) { if (sl_mode == "SL_SPECIFIED_VALUE") { return value; @@ -12,8 +23,19 @@ double StopLogic::sl_specified_value_switch(string sl_mode, double inp_sl_var, d } } +// --------------------------------------------------------------------- +// Selects take profit value based on TP mode. +// +// Parameters: +// - tp_mode : Take profit mode ("TP_SPECIFIED_VALUE", etc). +// - inp_tp_var : User-input TP value (pips, percent, etc). +// - value : Directly specified TP value. +// +// Returns: +// - `value` if TP mode is "TP_SPECIFIED_VALUE", otherwise `inp_tp_var`. +// --------------------------------------------------------------------- double StopLogic::tp_specified_value_switch(string tp_mode, double inp_tp_var, double value) { - if (tp_mode == "SL_SPECIFIED_VALUE") { + if (tp_mode == "TP_SPECIFIED_VALUE") { return value; } else { return inp_tp_var; diff --git a/Orders/TrailingLogic.mqh b/Orders/TrailingLogic.mqh deleted file mode 100644 index c10afc1..0000000 --- a/Orders/TrailingLogic.mqh +++ /dev/null @@ -1,81 +0,0 @@ -#include - -class TrailingLogic { - protected: - CTrade trade; - - public: - void break_even_stop(string symbol, ulong magic_number, int be_trigger_points, int be_puffer); - void nnfx_trailing_stop(string symbol, double sl_var, double tp_var, double atr_value, ulong magic_number); -}; - -void TrailingLogic::break_even_stop(string symbol, ulong magic_number, int be_trigger_points, int be_puffer) { - for (int i = PositionsTotal() - 1; i >= 0; i--) { - if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number) { - int symbol_digits = (int) SymbolInfoInteger(symbol, SYMBOL_DIGITS); - double symbol_point = SymbolInfoDouble(symbol, SYMBOL_POINT); - double ask = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_ASK), symbol_digits); - double bid = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_BID), symbol_digits); - - if (be_trigger_points != 0) { - ulong ticket = PositionGetTicket(i); - if (PositionSelectByTicket(ticket)) { - double position_open_price = PositionGetDouble(POSITION_PRICE_OPEN); - double position_volume = PositionGetDouble(POSITION_VOLUME); - double position_sl = PositionGetDouble(POSITION_SL); - double position_tp = PositionGetDouble(POSITION_TP); - ENUM_POSITION_TYPE position_type = (ENUM_POSITION_TYPE) PositionGetInteger(POSITION_TYPE); - - if (position_type == POSITION_TYPE_BUY && bid > position_open_price + be_trigger_points * symbol_point) { - double sl = NormalizeDouble(position_open_price + be_puffer * symbol_point, symbol_digits); - if (sl > position_sl) { - trade.PositionModify(ticket, sl, position_tp); - Print("-----------------------------------Stop moved to break even"); - } - } - - if (position_type == POSITION_TYPE_SELL && ask < position_open_price - be_trigger_points * symbol_point) { - double sl = NormalizeDouble(position_open_price - be_puffer * symbol_point, symbol_digits); - if (sl < position_sl) { - trade.PositionModify(ticket, sl, position_tp); - Print("-----------------------------------Stop moved to break even"); - } - } - } - } - } - } -} - -void TrailingLogic::nnfx_trailing_stop(string symbol, double sl_var, double tp_var, double atr_value, ulong magic_number) { - for (int i = PositionsTotal() - 1; i >= 0; i--) { - ulong ticket = PositionGetTicket(i); - if (PositionSelectByTicket(ticket)) { - if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number) { - int symbol_digits = (int) SymbolInfoInteger(symbol, SYMBOL_DIGITS); - double symbol_point = SymbolInfoDouble(symbol, SYMBOL_POINT); - double ask = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_ASK), symbol_digits); - double bid = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_BID), symbol_digits); - - double position_open_price = PositionGetDouble(POSITION_PRICE_OPEN); - double position_sl = PositionGetDouble(POSITION_SL); - double position_tp = PositionGetDouble(POSITION_TP); - ENUM_POSITION_TYPE position_type = (ENUM_POSITION_TYPE) PositionGetInteger(POSITION_TYPE); - - if (position_type == POSITION_TYPE_BUY && bid > position_open_price + (atr_value * tp_var)) { - double sl = NormalizeDouble(bid - (atr_value * sl_var), symbol_digits); - if (sl > (position_sl + (atr_value * 0.5))) { - trade.PositionModify(ticket, sl, position_tp); - } - } - - if (position_type == POSITION_TYPE_SELL && ask < position_open_price - (atr_value * tp_var)) { - double sl = NormalizeDouble(ask + (atr_value * sl_var), symbol_digits); - if (sl < (position_sl + (atr_value * 0.5))) { - trade.PositionModify(ticket, sl, position_tp); - } - } - } - } - } -} diff --git a/RiskManagement/DrawdownControl.mqh b/RiskManagement/DrawdownControl.mqh index 862e27a..4152211 100644 --- a/RiskManagement/DrawdownControl.mqh +++ b/RiskManagement/DrawdownControl.mqh @@ -1,11 +1,11 @@ #property library #include -#include +#include class DrawdownControl : public CObject { protected: CTrade trade; - BarUtils bar_utils; + MarketDataUtils m_utils; string data_file; double daily_max_dd_per; @@ -31,6 +31,17 @@ class DrawdownControl : public CObject { double lot_correction_dynamic(double acc_dd_percent, double min_lot_factor, double max_lot_factor); }; +// --------------------------------------------------------------------- +// Initializes the drawdown control with input parameters and reads +// or resets persistent drawdown state. +// +// Parameters: +// - inp_data_file : Filename for drawdown data storage. +// - inp_acc_max_dd_per : Max absolute drawdown percentage. +// - inp_daily_max_dd_per : Max daily drawdown percentage. +// - inp_daily_reset_time : Reset time (e.g., "00:00"). +// - inp_print_statments : Optional flag to print debug info. +// --------------------------------------------------------------------- void DrawdownControl::init_dd_control(string inp_data_file, double inp_acc_max_dd_per, double inp_daily_max_dd_per, string inp_daily_reset_time, bool inp_print_statments = true) { data_file = inp_data_file; @@ -82,6 +93,13 @@ void DrawdownControl::init_dd_control(string inp_data_file, double inp_acc_max_d print_messages(); } +// --------------------------------------------------------------------- +// Checks if current equity has breached the daily drawdown threshold. +// If so, closes all trades and cancels orders. +// +// Returns: +// - true if daily drawdown limit has been reached. +// --------------------------------------------------------------------- bool DrawdownControl::determine_daily_dd_limit() { // Reset max equity at the start of each day: @@ -116,7 +134,19 @@ bool DrawdownControl::determine_daily_dd_limit() { return daily_dd_limit_reached; } -// Reduces lot size as account apporchaes max allowed drawdown limit. +// --------------------------------------------------------------------- +// Calculates a corrected lot multiplier based on account drawdown. +// +// Parameters: +// - acc_equity_start : Starting equity reference. +// - min_lot_factor : Minimum lot scaling factor. +// - max_lot_factor : Maximum lot scaling factor. +// - dynm_lot_factor : Enable trailing dynamic lot logic. +// - dlf_trail_per : Percent buffer for dynamic trail. +// +// Returns: +// - Scaled lot factor between min and max bounds. +// --------------------------------------------------------------------- double DrawdownControl::lot_correction_factor(double acc_equity_start, double min_lot_factor, double max_lot_factor, bool dynm_lot_factor=false, double dlf_trail_per=20) { double account_value = fmin(AccountInfoDouble(ACCOUNT_EQUITY), AccountInfoDouble(ACCOUNT_BALANCE)); @@ -147,7 +177,17 @@ double DrawdownControl::lot_correction_factor(double acc_equity_start, double mi return max_lot_factor; } - +// --------------------------------------------------------------------- +// Computes dynamic lot factor based on trailing equity bounds. +// +// Parameters: +// - acc_dd_percent : Dynamic trailing buffer in percent. +// - min_lot_factor : Minimum lot factor. +// - max_lot_factor : Maximum lot factor. +// +// Returns: +// - Interpolated lot factor. +// --------------------------------------------------------------------- double DrawdownControl::lot_correction_dynamic(double acc_dd_percent, double min_lot_factor, double max_lot_factor) { double account_value = fmin(AccountInfoDouble(ACCOUNT_EQUITY), AccountInfoDouble(ACCOUNT_BALANCE)); @@ -167,7 +207,7 @@ double DrawdownControl::lot_correction_dynamic(double acc_dd_percent, double min } // back-up to file every hour: - if(bar_utils.is_new_bar(_Symbol, PERIOD_H1) == true){ + if(m_utils.is_new_bar(_Symbol, PERIOD_H1) == true){ write_global_var_data(); } diff --git a/Strategy/EntryLogic_nnfx.mqh b/Strategy/EntryLogic_nnfx.mqh deleted file mode 100644 index 1a81a31..0000000 --- a/Strategy/EntryLogic_nnfx.mqh +++ /dev/null @@ -1,105 +0,0 @@ -class EntryState { - public: - int last_trigger_bar_long; - int last_trigger_bar_short; - int last_bl_cross_long; - int last_bl_cross_short; - int last_entry_long; - int last_entry_short; - - // Constructor - EntryState() { - reset(); - } - - void reset() { - last_trigger_bar_long = -1000; - last_trigger_bar_short = -1000; - last_bl_cross_long = -1000; - last_bl_cross_short = -1000; - last_entry_long = -1000; - last_entry_short = -1000; - } - - void update_trigger(bool trig_long, bool trig_short, int curr_bar) { - if (trig_long) last_trigger_bar_long = curr_bar; - if (trig_short) last_trigger_bar_short = curr_bar; - } - - void update_baseline_cross(int curr_bar, double price, double baseline, double prev_price, double prev_baseline) { - if (prev_price < prev_baseline && price > baseline) last_bl_cross_long = curr_bar; - if (prev_price > prev_baseline && price < baseline) last_bl_cross_short = curr_bar; - } - - void update_entry(bool is_long, int curr_bar) { - if (is_long) - last_entry_long = curr_bar; - else - last_entry_short = curr_bar; - } - int get_last_trigger(bool is_long) { - return is_long ? last_trigger_bar_long : last_trigger_bar_short; - } - int get_last_cross(bool is_long) { - return is_long ? last_bl_cross_long : last_bl_cross_short; - } - int get_last_entry(bool is_long) { - return is_long ? last_entry_long : last_entry_short; - } -}; - -// Snapshot of conditions for a potential entry signal -struct EntryContext { - bool trigger; - bool confirm; - bool volume; - bool recent; - bool base_ok; - bool near; - bool far; - int last_entry; - int last_cross; -}; - -// Utility: check if signal occurred recently -bool is_recent(int signal_bar, int curr_bar, int lookback) { - return (curr_bar - signal_bar) < lookback; -} - -// Utility: build current entry condition context -EntryContext build_entry_context(bool is_long, double price, double baseline, double atr, int last_cross, int last_entry, bool trigger, - bool confirm, bool volume, bool recent) { - EntryContext ctx; - ctx.trigger = trigger; - ctx.confirm = confirm; - ctx.volume = volume; - ctx.recent = recent; - ctx.base_ok = is_long ? (price > baseline) : (price < baseline); - ctx.near = MathAbs(price - baseline) <= atr; - ctx.far = MathAbs(price - baseline) > atr; - ctx.last_entry = last_entry; - ctx.last_cross = last_cross; - return ctx; -} - -// Class wrapper for entry logic -class EntryLogic { - public: - bool is_standard_entry(const EntryContext& ctx) { - return ctx.trigger && ctx.confirm && ctx.volume && ctx.recent && ctx.base_ok && ctx.near; - } - - bool is_pullback_entry(const EntryContext& ctx, int curr_bar) { - return (curr_bar - ctx.last_cross <= 2) && ctx.confirm && ctx.volume && ctx.base_ok && ctx.far; - } - - bool is_baseline_cross_entry(const EntryContext& ctx, double prev_price, double prev_baseline, double price, double baseline, - bool is_long) { - bool crossed = is_long ? (prev_price < prev_baseline && price > baseline) : (prev_price > prev_baseline && price < baseline); - return crossed && ctx.confirm && ctx.volume && ctx.near; - } - - bool is_continuation_entry(const EntryContext& ctx, int curr_bar, int look_back) { - return (curr_bar - ctx.last_entry <= look_back) && ctx.last_entry > ctx.last_cross && ctx.trigger && ctx.confirm && ctx.base_ok; - } -}; diff --git a/Strategy/Range/RangeCalculator.mqh b/Strategy/RangeCalculator.mqh similarity index 77% rename from Strategy/Range/RangeCalculator.mqh rename to Strategy/RangeCalculator.mqh index 97b71d3..a6d7fe5 100644 --- a/Strategy/Range/RangeCalculator.mqh +++ b/Strategy/RangeCalculator.mqh @@ -50,7 +50,6 @@ class RangeCalculator : public CObject{ public: void calculate_range(); - double get_range_high(); double get_range_low(); double get_range_mid(); @@ -65,6 +64,18 @@ class RangeCalculator : public CObject{ }; +// --------------------------------------------------------------------- +// Sets the allowed days for range calculation. +// +// Parameters: +// - _inp_sun : Allow Sunday. +// - _inp_mon : Allow Monday. +// - _inp_tue : Allow Tuesday. +// - _inp_wed : Allow Wednesday. +// - _inp_thu : Allow Thursday. +// - _inp_fri : Allow Friday. +// - _inp_sat : Allow Saturday. +// --------------------------------------------------------------------- void RangeCalculator::range_days(bool _inp_sun, bool _inp_mon, bool _inp_tue, bool _inp_wed, bool _inp_thu, bool _inp_fri, bool _inp_sat){ sun = _inp_sun; mon = _inp_mon; @@ -76,6 +87,22 @@ void RangeCalculator::range_days(bool _inp_sun, bool _inp_mon, bool _inp_tue, bo days_initlised = true; } +// --------------------------------------------------------------------- +// Initializes the range parameters. +// +// Parameters: +// - inp_symbol : The symbol for the range. +// - _calc_period : Timeframe for range calculation. +// - t1 : Start time string. +// - t2 : End time string. +// - t3 : Expiry time string. +// - t4 : Close time string. +// - time_zone : Timezone name. +// - plot_range_inp : Whether to plot the range. +// +// Returns: +// - true if initialization was successful; false otherwise. +// --------------------------------------------------------------------- bool RangeCalculator::initilise_range(string inp_symbol, ENUM_TIMEFRAMES _calc_period, string t1, string t2, string t3, string t4, string time_zone, bool plot_range_inp){ inp_r_start_string = t1; inp_timezone = time_zone; @@ -113,7 +140,18 @@ bool RangeCalculator::initilise_range(string inp_symbol, ENUM_TIMEFRAMES _calc_p return true; } - +// --------------------------------------------------------------------- +// Converts time input strings to time deltas for range definition. +// +// Parameters: +// - t1 : Start time string. +// - t2 : End time string. +// - t3 : Expiry time string. +// - t4 : Close time string. +// +// Returns: +// - true if times were converted successfully; false on error. +// --------------------------------------------------------------------- bool RangeCalculator::convert_input_time_strings(string t1, string t2, string t3, string t4){ datetime _t1 = StringToTime(t1); @@ -149,44 +187,93 @@ bool RangeCalculator::convert_input_time_strings(string t1, string t2, string t3 return true; } -// high of the range +// --------------------------------------------------------------------- +// Returns the high value of the current range. +// +// Returns: +// - High price of the range. +// --------------------------------------------------------------------- double RangeCalculator::get_range_high(){ return high; }; -// low of the range +// --------------------------------------------------------------------- +// Returns the low value of the current range. +// +// Returns: +// - Low price of the range. +// --------------------------------------------------------------------- double RangeCalculator::get_range_low(){ return low; }; -// mid of the range +// --------------------------------------------------------------------- +// Returns the mid value of the current range. +// +// Returns: +// - Mid price of the range. +// --------------------------------------------------------------------- double RangeCalculator::get_range_mid(){ return mid; }; - +// --------------------------------------------------------------------- +// Returns the start time of the current range. +// +// Returns: +// - Range start time. +// --------------------------------------------------------------------- datetime RangeCalculator::get_range_start(){ return start_time; }; +// --------------------------------------------------------------------- +// Returns the end time of the current range. +// +// Returns: +// - Range end time. +// --------------------------------------------------------------------- datetime RangeCalculator::get_range_end(){ return end_time; }; + +// --------------------------------------------------------------------- +// Returns the expiration time for range-based orders. +// +// Returns: +// - Order expiration time. +// --------------------------------------------------------------------- datetime RangeCalculator::get_order_expire_time(){ return order_expire_time; }; +// --------------------------------------------------------------------- +// Returns the close time of the current range. +// +// Returns: +// - Range close time. +// --------------------------------------------------------------------- datetime RangeCalculator::get_range_close(){ return close_time; }; -// flag if a high breakout occurred +// --------------------------------------------------------------------- +// Returns the high breakout flag of the current range. +// +// Returns: +// - true if high breakout occurred; false otherwise. +// --------------------------------------------------------------------- bool RangeCalculator::get_range_high_breakout(){ return f_high_breakout; }; -// flag if a low breakout occurred +// --------------------------------------------------------------------- +// Returns the low breakout flag of the current range. +// +// Returns: +// - true if low breakout occurred; false otherwise. +// --------------------------------------------------------------------- bool RangeCalculator::get_range_low_breakout(){ return f_low_breakout; }; diff --git a/Utils/AtrHandleManager.mqh b/Utils/AtrHandleManager.mqh index c9f904b..96307a7 100644 --- a/Utils/AtrHandleManager.mqh +++ b/Utils/AtrHandleManager.mqh @@ -12,7 +12,19 @@ private: AtrEntry cache[]; // internal cache of ATR handles public: - // Get or create an ATR handle for a specific symbol/timeframe/period + + // --------------------------------------------------------------------- + // Returns a valid ATR handle for the given symbol, timeframe, and period. + // Creates and caches the handle if not already available. + // + // Parameters: + // - symbol : Trading symbol (e.g., "EURUSD"). + // - tf : Timeframe (e.g., PERIOD_H1). + // - period : ATR period (e.g., 14). + // + // Returns: + // - The ATR indicator handle, or INVALID_HANDLE if failed. + // --------------------------------------------------------------------- int get_atr_handle(string symbol, ENUM_TIMEFRAMES tf, int period) { for (int i = 0; i < ArraySize(cache); i++) { if (cache[i].symbol == symbol && cache[i].tf == tf && cache[i].period == period) @@ -32,7 +44,18 @@ public: return handle; } - // Get ATR value from buffer (returns EMPTY_VALUE if failure) + // --------------------------------------------------------------------- + // Gets the ATR value for a given symbol, timeframe, and period. + // + // Parameters: + // - symbol : Trading symbol (e.g., "EURUSD"). + // - tf : Timeframe to use (e.g., PERIOD_H1). + // - period : ATR period to calculate. + // - shift : Bar shift to read the value from (default is 1 for last closed bar. NEVER use 0!). + // + // Returns: + // - ATR value at the given shift, or EMPTY_VALUE on failure. + // --------------------------------------------------------------------- double get_atr_value(string symbol, ENUM_TIMEFRAMES tf, int period, int shift = 1) { int handle = get_atr_handle(symbol, tf, period); if (handle == INVALID_HANDLE) { @@ -51,7 +74,13 @@ public: return buffer[0]; } - // Release all handles in the cache + // --------------------------------------------------------------------- + // Releases all cached ATR handles and clears the internal cache. + // + // Logic: + // - Calls IndicatorRelease for each handle. + // - Clears the `cache` array. + // --------------------------------------------------------------------- void release_handles() { for (int i = 0; i < ArraySize(cache); i++) { if (cache[i].handle != INVALID_HANDLE) @@ -60,4 +89,3 @@ public: ArrayResize(cache, 0); } }; - diff --git a/Utils/ChartUtils.mqh b/Utils/ChartUtils.mqh index 79eeaa9..aa5783c 100644 --- a/Utils/ChartUtils.mqh +++ b/Utils/ChartUtils.mqh @@ -1,31 +1,43 @@ #include class ChartUtils : public CObject { - -public: - void draw_line(double value, string name, color clr = clrBlack); + public: + void draw_line(double value, string name, color clr = clrBlack); }; +// --------------------------------------------------------------------- +// Draws or updates a horizontal line on the chart at the given price level. +// +// Parameters: +// - value : Price level at which to draw the line. +// - name : Unique name for the line object. +// - clr : Line color (default is black). +// +// Logic: +// - If the object doesn't exist, it creates a new horizontal line. +// - If the object exists, it moves it to the new price level. +// - Calls ChartRedraw to update the chart visually. +// --------------------------------------------------------------------- void ChartUtils::draw_line(double value, string name, color clr) { - if (ObjectFind(0, name) < 0) { - ResetLastError(); + if (ObjectFind(0, name) < 0) { + ResetLastError(); - if (!ObjectCreate(0, name, OBJ_HLINE, 0, 0, value)) { - Print(__FUNCTION__, ": failed to create a horizontal line! Error code = ", GetLastError()); - return; - } + if (!ObjectCreate(0, name, OBJ_HLINE, 0, 0, value)) { + Print(__FUNCTION__, ": failed to create a horizontal line! Error code = ", GetLastError()); + return; + } - ObjectSetInteger(0, name, OBJPROP_COLOR, clr); - ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID); - ObjectSetInteger(0, name, OBJPROP_WIDTH, 1); - } + ObjectSetInteger(0, name, OBJPROP_COLOR, clr); + ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_SOLID); + ObjectSetInteger(0, name, OBJPROP_WIDTH, 1); + } - ResetLastError(); + ResetLastError(); - if (!ObjectMove(0, name, 0, 0, value)) { - Print(__FUNCTION__, ": failed to move the horizontal line! Error code = ", GetLastError()); - return; - } + if (!ObjectMove(0, name, 0, 0, value)) { + Print(__FUNCTION__, ": failed to move the horizontal line! Error code = ", GetLastError()); + return; + } - ChartRedraw(); + ChartRedraw(); } diff --git a/Utils/ExpertFileHeader.mqh b/Utils/ExpertFileHeader.mqh deleted file mode 100644 index 79a697d..0000000 --- a/Utils/ExpertFileHeader.mqh +++ /dev/null @@ -1,28 +0,0 @@ -#include -#include -#include -#include -#include -CustomMax c_max; -// MyFunctions mf; -// OrderManagment om; -//--- -input LOT_MODE inp_lot_mode = LOT_MODE_PCT_RISK; // Lot Size Mode -input double inp_lot_var = 2; // Lot Size Var -input SL_MODE inp_sl_mode = SL_ATR_MULTIPLE; // Stop-loss Mode -input double inp_sl_var = 1.5; // Stop-loss Var -input TP_MODE inp_tp_mode = TP_ATR_MULTIPLE; // Take-profit Mode -input double inp_tp_var = 1; // Take-Profit Var -string lot_mode = EnumToString(inp_lot_mode); -string sl_mode = EnumToString(inp_sl_mode); -string tp_mode = EnumToString(inp_tp_mode); -input CUSTOM_MAX_TYPE inp_custom_criteria = CM_WIN_PERCENT; -input int inp_opt_min_trades = 0; // 0/off -input MODE_SPLIT_DATA inp_data_split_method = NO_SPLIT; -input int inp_force_opt = 1; -input group "-----------------------------------------" - - - - - diff --git a/Utils/MarketDataUtils.mqh b/Utils/MarketDataUtils.mqh index 5b319db..6bbc175 100644 --- a/Utils/MarketDataUtils.mqh +++ b/Utils/MarketDataUtils.mqh @@ -1,94 +1,148 @@ class MarketDataUtils { -public: - bool is_new_bar(string symbol, ENUM_TIMEFRAMES time_frame, string daily_start_time = "00:10"); - double get_latest_buffer_value(int handle); - double get_buffer_value(int handle, int shift); - double adjusted_point(string symbol); - double get_bid_ask_price(string symbol, int price_side); + public: + bool is_new_bar(string symbol, ENUM_TIMEFRAMES time_frame, string daily_start_time = "00:10"); + double get_latest_buffer_value(int handle); + double get_buffer_value(int handle, int shift); + double adjusted_point(string symbol); + double get_bid_ask_price(string symbol, int price_side); -protected: - datetime previousTimes[]; // Stores last recorded open time per key - string bar_keys[]; // Keys are symbol+TF combinations, e.g. "EURUSD_PERIOD_H1" + protected: + datetime previousTimes[]; // Stores last recorded open time per key + string bar_keys[]; // Keys are symbol+TF combinations, e.g. "EURUSD_PERIOD_H1" }; -// Helper function to find index of a key in an array -int LinearSearch(string &arr[], string target) { - for (int i = 0; i < ArraySize(arr); i++) { - if (arr[i] == target) - return i; - } - return -1; // Not found +// --------------------------------------------------------------------- +// Performs linear search on a string array. +// +// Parameters: +// - arr : Array of strings. +// - target : Target string to find. +// +// Returns: +// - Index of the target, or -1 if not found. +// --------------------------------------------------------------------- +int LinearSearch(string& arr[], string target) { + for (int i = 0; i < ArraySize(arr); i++) { + if (arr[i] == target) return i; + } + return -1; } -// Checks if a new bar has opened on the given timeframe and symbol +// --------------------------------------------------------------------- +// Implementation of is_new_bar. Tracks the open time of the last bar. +// +// Parameters: +// - symbol : Symbol to check. +// - time_frame : Timeframe to check. +// - daily_start_time: Time string for daily bar sync. +// +// Returns: +// - true if a new bar has formed, false otherwise. +// --------------------------------------------------------------------- bool MarketDataUtils::is_new_bar(string symbol, ENUM_TIMEFRAMES time_frame, string daily_start_time) { - datetime bar_open_time = iTime(symbol, time_frame, 0); // Current open time - string key = symbol + "_" + EnumToString(time_frame); + datetime bar_open_time = iTime(symbol, time_frame, 0); // Current open time + string key = symbol + "_" + EnumToString(time_frame); - int idx = LinearSearch(bar_keys, key); - if (idx == -1) { - int new_size = ArraySize(bar_keys) + 1; - ArrayResize(bar_keys, new_size); - ArrayResize(previousTimes, new_size); + int idx = LinearSearch(bar_keys, key); + if (idx == -1) { + int new_size = ArraySize(bar_keys) + 1; + ArrayResize(bar_keys, new_size); + ArrayResize(previousTimes, new_size); - idx = new_size - 1; - bar_keys[idx] = key; - previousTimes[idx] = 0; - } + idx = new_size - 1; + bar_keys[idx] = key; + previousTimes[idx] = 0; + } - if (previousTimes[idx] != bar_open_time) { - // For daily timeframe, wait for specific time (e.g., 00:10) before triggering - if (PeriodSeconds(time_frame) == PeriodSeconds(PERIOD_D1)) { - if (TimeCurrent() > StringToTime(daily_start_time)) { + if (previousTimes[idx] != bar_open_time) { + if (PeriodSeconds(time_frame) == PeriodSeconds(PERIOD_D1)) { + if (TimeCurrent() > StringToTime(daily_start_time)) { + previousTimes[idx] = bar_open_time; + return true; + } + } else { previousTimes[idx] = bar_open_time; return true; - } - } else { - previousTimes[idx] = bar_open_time; - return true; - } - } + } + } - return false; // No new bar + return false; } -// 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. +// --------------------------------------------------------------------- +// Implementation of get_buffer_value. +// +// Parameters: +// - handle : Indicator handle. +// - shift : Shift index for historical bars. +// +// Returns: +// - The buffer value, or EMPTY_VALUE if error. +// --------------------------------------------------------------------- double MarketDataUtils::get_buffer_value(int handle, int shift) { - double val[]; - ArraySetAsSeries(val, true); + double val[]; + ArraySetAsSeries(val, true); - int copied = CopyBuffer(handle, 0, shift, 1, val); - if (copied <= 0) { - Print("CopyBuffer failed: handle=", handle, " shift=", shift); - return EMPTY_VALUE; - } + int copied = CopyBuffer(handle, 0, shift, 1, val); + if (copied <= 0) { + Print("CopyBuffer failed: handle=", handle, " shift=", shift); + return EMPTY_VALUE; + } - if (val[0] == EMPTY_VALUE) { - Print("EMPTY_VALUE returned for buffer at shift=", shift); - return EMPTY_VALUE; - } + if (val[0] == EMPTY_VALUE) { + Print("EMPTY_VALUE returned for buffer at shift=", shift); + return EMPTY_VALUE; + } - return val[0]; + return val[0]; } -// Adjusts the point value for symbol to account for fractional pips (e.g., 5-digit brokers) +// --------------------------------------------------------------------- +// Gets the latest (live) value from buffer (shift = 0). +// +// Parameters: +// - handle : Indicator handle. +// +// Returns: +// - Buffer value at shift 0 or EMPTY_VALUE if failed. +// --------------------------------------------------------------------- +double MarketDataUtils::get_latest_buffer_value(int handle) { + return get_buffer_value(handle, 0); +} + +// --------------------------------------------------------------------- +// Computes adjusted point value considering fractional pip brokers. +// +// Parameters: +// - symbol : Symbol name. +// +// Returns: +// - Adjusted point multiplier. +// --------------------------------------------------------------------- double MarketDataUtils::adjusted_point(string symbol) { - int symbol_digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); - int digits_adjust = (symbol_digits == 3 || symbol_digits == 5) ? 10 : 1; - double point_val = SymbolInfoDouble(symbol, SYMBOL_POINT); - return point_val * digits_adjust; // Adjusted pip value + int symbol_digits = (int) SymbolInfoInteger(symbol, SYMBOL_DIGITS); + int digits_adjust = (symbol_digits == 3 || symbol_digits == 5) ? 10 : 1; + double point_val = SymbolInfoDouble(symbol, SYMBOL_POINT); + return point_val * digits_adjust; } -// Returns current Bid or Ask price for a symbol based on side (1 = Ask, 2 = Bid) +// --------------------------------------------------------------------- +// Returns bid or ask price for a given symbol. +// +// Parameters: +// - symbol : Symbol name. +// - price_side : 1 = Ask, 2 = Bid. +// +// Returns: +// - Price value or 0.0 if input is invalid. +// --------------------------------------------------------------------- double MarketDataUtils::get_bid_ask_price(string symbol, int price_side) { - int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); - double ask = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_ASK), digits); - double bid = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_BID), digits); + int digits = (int) SymbolInfoInteger(symbol, SYMBOL_DIGITS); + double ask = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_ASK), digits); + double bid = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_BID), digits); - if (price_side == 1) return ask; - if (price_side == 2) return bid; + if (price_side == 1) return ask; + if (price_side == 2) return bid; - return 0.0; // Invalid input + return 0.0; } diff --git a/Utils/MultiSymbolSignalTracker.mqh b/Utils/MultiSymbolSignalTracker.mqh index 17d35b3..2bce10f 100644 --- a/Utils/MultiSymbolSignalTracker.mqh +++ b/Utils/MultiSymbolSignalTracker.mqh @@ -1,86 +1,82 @@ #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 +private: + + // --------------------------------------------------------------------- + // Internal struct to associate a symbol with a SignalStateTracker. + // --------------------------------------------------------------------- struct SymbolTracker { - string symbol; // Symbol name (e.g. "EURUSD", "HSI") - SignalStateTracker* tracker; // Pointer to the signal tracker for that symbol + string symbol; + SignalStateTracker* tracker; }; - SymbolTracker trackers[]; // Dynamic array of symbol-tracker pairs + SymbolTracker trackers[]; // Dynamic array of symbol-tracker mappings - // Helper function: Find the index of the given symbol in the array + // --------------------------------------------------------------------- + // Finds the index of the symbol in the tracker 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 + if (trackers[i].symbol == symbol) + return i; } - return -1; // Not found + return -1; } - public: - // Destructor: automatically called when this object is destroyed (e.g. at EA shutdown) +public: + + // --------------------------------------------------------------------- + // Destructor. Cleans up allocated memory when the object is destroyed. + // --------------------------------------------------------------------- ~MultiSymbolSignalTracker() { - clear(); // Clean up memory when done + clear(); } - // Returns the SignalStateTracker for the given symbol. - // If it doesn't exist yet, it creates one and stores it. + // --------------------------------------------------------------------- + // Retrieves the SignalStateTracker instance for a given symbol. + // Creates and stores a new one if it doesn't exist yet. + // + // Parameters: + // - symbol : Symbol for which to retrieve the signal tracker. + // + // Returns: + // - Pointer to the SignalStateTracker instance. + // --------------------------------------------------------------------- SignalStateTracker* get_tracker(const string& symbol) { int idx = find_index(symbol); - if (idx != -1) return trackers[idx].tracker; // Tracker already exists — return it + if (idx != -1) + return trackers[idx].tracker; - // If not found, create a new tracker for this symbol + // Create new tracker 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. + // --------------------------------------------------------------------- + // Clears all SignalStateTracker instances and resets the internal array. + // Should be called in `OnDeinit()` to avoid memory leaks. + // + // Logic: + // - Deletes each dynamically allocated SignalStateTracker. + // - Resets array size to 0. + // --------------------------------------------------------------------- void clear() { for (int i = 0; i < ArraySize(trackers); i++) { - delete trackers[i].tracker; // Manually free each dynamically created tracker + delete trackers[i].tracker; } - ArrayResize(trackers, 0); // Reset array to empty + ArrayResize(trackers, 0); } }; - - -/* --------------------------------------------------------------------------- -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/ResourceManager.mqh b/Utils/ResourceManager.mqh index c59f52f..6144fa0 100644 --- a/Utils/ResourceManager.mqh +++ b/Utils/ResourceManager.mqh @@ -1,44 +1,61 @@ #include -//+------------------------------------------------------------------+ -//| ResourceManager | -//| | -//| Tracks and releases indicator handles (iMA, iRSI, etc.) | -//| Also delegates ATR handle cleanup to an external ATR manager | -//+------------------------------------------------------------------+ +// --------------------------------------------------------------------- +// ResourceManager +// +// Tracks and releases indicator handles (e.g., iMA, iRSI, iCCI). +// Delegates ATR handle management to an external AtrHandleManager instance. +// +// Usage: +// - Register indicator handles via `register_handle()`. +// - Call `release_all_handles()` to release all cached handles. +// --------------------------------------------------------------------- class ResourceManager { public: - // --- Pointer to external ATR manager (set externally) - // Used for releasing any internally cached ATR handles + AtrHandleManager* atr_manager; - // --- Register a new indicator handle to be released later - // Only valid (non-INVALID_HANDLE) handles are stored + // --------------------------------------------------------------------- + // Registers a generic indicator handle to be released later. + // + // Parameters: + // - handle : A valid (non-INVALID_HANDLE) indicator handle. + // + // Logic: + // - If the handle is valid, it is added to an internal list. + // --------------------------------------------------------------------- void register_handle(int handle) { if (handle != INVALID_HANDLE) add_handle(handle); } - // --- Release all tracked resources: + // --------------------------------------------------------------------- + // Releases all tracked indicator resources. + // + // Logic: + // - Releases generic handles tracked via `register_handle()`. + // - Also invokes `atr_manager.release_handles()` if assigned. + // --------------------------------------------------------------------- void release_all_handles() { release_internal_handles(); // ATR manager release_tracked_handles(); // Generic indicator handles } private: - // --- Dynamic array of general indicator handles int handles[]; - // --- Append a valid indicator handle to the internal array - // Used by register_handle() + // --------------------------------------------------------------------- + // Adds a handle to the internal tracking list. + // --------------------------------------------------------------------- void add_handle(int handle) { int size = ArraySize(handles); ArrayResize(handles, size + 1); handles[size] = handle; } - // --- Release all generic indicator handles tracked internally - // This covers iMA, iRSI, iCCI, etc. + // --------------------------------------------------------------------- + // Releases all tracked indicator handles (iMA, iRSI, etc.). + // --------------------------------------------------------------------- void release_tracked_handles() { for (int i = 0; i < ArraySize(handles); i++) { if (handles[i] != INVALID_HANDLE) @@ -47,8 +64,9 @@ private: ArrayFree(handles); } - // --- Release ATR handles via external AtrHandleManager - // No-op if atr_manager is not assigned + // --------------------------------------------------------------------- + // Releases any cached ATR handles using the external AtrHandleManager. + // --------------------------------------------------------------------- void release_internal_handles() { if (atr_manager != NULL) atr_manager.release_handles(); diff --git a/Utils/SignalStateTracker.mqh b/Utils/SignalStateTracker.mqh index 9aca440..602e5c9 100644 --- a/Utils/SignalStateTracker.mqh +++ b/Utils/SignalStateTracker.mqh @@ -1,22 +1,51 @@ class SignalStateTracker { - private: +private: + + // --------------------------------------------------------------------- + // Index of the last long signal detected (default -1000 when unset). + // --------------------------------------------------------------------- int last_signal_long; + + // --------------------------------------------------------------------- + // Index of the last short signal detected (default -1000 when unset). + // --------------------------------------------------------------------- int last_signal_short; - public: - // Constructor initializes both directions to a default "unset" state. +public: + + // --------------------------------------------------------------------- + // Constructor initializes the signal tracker to a reset state. + // + // Logic: + // - Sets both long and short signal indices to -1000. + // --------------------------------------------------------------------- SignalStateTracker() { reset(); } - // Resets the tracked signal bar indexes to an invalid default value (-1000). + // --------------------------------------------------------------------- + // Resets the signal tracker. + // + // Logic: + // - Sets `last_signal_long` and `last_signal_short` to -1000, + // representing no signal recorded. + // --------------------------------------------------------------------- void reset() { last_signal_long = -1000; last_signal_short = -1000; } - // Updates the signal tracker based on trigger presence per bar. - // If signal is detected, sets to 1. If not, increments previous value. + // --------------------------------------------------------------------- + // Updates internal state based on whether long/short signals occurred. + // + // Parameters: + // - signal_long : True if a long signal occurred this bar. + // - signal_short : True if a short signal occurred this bar. + // + // Logic: + // - If a signal is detected, sets the index to 1 (bar 1). + // - Otherwise, increments the previous value if it was positive. + // --------------------------------------------------------------------- void update_signal_tracker(bool signal_long, bool signal_short) { if (signal_long) { last_signal_long = 1; @@ -31,34 +60,68 @@ class SignalStateTracker { } } - // Returns true if a long signal occurred within the last `max_bars` bars. + // --------------------------------------------------------------------- + // Checks if a long signal occurred within the last N bars. + // + // Parameters: + // - max_bars : Number of bars to look back for the signal. + // + // Returns: + // - True if a long signal occurred within `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. + // --------------------------------------------------------------------- + // Checks if a short signal occurred within the last N bars. + // + // Parameters: + // - max_bars : Number of bars to look back for the signal. + // + // Returns: + // - True if a short signal occurred within `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. + // --------------------------------------------------------------------- + // Indicates if any long signal has ever been recorded. + // + // Returns: + // - True if a long signal index is not equal to -1000. + // --------------------------------------------------------------------- bool has_long_signal() const { return last_signal_long != -1000; } - // Returns true if a signal has been recorded for the short direction. + // --------------------------------------------------------------------- + // Indicates if any short signal has ever been recorded. + // + // Returns: + // - True if a short signal index is not equal to -1000. + // --------------------------------------------------------------------- bool has_short_signal() const { return last_signal_short != -1000; } - // Returns the last recorded signal bar index for the long direction. + // --------------------------------------------------------------------- + // Returns the last bar index at which a long signal occurred. + // + // Returns: + // - Integer index representing bars since long signal. + // --------------------------------------------------------------------- int get_long_signal() const { return last_signal_long; } - // Returns the last recorded signal bar index for the short direction. + // --------------------------------------------------------------------- + // Returns the last bar index at which a short signal occurred. + // + // Returns: + // - Integer index representing bars since short signal. + // --------------------------------------------------------------------- int get_short_signal() const { return last_signal_short; } diff --git a/Utils/TimeZones.mqh b/Utils/TimeZones.mqh index fd41c7a..4d1f228 100644 --- a/Utils/TimeZones.mqh +++ b/Utils/TimeZones.mqh @@ -1,144 +1,176 @@ #property library -#include #include +#include -class TimeZones: public CObject{ - - protected: - string dt_s; - int len; - string dt_string; - datetime tC, tGMT, tNY, tLon, tFfm, tMosc, tSyd, tTok; - datetime tz_time; - string tz_date; - datetime time_start; - datetime time_end; - bool is_time; - datetime tGIVEN; - datetime tREQ; - datetime tzt; - datetime tz_req; - double ny_daily_close_protected(string symbol, int shift_days, bool print_data=false); - double required_close; +class TimeZones : public CObject { + protected: + string dt_s; + int len; + string dt_string; + datetime tC, tGMT, tNY, tLon, tFfm, tMosc, tSyd, tTok; + datetime tz_time; + string tz_date; + datetime time_start; + datetime time_end; + bool is_time; + datetime tGIVEN; + datetime tREQ; + datetime tzt; + datetime tz_req; + double required_close; - public: - string get_date_string_from_datetime(datetime dt); - datetime get_timezone_time(string time_zone, bool print_time); - datetime timezone_conversions(string time_zone_known, datetime time_given, string time_zone_required); - double ny_daily_close(string symbol, int shift_days, bool print_data=false); + double ny_daily_close_protected(string symbol, int shift_days, bool print_data = false); + + public: + string get_date_string_from_datetime(datetime dt); + datetime get_timezone_time(string time_zone, bool print_time); + datetime timezone_conversions(string time_zone_known, datetime time_given, string time_zone_required); + double ny_daily_close(string symbol, int shift_days, bool print_data = false); }; -string TimeZones::get_date_string_from_datetime(datetime dt){ - dt_s = TimeToString(dt); - len = StringLen(dt_s); - dt_string = StringSubstr(dt_s, 0, len-5); - return dt_string; +// --------------------------------------------------------------------- +// Converts a datetime to a string excluding seconds. +// +// Parameters: +// - dt : Datetime object. +// +// Returns: +// - A string in the format "yyyy.mm.dd hh:mi". +// --------------------------------------------------------------------- +string TimeZones::get_date_string_from_datetime(datetime dt) { + dt_s = TimeToString(dt); + len = StringLen(dt_s); + dt_string = StringSubstr(dt_s, 0, len - 5); + return dt_string; } +// --------------------------------------------------------------------- +// Gets the current time in the specified time zone. +// +// Parameters: +// - time_zone : One of "NY", "Lon", "Ffm", "Syd", "Mosc", "Tok". +// - print_time : If true, logs various times for debugging. +// +// Returns: +// - Current time in the specified time zone. +// --------------------------------------------------------------------- +datetime TimeZones::get_timezone_time(string time_zone, bool print_time) { + checkTimeOffset(TimeCurrent()); // Adjust DST -datetime TimeZones::get_timezone_time(string time_zone, bool print_time){ - // https://www.mql5.com/en/code/45287 - // https://www.mql5.com/en/articles/9926 - // https://www.mql5.com/en/articles/9929 + tC = TimeCurrent(); + tGMT = TimeCurrent() + OffsetBroker.actOffset; + tNY = tGMT - (NYShift + DST_USD); + tLon = tGMT - (LondonShift + DST_EUR); + tFfm = tGMT - (FfmShift + DST_EUR); + tSyd = tGMT - (SidneyShift + DST_AUD); + tMosc = tGMT - (MoskwaShift + DST_RUS); + tTok = tGMT - (TokyoShift); - checkTimeOffset(TimeCurrent()); // check changes of DST - // cto(); + if (print_time) { + Print("----------------------------------"); + Print("Broker: ", tC); + Print("GMT: ", tGMT); + Print("time in New York: ", tNY); + Print("time in London: ", tLon); + Print("time in Frankfurt: ", tFfm); + Print("time in Sidney: ", tSyd); + Print("time in Moscow: ", tMosc); + Print("time in Tokyo: ", tTok); + } - tC = TimeCurrent(); - tGMT = TimeCurrent() + OffsetBroker.actOffset; // GMT - tNY = tGMT - (NYShift+DST_USD); // time in New York (EST) - tLon = tGMT - (LondonShift+DST_EUR); // time in London - tFfm = tGMT - (FfmShift+DST_EUR); // time in Frankfurt - tSyd = tGMT - (SidneyShift+DST_AUD); // time in Sidney - tMosc = tGMT - (MoskwaShift+DST_RUS); // time in Moscow - tTok = tGMT - (TokyoShift); // time in Tokyo - no DST + if (time_zone == "NY") return tNY; + if (time_zone == "Lon") return tLon; + if (time_zone == "Ffm") return tFfm; + if (time_zone == "Syd") return tSyd; + if (time_zone == "Mosc") return tMosc; + if (time_zone == "Tok") return tTok; - if(print_time==true){ - Print("----------------------------------"); - Print("Broker: ", tC); - Print("GMT: ", tGMT); - Print("time in New York: ", tNY); - Print("time in London: ", tLon); - Print("time in Frankfurt: ", tFfm); - Print("time in Sidney: ", tSyd); - Print("time in Moscow: ", tMosc); - Print("time in Tokyo: ", tTok); - } - - if(time_zone=="NY"){return tNY;} - if(time_zone=="Lon"){return tLon;} - if(time_zone=="Ffm"){return tFfm;} - if(time_zone=="Syd"){return tSyd;} - if(time_zone=="Mosc"){return tMosc;} - if(time_zone=="Tok"){return tTok;} - - return NULL; + return NULL; } +// --------------------------------------------------------------------- +// Converts a datetime from one timezone to another. +// +// Parameters: +// - time_zone_known : Original timezone of the datetime. +// - time_given : The datetime to convert. +// - time_zone_required : Desired output timezone. +// +// Returns: +// - The equivalent datetime in the target timezone. +// --------------------------------------------------------------------- +datetime TimeZones::timezone_conversions(string time_zone_known, datetime time_given, string time_zone_required) { + tGIVEN = time_given; + checkTimeOffset(tGIVEN); // Adjust DST -datetime TimeZones::timezone_conversions(string time_zone_known, datetime time_given, string time_zone_required){ - // https://www.mql5.com/en/code/45287 - // https://www.mql5.com/en/articles/9926 - // https://www.mql5.com/en/articles/9929 + // Step 1: Convert known timezone to GMT + if (time_zone_known == "GMT") tGMT = tGIVEN; + if (time_zone_known == "Broker") tGMT = tGIVEN + OffsetBroker.actOffset; + if (time_zone_known == "NY") tGMT = tGIVEN + (NYShift + DST_USD); + if (time_zone_known == "Lon") tGMT = tGIVEN + (LondonShift + DST_EUR); + if (time_zone_known == "Ffm") tGMT = tGIVEN + (FfmShift + DST_EUR); + if (time_zone_known == "Syd") tGMT = tGIVEN + (SidneyShift + DST_AUD); + if (time_zone_known == "Mosc") tGMT = tGIVEN + (MoskwaShift + DST_RUS); + if (time_zone_known == "Tok") tGMT = tGIVEN + (TokyoShift); - tGIVEN = time_given; //StringToTime(time_given); - - checkTimeOffset(tGIVEN); // check changes of DST + // Step 2: Convert GMT to required timezone + if (time_zone_required == "GMT") tREQ = tGMT; + if (time_zone_required == "Broker") tREQ = tGMT - OffsetBroker.actOffset; + if (time_zone_required == "NY") tREQ = tGMT - (NYShift + DST_USD); + if (time_zone_required == "Lon") tREQ = tGMT - (LondonShift + DST_EUR); + if (time_zone_required == "Ffm") tREQ = tGMT - (FfmShift + DST_EUR); + if (time_zone_required == "Syd") tREQ = tGMT - (SidneyShift + DST_AUD); + if (time_zone_required == "Mosc") tREQ = tGMT - (MoskwaShift + DST_RUS); + if (time_zone_required == "Tok") tREQ = tGMT - (TokyoShift); - // Get GMT: - if(time_zone_known=="GMT" ){tGMT = tGIVEN;} - if(time_zone_known=="Broker" ){tGMT = tGIVEN + OffsetBroker.actOffset;} - if(time_zone_known=="NY" ){tGMT = tGIVEN + (NYShift+DST_USD);} - if(time_zone_known=="Lon" ){tGMT = tGIVEN + (LondonShift+DST_EUR);} - if(time_zone_known=="Ffm" ){tGMT = tGIVEN + (FfmShift+DST_EUR);} - if(time_zone_known=="Syd" ){tGMT = tGIVEN + (SidneyShift+DST_AUD);} - if(time_zone_known=="Mosc" ){tGMT = tGIVEN + (MoskwaShift+DST_RUS);} - if(time_zone_known=="Tok" ){tGMT = tGIVEN + (TokyoShift);} - - // define the required time: - tREQ = NULL; - if(time_zone_required=="GMT" ){tREQ = tGMT;} - if(time_zone_required=="Broker" ){tREQ = tGMT - OffsetBroker.actOffset;} - if(time_zone_required=="NY" ){tREQ = tGMT - (NYShift+DST_USD);} - if(time_zone_required=="Lon" ){tREQ = tGMT - (LondonShift+DST_EUR);} - if(time_zone_required=="Ffm" ){tREQ = tGMT - (FfmShift+DST_EUR);} - if(time_zone_required=="Syd" ){tREQ = tGMT - (SidneyShift+DST_AUD) ;} - if(time_zone_required=="Mosc" ){tREQ = tGMT - (MoskwaShift+DST_RUS);} - if(time_zone_required=="Tok" ){tREQ = tGMT - (TokyoShift);} - - return tREQ; + return tREQ; } -// Calculte NY close time: -double TimeZones::ny_daily_close(string symbol, int shift_days, bool print_data=false){ - required_close = ny_daily_close_protected(symbol, shift_days, print_data); - return required_close; +// --------------------------------------------------------------------- +// Returns the most recent NY daily close price. +// +// Parameters: +// - symbol : Trading symbol. +// - shift_days : How many NY daily closes back to return. +// - print_data : If true, logs debug information. +// +// Returns: +// - The NY close price. +// --------------------------------------------------------------------- +double TimeZones::ny_daily_close(string symbol, int shift_days, bool print_data) { + required_close = ny_daily_close_protected(symbol, shift_days, print_data); + return required_close; } -double TimeZones::ny_daily_close_protected(string symbol, int shift_days, bool print_data=false){ - // Get the brokers times for when NY openend today and tomorrow: - datetime time_5pm = iTime(symbol, PERIOD_D1 , 0) - (PeriodSeconds(PERIOD_H1) * 7); - datetime ny_close_in_brokers_time = timezone_conversions("NY", time_5pm, "Broker"); - datetime ny_close_time = ny_close_in_brokers_time + PeriodSeconds(PERIOD_D1); // ny close tomorrow +// --------------------------------------------------------------------- +// Internal implementation to compute NY daily close price. +// +// Logic: +// - Defines NY close as 5pm NY time = 00:00 broker + 17H back. +// - Adjusts for day shifts if required. +// - Returns the close price of the NY daily session. +// --------------------------------------------------------------------- +double TimeZones::ny_daily_close_protected(string symbol, int shift_days, bool print_data) { + datetime time_5pm = iTime(symbol, PERIOD_D1, 0) - (PeriodSeconds(PERIOD_H1) * 7); + datetime ny_close_in_brokers_time = timezone_conversions("NY", time_5pm, "Broker"); + datetime ny_close_time = ny_close_in_brokers_time + PeriodSeconds(PERIOD_D1); - if(TimeCurrent() StringToTime(daily_start_time)) { - previousTime = bar_open_time; // Update the marker - return true; - } - } else { - // For all non-daily timeframes, treat any change in bar time as new bar - previousTime = bar_open_time; - return true; - } - } - - // No new bar detected - return false; -} - diff --git a/_old/BufferUtils.mqh b/_old/BufferUtils.mqh deleted file mode 100644 index fb3164e..0000000 --- a/_old/BufferUtils.mqh +++ /dev/null @@ -1,43 +0,0 @@ -class BufferUtils { - -public: - double get_latest_buffer_value(int handle); - double get_buffer_value(int handle, int shift); -}; - - -// ---- IMPLEMENTATION BELOW ---- - -/** - * Get the most recent value from the specified indicator buffer. - * - * param handle: Indicator handle (must be valid and previously created) - * return: Most recent buffer value (shift 0), or 0.0 if retrieval fails - */ -double BufferUtils::get_latest_buffer_value(int handle) { - double val[]; - ArraySetAsSeries(val, true); - - if (CopyBuffer(handle, 0, 0, 1, val) == 1) - return val[0]; - - return 0.0; -} - - -/** - * Get a specific historical value from the specified indicator buffer. - * - * param handle: Indicator handle - * param shift: Bar index (0 = current, 1 = previous, etc.) - * return: Buffer value at shift, or 0.0 if retrieval fails - */ -double BufferUtils::get_buffer_value(int handle, int shift) { - double val[]; - ArraySetAsSeries(val, true); - - if (CopyBuffer(handle, 0, shift, 1, val) == 1) - return val[0]; - - return 0.0; -} diff --git a/_old/CalculatePositionData.mqh b/_old/CalculatePositionData.mqh deleted file mode 100644 index 254eda0..0000000 --- a/_old/CalculatePositionData.mqh +++ /dev/null @@ -1,278 +0,0 @@ -#property library -#include -#include -#include - -class CalculatePositionData : public CObject{ - - protected: - CTrade trade; - TimeZones tz; - CPositionInfo position; - MyFunctions mf; - - bool check_lots(double &lots, string symbol); - bool normalise_price(double price, double &normalizedPrice, string symbol); - // double adjusted_point(string symbol); - - public: - - double calculate_stoploss(string symbol, double price, int order_side, string _sl_mode, double sl_var, ENUM_TIMEFRAMES atr_period); - double calculate_take_profit(string symbol, double price, double stoploss, int order_side, string mode_tp, double tp_var, ENUM_TIMEFRAMES atr_period); - double calculate_lots(string symbol, double sl_distance, double price, string mode_lot, double lot_var); - double calculate_trading_cost(string symbol, ulong position_ticket); - -}; - -double CalculatePositionData::calculate_stoploss(string symbol, double price, int order_side, string mode_sl, double sl_var, ENUM_TIMEFRAMES atr_period){ - // order_side int must be 1 for BUY or 2 for - - double sl=0; - - if(mode_sl=="NO_STOPLOSS"){ - sl=0; - } - - if(mode_sl=="SL_BREAKEVEN"){ - // https://www.youtube.com/watch?v=idPulZ3_iR0 - Alert("Not implemented yet yet"); - } - - if(mode_sl=="SL_FIXED_PIPS"){ - // pips/poins = https://www.mql5.com/en/forum/187757 - double adj_point = mf.adjusted_point(symbol); - - if(order_side == 1){ - sl = price - sl_var * adj_point; - if(!normalise_price(sl,sl,symbol)){return false;} - } - if(order_side == 2){ - sl = price + sl_var * adj_point; - if(!normalise_price(sl,sl,symbol)){return false;} - } - } - - if(mode_sl=="SL_FIXED_PERCENT"){ - if(order_side == 1){ - sl = (-1.0 * sl_var * price / 100.00) + price; - if(!normalise_price(sl,sl,symbol)){return false;} - } - if(order_side == 2){ - sl = sl_var * price / 100.00 + price; - if(!normalise_price(sl,sl,symbol)){return false;} - } - } - - if(mode_sl=="SL_ATR_MULTIPLE"){ - - int atr_handle = iATR(symbol,atr_period,14); - double atr[]; - ArraySetAsSeries(atr,true); - CopyBuffer(atr_handle,MAIN_LINE,1,1,atr); - - if(order_side == 1){ - sl = price - (atr[0] * sl_var); - if(!normalise_price(sl,sl,symbol)){return false;} - - } - if(order_side == 2){ - sl = price + (atr[0] * sl_var); - if(!normalise_price(sl,sl,symbol)){return false;} - } - } - - if(mode_sl=="SL_SPECIFIED_VALUE"){ - - double adj_point = mf.adjusted_point(symbol); - - if(order_side == 1){ - - double pip_50_sl = price - 10 * adj_point; - if(sl_var >= pip_50_sl){ - sl = pip_50_sl; - } - else sl = sl_var; - - if(!normalise_price(sl,sl,symbol)){return false;} - } - if(order_side == 2){ - double pip_50_sl = price + 10 * adj_point; - if(sl_var <= pip_50_sl){ - sl = pip_50_sl; - } - else sl = sl_var; - - sl = sl = sl_var; - if(!normalise_price(sl,sl,symbol)){return false;} - } - } - - return sl; -} - -double CalculatePositionData::calculate_take_profit(string symbol, double price, double stoploss, int order_side, string mode_tp, double _tp_var, ENUM_TIMEFRAMES atr_period){ - // order_side int must be 1 for BUY or 2 for SELL - - double tp=0; - - if(mode_tp=="NO_TAKE_PROFIT"){ - tp=0; - } - - if(mode_tp=="TP_FIXED_PIPS"){ - - double adj_point = mf.adjusted_point(symbol); - if(order_side == 1){ - tp = price + _tp_var * adj_point; - if(!normalise_price(tp,tp,symbol)){return false;} - } - if(order_side == 2){ - tp = price - _tp_var * adj_point; - if(!normalise_price(tp,tp,symbol)){return false;} - } - } - - if(mode_tp=="TP_FIXED_PERCENT"){ - if(order_side == 1){ - tp = _tp_var * price / 100.00 + price; - if(!normalise_price(tp,tp,symbol)){return false;} - } - if(order_side == 2){ - tp = (-1 * _tp_var * price / 100.00) + price; - if(!normalise_price(tp,tp,symbol)){return false;} - } - } - - if(mode_tp=="TP_ATR_MULTIPLE"){ - - int atr_handle = iATR(symbol,atr_period,14); - double atr[]; - ArraySetAsSeries(atr,true); - CopyBuffer(atr_handle,MAIN_LINE,1,1,atr); - - if(order_side == 1){ - tp = price + (atr[0] * _tp_var); - if(!normalise_price(tp,tp,symbol)){return false;} - } - if(order_side == 2){ - tp = price - (atr[0] * _tp_var); - if(!normalise_price(tp,tp,symbol)){return false;} - } - } - - if(mode_tp=="TP_SL_MULTIPLE"){ - if(order_side == 1){ - double sl_size = price - stoploss; - tp = price + (_tp_var * sl_size); - if(!normalise_price(tp,tp,symbol)){return false;} - } - if(order_side == 2){ - double sl_size = stoploss - price; - tp = price - (_tp_var * sl_size); - if(!normalise_price(tp,tp,symbol)){return false;} - } - } - - if(mode_tp=="TP_SPECIFIED_VALUE"){ - - if(_tp_var!=0){ - double adj_point = mf.adjusted_point(symbol); - - if(order_side == 1){ - double pip_limit = price + 10 * adj_point; - if(_tp_var <= pip_limit){ - tp = pip_limit; - } - else tp = _tp_var; - - if(!normalise_price(tp,tp,symbol)){return false;} - } - if(order_side == 2){ - double pip_limit = price - 10 * adj_point; - if(_tp_var >= pip_limit){ - tp = pip_limit; - } - else tp = _tp_var; - tp = tp = _tp_var; - if(!normalise_price(tp,tp,symbol)){return false;} - } - } - } - return tp; - -} - -double CalculatePositionData::calculate_lots(string symbol, double sl_distance, double price, string mode_lot, double lot_var){ - - double lots = 0; - double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE); - double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE); - double volume_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); - - double account_value = fmin(fmin(AccountInfoDouble(ACCOUNT_EQUITY),AccountInfoDouble(ACCOUNT_BALANCE)),AccountInfoDouble(ACCOUNT_MARGIN_FREE)); - double risk_money = account_value * lot_var / 100; - - if(mode_lot=="LOT_MODE_FIXED"){ - lots = lot_var; - } - - if(mode_lot=="LOT_MODE_PCT_RISK"){ - double money_lot_step = (sl_distance / tick_size) * tick_value * volume_step; - lots = MathFloor(risk_money/money_lot_step) * volume_step; - } - - if(mode_lot=="LOT_MODE_PCT_ACCOUNT"){ - double money_lot_step = (price / tick_size) * tick_value * volume_step; - lots = MathFloor(risk_money/money_lot_step) * volume_step; - } - - if(!check_lots(lots, symbol)){return false;} - return lots; -} - -bool CalculatePositionData::check_lots(double &lots, string symbol){ - - double min = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MIN); - double max = SymbolInfoDouble(symbol, SYMBOL_VOLUME_MAX); - double step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); - - if(lotsmax){ - Print("Lot size greater than maximum allowed volume. lots:",lots,"max:",max); - return false; - } - - lots = (int)MathFloor(lots/step) * step; - return true; -} - -bool CalculatePositionData::normalise_price(double price, double &normalizedPrice, string symbol){ - double tickSize; - if(!SymbolInfoDouble(symbol,SYMBOL_TRADE_TICK_SIZE,tickSize)){ - Print("Failed to get tick size"); - return false; - } - int symbol_digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); - normalizedPrice = NormalizeDouble(MathRound(price/tickSize)*tickSize, symbol_digits); - return true; -} - -double CalculatePositionData::calculate_trading_cost(string symbol, ulong position_ticket){ - - position.SelectByTicket(position_ticket); - - double swap = PositionGetDouble(POSITION_SWAP); - double commission = PositionGetDouble(POSITION_COMMISSION); - double tick_size = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE); - double tick_value = SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_VALUE); - double lot_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); - double lots = PositionGetDouble(POSITION_VOLUME); - double trading_cost = -1 * ((commission + swap) / tick_value * tick_size / lots); - - return trading_cost; -} \ No newline at end of file diff --git a/_old/MyFunctions.mqh b/_old/MyFunctions.mqh deleted file mode 100644 index f48cbf0..0000000 --- a/_old/MyFunctions.mqh +++ /dev/null @@ -1,211 +0,0 @@ -#property library -#include -#include -#include - -class MyFunctions : public CObject{ - - protected: - CTrade trade; - TradingWindow tw; - datetime previousTime; - datetime bar_open_time; - - public: - void draw_line(double value, string name,color clr); - bool check_indicator_handles(int &indicator_handles[]); - double adjusted_point(string symbol); - double get_bid_ask_price(string symbol, int price_side); - bool is_new_bar(string symbol, ENUM_TIMEFRAMES time_frame, string daily_start_time="00:10"); - bool trade_window(string t1, string t2, string time_zone, bool plot_range_inp=true); - bool in_test_period(MODE_SPLIT_DATA data_period); - void get_white_list(MULTI_SYM_MODE mode, string& DataArray[]); -}; - -void MyFunctions::get_white_list(MULTI_SYM_MODE mode, string& DataArray[]){ - - if(mode==MULTI_SYM_CHART){ - string a[] = {_Symbol}; - ArrayResize(DataArray, ArraySize(a)); - for(int i = 0; i < ArraySize(DataArray); i++){ - DataArray[i]=a[i]; - } - } - if(mode==MULTI_SYM_FX_B5){ - string a[] = {"EURUSD", "AUDNZD", "EURGBP", "AUDCAD", "CHFJPY"}; - ArrayResize(DataArray, ArraySize(a)); - for(int i = 0; i < ArraySize(DataArray); i++){ - DataArray[i]=a[i]; - } - } - if(mode==MULTI_SYM_FX_28){ - string a[] = {"EURUSD","AUDNZD","AUDUSD","AUDJPY","EURCHF","EURGBP","EURJPY","GBPCHF","GBPJPY","GBPUSD","NZDUSD","USDCAD","USDCHF","USDJPY","CADJPY","EURAUD","CHFJPY","EURCAD","AUDCAD","AUDCHF","CADCHF","EURNZD","GBPAUD","GBPCAD","GBPNZD","NZDCAD","NZDCHF","NZDJPY",}; - ArrayResize(DataArray, ArraySize(a)); - for(int i = 0; i < ArraySize(DataArray); i++){ - DataArray[i]=a[i]; - } - } -} - -bool MyFunctions::trade_window(string t1, string t2, string time_zone="Broker", bool plot_range_inp=true){ - bool in_window = tw.define_window(t1, t2, time_zone, plot_range_inp); - return in_window; -} - -//if(!mf.is_new_bar(symbol, PERIOD_D1, "00:06")){return;} -bool MyFunctions::is_new_bar(string symbol, ENUM_TIMEFRAMES time_frame, string daily_start_time="00:10"){ - - bar_open_time = iTime(symbol, time_frame, 0); - if(previousTime!=bar_open_time){ - - if(PeriodSeconds(time_frame)==PeriodSeconds(PERIOD_D1)){ - if(TimeCurrent() > StringToTime(daily_start_time)){ - previousTime=bar_open_time; - return true; - } - } - - else{ - previousTime=bar_open_time; - return true; - } - - } - return false; -} - -//if(!mf.in_test_period(data_split_method){return;} -bool MyFunctions::in_test_period(MODE_SPLIT_DATA data_split_method){ - - string result[]; - string string_tc = TimeToString(TimeCurrent()); - ushort u_sep = StringGetCharacter(".",0); - int split_string = StringSplit(string_tc, u_sep, result); - bool odd_year = int(result[0]) % 2; - bool odd_month = int(result[1]) % 2; - - // get week of the year. rough estimate can be late the first week of jan: - MqlDateTime dt; - TimeToStruct(TimeCurrent(),dt); - int iDay = (dt.day_of_week + 6 ) % 7 + 1; // convert day to standard index (1=Mon,...,7=Sun) - int iWeek = (dt.day_of_year - iDay + 10 ) / 7; // calculate standard week number - - bool odd_week = iWeek % 2; - - - if(data_split_method==NO_SPLIT){ - return true; - } - - if(data_split_method==ODD_YEARS){ - if (odd_year){ - return true; - } - } - - if(data_split_method==EVEN_YEARS){ - if (!odd_year){ - return true; - } - } - - if(data_split_method==ODD_MONTHS){ - if (odd_month){ - return true; - } - } - - if(data_split_method==EVEN_MONTHS){ - - if (!odd_month){ - return true; - } - } - - if(data_split_method==ODD_WEEKS){ - if (odd_week){ - return true; - } - } - - if(data_split_method==EVEN_WEEKS){ - - if (!odd_week){ - return true; - } - } - - return false; -} - -void MyFunctions::draw_line(double value, string name,color clr=clrBlack){ - // EG: - // ArrayResize(bar,1000); - // ArraySetAsSeries(bar, true); - // CopyRates(symbol,PERIOD_CURRENT,1,1000,bar); - // double close = bar[0].close; - // draw_line(close,"CLOSE",clrBlue); - - if(ObjectFind(0,name)<0){ - ResetLastError(); - - if(!ObjectCreate(0,name,OBJ_HLINE,0,0,value)){ - Print(__FUNCTION__,": failed to create a horizontal line! Error code = ",GetLastError()); - return; - } - - ObjectSetInteger(0,name,OBJPROP_COLOR,clr); - ObjectSetInteger(0,name,OBJPROP_STYLE,STYLE_SOLID); - ObjectSetInteger(0,name,OBJPROP_WIDTH,1); - } - - ResetLastError(); - - if(!ObjectMove(0,name,0,0,value)){ - Print(__FUNCTION__,": failed to move the horizontal line! Error code = ",GetLastError()); - return; - } - - ChartRedraw(); -} - -double MyFunctions::adjusted_point(string symbol){ - - int symbol_digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); - int digits_adjust=1; - if(symbol_digits==3 || symbol_digits==5){ - digits_adjust=10; - } - - double symbol_point_val = SymbolInfoDouble(symbol,SYMBOL_POINT); - double m_adjusted_point; - m_adjusted_point = symbol_point_val * digits_adjust; - - return m_adjusted_point; - -} -// price side - 1 for the ask price and 2 for the bid price -double MyFunctions::get_bid_ask_price(string symbol, int price_side){ - - int symbol_digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); - double symbol_point = SymbolInfoDouble(symbol, SYMBOL_POINT); - - double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); - ask = NormalizeDouble(ask, symbol_digits); - - double bid = SymbolInfoDouble(symbol, SYMBOL_BID); - bid = NormalizeDouble(bid, symbol_digits); - - double price = 0; - - if(price_side==1){ - price = ask; - } - - else if(price_side==2){ - price = bid; - } - - return price; - -} \ No newline at end of file diff --git a/_old/OrderManagement.mqh b/_old/OrderManagement.mqh deleted file mode 100644 index 5f5b35a..0000000 --- a/_old/OrderManagement.mqh +++ /dev/null @@ -1,556 +0,0 @@ -#property library -#include -#include -#include -#include -#include -#include -#include - -class OrderManagment : public CObject{ - - protected: - CTrade trade; - TimeZones tz; - CalculatePositionData cpd; - CPositionInfo m_position; - COrderInfo m_order; - - double stop_loss; - double take_profit; - ulong posTicket; - int time_difference; - int total_open_buy_orders; - int total_open_sell_orders; - double current_price; - int total_pos; - long position_open_time; - long first_allowed_close_time; - datetime current_bar_open_time; - - public: - bool open_buy_orders(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var,long magic_number); - bool open_sell_orders(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var,long magic_number); - bool open_nnfx_buy_orders(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var,long magic_number); - bool open_nnfx_sell_orders(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var,long magic_number); - - bool open_buy_stop_order(string symbol, bool condition, double entry_price, datetime experation, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var,long magic_number); - bool open_sell_stop_order(string symbol, bool condition, double entry_price, datetime experation, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var,long magic_number); - bool close_buy_orders(string symbol, bool buy_out, int close_bars, ENUM_TIMEFRAMES close_bar_period, long magic_number); - bool close_sell_orders(string symbol, bool sell_out, int close_bars, ENUM_TIMEFRAMES close_bar_period, long magic_number); - bool first_profitable_close_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, long magic_number); - bool daily_timed_exit(string symbol, datetime exit_time, int delay_days, long magic_number); - bool daily_timed_profit_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, string exit_time, string tz, int delay_days, long magic_number); - int count_all_positions(string symbol, long magic_number); - int count_pending_orders(string symbol, ENUM_ORDER_TYPE pendingType, long magic); - double sl_specified_value_switch(string _sl_mode, double _inp_sl_var, double value); - double tp_specified_value_switch(string _tp_mode, double _inp_tp_var, double value); - int count_open_positions(string symbol,int order_side, long magic_number); - void break_even_stop(string symbol, ulong magic_number, int be_trigger_points, int be_puffer); - void nnfx_trailing_stop(string symbol, double sl_var, double tp_var, double atr_value, ulong magic_number); - }; - -bool OrderManagment::open_buy_orders(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var, long magic_number){ - - if(condition == true){ - current_price = SymbolInfoDouble(symbol, SYMBOL_ASK); // ask for buy side - - total_open_buy_orders = count_open_positions(symbol, 1, magic_number); - if(total_open_buy_orders == 0){ - - stop_loss = cpd.calculate_stoploss(symbol, current_price, 1, _sl_mode, sl_var, atr_period); - take_profit = cpd.calculate_take_profit(symbol, current_price, stop_loss, 1, _tp_mode, tp_var, atr_period); - - double sl_distance = current_price-stop_loss; - double lots = cpd.calculate_lots(symbol, sl_distance, current_price, _lot_mode, lot_var); - - trade.SetExpertMagicNumber(magic_number); - string comment = "Magic Number: " + IntegerToString(magic_number); - trade.PositionOpen(symbol,ORDER_TYPE_BUY,lots,current_price,stop_loss,take_profit,comment); - } - } - return true; -} - - -bool OrderManagment::open_sell_orders(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var,long magic_number){ - - if(condition == true){ - - // if(!SymbolInfoTick(symbol,currentTick)){Print("FAILED TO GET TICK:", symbol);return false;} - current_price = SymbolInfoDouble(symbol, SYMBOL_BID); // bid for sell side - - total_open_sell_orders = count_open_positions(symbol, 2, magic_number); - if(total_open_sell_orders == 0){ - - stop_loss = cpd.calculate_stoploss(symbol, current_price, 2, _sl_mode, sl_var, atr_period); - take_profit = cpd.calculate_take_profit(symbol, current_price, stop_loss, 2, _tp_mode, tp_var, atr_period); - - double sl_distance = stop_loss-current_price; - double lots = cpd.calculate_lots(symbol, sl_distance, current_price, _lot_mode, lot_var); - - trade.SetExpertMagicNumber(magic_number); - string comment = "Magic Number: " + IntegerToString(magic_number); - trade.PositionOpen(symbol,ORDER_TYPE_SELL,lots,current_price,stop_loss,take_profit,comment); - } - } - return true; -} - -bool OrderManagment::open_nnfx_buy_orders(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var, long magic_number){ - - if(condition == true){ - current_price = SymbolInfoDouble(symbol, SYMBOL_ASK); // ask for buy side - - total_open_buy_orders = count_open_positions(symbol, 1, magic_number); - if(total_open_buy_orders == 0){ - - stop_loss = cpd.calculate_stoploss(symbol, current_price, 1, _sl_mode, sl_var, atr_period); - take_profit = cpd.calculate_take_profit(symbol, current_price, stop_loss, 1, _tp_mode, tp_var, atr_period); - - double sl_distance = current_price-stop_loss; - double lots = cpd.calculate_lots(symbol, sl_distance, current_price, _lot_mode, lot_var/2); - - trade.SetExpertMagicNumber(magic_number); - string comment = "Magic Number: " + IntegerToString(magic_number); - trade.PositionOpen(symbol,ORDER_TYPE_BUY,lots,current_price,stop_loss,take_profit,comment); - trade.PositionOpen(symbol,ORDER_TYPE_BUY,lots,current_price,stop_loss,0,comment); - } - } - return true; -} - - -bool OrderManagment::open_nnfx_sell_orders(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var,long magic_number){ - - if(condition == true){ - - // if(!SymbolInfoTick(symbol,currentTick)){Print("FAILED TO GET TICK:", symbol);return false;} - current_price = SymbolInfoDouble(symbol, SYMBOL_BID); // bid for sell side - - total_open_sell_orders = count_open_positions(symbol, 2, magic_number); - if(total_open_sell_orders == 0){ - - stop_loss = cpd.calculate_stoploss(symbol, current_price, 2, _sl_mode, sl_var, atr_period); - take_profit = cpd.calculate_take_profit(symbol, current_price, stop_loss, 2, _tp_mode, tp_var, atr_period); - - double sl_distance = stop_loss-current_price; - double lots = cpd.calculate_lots(symbol, sl_distance, current_price, _lot_mode, lot_var); - - trade.SetExpertMagicNumber(magic_number); - string comment = "Magic Number: " + IntegerToString(magic_number); - trade.PositionOpen(symbol,ORDER_TYPE_SELL,lots,current_price,stop_loss,take_profit,comment); - trade.PositionOpen(symbol,ORDER_TYPE_SELL,lots,current_price,stop_loss,0,comment); - } - } - return true; -} -// some usfull comment here -bool OrderManagment::open_buy_stop_order(string symbol, bool condition, double entry_price, datetime experation, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var,string _tp_mode, double tp_var, string _lot_mode, double lot_var,long magic_number){ - - if(condition == true){ - - total_open_buy_orders = count_open_positions(symbol, 1, magic_number); - if(total_open_buy_orders == 0){ - - stop_loss = cpd.calculate_stoploss(symbol, entry_price, 1, _sl_mode, sl_var, atr_period); - take_profit = cpd.calculate_take_profit(symbol, entry_price, stop_loss, 1, _tp_mode, tp_var, atr_period); - - double sl_distance = entry_price-stop_loss; - double lots = cpd.calculate_lots(symbol, sl_distance, entry_price, _lot_mode, lot_var); - - trade.SetExpertMagicNumber(magic_number); - string comment = "Magic Number: " + IntegerToString(magic_number); - trade.BuyStop(lots, entry_price, symbol, stop_loss, take_profit, ORDER_TIME_SPECIFIED, experation, comment); - } - } - return true; -} - - -bool OrderManagment::open_sell_stop_order(string symbol, bool condition, double entry_price, datetime experation, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode, double tp_var, string _lot_mode, double lot_var,long magic_number){ - - if(condition == true){ - - total_open_sell_orders = count_open_positions(symbol, 2, magic_number); - if(total_open_sell_orders == 0){ - - stop_loss = cpd.calculate_stoploss(symbol, entry_price, 2, _sl_mode, sl_var, atr_period); - take_profit = cpd.calculate_take_profit(symbol, entry_price, stop_loss, 2, _tp_mode, tp_var, atr_period); - - double sl_distance = stop_loss-entry_price; - double lots = cpd.calculate_lots(symbol, sl_distance, entry_price, _lot_mode, lot_var); - - trade.SetExpertMagicNumber(magic_number); - string comment = "Magic Number: " + IntegerToString(magic_number); - trade.SellStop(lots, entry_price, symbol, stop_loss, take_profit, ORDER_TIME_SPECIFIED, experation, comment); - } - } - return true; -} - -bool OrderManagment::close_buy_orders(string symbol, bool condition, int close_bars, ENUM_TIMEFRAMES close_bar_period, long magic_number){ - - for(int i = PositionsTotal()-1; i >=0; i--){ - posTicket = PositionGetTicket(i); - - if(PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number){ - - time_difference = Bars(symbol, close_bar_period, PositionGetInteger(POSITION_TIME), TimeCurrent()) - 1; - - if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){ - - if(condition){ - trade.PositionClose(posTicket); - } - - if(close_bars > 0){ - if(time_difference >= close_bars){ - trade.PositionClose(posTicket); - } - } - } - } - } - return true; -} - -bool OrderManagment::close_sell_orders(string symbol, bool condition, int close_bars, ENUM_TIMEFRAMES close_bar_period, long magic_number){ - - for(int i = PositionsTotal()-1; i >=0; i--){ - posTicket = PositionGetTicket(i); - - if(PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number){ - - time_difference = Bars(symbol, close_bar_period, PositionGetInteger(POSITION_TIME), TimeCurrent()) - 1; - - if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){ - - if(condition){trade.PositionClose(posTicket);} - - if(close_bars > 0){ - if(time_difference >= close_bars){ - trade.PositionClose(posTicket); - } - } - } - } - } - return true; -} - -// order_side int must be 1 for BUY or 2 for SELL -int OrderManagment::count_open_positions(string symbol,int order_side, long magic_number){ - - - int count = 0; - bool match = (PositionGetInteger(POSITION_MAGIC)==magic_number); - - for(int i = PositionsTotal()-1; i >=0; i--){ - ulong ticket = PositionGetTicket(i); - - if(PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC)==magic_number){ - - // Count only Buy orders: - if(order_side == 1){ - if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){ - count = count + 1; - } - } - - // Count only Sell orders: - if(order_side == 2){ - if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){ - count = count + 1; - } - } - } - } - return count; -} - -int OrderManagment::count_all_positions(string symbol, long magic_number){ - - int count = 0; - for(int i = PositionsTotal()-1; i >=0; i--){ - ulong ticket = PositionGetTicket(i); - - if(PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC)==magic_number){ - count = count + 1; - } - } - return count; -} - -bool OrderManagment::daily_timed_exit(string symbol, datetime exit_time, int delay_days, long magic_number){ - - for(int i = PositionsTotal()-1; i >=0; i--){ - posTicket = PositionGetTicket(i); - position_open_time = PositionGetInteger(POSITION_TIME); - - if((int)position_open_time>0){ - - first_allowed_close_time = position_open_time + (delay_days * PeriodSeconds(PERIOD_D1)); - if(TimeCurrent() > first_allowed_close_time){ - - // datetime broker_close_time = tz.timezone_conversions(cw_tzone, StringToTime(exit_time), "Broker"); - if(TimeCurrent()>= exit_time){ - - if(PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number){ - - if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){ - trade.PositionClose(posTicket); - } - - // Sell orders: - if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){ - trade.PositionClose(posTicket); - } - } - } - } - } - } -return true; -} - -bool OrderManagment::daily_timed_profit_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, string exit_time, string cw_tzone, int delay_days, long magic_number){ - - // om.daily_timed_profit_exit(_Symbol, PERIOD_CURRENT, "16:45", "17:00", "NY", 1, inp_magic); - - for(int i = PositionsTotal()-1; i >=0; i--){ - posTicket = PositionGetTicket(i); - position_open_time = PositionGetInteger(POSITION_TIME); - - if((int)position_open_time>0){ - - first_allowed_close_time = position_open_time + (delay_days * PeriodSeconds(PERIOD_D1)); - if(TimeCurrent() > first_allowed_close_time){ - - - datetime broker_close_time = tz.timezone_conversions(cw_tzone, StringToTime(exit_time), "Broker"); - if(TimeCurrent()>= broker_close_time){ - - if(PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number){ - - double position_open_price = PositionGetDouble(POSITION_PRICE_OPEN); - double spread = SymbolInfoDouble(symbol,SYMBOL_ASK) - SymbolInfoDouble(symbol,SYMBOL_BID); - double bar_close = iClose(_Symbol, close_bar_period, 1); // shift 1 because 0 = live candle. - double trading_cost = cpd.calculate_trading_cost(symbol, posTicket); - - - if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){ - if(bar_close > (position_open_price + spread + trading_cost)){ - trade.PositionClose(posTicket); - - } - } - - // Sell orders: - if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){ - if(bar_close < position_open_price - spread - trading_cost){ - trade.PositionClose(posTicket); - } - } - } - } - } - } - } -return true; -} - - -bool OrderManagment::first_profitable_close_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, long magic_number){ - // om.first_profitable_close_exit(_Symbol, PERIOD_CURRENT, inp_magic); - - position_open_time = PositionGetInteger(POSITION_TIME); - first_allowed_close_time = position_open_time + PeriodSeconds(close_bar_period); - - if((int)position_open_time>0){ - - if(TimeCurrent() > first_allowed_close_time){ - for(int i = PositionsTotal()-1; i >=0; i--){ - posTicket = PositionGetTicket(i); - - if(PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number){ - - double position_open_price = PositionGetDouble(POSITION_PRICE_OPEN); - double spread = SymbolInfoDouble(symbol,SYMBOL_ASK) - SymbolInfoDouble(symbol,SYMBOL_BID); - double bar_close = iClose(_Symbol,close_bar_period, 1); // shift 1 because 0 = live candle. - double trading_cost = cpd.calculate_trading_cost(symbol, posTicket); - - - if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY){ - if(bar_close > (position_open_price + spread + trading_cost)){ - trade.PositionClose(posTicket); - - } - } - - // Sell orders: - if(PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL){ - if(bar_close < position_open_price - spread - trading_cost){ - trade.PositionClose(posTicket); - } - } - } - } - } - } -return true; -} - -// e.g. int buy_stop_count = om.count_pending_orders(symbol, ORDER_TYPE_BUY_STOP, inp_magic); -// order types: ORDER_TYPE_BUY_LIMIT, ORDER_TYPE_SELL_LIMIT, ORDER_TYPE_BUY_STOP, ORDER_TYPE_SELL_STOP -int OrderManagment::count_pending_orders(string symbol, ENUM_ORDER_TYPE order_type, long magic){ - int count = 0; - - for(int i=OrdersTotal()-1;i>=0;i--) { - - if(m_order.SelectByIndex(i)){ - if( OrderGetInteger(ORDER_MAGIC) == magic && OrderGetString(ORDER_SYMBOL) == symbol){ - - if(m_order.OrderType()==order_type){ - count++; - } - } - } - } - return(count); -} - -void OrderManagment::break_even_stop(string symbol, ulong magic_number, int be_trigger_points, int be_puffer){ - - for(int i = PositionsTotal()-1; i >=0; i--){ - if(PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number){ - - int symbol_digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); - double symbol_point = SymbolInfoDouble(symbol, SYMBOL_POINT); - - double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); - ask = NormalizeDouble(ask, symbol_digits); - - double bid = SymbolInfoDouble(symbol, SYMBOL_BID); - bid = NormalizeDouble(bid, symbol_digits); - - if(be_trigger_points !=0){ - - - ulong ticket = PositionGetTicket(i); - if(PositionSelectByTicket(ticket)){ - - double position_open_price = PositionGetDouble(POSITION_PRICE_OPEN); - double position_volume = PositionGetDouble(POSITION_VOLUME); - double position_sl = PositionGetDouble(POSITION_SL); - double position_tp = PositionGetDouble(POSITION_TP); - ENUM_POSITION_TYPE position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); - - if(position_type == POSITION_TYPE_BUY){ - - if(bid > position_open_price + be_trigger_points * symbol_point){ - - double sl = position_open_price + be_puffer * symbol_point; - sl = NormalizeDouble(sl, symbol_digits); - if(sl > position_sl){ - - if(trade.PositionModify(ticket, sl, position_tp)){ - Print("-----------------------------------Stop moved to break even"); - } - } - } - } - else if(position_type == POSITION_TYPE_SELL){ - - if(ask < position_open_price - be_trigger_points * symbol_point){ - - double sl = position_open_price - be_puffer * symbol_point; - sl = NormalizeDouble(sl, symbol_digits); - if(sl < position_sl){ - - if(trade.PositionModify(ticket, sl, position_tp)){ - Print("-----------------------------------Stop moved to break even"); - } - } - } - } - } - } - } - } -} - - -void OrderManagment::nnfx_trailing_stop(string symbol, double sl_var, double tp_var, double atr_value, ulong magic_number){ - - MyFunctions mf3; - - for(int i = PositionsTotal()-1; i >=0; i--){ - - ulong ticket = PositionGetTicket(i); - if(PositionSelectByTicket(ticket)){ - - if(PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number){ - - int symbol_digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); - double symbol_point = SymbolInfoDouble(symbol, SYMBOL_POINT); - - double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); - ask = NormalizeDouble(ask, symbol_digits); - - double bid = SymbolInfoDouble(symbol, SYMBOL_BID); - bid = NormalizeDouble(bid, symbol_digits); - - double position_open_price = PositionGetDouble(POSITION_PRICE_OPEN); - double position_sl = PositionGetDouble(POSITION_SL); - double position_tp = PositionGetDouble(POSITION_TP); - ENUM_POSITION_TYPE position_type = (ENUM_POSITION_TYPE)PositionGetInteger(POSITION_TYPE); - - if(position_type == POSITION_TYPE_BUY){ - - if(bid > position_open_price + (atr_value * tp_var)){ - - double sl = bid - (atr_value * sl_var); - sl = NormalizeDouble(sl, symbol_digits); - if(sl > (position_sl + (atr_value * 0.5))){ - - if(trade.PositionModify(ticket, sl, position_tp)){ - - } - } - } - } - - - else if(position_type == POSITION_TYPE_SELL){ - - if(ask < position_open_price - (atr_value * tp_var)){ - - double sl = ask + (atr_value * sl_var); - sl = NormalizeDouble(sl, symbol_digits); - if(sl < (position_sl + (atr_value * 0.5))){ - - if(trade.PositionModify(ticket, sl, position_tp)){ - } - } - } - } - } - } - - - } -} - -double OrderManagment::sl_specified_value_switch(string _sl_mode, double _inp_sl_var, double value){ - double sl = 0; - if(_sl_mode=="SL_SPECIFIED_VALUE"){sl = value;} - if(_sl_mode!="SL_SPECIFIED_VALUE"){sl = _inp_sl_var;} - return sl; -} -double OrderManagment::tp_specified_value_switch(string _tp_mode, double _inp_tp_var, double value){ - double tp = 0; - if(_tp_mode=="SL_SPECIFIED_VALUE"){tp = value;} - if(_tp_mode!="SL_SPECIFIED_VALUE"){tp = _inp_tp_var;} - return tp; -} \ No newline at end of file diff --git a/_old/SymbolUtils.mqh b/_old/SymbolUtils.mqh deleted file mode 100644 index 3212e8a..0000000 --- a/_old/SymbolUtils.mqh +++ /dev/null @@ -1,35 +0,0 @@ -class SymbolUtils { - -public: - double adjusted_point(string symbol); - double get_bid_ask_price(string symbol, int price_side); -}; - -/** - * Adjusts the point size for symbols with 3 or 5 digits (e.g. JPY pairs or fractional pips). - * Example: if symbol has 5 digits, 1 pip = 10 points. - */ -double SymbolUtils::adjusted_point(string symbol) { - int symbol_digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); - int digits_adjust = (symbol_digits == 3 || symbol_digits == 5) ? 10 : 1; - - double point_val = SymbolInfoDouble(symbol, SYMBOL_POINT); - return point_val * digits_adjust; -} - - -/** - * Returns either bid or ask price for a symbol, normalised to correct digits. - * - * param price_side: 1 for ASK, 2 for BID - */ -double SymbolUtils::get_bid_ask_price(string symbol, int price_side) { - int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); - double ask = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_ASK), digits); - double bid = NormalizeDouble(SymbolInfoDouble(symbol, SYMBOL_BID), digits); - - if (price_side == 1) return ask; - if (price_side == 2) return bid; - - return 0.0; // fallback if invalid side passed -} diff --git a/_old/TradingWindow.mqh b/_old/TradingWindow.mqh deleted file mode 100644 index 4c72a94..0000000 --- a/_old/TradingWindow.mqh +++ /dev/null @@ -1,65 +0,0 @@ -#property library -#include -#include - -class TradingWindow : public CObject{ - - protected: - TimeZones tz; - bool in_window; - datetime start_time; - datetime end_time; - - public: - bool define_window(string t1, string t2, string time_zone, bool plot_range_inp=true); -}; - - -bool TradingWindow::define_window(string t1, string t2, string time_zone, bool plot_range=true){ - - datetime _t1 = StringToTime(t1); - datetime _t2 = StringToTime(t2); - if(_t1 > _t2){ - _t2 = _t2 + PeriodSeconds(PERIOD_D1); - } - int w_duration = (int)(_t2 - _t1); - - // window flag - if(TimeCurrent() >= start_time && TimeCurrent() <= end_time){ - in_window = true; - } - - // define new window - if(TimeCurrent() >= end_time){ - - in_window = false; - start_time = tz.timezone_conversions(time_zone, StringToTime(t1), "Broker"); - - if(TimeCurrent()>=start_time){ - start_time += PeriodSeconds(PERIOD_D1); - } - - end_time = start_time + w_duration; - - if(plot_range){ - - string name = "Start Time" + (string)start_time; - if(start_time>0){ - ObjectCreate(NULL, name, OBJ_VLINE, 0, start_time, 0); - ObjectSetInteger(NULL, name,OBJPROP_COLOR, clrBlue); - ObjectSetInteger(NULL, name,OBJPROP_BACK, true); - } - - name = "End Time" + (string)end_time; - if(end_time>0){ - ObjectCreate(NULL, name, OBJ_VLINE, 0, end_time, 0); - ObjectSetInteger(NULL, name,OBJPROP_COLOR, C'56,108,26'); - ObjectSetInteger(NULL, name,OBJPROP_BACK, true); - } - ChartRedraw(); - } - } - return in_window; -} - - diff --git a/_old/_MyLibs_old.zip b/_old/_MyLibs_old.zip deleted file mode 100644 index a1c1ea6bf185a95365c1f823cdb95ef598e524e3..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 26431 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 z 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 #include +// --------------------------------------------------------------------- +// CLASS: AtrBands +// --------------------------------------------------------------------- +// Provides ATR-based upper/lower/middle band calculations and checks. +// Useful for volatility-based stop placement, trend filters, or overlays. +// --------------------------------------------------------------------- class AtrBands { private: MarketDataUtils market_data_utils; AtrHandleManager atr_manager; + double get_atr(string symbol, int atr_period, ENUM_TIMEFRAMES tf, int shift); + public: - // Core band accessors double upper_band(string symbol, double trendline_var, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1); double lower_band(string symbol, double trendline_var, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1); double middle_band(string symbol, double trendline_var, ENUM_TIMEFRAMES tf, int shift = 1); - - // Band checkers bool inside_upper_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1); bool inside_lower_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1); bool crossed_below_upper_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1); bool crossed_above_lower_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1); - - // Plotting void plot_bands(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int bars = 100, color clr = clrSkyBlue, int width = 1); - - -protected: - double get_atr(string symbol, int atr_period, ENUM_TIMEFRAMES tf, int shift); }; - -// ------------------- -// Public Methods -// ------------------- - +// --------------------------------------------------------------------- +// Returns the upper ATR band level. +// +// Parameters: +// - symbol : Symbol to use. +// - trendline_var : Base trendline value (e.g., MA). +// - atr_period : ATR period. +// - tf : Timeframe for ATR. +// - mult : ATR multiplier. +// - shift : Bar shift to evaluate. +// --------------------------------------------------------------------- double AtrBands::upper_band(string symbol, double trendline_var, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift) { double atr = get_atr(symbol, atr_period, tf, shift); return trendline_var + atr * mult; } +// --------------------------------------------------------------------- +// Returns the lower ATR band level. +// +// Parameters: +// - Same as upper_band, except lower band logic. +// --------------------------------------------------------------------- double AtrBands::lower_band(string symbol, double trendline_var, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift) { double atr = get_atr(symbol, atr_period, tf, shift); return trendline_var - atr * mult; } +// --------------------------------------------------------------------- +// Returns the middle band, which is simply the trendline. +// +// Parameters: +// - symbol : Symbol. +// - trendline_var : The trendline value. +// - tf : Timeframe (unused here). +// - shift : Shift index (unused here). +// --------------------------------------------------------------------- double AtrBands::middle_band(string symbol, double trendline_var, ENUM_TIMEFRAMES tf, int shift) { return trendline_var; } +// --------------------------------------------------------------------- +// Checks if price is between trendline and upper band. +// +// Logic: +// - Retrieves trendline and price. +// - Compares if price lies between trendline and upper band. +// --------------------------------------------------------------------- bool AtrBands::inside_upper_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift) { 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); - if (price == EMPTY_VALUE || trendline_var == EMPTY_VALUE || upper == EMPTY_VALUE) return false; + if (price == EMPTY_VALUE || trendline_var == EMPTY_VALUE || upper == EMPTY_VALUE) + return false; return (price > trendline_var && price < upper); } +// --------------------------------------------------------------------- +// Checks if price is between trendline and lower band. +// +// Logic: +// - Retrieves trendline and price. +// - Compares if price lies between trendline and lower band. +// --------------------------------------------------------------------- bool AtrBands::inside_lower_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift) { 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); - if (price == EMPTY_VALUE || trendline_var == EMPTY_VALUE || lower == EMPTY_VALUE) return false; + if (price == EMPTY_VALUE || trendline_var == EMPTY_VALUE || lower == EMPTY_VALUE) + return false; return (price < trendline_var && price > lower); } +// --------------------------------------------------------------------- +// Detects if price has crossed below the upper band. +// +// Logic: +// - Checks crossover from above to below upper band between bars [shift+1] and [shift]. +// --------------------------------------------------------------------- bool AtrBands::crossed_below_upper_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift) { double price = iClose(symbol, tf, shift); double prev_price = iClose(symbol, tf, shift + 1); - double trendline_var = market_data_utils.get_buffer_value(handle, shift); double prev_trendline_var = market_data_utils.get_buffer_value(handle, shift + 1); - double upper = upper_band(symbol, trendline_var, atr_period, tf, mult, shift); double prev_upper = upper_band(symbol, prev_trendline_var, atr_period, tf, mult, shift + 1); - if (price == EMPTY_VALUE || prev_price == EMPTY_VALUE || trendline_var == EMPTY_VALUE || prev_trendline_var == EMPTY_VALUE) return false; + if (price == EMPTY_VALUE || prev_price == EMPTY_VALUE || trendline_var == EMPTY_VALUE || prev_trendline_var == EMPTY_VALUE) + return false; return (prev_price > prev_upper && price < upper); } +// --------------------------------------------------------------------- +// Detects if price has crossed above the lower band. +// +// Logic: +// - Checks crossover from below to above lower band between bars [shift+1] and [shift]. +// --------------------------------------------------------------------- bool AtrBands::crossed_above_lower_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift) { double price = iClose(symbol, tf, shift); double prev_price = iClose(symbol, tf, shift + 1); - double trendline_var = market_data_utils.get_buffer_value(handle, shift); double prev_trendline_var = market_data_utils.get_buffer_value(handle, shift + 1); - double lower = lower_band(symbol, trendline_var, atr_period, tf, mult, shift); double prev_lower = lower_band(symbol, prev_trendline_var, atr_period, tf, mult, shift + 1); - if (price == EMPTY_VALUE || prev_price == EMPTY_VALUE || trendline_var == EMPTY_VALUE || prev_trendline_var == EMPTY_VALUE) return false; + if (price == EMPTY_VALUE || prev_price == EMPTY_VALUE || trendline_var == EMPTY_VALUE || prev_trendline_var == EMPTY_VALUE) + return false; return (prev_price < prev_lower && price > lower); } +// --------------------------------------------------------------------- +// Plots the ATR bands as OBJ_TREND lines on chart. +// +// Parameters: +// - symbol : Symbol to draw on. +// - handle : Trendline handle (e.g., MA). +// - atr_period : ATR period for bands. +// - tf : Timeframe. +// - mult : ATR multiplier. +// - bars : Number of bars to plot. +// - clr : Line color. +// - width : Line width. +// --------------------------------------------------------------------- void AtrBands::plot_bands(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult, int bars, color line_color, int width) { for (int i = bars; i >= 1; i--) { double trendline = market_data_utils.get_buffer_value(handle, i); - if (trendline == EMPTY_VALUE) continue; + if (trendline == EMPTY_VALUE) + continue; double atr = get_atr(symbol, atr_period, tf, i); - if (atr == EMPTY_VALUE) continue; + if (atr == EMPTY_VALUE) + continue; double upper = trendline + atr * mult; double lower = trendline - atr * mult; - datetime time1 = iTime(symbol, tf, i); datetime time2 = iTime(symbol, tf, i - 1); - string upper_name = "ATR_Upper_" + symbol + "_" + TimeToString(time1, TIME_DATE | TIME_MINUTES); - string lower_name = "ATR_Lower_" + symbol + "_" + TimeToString(time1, TIME_DATE | TIME_MINUTES); - string middle_name = "ATR_middle_" + symbol + "_" + TimeToString(time1, TIME_DATE | TIME_MINUTES); + string upper_name = "ATR_Upper_" + symbol + "_" + TimeToString(time1, TIME_DATE | TIME_MINUTES); + string lower_name = "ATR_Lower_" + symbol + "_" + TimeToString(time1, TIME_DATE | TIME_MINUTES); + string middle_name = "ATR_Middle_" + symbol + "_" + TimeToString(time1, TIME_DATE | TIME_MINUTES); if (ObjectFind(0, upper_name) < 0) { ObjectCreate(0, upper_name, OBJ_TREND, 0, time1, upper, time2, upper); @@ -140,6 +200,18 @@ void AtrBands::plot_bands(string symbol, int handle, int atr_period, ENUM_TIMEFR ChartRedraw(); } +// --------------------------------------------------------------------- +// Retrieves the ATR value via the shared ATR manager. +// +// Parameters: +// - symbol : Symbol to calculate ATR on. +// - atr_period : ATR calculation period. +// - tf : Timeframe of the ATR. +// - shift : Bar index to retrieve. +// +// Returns: +// - The ATR value at the given shift. +// --------------------------------------------------------------------- double AtrBands::get_atr(string symbol, int atr_period, ENUM_TIMEFRAMES tf, int shift) { return atr_manager.get_atr_value(symbol, tf, atr_period, shift); } diff --git a/indicators/TrendlineAnalyser.mqh b/indicators/TrendlineAnalyser.mqh index 71bf26a..cde0d06 100644 --- a/indicators/TrendlineAnalyser.mqh +++ b/indicators/TrendlineAnalyser.mqh @@ -1,59 +1,76 @@ #include -//+------------------------------------------------------------------+ -//| TrendlineSignal: A utility class to detect trendline crossovers | -//+------------------------------------------------------------------+ + +// --------------------------------------------------------------------- +// CLASS: TrendlineAnalyser +// --------------------------------------------------------------------- +// A utility class to detect price crossovers with a trendline buffer. +// Supports both crossover detection and trend direction checks. +// --------------------------------------------------------------------- class TrendlineAnalyser { - private: +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, int shift=1) { - cross_long = false; - cross_short = false; - - // --- Retrieve prices and trendline values from last 2 closed bars - double price = iClose(symbol, PERIOD_CURRENT, shift); // most recent closed bar - double prev_price = iClose(symbol, PERIOD_CURRENT, shift + 1); // bar before that - - double trendline = market_data_utils.get_buffer_value(handle, shift); // trendline now - double prev_trendline = market_data_utils.get_buffer_value(handle, shift+1); // 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); - } +public: + void detect_cross(string symbol, int handle, bool& cross_long, bool& cross_short, int shift = 1); + void trend_direction(string symbol, int handle, bool& direction_long, bool& direction_short, int shift = 1); }; + +// --------------------------------------------------------------------- +// Detects whether price has crossed above (long) or below (short) +// a trendline between the two most recent closed bars. +// +// Parameters: +// - symbol : Trading symbol (e.g., "EURUSD"). +// - handle : Indicator handle for the trendline buffer. +// - cross_long : Output true if bullish crossover detected. +// - cross_short : Output true if bearish crossover detected. +// - shift : Bar shift to evaluate (default: 1 = last closed bar). +// +// Logic: +// - Retrieves price and trendline values for the current and previous bar. +// - Checks for a crossover by comparing price vs trendline movement. +// --------------------------------------------------------------------- +void TrendlineAnalyser::detect_cross(string symbol, int handle, bool& cross_long, bool& cross_short, int shift) { + cross_long = false; + cross_short = false; + + double price = iClose(symbol, PERIOD_CURRENT, shift); + double prev_price = iClose(symbol, PERIOD_CURRENT, shift + 1); + + double trendline = market_data_utils.get_buffer_value(handle, shift); + double prev_trendline = market_data_utils.get_buffer_value(handle, shift + 1); + + if (price == EMPTY_VALUE || prev_price == EMPTY_VALUE || trendline == EMPTY_VALUE || prev_trendline == EMPTY_VALUE) + return; + + cross_long = (prev_price < prev_trendline && price > trendline); + cross_short = (prev_price > prev_trendline && price < trendline); +} + +// --------------------------------------------------------------------- +// Checks if price is currently above or below the trendline. +// +// Parameters: +// - symbol : Trading symbol (e.g., "EURUSD"). +// - handle : Trendline indicator handle. +// - direction_long : Output true if price is above trendline. +// - direction_short : Output true if price is below trendline. +// - shift : Bar index to check (default: 1). +// +// Logic: +// - Retrieves price and trendline at the specified shift. +// - Compares relative position of price to trendline. +// --------------------------------------------------------------------- +void TrendlineAnalyser::trend_direction(string symbol, int handle, bool& direction_long, bool& direction_short, int shift) { + 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); +}