diff --git a/Orders/AdjustPosition.mqh b/Orders/AdjustPosition.mqh index 24ddb82..1209aea 100644 --- a/Orders/AdjustPosition.mqh +++ b/Orders/AdjustPosition.mqh @@ -1,13 +1,20 @@ +#include +#include + +CTrade trade; +AtrHandleManager atr_manager; + class AdjustPosition { public: void set_breakeven_sl(string symbol, int runner_magic_no, double buffer_points = 5); - void set_breakeven_sl_if_tp_crossed(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); 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); + 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); }; // --------------------------------------------------------- @@ -32,65 +39,136 @@ void AdjustPosition::set_breakeven_sl(string symbol, int runner_magic_no, double } // --------------------------------------------------------- -/** - * Check runner trades for virtual take-profit hits and set SL to breakeven if crossed. - * - * Runner trades are assumed to be opened with no TP and a special comment in the format: "runner_tp:1.10500". - * This method parses that comment to determine the virtual TP, and if price has crossed it, moves SL to breakeven ± buffer. - * - * param symbol: Trading symbol to check positions for - * param runner_magic_no: Magic number assigned to runner trades - * param buffer_points: Buffer to add/subtract from entry price when setting SL (in points, converted internally) - */ -void AdjustPosition::set_breakeven_sl_if_tp_crossed(string symbol, int runner_magic_no, double buffer_points) { - double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); // Current ask price (for sell comparisons) - double bid = SymbolInfoDouble(symbol, SYMBOL_BID); // Current bid price (for buy comparisons) - int digits = (int) SymbolInfoInteger(symbol, SYMBOL_DIGITS); // Symbol's decimal precision - double buffer_price = buffer_points * _Point; // Convert buffer from points to price +// Check runner trades for virtual TP hits and set SL to breakeven if crossed. +// Optimized to avoid unnecessary processing on every OnTimer()/OnTick() call. +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); + double buffer_price = buffer_points * _Point; + double price_margin = 50 * _Point; // avoid premature checking if far from TP + // --- 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); // Get the position ticket - if (!PositionSelectByTicket(ticket)) continue; // Select the position - if (PositionGetString(POSITION_SYMBOL) != symbol) continue; // Only process positions for this symbol - if ((int) PositionGetInteger(POSITION_MAGIC) != runner_magic_no) continue; // Ensure it matches runner magic + ulong ticket = PositionGetTicket(i); + if (!PositionSelectByTicket(ticket)) continue; + if (PositionGetString(POSITION_SYMBOL) != symbol) continue; + if ((int)PositionGetInteger(POSITION_MAGIC) == runner_magic_no) { + has_runner = true; + break; + } + } + if (!has_runner) return; - // --- Get position details --- - long order_type = PositionGetInteger(POSITION_TYPE); // POSITION_TYPE_BUY or POSITION_TYPE_SELL - double entry = PositionGetDouble(POSITION_PRICE_OPEN); // Entry price of the trade - double sl = PositionGetDouble(POSITION_SL); // Current stop loss - double tp = PositionGetDouble(POSITION_TP); // Should be 0 for runners - string comment = PositionGetString(POSITION_COMMENT); // Read comment to check for virtual TP + // --- 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; - // --- Parse virtual TP from comment --- - // Format expected: "runner_tp:1.10500" + long order_type = PositionGetInteger(POSITION_TYPE); + double entry = PositionGetDouble(POSITION_PRICE_OPEN); + double sl = PositionGetDouble(POSITION_SL); + 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:")); // Extract number part - virtual_tp = StringToDouble(tp_str); // Convert to double + string tp_str = StringSubstr(comment, StringLen("runner_tp:")); + virtual_tp = StringToDouble(tp_str); } - // Skip if no virtual TP was found or invalid - if (virtual_tp <= 0.0) continue; - - // Warn if a TP is still set (runner should not have one) + if (virtual_tp <= 0.0) continue; // no valid virtual TP if (tp > 0.0) { - PrintFormat("Warning: Runner trade on %s (ticket %d) has a non-zero TP: %.5f (should be 0)", symbol, ticket, tp); + PrintFormat("Warning: Runner trade on %s (ticket %d) has TP set: %.5f", symbol, ticket, tp); } - // --- Check if price has hit virtual 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) // For buys, bid must be ≥ virtual TP - tp_hit = true; - if (order_type == POSITION_TYPE_SELL && ask <= virtual_tp) // For sells, ask must be ≤ virtual TP - tp_hit = true; + if (order_type == POSITION_TYPE_BUY && bid >= virtual_tp) tp_hit = true; + if (order_type == POSITION_TYPE_SELL && ask <= virtual_tp) tp_hit = true; if (!tp_hit) continue; - // --- Set breakeven SL if TP was hit --- - // Move SL to entry ± buffer. Leave TP unchanged (but should be 0.0 at init for runners) + // 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 +// --------------------------------------------------------------------- +// This function updates the stop-loss of runner trades based on ATR. +// It only applies to trades with the given magic number and symbol. +// +// Parameters: +// - symbol : The trading symbol. +// - _magic_number : Magic number to identify trades. +// - tf : Timeframe used for ATR calculation. +// - activation_mult: Multiplier to determine when to activate trailing. +// - trail_mult : Multiplier to determine trailing distance. +// - atr_period : ATR period to use. +// - use_bar_close : If true, use bar close instead of live price. +// +// Logic: +// - 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) { + + double atr = atr_manager.get_atr_value(symbol, tf, atr_period); + if (atr == EMPTY_VALUE) return; + + double bid = SymbolInfoDouble(symbol, SYMBOL_BID); + double ask = SymbolInfoDouble(symbol, SYMBOL_ASK); + int digits = (int) SymbolInfoInteger(symbol, SYMBOL_DIGITS); + + 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) != _magic_number) continue; + + long type = PositionGetInteger(POSITION_TYPE); + 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); + 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 (!trade.PositionModify(ticket, new_sl, PositionGetDouble(POSITION_TP))) + PrintFormat("Trailing SL update failed for %s ticket=%d", symbol, ticket); + else + 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) { @@ -112,7 +190,7 @@ void AdjustPosition::set_breakeven_sl_for_ticket(string symbol, ulong ticket, lo 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); + Print("Runner upgraded to trailing: SL at breakeven, TP removed for ", symbol); } } diff --git a/Orders/CalculatePositionData.mqh b/Orders/CalculatePositionData.mqh index 728caa9..8e6bc67 100644 --- a/Orders/CalculatePositionData.mqh +++ b/Orders/CalculatePositionData.mqh @@ -1,303 +1,175 @@ #include #include +#include #include class CalculatePositionData : public CObject { - protected: +protected: CTrade trade; CPositionInfo position; MarketDataUtils mdu; + AtrHandleManager atr_manager; bool check_lots(double& lots, string symbol); bool normalise_price(double price, double& normalizedPrice, 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); +public: + double calculate_stoploss(string symbol, double price, int order_side, string mode_sl, double sl_var, ENUM_TIMEFRAMES atr_tf); + double calculate_take_profit(string symbol, double price, double stoploss, int order_side, string mode_tp, double tp_var, ENUM_TIMEFRAMES atr_tf); 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 calculate_trading_cost(string symbol, ulong 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 CalculatePositionData::calculate_stoploss(string symbol, double price, int order_side, string mode_sl, double sl_var, ENUM_TIMEFRAMES atr_tf) { 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 == "NO_STOPLOSS") return 0; if (mode_sl == "SL_FIXED_PIPS") { - // pips/poins = https://www.mql5.com/en/forum/187757 double adj_point = mdu.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; - } - } + sl = (order_side == 1) ? price - sl_var * adj_point : price + sl_var * adj_point; + if (!normalise_price(sl, sl, symbol)) return 0; } 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; - } - } + sl = (order_side == 1) ? price - (sl_var * price / 100.0) : price + (sl_var * price / 100.0); + if (!normalise_price(sl, sl, symbol)) return 0; } 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; - } - } + double atr = atr_manager.get_atr_value(symbol, atr_tf, 14); + if (atr == EMPTY_VALUE) return 0; + sl = (order_side == 1) ? price - atr * sl_var : price + atr * sl_var; + if (!normalise_price(sl, sl, symbol)) return 0; } if (mode_sl == "SL_SPECIFIED_VALUE") { double adj_point = mdu.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; - } - } + double limit_sl = (order_side == 1) ? price - 10 * adj_point : price + 10 * adj_point; + sl = (order_side == 1) ? fmax(sl_var, limit_sl) : fmin(sl_var, limit_sl); + if (!normalise_price(sl, sl, symbol)) return 0; } 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 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; - if (mode_tp == "NO_TAKE_PROFIT") { - tp = 0; - } + if (mode_tp == "NO_TAKE_PROFIT") return 0; if (mode_tp == "TP_FIXED_PIPS") { double adj_point = mdu.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; - } - } + tp = (order_side == 1) ? price + tp_var * adj_point : price - tp_var * adj_point; + if (!normalise_price(tp, tp, symbol)) return 0; } 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; - } - } + tp = (order_side == 1) ? price + tp_var * price / 100.0 : price - tp_var * price / 100.0; + if (!normalise_price(tp, tp, symbol)) return 0; } 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; - } - } + double atr = atr_manager.get_atr_value(symbol, atr_tf, 14); + if (atr == EMPTY_VALUE) return 0; + tp = (order_side == 1) ? price + atr * tp_var : price - atr * tp_var; + if (!normalise_price(tp, tp, symbol)) return 0; } 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; - } - } + double sl_size = (order_side == 1) ? price - stoploss : stoploss - price; + tp = (order_side == 1) ? price + tp_var * sl_size : price - tp_var * sl_size; + if (!normalise_price(tp, tp, symbol)) return 0; } if (mode_tp == "TP_SPECIFIED_VALUE") { - if (_tp_var != 0) { - double adj_point = mdu.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; - } - } - } + double adj_point = mdu.adjusted_point(symbol); + double limit_tp = (order_side == 1) ? price + 10 * adj_point : price - 10 * adj_point; + tp = (order_side == 1) ? fmin(tp_var, limit_tp) : fmax(tp_var, limit_tp); + if (!normalise_price(tp, tp, symbol)) return 0; } + 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; + double account_value = fmin(fmin(AccountInfoDouble(ACCOUNT_EQUITY), AccountInfoDouble(ACCOUNT_BALANCE)), AccountInfoDouble(ACCOUNT_MARGIN_FREE)); + double risk_money = account_value * lot_var / 100.0; 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; + double money_per_step = (sl_distance / tick_size) * tick_value * volume_step; + lots = MathFloor(risk_money / money_per_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; + double money_per_step = (price / tick_size) * tick_value * volume_step; + lots = MathFloor(risk_money / money_per_step) * volume_step; } - if (!check_lots(lots, symbol)) { - return false; - } + if (!check_lots(lots, symbol)) return 0; 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 (lots < min) { - Print("Lot size will be set to minimum allowed volume"); lots = min; return true; } if (lots > max) { - Print("Lot size greater than maximum allowed volume. lots:", lots, "max:", max); + Print("Lot size exceeds max for ", symbol); return false; } - lots = (int) MathFloor(lots / step) * step; + 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"); + double tick_size; + if (!SymbolInfoDouble(symbol, SYMBOL_TRADE_TICK_SIZE, tick_size)) { + Print("Failed to get tick size for ", symbol); return false; } - int symbol_digits = (int) SymbolInfoInteger(symbol, SYMBOL_DIGITS); - normalizedPrice = NormalizeDouble(MathRound(price / tickSize) * tickSize, symbol_digits); + + int digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); + normalizedPrice = NormalizeDouble(MathRound(price / tick_size) * tick_size, 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); +// double CalculatePositionData::calculate_trading_cost(string symbol, ulong ticket) { +// position.SelectByTicket(ticket); - return trading_cost; -} \ No newline at end of file +// 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 52fc5e1..e877b40 100644 --- a/Orders/EntryOrders.mqh +++ b/Orders/EntryOrders.mqh @@ -2,11 +2,11 @@ #include class EntryOrders { - protected: +protected: CTrade trade; CalculatePositionData calc; - public: +public: int count_open_positions(string symbol, int order_side, long _magic_number); bool open_buy_orders(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, double sl_var, string _tp_mode, @@ -15,11 +15,11 @@ class EntryOrders { 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_buy_stop_order(string symbol, bool condition, double entry_price, datetime experation, ENUM_TIMEFRAMES atr_period, + bool 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); - bool open_sell_stop_order(string symbol, bool condition, double entry_price, datetime experation, ENUM_TIMEFRAMES atr_period, + bool 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); @@ -28,6 +28,7 @@ class EntryOrders { 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); + }; int EntryOrders::count_open_positions(string symbol, int order_side, long _magic_number) { @@ -36,8 +37,6 @@ int EntryOrders::count_open_positions(string symbol, int order_side, long _magic ulong ticket = PositionGetTicket(i); if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == _magic_number) { int type = (int) PositionGetInteger(POSITION_TYPE); - - // Count: order_side == 0 (both sides), order_side == 1 (longs only), order_side == 2 (shorts only) if (order_side == 0 || (order_side == 1 && type == POSITION_TYPE_BUY) || (order_side == 2 && type == POSITION_TYPE_SELL)) { count++; } @@ -49,7 +48,6 @@ int EntryOrders::count_open_positions(string symbol, int order_side, long _magic 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; - double current_price = SymbolInfoDouble(symbol, SYMBOL_ASK); if (count_open_positions(symbol, 1, _magic_number) > 0) return false; @@ -58,15 +56,21 @@ bool EntryOrders::open_buy_orders(string symbol, bool condition, ENUM_TIMEFRAMES double sl_distance = current_price - stop_loss; double lots = calc.calculate_lots(symbol, sl_distance, current_price, _lot_mode, lot_var); + if (lots <= 0) { + Print("Lot calculation failed for ", symbol); + return false; + } + trade.SetExpertMagicNumber(_magic_number); string comment = "Magic Number: " + IntegerToString(_magic_number); - return trade.PositionOpen(symbol, ORDER_TYPE_BUY, lots, current_price, stop_loss, take_profit, comment); + bool result = trade.PositionOpen(symbol, ORDER_TYPE_BUY, lots, current_price, stop_loss, take_profit, comment); + if (!result) Print("Trade open failed for BUY ", symbol); + return result; } 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; - double current_price = SymbolInfoDouble(symbol, SYMBOL_BID); if (count_open_positions(symbol, 2, _magic_number) > 0) return false; @@ -75,12 +79,19 @@ bool EntryOrders::open_sell_orders(string symbol, bool condition, ENUM_TIMEFRAME double sl_distance = stop_loss - current_price; double lots = calc.calculate_lots(symbol, sl_distance, current_price, _lot_mode, lot_var); + if (lots <= 0) { + Print("Lot calculation failed for ", symbol); + return false; + } + trade.SetExpertMagicNumber(_magic_number); string comment = "Magic Number: " + IntegerToString(_magic_number); - return trade.PositionOpen(symbol, ORDER_TYPE_SELL, lots, current_price, stop_loss, take_profit, comment); + bool result = trade.PositionOpen(symbol, ORDER_TYPE_SELL, lots, current_price, stop_loss, take_profit, comment); + if (!result) Print("Trade open failed for SELL ", symbol); + return result; } -bool EntryOrders::open_buy_stop_order(string symbol, bool condition, double entry_price, datetime experation, ENUM_TIMEFRAMES atr_period, +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) { if (!condition) return false; @@ -91,12 +102,19 @@ bool EntryOrders::open_buy_stop_order(string symbol, bool condition, double entr double sl_distance = entry_price - stop_loss; double lots = calc.calculate_lots(symbol, sl_distance, entry_price, _lot_mode, lot_var); + if (lots <= 0) { + Print("Lot calculation failed for BUY STOP ", symbol); + return false; + } + trade.SetExpertMagicNumber(_magic_number); string comment = "Magic Number: " + IntegerToString(_magic_number); - return trade.BuyStop(lots, entry_price, symbol, stop_loss, take_profit, ORDER_TIME_SPECIFIED, experation, comment); + bool result = trade.BuyStop(lots, entry_price, symbol, stop_loss, take_profit, ORDER_TIME_SPECIFIED, expiration, comment); + if (!result) Print("BuyStop order failed for ", symbol); + return result; } -bool EntryOrders::open_sell_stop_order(string symbol, bool condition, double entry_price, datetime experation, ENUM_TIMEFRAMES atr_period, +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) { if (!condition) return false; @@ -107,9 +125,16 @@ bool EntryOrders::open_sell_stop_order(string symbol, bool condition, double ent double sl_distance = stop_loss - entry_price; double lots = calc.calculate_lots(symbol, sl_distance, entry_price, _lot_mode, lot_var); + if (lots <= 0) { + Print("Lot calculation failed for SELL STOP ", symbol); + return false; + } + trade.SetExpertMagicNumber(_magic_number); string comment = "Magic Number: " + IntegerToString(_magic_number); - return trade.SellStop(lots, entry_price, symbol, stop_loss, take_profit, ORDER_TIME_SPECIFIED, experation, comment); + bool result = trade.SellStop(lots, entry_price, symbol, stop_loss, take_profit, ORDER_TIME_SPECIFIED, expiration, comment); + if (!result) Print("SellStop order failed for ", symbol); + return result; } bool EntryOrders::open_runner_buy_order_with_virtual_tp(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, @@ -124,9 +149,16 @@ bool EntryOrders::open_runner_buy_order_with_virtual_tp(string symbol, bool cond double sl_distance = current_price - stop_loss; double lots = calc.calculate_lots(symbol, sl_distance, current_price, _lot_mode, lot_var); + if (lots <= 0) { + Print("Lot calculation failed for runner BUY ", symbol); + return false; + } + trade.SetExpertMagicNumber(_magic_number); string comment = StringFormat("runner_tp:%.5f", virtual_tp); - return trade.PositionOpen(symbol, ORDER_TYPE_BUY, lots, current_price, stop_loss, 0.0, comment); + bool result = trade.PositionOpen(symbol, ORDER_TYPE_BUY, lots, current_price, stop_loss, 0.0, comment); + if (!result) Print("Runner BUY order failed for ", symbol); + return result; } bool EntryOrders::open_runner_sell_order_with_virtual_tp(string symbol, bool condition, ENUM_TIMEFRAMES atr_period, string _sl_mode, @@ -138,11 +170,17 @@ bool EntryOrders::open_runner_sell_order_with_virtual_tp(string symbol, bool con double stop_loss = calc.calculate_stoploss(symbol, current_price, 2, _sl_mode, sl_var, atr_period); double virtual_tp = calc.calculate_take_profit(symbol, current_price, stop_loss, 2, _tp_mode, tp_var, atr_period); - double sl_distance = stop_loss - current_price; double lots = calc.calculate_lots(symbol, sl_distance, current_price, _lot_mode, lot_var); + if (lots <= 0) { + Print("Lot calculation failed for runner SELL ", symbol); + return false; + } + trade.SetExpertMagicNumber(_magic_number); string comment = StringFormat("runner_tp:%.5f", virtual_tp); - return trade.PositionOpen(symbol, ORDER_TYPE_SELL, lots, current_price, stop_loss, 0.0, comment); + bool result = trade.PositionOpen(symbol, ORDER_TYPE_SELL, lots, current_price, stop_loss, 0.0, comment); + if (!result) Print("Runner SELL order failed for ", symbol); + return result; } diff --git a/Utils/AtrHandleManager.mqh b/Utils/AtrHandleManager.mqh new file mode 100644 index 0000000..c9f904b --- /dev/null +++ b/Utils/AtrHandleManager.mqh @@ -0,0 +1,63 @@ +#include + +class AtrHandleManager { +private: + struct AtrEntry { + string symbol; + ENUM_TIMEFRAMES tf; + int period; + int handle; + }; + + AtrEntry cache[]; // internal cache of ATR handles + +public: + // Get or create an ATR handle for a specific symbol/timeframe/period + 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) + return cache[i].handle; + } + + int handle = iATR(symbol, tf, period); + if (handle == INVALID_HANDLE) { + Print("Failed to create ATR handle for ", symbol); + return INVALID_HANDLE; + } + + AtrEntry entry = { symbol, tf, period, handle }; + ArrayResize(cache, ArraySize(cache) + 1); + cache[ArraySize(cache) - 1] = entry; + + return handle; + } + + // Get ATR value from buffer (returns EMPTY_VALUE if 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) { + PrintFormat("Invalid ATR handle for %s (TF=%d, Period=%d)", symbol, tf, period); + return EMPTY_VALUE; + } + + double buffer[]; + ArraySetAsSeries(buffer, true); + + if (CopyBuffer(handle, 0, shift, 1, buffer) != 1 || buffer[0] == EMPTY_VALUE) { + PrintFormat("Failed to read ATR buffer for %s shift=%d", symbol, shift); + return EMPTY_VALUE; + } + + return buffer[0]; + } + + // Release all handles in the cache + void release_handles() { + for (int i = 0; i < ArraySize(cache); i++) { + if (cache[i].handle != INVALID_HANDLE) + IndicatorRelease(cache[i].handle); + } + ArrayResize(cache, 0); + } +}; + diff --git a/Utils/MarketDataUtils.mqh b/Utils/MarketDataUtils.mqh index 57f39fb..5b319db 100644 --- a/Utils/MarketDataUtils.mqh +++ b/Utils/MarketDataUtils.mqh @@ -7,23 +7,44 @@ public: double get_bid_ask_price(string symbol, int price_side); protected: - datetime previousTime; // Stores the last recorded bar open time - datetime bar_open_time; // Stores the current bar's open time + 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 +} + // Checks if a new bar has opened on the given timeframe and symbol bool MarketDataUtils::is_new_bar(string symbol, ENUM_TIMEFRAMES time_frame, string daily_start_time) { - bar_open_time = iTime(symbol, time_frame, 0); // Current open time + datetime bar_open_time = iTime(symbol, time_frame, 0); // Current open time + string key = symbol + "_" + EnumToString(time_frame); - if (previousTime != bar_open_time) { + 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; + } + + 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)) { - previousTime = bar_open_time; + previousTimes[idx] = bar_open_time; return true; } } else { - previousTime = bar_open_time; + previousTimes[idx] = bar_open_time; return true; } } @@ -35,17 +56,23 @@ bool MarketDataUtils::is_new_bar(string symbol, ENUM_TIMEFRAMES time_frame, stri // shift = 1 is the most recently closed candle // shift = 2 is the one before that, etc. 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; + } - if (CopyBuffer(handle, 0, shift, 1, val) == 1 && val[0] != EMPTY_VALUE) - return val[0]; + if (val[0] == EMPTY_VALUE) { + Print("EMPTY_VALUE returned for buffer at shift=", shift); + return EMPTY_VALUE; + } - return EMPTY_VALUE; + return val[0]; } - // Adjusts the point value for symbol to account for fractional pips (e.g., 5-digit brokers) double MarketDataUtils::adjusted_point(string symbol) { int symbol_digits = (int)SymbolInfoInteger(symbol, SYMBOL_DIGITS); diff --git a/Utils/ResourceManager.mqh b/Utils/ResourceManager.mqh new file mode 100644 index 0000000..c59f52f --- /dev/null +++ b/Utils/ResourceManager.mqh @@ -0,0 +1,56 @@ +#include + +//+------------------------------------------------------------------+ +//| ResourceManager | +//| | +//| Tracks and releases indicator handles (iMA, iRSI, etc.) | +//| Also delegates ATR handle cleanup to an external ATR manager | +//+------------------------------------------------------------------+ +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 + void register_handle(int handle) { + if (handle != INVALID_HANDLE) + add_handle(handle); + } + + // --- Release all tracked resources: + 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() + 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. + void release_tracked_handles() { + for (int i = 0; i < ArraySize(handles); i++) { + if (handles[i] != INVALID_HANDLE) + IndicatorRelease(handles[i]); + } + ArrayFree(handles); + } + + // --- Release ATR handles via external AtrHandleManager + // No-op if atr_manager is not assigned + void release_internal_handles() { + if (atr_manager != NULL) + atr_manager.release_handles(); + } +}; diff --git a/Utils/SignalStateTracker.mqh b/Utils/SignalStateTracker.mqh index 5f17547..9aca440 100644 --- a/Utils/SignalStateTracker.mqh +++ b/Utils/SignalStateTracker.mqh @@ -15,11 +15,20 @@ class SignalStateTracker { last_signal_short = -1000; } - // Updates the signal for both long and short directions in one call, if the respective triggers are true. - // curr_bar should refer to the last CLOSED bar (typically bar index 1). - void update_signal(bool trig_long, bool trig_short, int curr_bar=1) { - if (trig_long) last_signal_long = curr_bar; - if (trig_short) last_signal_short = curr_bar; + // Updates the signal tracker based on trigger presence per bar. + // If signal is detected, sets to 1. If not, increments previous value. + void update_signal_tracker(bool signal_long, bool signal_short) { + if (signal_long) { + last_signal_long = 1; + } else if (last_signal_long > 0) { + last_signal_long++; + } + + if (signal_short) { + last_signal_short = 1; + } else if (last_signal_short > 0) { + last_signal_short++; + } } // Returns true if a long signal occurred within the last `max_bars` bars. diff --git a/indicators/AtrBands.mqh b/indicators/AtrBands.mqh index 0463fa4..1d94d30 100644 --- a/indicators/AtrBands.mqh +++ b/indicators/AtrBands.mqh @@ -1,139 +1,145 @@ #include +#include class AtrBands { - private: +private: MarketDataUtils market_data_utils; + AtrHandleManager atr_manager; - public: +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, - color clr = clrDodgerBlue, int width = 1, bool plot = true); - - double lower_band(string symbol, double trendline_var, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1, - color clr = clrDodgerBlue, int width = 1, bool plot = true); - - double middle_band(string symbol, double trendline_var, ENUM_TIMEFRAMES tf, int shift = 1, color clr = clrDodgerBlue, int width = 1, - bool plot = true); + 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 plot = true); + 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); - bool inside_lower_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1, bool plot = true); + // 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); - bool crossed_below_upper_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1, - bool plot = true); - bool crossed_above_lower_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult = 1.0, int shift = 1, - bool plot = true); - protected: +protected: double get_atr(string symbol, int atr_period, ENUM_TIMEFRAMES tf, int shift); - void draw_line(string name, double price, color clr, int width); }; + // ------------------- // Public Methods // ------------------- -double AtrBands::upper_band(string symbol, double trendline_var, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift, color clr, int width, bool plot) { +double 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); - double upper = trendline_var + atr * mult; - if (plot && shift == 1) draw_line("ATRBand_Upper_" + symbol, upper, clr, width); // persistent line - return upper; + return trendline_var + atr * mult; } -double AtrBands::lower_band(string symbol, double trendline_var, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift, color clr, int width, bool plot) { +double 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); - double lower = trendline_var - atr * mult; - if (plot && shift == 1) draw_line("ATRBand_Lower_" + symbol, lower, clr, width); // persistent line - return lower; + return trendline_var - atr * mult; } -double AtrBands::middle_band(string symbol, double trendline_var, ENUM_TIMEFRAMES tf, int shift, color clr, int width, bool plot) { - if (plot && shift == 1) draw_line("ATRBand_Mid_" + symbol, trendline_var, clr, width); // persistent line +double AtrBands::middle_band(string symbol, double trendline_var, ENUM_TIMEFRAMES tf, int shift) { return trendline_var; } -bool AtrBands::inside_upper_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift, bool plot) { +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, clrDodgerBlue, 1, plot); - + 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; return (price > trendline_var && price < upper); } -bool AtrBands::inside_lower_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift, bool plot) { +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, clrDodgerBlue, 1, plot); - + 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; return (price < trendline_var && price > lower); } -// pull-back inside upper atr band: -bool AtrBands::crossed_below_upper_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift, bool plot) { - //--- checked bar +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 trendline_var = market_data_utils.get_buffer_value(handle, shift); - double atr_upper_band = upper_band(symbol, trendline_var, atr_period, tf, mult, shift, clrDodgerBlue, 1, plot); - - //--- previous bar double prev_price = iClose(symbol, tf, shift + 1); + + double trendline_var = market_data_utils.get_buffer_value(handle, shift); double prev_trendline_var = market_data_utils.get_buffer_value(handle, shift + 1); - double prev_atr_upper_band = upper_band(symbol, prev_trendline_var, atr_period, tf, mult, shift + 1, clrDodgerBlue, 1, plot); - if (price == EMPTY_VALUE || trendline_var == EMPTY_VALUE || atr_upper_band == EMPTY_VALUE || prev_price == EMPTY_VALUE - || prev_trendline_var == EMPTY_VALUE || prev_atr_upper_band == EMPTY_VALUE) return false; + 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); - return (prev_price > prev_atr_upper_band && price < atr_upper_band); + 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); } -// pull-back inside lower atr band: -bool AtrBands::crossed_above_lower_band(string symbol, int handle, int atr_period, ENUM_TIMEFRAMES tf, double mult, int shift, bool plot) { - //--- checked bar +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 trendline_var = market_data_utils.get_buffer_value(handle, shift); - double atr_lower_band = lower_band(symbol, trendline_var, atr_period, tf, mult, shift, clrDodgerBlue, 1, plot); - - //--- previous bar double prev_price = iClose(symbol, tf, shift + 1); + + double trendline_var = market_data_utils.get_buffer_value(handle, shift); double prev_trendline_var = market_data_utils.get_buffer_value(handle, shift + 1); - double prev_atr_lower_band = lower_band(symbol, prev_trendline_var, atr_period, tf, mult, shift + 1, clrDodgerBlue, 1, plot); - if (price == EMPTY_VALUE || trendline_var == EMPTY_VALUE || atr_lower_band == EMPTY_VALUE || prev_price == EMPTY_VALUE - || prev_trendline_var == EMPTY_VALUE || prev_atr_lower_band == EMPTY_VALUE) return false; + 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); - return (prev_price < prev_atr_lower_band && price > atr_lower_band); + 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); } -// ------------------- -// Internal Helpers -// ------------------- +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; + + double atr = get_atr(symbol, atr_period, tf, i); + 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); + + if (ObjectFind(0, upper_name) < 0) { + ObjectCreate(0, upper_name, OBJ_TREND, 0, time1, upper, time2, upper); + ObjectSetInteger(0, upper_name, OBJPROP_COLOR, line_color); + ObjectSetInteger(0, upper_name, OBJPROP_WIDTH, width); + ObjectSetInteger(0, upper_name, OBJPROP_STYLE, STYLE_SOLID); + ObjectSetInteger(0, upper_name, OBJPROP_BACK, true); + ObjectSetInteger(0, upper_name, OBJPROP_SELECTED, false); + } + + if (ObjectFind(0, lower_name) < 0) { + ObjectCreate(0, lower_name, OBJ_TREND, 0, time1, lower, time2, lower); + ObjectSetInteger(0, lower_name, OBJPROP_COLOR, line_color); + ObjectSetInteger(0, lower_name, OBJPROP_WIDTH, width); + ObjectSetInteger(0, lower_name, OBJPROP_STYLE, STYLE_SOLID); + ObjectSetInteger(0, lower_name, OBJPROP_BACK, true); + ObjectSetInteger(0, lower_name, OBJPROP_SELECTED, false); + } + + if (ObjectFind(0, middle_name) < 0) { + ObjectCreate(0, middle_name, OBJ_TREND, 0, time1, trendline, time2, trendline); + ObjectSetInteger(0, middle_name, OBJPROP_COLOR, line_color); + ObjectSetInteger(0, middle_name, OBJPROP_WIDTH, width); + ObjectSetInteger(0, middle_name, OBJPROP_STYLE, STYLE_SOLID); + ObjectSetInteger(0, middle_name, OBJPROP_BACK, true); + ObjectSetInteger(0, middle_name, OBJPROP_SELECTED, false); + } + } + + ChartRedraw(); +} double AtrBands::get_atr(string symbol, int atr_period, ENUM_TIMEFRAMES tf, int shift) { - int handle = iATR(symbol, tf, atr_period); - if (handle == INVALID_HANDLE) { - Print("Failed to create ATR handle for ", symbol); - return 0.0; - } - - double buf[]; - ArraySetAsSeries(buf, true); - if (CopyBuffer(handle, 0, shift, 1, buf) == 1 && buf[0] != EMPTY_VALUE) return buf[0]; - - return 0.0; -} - -void AtrBands::draw_line(string name, double price, color clr, int width) { - if (ObjectFind(0, name) < 0) { - ObjectCreate(0, name, OBJ_HLINE, 0, 0, price); - } else { - ObjectSetDouble(0, name, OBJPROP_PRICE, price); - } - - ObjectSetInteger(0, name, OBJPROP_COLOR, clr); - ObjectSetInteger(0, name, OBJPROP_WIDTH, width); - ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DASH); - ObjectSetInteger(0, name, OBJPROP_BACK, true); + return atr_manager.get_atr_value(symbol, tf, atr_period, shift); } diff --git a/indicators/TrendlineAnalyser.mqh b/indicators/TrendlineAnalyser.mqh index 8d6edc6..71bf26a 100644 --- a/indicators/TrendlineAnalyser.mqh +++ b/indicators/TrendlineAnalyser.mqh @@ -13,16 +13,16 @@ class TrendlineAnalyser { // - 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) { + 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, 1); // most recent closed bar - double prev_price = iClose(symbol, PERIOD_CURRENT, 2); // bar before that + 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, 1); // trendline now - double prev_trendline = market_data_utils.get_buffer_value(handle, 2); // trendline before + 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) {