Donchian Macd Strategy
Стратегия совмещает пробой канала Дончиана с подтверждением тренда по MACD.
Тестирование показывает среднегодичную доходность около 148%. Стратегию лучше запускать на рынке Форекс.
Стратегия ожидает пробоя канала Дончиана и проверяет импульс с помощью MACD. Вход в длинные или короткие позиции осуществляется, когда MACD подтверждает движение.
Предназначена для любителей пробоев, которые хотят подтверждения. Стопы ставятся с использованием множителя ATR.
Детали
- Критерии входа:
- Long:
Price breaks Donchian high && MACD > Signal - Short:
Price breaks Donchian low && MACD < Signal
- Long:
- Long/Short: Оба направления
- Критерии выхода: разворот MACD
- Стопы: процентный на основе
StopLossPercent - Значения по умолчанию:
DonchianPeriod= 20MacdFast= 12MacdSlow= 26MacdSignal= 9StopLossPercent= 2mCandleType= TimeSpan.FromMinutes(5).TimeFrame()
- Фильтры:
- Категория: Breakout
- Направление: Оба
- Индикаторы: Donchian Channel, MACD
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Среднесрочный
- Сезонность: Нет
- Нейронные сети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
using StockSharp.Algo.Candles;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy combining Donchian Channel breakout with MACD trend confirmation.
/// </summary>
public class DonchianMacdStrategy : Strategy
{
private readonly StrategyParam<int> _donchianPeriod;
private readonly StrategyParam<int> _macdFast;
private readonly StrategyParam<int> _macdSlow;
private readonly StrategyParam<int> _macdSignal;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private DonchianChannels _donchian;
private MovingAverageConvergenceDivergenceSignal _macd;
private decimal? _previousHighest;
private decimal? _previousLowest;
private decimal? _previousMacd;
private decimal? _previousSignal;
private decimal? _entryPrice;
private int _cooldown;
/// <summary>
/// Donchian channel period.
/// </summary>
public int DonchianPeriod
{
get => _donchianPeriod.Value;
set => _donchianPeriod.Value = value;
}
/// <summary>
/// MACD fast period.
/// </summary>
public int MacdFast
{
get => _macdFast.Value;
set => _macdFast.Value = value;
}
/// <summary>
/// MACD slow period.
/// </summary>
public int MacdSlow
{
get => _macdSlow.Value;
set => _macdSlow.Value = value;
}
/// <summary>
/// MACD signal period.
/// </summary>
public int MacdSignal
{
get => _macdSignal.Value;
set => _macdSignal.Value = value;
}
/// <summary>
/// Bars to wait between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Stop loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type for strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="DonchianMacdStrategy"/>.
/// </summary>
public DonchianMacdStrategy()
{
_donchianPeriod = Param(nameof(DonchianPeriod), 20)
.SetRange(5, 50)
.SetDisplay("Donchian Period", "Channel lookback period", "Indicators");
_macdFast = Param(nameof(MacdFast), 12)
.SetRange(8, 20)
.SetDisplay("MACD Fast Period", "Fast EMA period for MACD", "Indicators");
_macdSlow = Param(nameof(MacdSlow), 26)
.SetRange(20, 40)
.SetDisplay("MACD Slow Period", "Slow EMA period for MACD", "Indicators");
_macdSignal = Param(nameof(MacdSignal), 9)
.SetRange(5, 15)
.SetDisplay("MACD Signal Period", "Signal line period for MACD", "Indicators");
_cooldownBars = Param(nameof(CooldownBars), 50)
.SetRange(1, 200)
.SetDisplay("Cooldown Bars", "Bars between entries", "General");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetRange(1m, 5m)
.SetDisplay("Stop-Loss %", "Stop-loss percentage from entry price", "Risk Management");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousHighest = 0;
_previousLowest = decimal.MaxValue;
_previousMacd = 0;
_previousSignal = 0;
_entryPrice = null;
_cooldown = 0;
_donchian = null;
_macd = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Initialize indicators
_donchian = new DonchianChannels
{
Length = DonchianPeriod
};
_macd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = MacdFast },
LongMa = { Length = MacdSlow },
},
SignalMa = { Length = MacdSignal }
};
// Create subscription and bind indicators
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_donchian, _macd, ProcessCandle)
.Start();
// Setup chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _donchian);
DrawIndicator(area, _macd);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue donchianValue, IIndicatorValue macdValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Wait until strategy and indicators are ready
if (!IsFormedAndOnlineAndAllowTrading())
return;
var macdTyped = (MovingAverageConvergenceDivergenceSignalValue)macdValue;
var signalValue = macdTyped.Signal;
var macdDec = macdTyped.Macd;
var isBullishCross = _previousMacd <= _previousSignal && macdDec > signalValue;
var isBearishCross = _previousMacd >= _previousSignal && macdDec < signalValue;
if (_cooldown > 0)
{
_cooldown--;
}
// Check for breakouts with MACD trend confirmation
// Long entry: Price breaks above Donchian high and MACD > Signal
if (_cooldown == 0 && candle.ClosePrice > _previousHighest * 1.001m && Position <= 0 && isBullishCross)
{
CancelActiveOrders();
var volume = Volume + Math.Abs(Position);
BuyMarket(volume);
_entryPrice = candle.ClosePrice;
_cooldown = CooldownBars;
}
// Short entry: Price breaks below Donchian low and MACD < Signal
else if (_cooldown == 0 && candle.ClosePrice < _previousLowest * 0.999m && Position >= 0 && isBearishCross)
{
CancelActiveOrders();
var volume = Volume + Math.Abs(Position);
SellMarket(volume);
_entryPrice = candle.ClosePrice;
_cooldown = CooldownBars;
}
// MACD trend reversal exit
else if ((Position > 0 && isBearishCross) ||
(Position < 0 && isBullishCross))
{
ClosePosition();
_entryPrice = null;
_cooldown = CooldownBars;
}
var donchianTyped = (DonchianChannelsValue)donchianValue;
// Update previous values for next candle
_previousHighest = donchianTyped.UpperBand;
_previousLowest = donchianTyped.LowerBand;
_previousMacd = macdDec;
_previousSignal = signalValue;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math, Decimal
from StockSharp.Messages import UnitTypes, Unit, DataType, CandleStates
from StockSharp.Algo.Indicators import DonchianChannels, MovingAverageConvergenceDivergenceSignal
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
class donchian_macd_strategy(Strategy):
"""
Strategy combining Donchian Channel breakout with MACD trend confirmation.
"""
def __init__(self):
super(donchian_macd_strategy, self).__init__()
self._previousHighest = 0.0
self._previousLowest = float("inf")
self._previousMacd = None
self._previousSignal = None
self._entryPrice = None
self._cooldown = 0
self._donchianPeriod = self.Param("DonchianPeriod", 20) \
.SetDisplay("Donchian Period", "Channel lookback period", "Indicators")
self._macdFast = self.Param("MacdFast", 12) \
.SetDisplay("MACD Fast Period", "Fast EMA period for MACD", "Indicators")
self._macdSlow = self.Param("MacdSlow", 26) \
.SetDisplay("MACD Slow Period", "Slow EMA period for MACD", "Indicators")
self._macdSignal = self.Param("MacdSignal", 9) \
.SetDisplay("MACD Signal Period", "Signal line period for MACD", "Indicators")
self._cooldownBars = self.Param("CooldownBars", 50) \
.SetRange(1, 200) \
.SetDisplay("Cooldown Bars", "Bars between entries", "General")
self._stopLossPercent = self.Param("StopLossPercent", 2.0) \
.SetDisplay("Stop-Loss %", "Stop-loss percentage from entry price", "Risk Management")
self._candleType = self.Param("CandleType", tf(15)) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
@property
def CandleType(self):
return self._candleType.Value
def OnReseted(self):
super(donchian_macd_strategy, self).OnReseted()
self._previousHighest = 0.0
self._previousLowest = float('inf')
self._previousMacd = None
self._previousSignal = None
self._entryPrice = None
self._cooldown = 0
def OnStarted2(self, time):
super(donchian_macd_strategy, self).OnStarted2(time)
self._previousHighest = 0.0
self._previousLowest = float('inf')
self._previousMacd = None
self._previousSignal = None
self._entryPrice = None
self._cooldown = 0
donchian = DonchianChannels()
donchian.Length = self._donchianPeriod.Value
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = self._macdFast.Value
macd.Macd.LongMa.Length = self._macdSlow.Value
macd.SignalMa.Length = self._macdSignal.Value
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(donchian, macd, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, donchian)
self.DrawIndicator(area, macd)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, donchianValue, macdValue):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
signalValue = macdValue.Signal
macdDec = macdValue.Macd
if signalValue is None or macdDec is None:
# Update donchian values
if donchianValue.UpperBand is not None:
self._previousHighest = float(donchianValue.UpperBand)
if donchianValue.LowerBand is not None:
self._previousLowest = float(donchianValue.LowerBand)
self._previousMacd = macdDec
self._previousSignal = signalValue
return
macd_f = float(macdDec)
signal_f = float(signalValue)
# Determine MACD crosses
isBullishCross = False
isBearishCross = False
if self._previousMacd is not None and self._previousSignal is not None:
prev_m = float(self._previousMacd)
prev_s = float(self._previousSignal)
isBullishCross = prev_m <= prev_s and macd_f > signal_f
isBearishCross = prev_m >= prev_s and macd_f < signal_f
if self._cooldown > 0:
self._cooldown -= 1
price = float(candle.ClosePrice)
cooldown_val = int(self._cooldownBars.Value)
# Long entry
if self._cooldown == 0 and price > self._previousHighest * 1.001 and self.Position <= 0 and isBullishCross:
self.CancelActiveOrders()
volume = self.Volume + abs(self.Position)
self.BuyMarket(volume)
self._entryPrice = price
self._cooldown = cooldown_val
# Short entry
elif self._cooldown == 0 and price < self._previousLowest * 0.999 and self.Position >= 0 and isBearishCross:
self.CancelActiveOrders()
volume = self.Volume + abs(self.Position)
self.SellMarket(volume)
self._entryPrice = price
self._cooldown = cooldown_val
# MACD trend reversal exit
elif (self.Position > 0 and isBearishCross) or (self.Position < 0 and isBullishCross):
self.ClosePosition()
self._entryPrice = None
self._cooldown = cooldown_val
# Update previous values
if donchianValue.UpperBand is not None:
self._previousHighest = float(donchianValue.UpperBand)
if donchianValue.LowerBand is not None:
self._previousLowest = float(donchianValue.LowerBand)
self._previousMacd = macdDec
self._previousSignal = signalValue
def CreateClone(self):
return donchian_macd_strategy()