Files
MyProEA/Include/Strategy.mqh
T

981 lines
31 KiB
Plaintext

//+------------------------------------------------------------------+
//| 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__