diff --git a/Orders/AdjustPosition.mqh b/Orders/AdjustPosition.mqh new file mode 100644 index 0000000..24ddb82 --- /dev/null +++ b/Orders/AdjustPosition.mqh @@ -0,0 +1,187 @@ +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_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); + + 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 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; + + 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 order_type = PositionGetInteger(POSITION_TYPE); + double entry = PositionGetDouble(POSITION_PRICE_OPEN); + double sl = PositionGetDouble(POSITION_SL); + double tp = PositionGetDouble(POSITION_TP); + + set_breakeven_sl_for_ticket(symbol, ticket, order_type, entry, sl, tp, digits, buffer_price, false); + } +} + +// --------------------------------------------------------- +/** + * 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 + + 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 + + // --- 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 + + // --- Parse virtual TP from comment --- + // Format expected: "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 + } + + // 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 (tp > 0.0) { + PrintFormat("Warning: Runner trade on %s (ticket %d) has a non-zero TP: %.5f (should be 0)", symbol, ticket, tp); + } + + // --- Check if price has hit virtual TP --- + 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 (!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) + set_breakeven_sl_for_ticket(symbol, ticket, order_type, entry, sl, tp, digits, buffer_price, true); + } +} + +// --------------------------------------------------------- +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 3eab4c4..728caa9 100644 --- a/Orders/CalculatePositionData.mqh +++ b/Orders/CalculatePositionData.mqh @@ -1,274 +1,303 @@ -#include -#include #include +#include +#include -class CalculatePositionData : public CObject{ - - protected: - CTrade trade; - CPositionInfo position; - MarketDataUtils mdu; +class CalculatePositionData : public CObject { + protected: + CTrade trade; + CPositionInfo position; + MarketDataUtils mdu; - bool check_lots(double &lots, string symbol); - bool normalise_price(double price, double &normalizedPrice, string symbol); + 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); - 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); + 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 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; + double sl = 0; - if(mode_sl=="NO_STOPLOSS"){ - sl=0; + if (mode_sl == "NO_STOPLOSS") { + sl = 0; } - if(mode_sl=="SL_BREAKEVEN"){ + if (mode_sl == "SL_BREAKEVEN") { // https://www.youtube.com/watch?v=idPulZ3_iR0 Alert("Not implemented yet yet"); } - if(mode_sl=="SL_FIXED_PIPS"){ + 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 == 1) { + sl = price - sl_var * adj_point; + if (!normalise_price(sl, sl, symbol)) { + return false; + } } - if(order_side == 2){ + if (order_side == 2) { sl = price + sl_var * adj_point; - if(!normalise_price(sl,sl,symbol)){return false;} + 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 (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){ + if (order_side == 2) { sl = sl_var * price / 100.00 + price; - if(!normalise_price(sl,sl,symbol)){return false;} + if (!normalise_price(sl, sl, symbol)) { + return false; + } } } - if(mode_sl=="SL_ATR_MULTIPLE"){ - - int _atr_handle = iATR(symbol,atr_period,14); + 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;} + 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){ + if (order_side == 2) { sl = price + (atr[0] * sl_var); - if(!normalise_price(sl,sl,symbol)){return false;} + if (!normalise_price(sl, sl, symbol)) { + return false; + } } } - if(mode_sl=="SL_SPECIFIED_VALUE"){ - + if (mode_sl == "SL_SPECIFIED_VALUE") { double adj_point = mdu.adjusted_point(symbol); - if(order_side == 1){ + 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; - double pip_50_sl = price - 10 * adj_point; - if(sl_var >= pip_50_sl){ - sl = pip_50_sl; + if (!normalise_price(sl, sl, symbol)) { + return false; } - 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; + 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;} + 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){ +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; + double tp = 0; - if(mode_tp=="NO_TAKE_PROFIT"){ - tp=0; + if (mode_tp == "NO_TAKE_PROFIT") { + tp = 0; } - if(mode_tp=="TP_FIXED_PIPS"){ - + 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 == 1) { + tp = price + _tp_var * adj_point; + if (!normalise_price(tp, tp, symbol)) { + return false; + } } - if(order_side == 2){ + if (order_side == 2) { tp = price - _tp_var * adj_point; - if(!normalise_price(tp,tp,symbol)){return false;} + 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 (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){ + if (order_side == 2) { tp = (-1 * _tp_var * price / 100.00) + price; - if(!normalise_price(tp,tp,symbol)){return false;} + if (!normalise_price(tp, tp, symbol)) { + return false; + } } } - if(mode_tp=="TP_ATR_MULTIPLE"){ - - int _atr_handle = iATR(symbol,atr_period,14); + 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); + ArraySetAsSeries(atr, true); + CopyBuffer(_atr_handle, MAIN_LINE, 1, 1, atr); - if(order_side == 1){ + if (order_side == 1) { tp = price + (atr[0] * _tp_var); - if(!normalise_price(tp,tp,symbol)){return false;} + if (!normalise_price(tp, tp, symbol)) { + return false; + } } - if(order_side == 2){ + if (order_side == 2) { tp = price - (atr[0] * _tp_var); - if(!normalise_price(tp,tp,symbol)){return false;} + if (!normalise_price(tp, tp, symbol)) { + return false; + } } } - if(mode_tp=="TP_SL_MULTIPLE"){ - if(order_side == 1){ + 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 (!normalise_price(tp, tp, symbol)) { + return false; + } } - if(order_side == 2){ + if (order_side == 2) { double sl_size = stoploss - price; - tp = price - (_tp_var * sl_size); - if(!normalise_price(tp,tp,symbol)){return false;} + tp = price - (_tp_var * sl_size); + if (!normalise_price(tp, tp, symbol)) { + return false; + } } } - if(mode_tp=="TP_SPECIFIED_VALUE"){ - - if(_tp_var!=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 (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; + if (!normalise_price(tp, tp, symbol)) { + return false; } - else tp = _tp_var; + } + 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;} + 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 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_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 volume_step = SymbolInfoDouble(symbol, SYMBOL_VOLUME_STEP); - double account_value = fmin(fmin(AccountInfoDouble(ACCOUNT_EQUITY),AccountInfoDouble(ACCOUNT_BALANCE)),AccountInfoDouble(ACCOUNT_MARGIN_FREE)); + 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"){ + if (mode_lot == "LOT_MODE_FIXED") { lots = lot_var; } - if(mode_lot=="LOT_MODE_PCT_RISK"){ + 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; + lots = MathFloor(risk_money / money_lot_step) * volume_step; } - if(mode_lot=="LOT_MODE_PCT_ACCOUNT"){ + 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; + lots = MathFloor(risk_money / money_lot_step) * volume_step; } - if(!check_lots(lots, symbol)){return false;} + 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); +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); + if (lots > max) { + Print("Lot size greater than maximum allowed volume. lots:", lots, "max:", max); 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){ +bool CalculatePositionData::normalise_price(double price, double& normalizedPrice, string symbol) { double tickSize; - if(!SymbolInfoDouble(symbol,SYMBOL_TRADE_TICK_SIZE,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); + 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){ +double CalculatePositionData::calculate_trading_cost(string symbol, ulong position_ticket) { + position.SelectByTicket(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 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; + return trading_cost; } \ No newline at end of file diff --git a/Orders/EntryOrders.mqh b/Orders/EntryOrders.mqh index e38c2d9..52fc5e1 100644 --- a/Orders/EntryOrders.mqh +++ b/Orders/EntryOrders.mqh @@ -1,112 +1,148 @@ -#include #include +#include class EntryOrders { - protected: - CTrade trade; - CalculatePositionData calc; - double stop_loss; - double take_profit; - int total_open_buy_orders; - int total_open_sell_orders; - double current_price; - - int count_open_positions(string symbol, int order_side, long magic_number); + CTrade trade; + CalculatePositionData calc; 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_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); + 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, + 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_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 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); + + 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) { + 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) { + int type = (int) PositionGetInteger(POSITION_TYPE); -int EntryOrders::count_open_positions(string symbol, int order_side, 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) { - if ((order_side == 1 && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) || - (order_side == 2 && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL)) { - count++; - } - } - } - return count; + // 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++; + } + } + } + return count; } -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) { - current_price = SymbolInfoDouble(symbol, SYMBOL_ASK); - total_open_buy_orders = count_open_positions(symbol, 1, magic_number); - if (total_open_buy_orders == 0) { - stop_loss = calc.calculate_stoploss(symbol, current_price, 1, _sl_mode, sl_var, atr_period); - take_profit = calc.calculate_take_profit(symbol, current_price, stop_loss, 1, _tp_mode, tp_var, atr_period); - double sl_distance = current_price - stop_loss; - double lots = calc.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 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; + + double stop_loss = calc.calculate_stoploss(symbol, current_price, 1, _sl_mode, sl_var, atr_period); + double take_profit = calc.calculate_take_profit(symbol, current_price, stop_loss, 1, _tp_mode, tp_var, atr_period); + double sl_distance = current_price - stop_loss; + double lots = calc.calculate_lots(symbol, sl_distance, current_price, _lot_mode, lot_var); + + 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 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) { - current_price = SymbolInfoDouble(symbol, SYMBOL_BID); - total_open_sell_orders = count_open_positions(symbol, 2, magic_number); - if (total_open_sell_orders == 0) { - stop_loss = calc.calculate_stoploss(symbol, current_price, 2, _sl_mode, sl_var, atr_period); - take_profit = 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); - 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 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; + + double stop_loss = calc.calculate_stoploss(symbol, current_price, 2, _sl_mode, sl_var, atr_period); + double take_profit = 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); + + 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 EntryOrders::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) { - total_open_buy_orders = count_open_positions(symbol, 1, magic_number); - if (total_open_buy_orders == 0) { - stop_loss = calc.calculate_stoploss(symbol, entry_price, 1, _sl_mode, sl_var, atr_period); - take_profit = calc.calculate_take_profit(symbol, entry_price, stop_loss, 1, _tp_mode, tp_var, atr_period); - double sl_distance = entry_price - stop_loss; - double lots = calc.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 EntryOrders::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) return false; + if (count_open_positions(symbol, 1, _magic_number) > 0) return false; + + double stop_loss = calc.calculate_stoploss(symbol, entry_price, 1, _sl_mode, sl_var, atr_period); + double take_profit = calc.calculate_take_profit(symbol, entry_price, stop_loss, 1, _tp_mode, tp_var, atr_period); + double sl_distance = entry_price - stop_loss; + double lots = calc.calculate_lots(symbol, sl_distance, entry_price, _lot_mode, lot_var); + + 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 EntryOrders::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) { - total_open_sell_orders = count_open_positions(symbol, 2, magic_number); - if (total_open_sell_orders == 0) { - stop_loss = calc.calculate_stoploss(symbol, entry_price, 2, _sl_mode, sl_var, atr_period); - take_profit = calc.calculate_take_profit(symbol, entry_price, stop_loss, 2, _tp_mode, tp_var, atr_period); - double sl_distance = stop_loss - entry_price; - double lots = calc.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 EntryOrders::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) return false; + if (count_open_positions(symbol, 2, _magic_number) > 0) return false; + + double stop_loss = calc.calculate_stoploss(symbol, entry_price, 2, _sl_mode, sl_var, atr_period); + double take_profit = calc.calculate_take_profit(symbol, entry_price, stop_loss, 2, _tp_mode, tp_var, atr_period); + double sl_distance = stop_loss - entry_price; + double lots = calc.calculate_lots(symbol, sl_distance, entry_price, _lot_mode, lot_var); + + 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 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) { + if (!condition) return false; + double current_price = SymbolInfoDouble(symbol, SYMBOL_ASK); + if (count_open_positions(symbol, 1, _magic_number) > 0) return false; + + double stop_loss = calc.calculate_stoploss(symbol, current_price, 1, _sl_mode, sl_var, atr_period); + double virtual_tp = calc.calculate_take_profit(symbol, current_price, stop_loss, 1, _tp_mode, tp_var, atr_period); + double sl_distance = current_price - stop_loss; + double lots = calc.calculate_lots(symbol, sl_distance, current_price, _lot_mode, lot_var); + + 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 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) { + if (!condition) return false; + double current_price = SymbolInfoDouble(symbol, SYMBOL_BID); + if (count_open_positions(symbol, 2, _magic_number) > 0) return false; + + 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); + + 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); } diff --git a/Orders/ExitOrders.mqh b/Orders/ExitOrders.mqh index e60bc35..f0ed9f1 100644 --- a/Orders/ExitOrders.mqh +++ b/Orders/ExitOrders.mqh @@ -1,153 +1,137 @@ -#include -#include #include +#include +#include class ExitOrders { - protected: - CTrade trade; - TimeZones tz; - CalculatePositionData cpd; + CTrade trade; + TimeZones tz; + CalculatePositionData calc; - ulong posTicket; - long position_open_time; - long first_allowed_close_time; + ulong posTicket; + long position_open_time; + long first_allowed_close_time; 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 first_profitable_close_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, long magic_number); + 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 first_profitable_close_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, long _magic_number); }; +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); -bool ExitOrders::close_buy_orders(string symbol, bool condition, int close_bars, ENUM_TIMEFRAMES close_bar_period, - long magic_number) { + if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == _magic_number) { + int time_difference = Bars(symbol, close_bar_period, PositionGetInteger(POSITION_TIME), TimeCurrent()) - 1; - for (int i = PositionsTotal() - 1; i >= 0; i--) { - posTicket = PositionGetTicket(i); - - if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number) { - int time_difference = Bars(symbol, close_bar_period, PositionGetInteger(POSITION_TIME), TimeCurrent()) - 1; - - if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { - if (condition || (close_bars > 0 && time_difference >= close_bars)) { - trade.PositionClose(posTicket); + if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { + if (condition || (close_bars > 0 && time_difference >= close_bars)) { + trade.PositionClose(posTicket); + } } - } - } - } - return true; + } + } + return true; } +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); -bool ExitOrders::close_sell_orders(string symbol, bool condition, int close_bars, ENUM_TIMEFRAMES close_bar_period, - long magic_number) { + if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == _magic_number) { + int time_difference = Bars(symbol, close_bar_period, PositionGetInteger(POSITION_TIME), TimeCurrent()) - 1; - for (int i = PositionsTotal() - 1; i >= 0; i--) { - posTicket = PositionGetTicket(i); - - if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number) { - int time_difference = Bars(symbol, close_bar_period, PositionGetInteger(POSITION_TIME), TimeCurrent()) - 1; - - if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { - if (condition || (close_bars > 0 && time_difference >= close_bars)) { - trade.PositionClose(posTicket); + if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { + if (condition || (close_bars > 0 && time_difference >= close_bars)) { + trade.PositionClose(posTicket); + } } - } - } - } - return true; + } + } + return true; } +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); + position_open_time = PositionGetInteger(POSITION_TIME); -bool ExitOrders::daily_timed_exit(string symbol, datetime exit_time, int delay_days, long magic_number) { + if ((int) position_open_time > 0) { + first_allowed_close_time = position_open_time + (delay_days * PeriodSeconds(PERIOD_D1)); - 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 && TimeCurrent() >= exit_time) { - if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number) { - trade.PositionClose(posTicket); + if (TimeCurrent() > first_allowed_close_time && TimeCurrent() >= exit_time) { + if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == _magic_number) { + trade.PositionClose(posTicket); + } } - } - } - } - return true; + } + } + return true; } +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--) { + posTicket = PositionGetTicket(i); + position_open_time = PositionGetInteger(POSITION_TIME); -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) { + if ((int) position_open_time > 0) { + first_allowed_close_time = position_open_time + (delay_days * PeriodSeconds(PERIOD_D1)); - for (int i = PositionsTotal() - 1; i >= 0; i--) { - posTicket = PositionGetTicket(i); - position_open_time = PositionGetInteger(POSITION_TIME); + if (TimeCurrent() > first_allowed_close_time) { + datetime broker_close_time = tz.timezone_conversions(cw_tzone, StringToTime(exit_time), "Broker"); - if ((int)position_open_time > 0) { - first_allowed_close_time = position_open_time + (delay_days * PeriodSeconds(PERIOD_D1)); + if (TimeCurrent() >= broker_close_time && 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 is live candle + double trading_cost = calc.calculate_trading_cost(symbol, posTicket); - if (TimeCurrent() > first_allowed_close_time) { - datetime broker_close_time = tz.timezone_conversions(cw_tzone, StringToTime(exit_time), "Broker"); + if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && + bar_close > (position_open_price + spread + trading_cost)) { + trade.PositionClose(posTicket); + } - if (TimeCurrent() >= broker_close_time && - 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 is live candle - double trading_cost = cpd.calculate_trading_cost(symbol, posTicket); - - if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && - bar_close > (position_open_price + spread + trading_cost)) { - trade.PositionClose(posTicket); - } - - if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && - bar_close < (position_open_price - spread - trading_cost)) { - trade.PositionClose(posTicket); - } + if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && + bar_close < (position_open_price - spread - trading_cost)) { + trade.PositionClose(posTicket); + } + } } - } - } - } - return true; + } + } + return true; } +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); -bool ExitOrders::first_profitable_close_exit(string symbol, ENUM_TIMEFRAMES close_bar_period, long magic_number) { + if ((int) position_open_time > 0 && TimeCurrent() > first_allowed_close_time) { + for (int i = PositionsTotal() - 1; i >= 0; i--) { + posTicket = PositionGetTicket(i); - position_open_time = PositionGetInteger(POSITION_TIME); - first_allowed_close_time = position_open_time + PeriodSeconds(close_bar_period); + 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); + double trading_cost = calc.calculate_trading_cost(symbol, posTicket); - if ((int)position_open_time > 0 && TimeCurrent() > first_allowed_close_time) { - for (int i = PositionsTotal() - 1; i >= 0; i--) { - posTicket = PositionGetTicket(i); + if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && bar_close > (position_open_price + spread + trading_cost)) { + trade.PositionClose(posTicket); + } - 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); - double trading_cost = cpd.calculate_trading_cost(symbol, posTicket); - - if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY && - bar_close > (position_open_price + spread + trading_cost)) { - trade.PositionClose(posTicket); + if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && bar_close < (position_open_price - spread - trading_cost)) { + trade.PositionClose(posTicket); + } } + } + } - if (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL && - bar_close < (position_open_price - spread - trading_cost)) { - trade.PositionClose(posTicket); - } - } - } - } - - return true; + return true; } diff --git a/Orders/OrderTracker.mqh b/Orders/OrderTracker.mqh index 8ab8eb1..7e198c3 100644 --- a/Orders/OrderTracker.mqh +++ b/Orders/OrderTracker.mqh @@ -2,68 +2,62 @@ #include class OrderTracker { - protected: - COrderInfo m_order; - CPositionInfo m_position; + COrderInfo m_order; + CPositionInfo m_position; public: - int count_open_positions(string symbol, int order_side, long magic_number); - int count_all_positions(string symbol, long magic_number); - int count_pending_orders(string symbol, ENUM_ORDER_TYPE order_type, long magic); + int count_open_positions(string symbol, int order_side, long magic_number); + int count_all_positions(string symbol, long magic_number); + int count_pending_orders(string symbol, ENUM_ORDER_TYPE order_type, long magic); }; int OrderTracker::count_open_positions(string symbol, int order_side, long magic_number) { - int count = 0; + int count = 0; - for (int i = PositionsTotal() - 1; i >= 0; i--) { - ulong ticket = PositionGetTicket(i); + for (int i = PositionsTotal() - 1; i >= 0; i--) { + ulong ticket = PositionGetTicket(i); - if (PositionGetString(POSITION_SYMBOL) == symbol && - PositionGetInteger(POSITION_MAGIC) == magic_number) { + if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number) { + if (order_side == 1 && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { + count++; + } - if (order_side == 1 && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY) { - count++; - } + if (order_side == 2 && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { + count++; + } + } + } - if (order_side == 2 && PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_SELL) { - count++; - } - } - } - - return count; + return count; } - int OrderTracker::count_all_positions(string symbol, long magic_number) { - int count = 0; + int count = 0; - for (int i = PositionsTotal() - 1; i >= 0; i--) { - ulong ticket = PositionGetTicket(i); + for (int i = PositionsTotal() - 1; i >= 0; i--) { + ulong ticket = PositionGetTicket(i); - if (PositionGetString(POSITION_SYMBOL) == symbol && - PositionGetInteger(POSITION_MAGIC) == magic_number) { - count++; - } - } + if (PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == magic_number) { + count++; + } + } - return count; + return count; } - int OrderTracker::count_pending_orders(string symbol, ENUM_ORDER_TYPE order_type, long magic) { - int count = 0; + 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++; + 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; + return count; } diff --git a/Orders/StopLogic.mqh b/Orders/StopLogic.mqh index e39e661..4be92fb 100644 --- a/Orders/StopLogic.mqh +++ b/Orders/StopLogic.mqh @@ -1,21 +1,21 @@ class StopLogic { 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); + 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); }; double StopLogic::sl_specified_value_switch(string sl_mode, double inp_sl_var, double value) { - if (sl_mode == "SL_SPECIFIED_VALUE") { - return value; - } else { - return inp_sl_var; - } + if (sl_mode == "SL_SPECIFIED_VALUE") { + return value; + } else { + return inp_sl_var; + } } double StopLogic::tp_specified_value_switch(string tp_mode, double inp_tp_var, double value) { - if (tp_mode == "SL_SPECIFIED_VALUE") { - return value; - } else { - return inp_tp_var; - } + if (tp_mode == "SL_SPECIFIED_VALUE") { + return value; + } else { + return inp_tp_var; + } } diff --git a/Orders/TrailingLogic.mqh b/Orders/TrailingLogic.mqh index 88a73c4..c10afc1 100644 --- a/Orders/TrailingLogic.mqh +++ b/Orders/TrailingLogic.mqh @@ -1,95 +1,81 @@ #include class TrailingLogic { - protected: - CTrade trade; + 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 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); + 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); - 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 (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 + (atr_value * tp_var)) { + 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"); + } + } - 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 - 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 - (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); - } - } - } - } - } + } + } +} + +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/Strategy/EntryLogic_nnfx.mqh b/Strategy/EntryLogic_nnfx.mqh index 6592bd4..1a81a31 100644 --- a/Strategy/EntryLogic_nnfx.mqh +++ b/Strategy/EntryLogic_nnfx.mqh @@ -1,111 +1,105 @@ - 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; + 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(); - } + // 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 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_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_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; - } + 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; + 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; + 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; +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; - } + 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_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_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; - } + 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/Utils/MarketDataUtils.mqh b/Utils/MarketDataUtils.mqh index 4551312..c7e8b4e 100644 --- a/Utils/MarketDataUtils.mqh +++ b/Utils/MarketDataUtils.mqh @@ -42,17 +42,21 @@ double MarketDataUtils::get_latest_buffer_value(int handle) { return 0.0; } -// Retrieves a historical buffer value at specified shift +// 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. double MarketDataUtils::get_buffer_value(int handle, int shift) { - double val[]; - ArraySetAsSeries(val, true); - if (CopyBuffer(handle, 0, shift, 1, val) == 1) - return val[0]; // Historical value at given shift + double val[]; + ArraySetAsSeries(val, true); - return 0.0; + if (CopyBuffer(handle, 0, shift, 1, val) == 1 && val[0] != EMPTY_VALUE) + return val[0]; + + return EMPTY_VALUE; } + // 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/indicators/AtrBands.mqh b/indicators/AtrBands.mqh new file mode 100644 index 0000000..5a1dbc7 --- /dev/null +++ b/indicators/AtrBands.mqh @@ -0,0 +1,96 @@ +// File: MyLibs/Utils/AtrBands.mqh + +class AtrBands { + protected: + string symbol; + int atr_period; + ENUM_TIMEFRAMES timeframe; + string prefix; + color upper_color; + color lower_color; + color mid_color; + int line_width; + bool plot_bands; + int atr_handle; + + public: + AtrBands(string _symbol, int _atr_period = 14, ENUM_TIMEFRAMES _timeframe = PERIOD_CURRENT, + color _upper = clrDodgerBlue, color _lower = clrDodgerBlue, color _mid = clrDodgerBlue, + int _width = 1, bool plot = true); + + double upper_band(double baseline, int shift = 1, double mult = 1.0); + double lower_band(double baseline, int shift = 1, double mult = 1.0); + double middle_band(double baseline, int shift = 1); + + protected: + double get_atr(int shift); + void draw_line(string name, int shift, double price, color clr); +}; + +// Constructor +AtrBands::AtrBands(string _symbol, int _atr_period, ENUM_TIMEFRAMES _timeframe, + color _upper, color _lower, color _mid, int _width, bool plot) { + symbol = _symbol; + atr_period = _atr_period; + timeframe = _timeframe; + prefix = "ATRBand"; + upper_color = _upper; + lower_color = _lower; + mid_color = _mid; + line_width = _width; + plot_bands = plot; + + atr_handle = iATR(symbol, timeframe, atr_period); + if (atr_handle == INVALID_HANDLE) { + Print("Failed to create ATR handle for symbol: ", symbol); + } +} + +// Public methods +double AtrBands::upper_band(double baseline, int shift, double mult) { + double atr = get_atr(shift); + double upper = baseline + atr * mult; + if (plot_bands) draw_line(prefix + "_Upper_" + symbol, shift, upper, upper_color); + return upper; +} + +double AtrBands::lower_band(double baseline, int shift, double mult) { + double atr = get_atr(shift); + double lower = baseline - atr * mult; + if (plot_bands) draw_line(prefix + "_Lower_" + symbol, shift, lower, lower_color); + return lower; +} + +double AtrBands::middle_band(double baseline, int shift) { + if (plot_bands) draw_line(prefix + "_Mid_" + symbol, shift, baseline, mid_color); + return baseline; +} + +// Internal: Get ATR value from buffer +double AtrBands::get_atr(int shift) { + if (atr_handle == INVALID_HANDLE) return 0; + + double buf[]; + ArraySetAsSeries(buf, true); + if (CopyBuffer(atr_handle, 0, shift, 1, buf) == 1 && buf[0] != EMPTY_VALUE) + return buf[0]; + + return 0; +} + +// Internal: Draw line for a given price at a bar +void AtrBands::draw_line(string name, int shift, double price, color clr) { + datetime time0 = iTime(symbol, timeframe, shift); + datetime time1 = time0 + PeriodSeconds(timeframe); + + if (ObjectFind(0, name) < 0) { + ObjectCreate(0, name, OBJ_TREND, 0, time0, price, time1, price); + } else { + ObjectMove(0, name, 0, time0, price); + ObjectMove(0, name, 1, time1, price); + } + + ObjectSetInteger(0, name, OBJPROP_COLOR, clr); + ObjectSetInteger(0, name, OBJPROP_WIDTH, line_width); + ObjectSetInteger(0, name, OBJPROP_STYLE, STYLE_DASH); +}