2025-08-13 19:40:28 -04:00
|
|
|
#include "..\\IStrategy.mqh"
|
|
|
|
|
#include "..\\KnowledgeBase.mqh"
|
|
|
|
|
#include "..\\TradeManager.mqh"
|
|
|
|
|
#include "..\\Indicators\\Donchian.mqh"
|
|
|
|
|
|
|
|
|
|
class CDonchianATRBreakoutStrategy : public IStrategy
|
|
|
|
|
{
|
|
|
|
|
private:
|
|
|
|
|
CDonchian m_dc; int m_dcPeriod;
|
|
|
|
|
int m_hATR; int m_atrPeriod; double m_atrMult;
|
|
|
|
|
// cached
|
|
|
|
|
double m_up1, m_lo1, m_close1, m_atr1;
|
|
|
|
|
public:
|
2026-02-05 23:31:20 -05:00
|
|
|
CDonchianATRBreakoutStrategy(const string symbol, const ENUM_TIMEFRAMES tf) : IStrategy("DonchianATRBreakoutStrategy", symbol, tf)
|
2025-08-13 19:40:28 -04:00
|
|
|
{
|
2026-02-05 23:31:20 -05:00
|
|
|
m_dcPeriod=20; m_atrPeriod=14; m_atrMult=2.0;
|
2025-08-13 19:40:28 -04:00
|
|
|
m_dc.Init(symbol, tf, m_dcPeriod);
|
|
|
|
|
m_hATR = iATR(symbol, tf, m_atrPeriod);
|
|
|
|
|
m_up1=m_lo1=m_close1=m_atr1=0;
|
|
|
|
|
}
|
|
|
|
|
virtual string Name(){ return "DonchianATRBreakoutStrategy"; }
|
|
|
|
|
virtual void Refresh()
|
|
|
|
|
{
|
2025-08-27 12:44:21 -04:00
|
|
|
// closed bar values
|
2026-02-05 23:31:20 -05:00
|
|
|
m_close1 = iClose(m_symbol, m_timeframe, 1);
|
2025-08-13 19:40:28 -04:00
|
|
|
m_up1 = m_dc.Upper(1);
|
|
|
|
|
m_lo1 = m_dc.Lower(1);
|
2025-08-27 12:44:21 -04:00
|
|
|
|
|
|
|
|
// ATR readiness checks and read
|
|
|
|
|
m_atr1 = 0.0;
|
|
|
|
|
if(m_hATR>0)
|
|
|
|
|
{
|
|
|
|
|
int bc = BarsCalculated(m_hATR);
|
|
|
|
|
if(bc>0)
|
|
|
|
|
{
|
|
|
|
|
double b[]; ArraySetAsSeries(b, true);
|
|
|
|
|
if(CopyBuffer(m_hATR, 0, 1, 1, b)==1)
|
|
|
|
|
m_atr1 = b[0];
|
|
|
|
|
}
|
|
|
|
|
}
|
2025-08-13 19:40:28 -04:00
|
|
|
}
|
|
|
|
|
virtual TradeOrder CheckSignal()
|
|
|
|
|
{
|
|
|
|
|
TradeOrder ord; ord.strategy_name=Name();
|
|
|
|
|
if(m_close1<=0 || m_up1<=0 || m_lo1<=0 || m_atr1<=0) return ord;
|
|
|
|
|
// Long breakout: price near upper channel
|
|
|
|
|
if(m_close1>=m_up1)
|
|
|
|
|
{
|
2025-08-27 12:44:21 -04:00
|
|
|
ord.action=ACTION_BUY; ord.order_type=ORDER_TYPE_BUY_STOP;
|
|
|
|
|
double point = SymbolInfoDouble(m_symbol, SYMBOL_POINT); if(point<=0) point=_Point;
|
|
|
|
|
int digits = (int)SymbolInfoInteger(m_symbol, SYMBOL_DIGITS);
|
|
|
|
|
double ask = SymbolInfoDouble(m_symbol, SYMBOL_ASK);
|
|
|
|
|
long stops_level_pts = SymbolInfoInteger(m_symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
|
|
|
|
double min_dist = (stops_level_pts>0 ? (double)stops_level_pts*point : 0.0);
|
|
|
|
|
double raw_price = m_up1 + 5.0*point;
|
|
|
|
|
// ensure buy stop above ask + min distance
|
|
|
|
|
double min_buy_stop = ask + min_dist;
|
|
|
|
|
if(raw_price < min_buy_stop) raw_price = min_buy_stop;
|
|
|
|
|
ord.price = NormalizeDouble(raw_price, digits);
|
2025-09-20 02:15:03 -04:00
|
|
|
// ATR-based SL/TP with spread/min distance checks
|
|
|
|
|
double atr = GetATR(m_atrPeriod, 0);
|
|
|
|
|
double spread = SymbolInfoDouble(_Symbol, SYMBOL_ASK) - SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
|
|
|
double min_stop = MathMax(atr * m_atrMult, MathMax(spread*3, SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point));
|
|
|
|
|
ord.stop_loss = NormalizeDouble(ord.price - min_stop, digits);
|
|
|
|
|
ord.take_profit = NormalizeDouble(ord.price + 2.0*min_stop, digits);
|
|
|
|
|
if(spread > min_stop*0.5) return ord;
|
2025-08-13 19:40:28 -04:00
|
|
|
ord.trailing_enabled=true; ord.trailing_type=TRAIL_ATR; ord.atr_period=m_atrPeriod; ord.atr_multiplier=m_atrMult; return ord;
|
|
|
|
|
}
|
|
|
|
|
// Short breakout
|
|
|
|
|
if(m_close1<=m_lo1)
|
|
|
|
|
{
|
2025-08-27 12:44:21 -04:00
|
|
|
ord.action=ACTION_SELL; ord.order_type=ORDER_TYPE_SELL_STOP;
|
|
|
|
|
double point = SymbolInfoDouble(m_symbol, SYMBOL_POINT); if(point<=0) point=_Point;
|
|
|
|
|
int digits = (int)SymbolInfoInteger(m_symbol, SYMBOL_DIGITS);
|
|
|
|
|
double bid = SymbolInfoDouble(m_symbol, SYMBOL_BID);
|
|
|
|
|
long stops_level_pts = SymbolInfoInteger(m_symbol, SYMBOL_TRADE_STOPS_LEVEL);
|
|
|
|
|
double min_dist = (stops_level_pts>0 ? (double)stops_level_pts*point : 0.0);
|
|
|
|
|
double raw_price = m_lo1 - 5.0*point;
|
|
|
|
|
// ensure sell stop below bid - min distance
|
|
|
|
|
double max_sell_stop = bid - min_dist;
|
|
|
|
|
if(raw_price > max_sell_stop) raw_price = max_sell_stop;
|
|
|
|
|
ord.price = NormalizeDouble(raw_price, digits);
|
2025-09-20 02:15:03 -04:00
|
|
|
// ATR-based SL/TP with spread/min distance checks
|
|
|
|
|
double atr = GetATR(m_atrPeriod, 0);
|
|
|
|
|
double spread = SymbolInfoDouble(_Symbol, SYMBOL_ASK) - SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
|
|
|
double min_stop = MathMax(atr * m_atrMult, MathMax(spread*3, SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL) * _Point));
|
|
|
|
|
ord.stop_loss = NormalizeDouble(ord.price + min_stop, digits);
|
|
|
|
|
ord.take_profit = NormalizeDouble(ord.price - 2.0*min_stop, digits);
|
|
|
|
|
if(spread > min_stop*0.5) return ord;
|
2025-08-13 19:40:28 -04:00
|
|
|
ord.trailing_enabled=true; ord.trailing_type=TRAIL_ATR; ord.atr_period=m_atrPeriod; ord.atr_multiplier=m_atrMult; return ord;
|
|
|
|
|
}
|
|
|
|
|
return ord;
|
|
|
|
|
}
|
|
|
|
|
virtual void ExportFeatures(CFeaturesKB* kb, const datetime ts)
|
|
|
|
|
{
|
|
|
|
|
if(CheckPointer(kb)==POINTER_INVALID) return;
|
|
|
|
|
(*kb).WriteKV(ts, m_symbol, Name(), "dc_upper", m_up1);
|
|
|
|
|
(*kb).WriteKV(ts, m_symbol, Name(), "dc_lower", m_lo1);
|
|
|
|
|
(*kb).WriteKV(ts, m_symbol, Name(), "atr", m_atr1);
|
|
|
|
|
}
|
|
|
|
|
};
|