//+------------------------------------------------------------------+ //| Strategy.mqh - Sweep breakout multi-timeframe swing structure EA | //| Implements trend filter on one timeframe and breakout setups on | //| a lower signal timeframe using confirmed swing structure, ATR | //| filtering, setup arming, and protected breakout entries. | //+------------------------------------------------------------------+ #ifndef __STRATEGY_MQH__ #define __STRATEGY_MQH__ #include "Signal.mqh" #include "MarketData.mqh" #include "Logger.mqh" #include "Config.mqh" #include "Utilities.mqh" // Local enums for strategy logic enum E_TREND_STATE { NO_TREND = 0, UPTREND_ACTIVE = 1, DOWNTREND_ACTIVE = -1, TREND_BROKEN = 2, WAITING_FOR_NEW_TREND = 3 }; struct SSwing { datetime time; double price; bool isHigh; ENUM_TIMEFRAMES timeframe; ulong id; }; struct SArmedSetup { bool active; E_SIGNAL direction; ulong setupID; ulong trendID; int barCountAtArm; double triggerPrice; bool traded; }; class CStrategy { private: CMarketData *mp_market_data; CLogger *mp_logger; ENUM_TIMEFRAMES m_signal_timeframe; ENUM_TIMEFRAMES m_filter_timeframe; int m_signal_bar_count; int m_filter_bar_count; int m_signal_atr_handle; int m_filter_atr_handle; E_TREND_STATE m_filter_state; ulong m_filter_trend_id; int m_trades_taken_in_current_trend; int m_cooldown_bars_remaining; bool m_trend_broken_on_tick; datetime m_trend_broken_time; SSwing m_signal_highs[]; SSwing m_signal_lows[]; SSwing m_filter_highs[]; SSwing m_filter_lows[]; ulong m_used_setup_ids[]; SArmedSetup m_buy_setup; SArmedSetup m_sell_setup; bool m_initialized; public: CStrategy(CMarketData *market_data, CLogger *logger) { mp_market_data = market_data; mp_logger = logger; m_signal_timeframe = g_strategy_entry_timeframe; m_filter_timeframe = g_strategy_trend_timeframe; m_signal_bar_count = 0; m_filter_bar_count = 0; m_signal_atr_handle = INVALID_HANDLE; m_filter_atr_handle = INVALID_HANDLE; m_calls = 0; m_filter_state = NO_TREND; m_filter_trend_id = 0; m_trades_taken_in_current_trend = 0; m_cooldown_bars_remaining = 0; m_trend_broken_on_tick = false; m_trend_broken_time = 0; m_buy_setup.active = false; m_sell_setup.active = false; m_buy_setup.traded = false; m_sell_setup.traded = false; m_initialized = false; } ~CStrategy() { Cleanup(); } bool Init() { const string symbol = mp_market_data.GetSymbol(); if(g_use_atr_filter) { m_signal_atr_handle = iATR(symbol, m_signal_timeframe, g_atr_period); if(m_signal_atr_handle == INVALID_HANDLE) { if(mp_logger) mp_logger.Error("Failed to create ATR indicator for signal timeframe"); return false; } m_filter_atr_handle = iATR(symbol, m_filter_timeframe, g_atr_period); if(m_filter_atr_handle == INVALID_HANDLE) { if(mp_logger) mp_logger.Error("Failed to create ATR indicator for filter timeframe"); IndicatorRelease(m_signal_atr_handle); m_signal_atr_handle = INVALID_HANDLE; return false; } } m_signal_bar_count = iBars(symbol, m_signal_timeframe); m_filter_bar_count = iBars(symbol, m_filter_timeframe); if(m_signal_bar_count <= 0 || m_filter_bar_count <= 0) { if(mp_logger) mp_logger.Error("Unable to read bars for configured timeframes"); return false; } ScanConfirmedSwings(m_filter_timeframe, g_filter_swing_depth, m_filter_highs, m_filter_lows); ScanConfirmedSwings(m_signal_timeframe, g_signal_swing_depth, m_signal_highs, m_signal_lows); UpdateFilterTrendState(); m_initialized = true; return true; } E_SIGNAL GetSignal() { return GetTradeSetup().signal; } TradeSetup GetTradeSetup() { TradeSetup setup; setup.signal = SIGNAL_NONE; setup.entryPrice = 0.0; setup.stopLoss = 0.0; setup.takeProfit = 0.0; setup.riskDistance = 0.0; setup.atrValue = 0.0; setup.reason = "No valid setup"; const string symbol = mp_market_data.GetSymbol(); m_calls++; if(!m_initialized) { if(!Init()) { setup.reason = "Strategy initialization failed"; return setup; } } m_trend_broken_on_tick = false; int filter_bar_count = iBars(symbol, m_filter_timeframe); bool filter_new_bar = (filter_bar_count > m_filter_bar_count); if(filter_new_bar) m_filter_bar_count = filter_bar_count; int signal_bar_count = iBars(symbol, m_signal_timeframe); bool signal_new_bar = (signal_bar_count > m_signal_bar_count); if(signal_new_bar) m_signal_bar_count = signal_bar_count; if(filter_new_bar) ScanConfirmedSwings(m_filter_timeframe, g_filter_swing_depth, m_filter_highs, m_filter_lows); if(signal_new_bar) ScanConfirmedSwings(m_signal_timeframe, g_signal_swing_depth, m_signal_highs, m_signal_lows); UpdateFilterTrendState(); EvaluateTrendBreak(filter_new_bar); if(signal_new_bar) { ExpireSetups(); ArmSetups(); } if(m_trend_broken_on_tick) { setup.reason = "Filter trend broken on current tick"; return setup; } if(m_filter_state != UPTREND_ACTIVE && m_filter_state != DOWNTREND_ACTIVE) { setup.reason = "No active filter trend"; return setup; } if(!mp_market_data.IsSpreadAcceptable(g_strategy_max_spread_points)) { setup.reason = "Spread too wide for breakout entry"; return setup; } if(GetOpenPositionCount(symbol) >= g_strategy_max_open_positions) { setup.reason = "Maximum open positions achieved"; return setup; } if(m_buy_setup.active && m_buy_setup.direction == SIGNAL_BUY) { if(m_filter_state == UPTREND_ACTIVE && m_buy_setup.trendID == m_filter_trend_id && !m_buy_setup.traded) { if(!IsSetupExpired(m_buy_setup)) { double ask = mp_market_data.GetAsk(); double buffer = CUtilities::PointsToPrice(symbol, g_entry_buffer_points); double threshold = m_buy_setup.triggerPrice + (g_use_breakout_entry ? buffer : 0.0); if(ask > threshold) { double stop_loss = CalculateStopLoss(symbol, SIGNAL_BUY, ask); double take_profit = CalculateTakeProfit(symbol, SIGNAL_BUY, ask, stop_loss); if(IsTradeLevelSetValid(SIGNAL_BUY, ask, stop_loss, take_profit)) { setup.signal = SIGNAL_BUY; setup.entryPrice = ask; setup.stopLoss = CUtilities::NormalizePrice(symbol, stop_loss); setup.takeProfit = CUtilities::NormalizePrice(symbol, take_profit); setup.riskDistance = MathAbs(ask - stop_loss); setup.atrValue = GetLatestATRValue(m_signal_timeframe, 1); setup.reason = StringFormat("BUY breakout signal: FilterTrendID=%I64u SetupID=%I64u trigger=%.5f", m_filter_trend_id, m_buy_setup.setupID, m_buy_setup.triggerPrice); MarkSetupTraded(m_buy_setup); return setup; } setup.reason = "BUY breakout rejected: invalid SL/TP"; } } } } if(m_sell_setup.active && m_sell_setup.direction == SIGNAL_SELL) { if(m_filter_state == DOWNTREND_ACTIVE && m_sell_setup.trendID == m_filter_trend_id && !m_sell_setup.traded) { if(!IsSetupExpired(m_sell_setup)) { double bid = mp_market_data.GetBid(); double buffer = CUtilities::PointsToPrice(symbol, g_entry_buffer_points); double threshold = m_sell_setup.triggerPrice - (g_use_breakout_entry ? buffer : 0.0); if(bid < threshold) { double stop_loss = CalculateStopLoss(symbol, SIGNAL_SELL, bid); double take_profit = CalculateTakeProfit(symbol, SIGNAL_SELL, bid, stop_loss); if(IsTradeLevelSetValid(SIGNAL_SELL, bid, stop_loss, take_profit)) { setup.signal = SIGNAL_SELL; setup.entryPrice = bid; setup.stopLoss = CUtilities::NormalizePrice(symbol, stop_loss); setup.takeProfit = CUtilities::NormalizePrice(symbol, take_profit); setup.riskDistance = MathAbs(stop_loss - bid); setup.atrValue = GetLatestATRValue(m_signal_timeframe, 1); setup.reason = StringFormat("SELL breakout signal: FilterTrendID=%I64u SetupID=%I64u trigger=%.5f", m_filter_trend_id, m_sell_setup.setupID, m_sell_setup.triggerPrice); MarkSetupTraded(m_sell_setup); return setup; } setup.reason = "SELL breakout rejected: invalid SL/TP"; } } } } setup.reason = "No valid breakout trigger"; return setup; } void LogDiagnostics() { if(mp_logger && g_debug_mode) { mp_logger.Info(StringFormat("Strategy diagnostics: calls=%I64d state=%d FilterTrendID=%I64u tradesInTrend=%d buyActive=%s sellActive=%s", m_calls, m_filter_state, m_filter_trend_id, m_trades_taken_in_current_trend, m_buy_setup.active ? "true" : "false", m_sell_setup.active ? "true" : "false")); } } private: long m_calls; void Cleanup() { if(m_signal_atr_handle != INVALID_HANDLE) { IndicatorRelease(m_signal_atr_handle); m_signal_atr_handle = INVALID_HANDLE; } if(m_filter_atr_handle != INVALID_HANDLE && m_filter_atr_handle != m_signal_atr_handle) { IndicatorRelease(m_filter_atr_handle); m_filter_atr_handle = INVALID_HANDLE; } } double GetLatestATRValue(ENUM_TIMEFRAMES timeframe, int shift) { if(!g_use_atr_filter) return 0.0; int handle = (timeframe == m_signal_timeframe) ? m_signal_atr_handle : m_filter_atr_handle; if(handle == INVALID_HANDLE) return 0.0; double value[]; ArraySetAsSeries(value, true); ArrayResize(value, 1); if(CopyBuffer(handle, 0, shift, 1, value) <= 0) return 0.0; return value[0]; } bool ScanConfirmedSwings(ENUM_TIMEFRAMES timeframe, int depth, SSwing &highs[], SSwing &lows[]) { const string symbol = mp_market_data.GetSymbol(); int barCount = iBars(symbol, timeframe); if(barCount < (depth * 2 + 3)) return false; int request = MathMin(barCount, depth * 20 + 50); if(request < (depth * 2 + 3)) request = depth * 2 + 3; MqlRates rates[]; ArraySetAsSeries(rates, true); ArrayResize(rates, request); int copied = CopyRates(symbol, timeframe, 0, request, rates); if(copied <= 0) return false; ArrayResize(rates, copied); for(int index = depth + 1; index < copied - depth; index++) { if(IsSwingHigh(rates, index, depth)) { SSwing swing; swing.time = rates[index].time; swing.price = rates[index].high; swing.isHigh = true; swing.timeframe = timeframe; swing.id = ((ulong)swing.time << 8) | (ulong)timeframe; if(!SwingExists(swing, highs)) { if(!IsSwingTooSmall(symbol, swing, lows)) AddSwing(highs, swing); } } else if(IsSwingLow(rates, index, depth)) { SSwing swing; swing.time = rates[index].time; swing.price = rates[index].low; swing.isHigh = false; swing.timeframe = timeframe; swing.id = ((ulong)swing.time << 8) | (ulong)timeframe; if(!SwingExists(swing, lows)) { if(!IsSwingTooSmall(symbol, swing, highs)) AddSwing(lows, swing); } } } TrimSwings(highs, 64); TrimSwings(lows, 64); return true; } bool IsSwingHigh(MqlRates &rates[], int index, int depth) { double price = rates[index].high; for(int offset = 1; offset <= depth; offset++) { if(price <= rates[index - offset].high || price <= rates[index + offset].high) return false; } return true; } bool IsSwingLow(MqlRates &rates[], int index, int depth) { double price = rates[index].low; for(int offset = 1; offset <= depth; offset++) { if(price >= rates[index - offset].low || price >= rates[index + offset].low) return false; } return true; } bool SwingExists(const SSwing &swing, SSwing &array[]) const { for(int i = 0; i < ArraySize(array); i++) { if(array[i].id == swing.id) return true; } return false; } void AddSwing(SSwing &array[], const SSwing &swing) { ArrayResize(array, ArraySize(array) + 1); array[ArraySize(array) - 1] = swing; } void TrimSwings(SSwing &array[], int keep) { int size = ArraySize(array); if(size <= keep) return; int remove = size - keep; for(int i = 0; i < keep; i++) array[i] = array[i + remove]; ArrayResize(array, keep); } bool IsSwingTooSmall(const string symbol, const SSwing &swing, SSwing &oppositeSwings[]) { if(!g_use_atr_filter) return false; double atrValue = GetLatestATRValue(swing.timeframe, 1); if(atrValue <= 0.0) return false; double minDistance = atrValue * g_min_swing_atr_multiplier; double lastOpposite = GetLastOppositeSwingPriceBefore(oppositeSwings, swing.time); if(lastOpposite <= 0.0) return false; if(swing.isHigh) return (swing.price - lastOpposite) < minDistance; return (lastOpposite - swing.price) < minDistance; } double GetLastOppositeSwingPriceBefore(SSwing &swings[], datetime beforeTime) const { double price = 0.0; for(int i = ArraySize(swings) - 1; i >= 0; i--) { if(swings[i].time < beforeTime) { price = swings[i].price; break; } } return price; } void UpdateFilterTrendState() { E_TREND_STATE detected = DetectFilterTrend(); if(detected == m_filter_state && (m_filter_state == UPTREND_ACTIVE || m_filter_state == DOWNTREND_ACTIVE)) return; if(detected == UPTREND_ACTIVE || detected == DOWNTREND_ACTIVE) { if(m_filter_state == TREND_BROKEN) return; if(m_filter_state == WAITING_FOR_NEW_TREND && !IsFreshFilterTrendAfterBreak()) return; if(m_filter_state != detected) StartNewFilterTrend(detected); } else if(m_filter_state == TREND_BROKEN && detected != TREND_BROKEN) { // remain in broken/waiting until a new trend is clearly established if(m_cooldown_bars_remaining <= 0) m_filter_state = WAITING_FOR_NEW_TREND; } else if(m_filter_state == WAITING_FOR_NEW_TREND && detected == NO_TREND) { // remain waiting until a fresh trend forms } else if(m_filter_state == NO_TREND && detected == NO_TREND) { // no state change } } void StartNewFilterTrend(E_TREND_STATE detected) { m_filter_trend_id++; m_trades_taken_in_current_trend = 0; m_cooldown_bars_remaining = 0; m_filter_state = detected; m_buy_setup.active = false; m_sell_setup.active = false; m_buy_setup.traded = false; m_sell_setup.traded = false; ArrayResize(m_used_setup_ids, 0); if(mp_logger && g_debug_mode) mp_logger.Info(StringFormat("New filter trend detected: %d, FilterTrendID=%I64u", detected, m_filter_trend_id)); } bool IsFreshFilterTrendAfterBreak() { if(m_trend_broken_time <= 0) return true; datetime lastHighTime = GetLastSwingTime(m_filter_highs); datetime lastLowTime = GetLastSwingTime(m_filter_lows); return (lastHighTime > m_trend_broken_time && lastLowTime > m_trend_broken_time); } E_TREND_STATE DetectFilterTrend() const { if(ArraySize(m_filter_highs) < (g_filter_trend_confirm_count + 1) || ArraySize(m_filter_lows) < (g_filter_trend_confirm_count + 1)) return NO_TREND; bool higherHighs = true; for(int i = ArraySize(m_filter_highs) - g_filter_trend_confirm_count - 1; i < ArraySize(m_filter_highs) - 1; i++) { if(m_filter_highs[i + 1].price <= m_filter_highs[i].price) { higherHighs = false; break; } } bool higherLows = true; for(int i = ArraySize(m_filter_lows) - g_filter_trend_confirm_count - 1; i < ArraySize(m_filter_lows) - 1; i++) { if(m_filter_lows[i + 1].price <= m_filter_lows[i].price) { higherLows = false; break; } } if(higherHighs && higherLows) return UPTREND_ACTIVE; bool lowerHighs = true; for(int i = ArraySize(m_filter_highs) - g_filter_trend_confirm_count - 1; i < ArraySize(m_filter_highs) - 1; i++) { if(m_filter_highs[i + 1].price >= m_filter_highs[i].price) { lowerHighs = false; break; } } bool lowerLows = true; for(int i = ArraySize(m_filter_lows) - g_filter_trend_confirm_count - 1; i < ArraySize(m_filter_lows) - 1; i++) { if(m_filter_lows[i + 1].price >= m_filter_lows[i].price) { lowerLows = false; break; } } if(lowerHighs && lowerLows) return DOWNTREND_ACTIVE; return NO_TREND; } void EvaluateTrendBreak(bool filter_new_bar) { if(m_filter_state != UPTREND_ACTIVE && m_filter_state != DOWNTREND_ACTIVE) { if(m_filter_state == TREND_BROKEN && filter_new_bar) { if(m_cooldown_bars_remaining > 0) m_cooldown_bars_remaining--; if(m_cooldown_bars_remaining <= 0) m_filter_state = WAITING_FOR_NEW_TREND; } return; } const string symbol = mp_market_data.GetSymbol(); double buffer = CUtilities::PointsToPrice(symbol, g_break_buffer_points); if(m_filter_state == UPTREND_ACTIVE) { double swingLow = GetLastSwingPrice(m_filter_lows); if(swingLow <= 0.0) return; bool broken = false; if(g_trend_break_mode == BREAK_MODE_BID_ASK_TOUCH) broken = (mp_market_data.GetBid() <= swingLow - buffer); else { double last_close = iClose(symbol, m_filter_timeframe, 1); broken = (last_close > 0.0 && last_close <= swingLow - buffer); } if(broken) { m_filter_state = TREND_BROKEN; m_trend_broken_on_tick = true; m_trend_broken_time = iTime(symbol, m_filter_timeframe, 1); if(m_trend_broken_time <= 0) m_trend_broken_time = TimeCurrent(); m_cooldown_bars_remaining = g_cooldown_bars_after_trend_break; CancelSetups("Filter uptrend broken"); if(mp_logger && g_debug_mode) mp_logger.Info("Filter uptrend broken"); } } else if(m_filter_state == DOWNTREND_ACTIVE) { double swingHigh = GetLastSwingPrice(m_filter_highs); if(swingHigh <= 0.0) return; bool broken = false; if(g_trend_break_mode == BREAK_MODE_BID_ASK_TOUCH) broken = (mp_market_data.GetAsk() >= swingHigh + buffer); else { double last_close = iClose(symbol, m_filter_timeframe, 1); broken = (last_close > 0.0 && last_close >= swingHigh + buffer); } if(broken) { m_filter_state = TREND_BROKEN; m_trend_broken_on_tick = true; m_trend_broken_time = iTime(symbol, m_filter_timeframe, 1); if(m_trend_broken_time <= 0) m_trend_broken_time = TimeCurrent(); m_cooldown_bars_remaining = g_cooldown_bars_after_trend_break; CancelSetups("Filter downtrend broken"); if(mp_logger && g_debug_mode) mp_logger.Info("Filter downtrend broken"); } } } double GetLastSwingPrice(SSwing &swings[]) const { int size = ArraySize(swings); if(size <= 0) return 0.0; return swings[size - 1].price; } void CancelSetups(const string reason) { if(m_buy_setup.active) { AddUsedSetupID(m_buy_setup.setupID); m_buy_setup.active = false; m_buy_setup.traded = true; } if(m_sell_setup.active) { AddUsedSetupID(m_sell_setup.setupID); m_sell_setup.active = false; m_sell_setup.traded = true; } if(mp_logger && g_debug_mode) mp_logger.Info("Canceling setups due to trend break: " + reason); } void ArmSetups() { const string symbol = mp_market_data.GetSymbol(); if(m_trades_taken_in_current_trend >= g_max_trades_per_filter_trend) return; if(m_filter_state == UPTREND_ACTIVE && !m_buy_setup.active) { if(HasBullishSignalStructure()) { double trigger = GetLastSwingPrice(m_signal_highs); ulong setupID = ((ulong)GetLastSwingTime(m_signal_highs) << 8) | (ulong)m_signal_timeframe; if(trigger > 0.0 && setupID > 0 && !IsSetupIDUsed(setupID) && mp_market_data.IsSpreadAcceptable(g_strategy_max_spread_points)) { m_buy_setup.active = true; m_buy_setup.direction = SIGNAL_BUY; m_buy_setup.setupID = setupID; m_buy_setup.trendID = m_filter_trend_id; m_buy_setup.barCountAtArm = m_signal_bar_count; m_buy_setup.triggerPrice = trigger; m_buy_setup.traded = false; if(mp_logger && g_debug_mode) mp_logger.Info(StringFormat("Armed BUY setup: trigger=%.5f trendID=%I64u", trigger, m_filter_trend_id)); } } } if(m_filter_state == DOWNTREND_ACTIVE && !m_sell_setup.active) { if(HasBearishSignalStructure()) { double trigger = GetLastSwingPrice(m_signal_lows); ulong setupID = ((ulong)GetLastSwingTime(m_signal_lows) << 8) | (ulong)m_signal_timeframe; if(trigger > 0.0 && setupID > 0 && !IsSetupIDUsed(setupID) && mp_market_data.IsSpreadAcceptable(g_strategy_max_spread_points)) { m_sell_setup.active = true; m_sell_setup.direction = SIGNAL_SELL; m_sell_setup.setupID = setupID; m_sell_setup.trendID = m_filter_trend_id; m_sell_setup.barCountAtArm = m_signal_bar_count; m_sell_setup.triggerPrice = trigger; m_sell_setup.traded = false; if(mp_logger && g_debug_mode) mp_logger.Info(StringFormat("Armed SELL setup: trigger=%.5f trendID=%I64u", trigger, m_filter_trend_id)); } } } } datetime GetLastSwingTime(SSwing &swings[]) const { int size = ArraySize(swings); if(size <= 0) return 0; return swings[size - 1].time; } bool HasBullishSignalStructure() const { return DetectSignalTrend(g_signal_trend_confirm_count) == UPTREND_ACTIVE; } bool HasBearishSignalStructure() const { return DetectSignalTrend(g_signal_trend_confirm_count) == DOWNTREND_ACTIVE; } E_TREND_STATE DetectSignalTrend(int confirmCount) const { if(ArraySize(m_signal_highs) < (confirmCount + 1) || ArraySize(m_signal_lows) < (confirmCount + 1)) return NO_TREND; bool higherHighs = true; for(int i = ArraySize(m_signal_highs) - confirmCount - 1; i < ArraySize(m_signal_highs) - 1; i++) { if(m_signal_highs[i + 1].price <= m_signal_highs[i].price) { higherHighs = false; break; } } bool higherLows = true; for(int i = ArraySize(m_signal_lows) - confirmCount - 1; i < ArraySize(m_signal_lows) - 1; i++) { if(m_signal_lows[i + 1].price <= m_signal_lows[i].price) { higherLows = false; break; } } if(higherHighs && higherLows) return UPTREND_ACTIVE; bool lowerHighs = true; for(int i = ArraySize(m_signal_highs) - confirmCount - 1; i < ArraySize(m_signal_highs) - 1; i++) { if(m_signal_highs[i + 1].price >= m_signal_highs[i].price) { lowerHighs = false; break; } } bool lowerLows = true; for(int i = ArraySize(m_signal_lows) - confirmCount - 1; i < ArraySize(m_signal_lows) - 1; i++) { if(m_signal_lows[i + 1].price >= m_signal_lows[i].price) { lowerLows = false; break; } } if(lowerHighs && lowerLows) return DOWNTREND_ACTIVE; return NO_TREND; } void ExpireSetups() { if(m_buy_setup.active && IsSetupExpired(m_buy_setup)) { AddUsedSetupID(m_buy_setup.setupID); m_buy_setup.active = false; m_buy_setup.traded = true; if(mp_logger && g_debug_mode) mp_logger.Info("Expired BUY setup due to age or trend change"); } if(m_sell_setup.active && IsSetupExpired(m_sell_setup)) { AddUsedSetupID(m_sell_setup.setupID); m_sell_setup.active = false; m_sell_setup.traded = true; if(mp_logger && g_debug_mode) mp_logger.Info("Expired SELL setup due to age or trend change"); } } bool IsSetupExpired(const SArmedSetup &setup) const { if(!setup.active) return true; if(setup.traded) return true; if(setup.trendID != m_filter_trend_id) return true; if(setup.direction == SIGNAL_BUY && m_filter_state != UPTREND_ACTIVE) return true; if(setup.direction == SIGNAL_SELL && m_filter_state != DOWNTREND_ACTIVE) return true; if(g_signal_expiration_bars >= 0 && (m_signal_bar_count - setup.barCountAtArm) >= g_signal_expiration_bars) return true; return false; } void MarkSetupTraded(SArmedSetup &setup) { AddUsedSetupID(setup.setupID); setup.traded = true; setup.active = false; m_trades_taken_in_current_trend++; } bool IsSetupIDUsed(ulong setupID) const { for(int i = 0; i < ArraySize(m_used_setup_ids); i++) { if(m_used_setup_ids[i] == setupID) return true; } return false; } void AddUsedSetupID(ulong setupID) { if(setupID == 0 || IsSetupIDUsed(setupID)) return; int size = ArraySize(m_used_setup_ids); ArrayResize(m_used_setup_ids, size + 1); m_used_setup_ids[size] = setupID; } int GetOpenPositionCount(const string symbol) const { int count = 0; for(int i = PositionsTotal() - 1; i >= 0; i--) { ulong ticket = PositionGetTicket(i); if(ticket == 0) continue; if(!PositionSelectByTicket(ticket)) continue; if(PositionGetString(POSITION_SYMBOL) == symbol && PositionGetInteger(POSITION_MAGIC) == g_magic_number) count++; } return count; } double CalculateStopLoss(const string symbol, E_SIGNAL direction, double entryPrice) { double point = CUtilities::GetPoint(symbol); double stopLoss = 0.0; if(g_stop_loss_mode == STOP_LOSS_STRUCTURE) { if(direction == SIGNAL_BUY) { double swingLow = GetLastSwingPrice(m_signal_lows); if(swingLow > 0.0) stopLoss = swingLow - g_sl_buffer_points * point; } else if(direction == SIGNAL_SELL) { double swingHigh = GetLastSwingPrice(m_signal_highs); if(swingHigh > 0.0) stopLoss = swingHigh + g_sl_buffer_points * point; } } else if(g_stop_loss_mode == STOP_LOSS_POINTS) { double distance = CUtilities::PointsToPrice(symbol, g_sl_points); stopLoss = (direction == SIGNAL_BUY) ? entryPrice - distance : entryPrice + distance; } else if(g_stop_loss_mode == STOP_LOSS_PERCENT) { double distance = MathAbs(entryPrice) * (g_sl_percent / 100.0); stopLoss = (direction == SIGNAL_BUY) ? entryPrice - distance : entryPrice + distance; } stopLoss = CUtilities::NormalizePrice(symbol, stopLoss); if(direction == SIGNAL_BUY && stopLoss >= entryPrice) return 0.0; if(direction == SIGNAL_SELL && stopLoss <= entryPrice) return 0.0; return stopLoss; } double CalculateTakeProfit(const string symbol, E_SIGNAL direction, double entryPrice, double stopLoss) { double tp = 0.0; double riskDistance = MathAbs(entryPrice - stopLoss); if(g_take_profit_mode == TAKE_PROFIT_POINTS) { double distance = CUtilities::PointsToPrice(symbol, g_tp_points); tp = (direction == SIGNAL_BUY) ? entryPrice + distance : entryPrice - distance; } else if(g_take_profit_mode == TAKE_PROFIT_PERCENT) { double distance = MathAbs(entryPrice) * (g_tp_percent / 100.0); tp = (direction == SIGNAL_BUY) ? entryPrice + distance : entryPrice - distance; } else if(g_take_profit_mode == TAKE_PROFIT_RISK_REWARD) { tp = (direction == SIGNAL_BUY) ? entryPrice + riskDistance * g_risk_reward_ratio : entryPrice - riskDistance * g_risk_reward_ratio; } tp = CUtilities::NormalizePrice(symbol, tp); if(direction == SIGNAL_BUY && tp <= entryPrice) return 0.0; if(direction == SIGNAL_SELL && tp >= entryPrice) return 0.0; return tp; } bool IsTradeLevelSetValid(E_SIGNAL direction, double entryPrice, double stopLoss, double takeProfit) const { if(stopLoss <= 0.0) return false; if(direction == SIGNAL_BUY && stopLoss >= entryPrice) return false; if(direction == SIGNAL_SELL && stopLoss <= entryPrice) return false; if(takeProfit <= 0.0) return false; if(direction == SIGNAL_BUY && takeProfit <= entryPrice) return false; if(direction == SIGNAL_SELL && takeProfit >= entryPrice) return false; return true; } }; #endif //__STRATEGY_MQH__