Стратегия MACD с мульти‑таймфреймным стоп‑лоссом
Мульти‑таймфреймная стратегия с оценкой MACD и ATR‑трейлинг стопом.
Детали
- Условия входа: оценка MACD становится положительной или отрицательной.
- Длинные/короткие: обе стороны.
- Условия выхода: обратный сигнал или пробой линии стопа.
- Стопы: ATR трейлинг стоп.
- Параметры по умолчанию:
FastLength= 12SlowLength= 26SignalLength= 9CrossScore= 10IndicatorScore= 8HistogramScore= 2StopLossFactor= 1.2StopLossPeriod= 10CandleType= TimeSpan.FromMinutes(1)
- Фильтры:
- Категория: Тренд
- Направление: Оба
- Индикаторы: MACD, ATR
- Стопы: Да
- Сложность: Средняя
- Таймфрейм: Внутридневной
- Сезонность: Нет
- Нейросети: Нет
- Дивергенции: Нет
- Уровень риска: Средний
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// MACD enhanced strategy with an ATR trailing stop line.
/// Every candle gets a MACD score: plus or minus CrossScore on a MACD/signal cross, plus or minus IndicatorScore for the MACD line
/// above or below zero, and plus or minus HistogramScore for a rising or falling histogram. The score turning positive goes long and
/// turning negative goes short, reversing an opposite position. The stop line trails StopLossFactor ATRs (StopLossPeriod) behind the
/// close and only moves in the trade direction; a close through it exits the position.
/// </summary>
public class MacdEnhancedMtfWithStopLossStrategy : Strategy
{
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<int> _signalLength;
private readonly StrategyParam<decimal> _crossScore;
private readonly StrategyParam<decimal> _indicatorScore;
private readonly StrategyParam<decimal> _histogramScore;
private readonly StrategyParam<decimal> _stopLossFactor;
private readonly StrategyParam<int> _stopLossPeriod;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevMacd;
private decimal? _prevSignal;
private decimal? _prevScore;
private decimal? _stopLine;
/// <summary>
/// MACD fast period.
/// </summary>
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
/// <summary>
/// MACD slow period.
/// </summary>
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.Value = value;
}
/// <summary>
/// MACD signal period.
/// </summary>
public int SignalLength
{
get => _signalLength.Value;
set => _signalLength.Value = value;
}
/// <summary>
/// Score of a MACD/signal cross.
/// </summary>
public decimal CrossScore
{
get => _crossScore.Value;
set => _crossScore.Value = value;
}
/// <summary>
/// Score of the MACD line side of zero.
/// </summary>
public decimal IndicatorScore
{
get => _indicatorScore.Value;
set => _indicatorScore.Value = value;
}
/// <summary>
/// Score of the histogram direction.
/// </summary>
public decimal HistogramScore
{
get => _histogramScore.Value;
set => _histogramScore.Value = value;
}
/// <summary>
/// ATR multiplier of the trailing stop line.
/// </summary>
public decimal StopLossFactor
{
get => _stopLossFactor.Value;
set => _stopLossFactor.Value = value;
}
/// <summary>
/// ATR period of the trailing stop line.
/// </summary>
public int StopLossPeriod
{
get => _stopLossPeriod.Value;
set => _stopLossPeriod.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public MacdEnhancedMtfWithStopLossStrategy()
{
_fastLength = Param(nameof(FastLength), 12)
.SetGreaterThanZero()
.SetDisplay("Fast Length", "MACD fast period", "MACD");
_slowLength = Param(nameof(SlowLength), 26)
.SetGreaterThanZero()
.SetDisplay("Slow Length", "MACD slow period", "MACD");
_signalLength = Param(nameof(SignalLength), 9)
.SetGreaterThanZero()
.SetDisplay("Signal Length", "MACD signal period", "MACD");
_crossScore = Param(nameof(CrossScore), 10m)
.SetNotNegative()
.SetDisplay("Cross Score", "Score of a MACD/signal cross", "Score");
_indicatorScore = Param(nameof(IndicatorScore), 8m)
.SetNotNegative()
.SetDisplay("Indicator Score", "Score of the MACD line side of zero", "Score");
_histogramScore = Param(nameof(HistogramScore), 2m)
.SetNotNegative()
.SetDisplay("Histogram Score", "Score of the histogram direction", "Score");
_stopLossFactor = Param(nameof(StopLossFactor), 1.2m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss Factor", "ATR multiplier of the trailing stop line", "Risk");
_stopLossPeriod = Param(nameof(StopLossPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Stop Loss Period", "ATR period of the trailing stop line", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).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();
ResetState();
}
private void ResetState()
{
_prevMacd = null;
_prevSignal = null;
_prevScore = null;
_stopLine = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var macd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = FastLength },
LongMa = { Length = SlowLength },
},
SignalMa = { Length = SignalLength }
};
var atr = new AverageTrueRange { Length = StopLossPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(macd, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
DrawIndicator(oscillators, macd);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!macdValue.IsFormed || !atrValue.IsFormed)
return;
if (macdValue is not IMovingAverageConvergenceDivergenceSignalValue { Macd: decimal macdLine, Signal: decimal signalLine })
return;
var prevMacd = _prevMacd;
var prevSignal = _prevSignal;
_prevMacd = macdLine;
_prevSignal = signalLine;
if (prevMacd is not decimal pm || prevSignal is not decimal ps)
return;
var crossPart = pm <= ps && macdLine > signalLine ? CrossScore
: pm >= ps && macdLine < signalLine ? -CrossScore
: 0m;
var indicatorPart = macdLine > 0 ? IndicatorScore : macdLine < 0 ? -IndicatorScore : 0m;
var histogram = macdLine - signalLine;
var prevHistogram = pm - ps;
var histogramPart = histogram > prevHistogram ? HistogramScore : histogram < prevHistogram ? -HistogramScore : 0m;
var score = crossPart + indicatorPart + histogramPart;
var prevScore = _prevScore;
_prevScore = score;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
var offset = atrValue.GetValue<decimal>() * StopLossFactor;
if (Position > 0 && _stopLine is decimal longLine)
{
if (close < longLine)
{
SellMarket(Position);
_stopLine = null;
return;
}
_stopLine = Math.Max(longLine, close - offset);
}
else if (Position < 0 && _stopLine is decimal shortLine)
{
if (close > shortLine)
{
BuyMarket(-Position);
_stopLine = null;
return;
}
_stopLine = Math.Min(shortLine, close + offset);
}
if (prevScore is not decimal prev)
return;
if (prev <= 0 && score > 0 && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_stopLine = close - offset;
}
else if (prev >= 0 && score < 0 && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_stopLine = close + offset;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import MovingAverageConvergenceDivergenceSignal, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class macd_enhanced_mtf_with_stop_loss_strategy(Strategy):
"""
MACD enhanced strategy with an ATR trailing stop line.
Every candle gets a MACD score: plus or minus CrossScore on a MACD/signal cross, plus or minus IndicatorScore for the MACD line
above or below zero, and plus or minus HistogramScore for a rising or falling histogram. The score turning positive goes long and
turning negative goes short, reversing an opposite position. The stop line trails StopLossFactor ATRs (StopLossPeriod) behind the
close and only moves in the trade direction; a close through it exits the position.
"""
def __init__(self):
super(macd_enhanced_mtf_with_stop_loss_strategy, self).__init__()
self._fast_length = self.Param("FastLength", 12).SetGreaterThanZero().SetDisplay("Fast Length", "MACD fast period", "MACD")
self._slow_length = self.Param("SlowLength", 26).SetGreaterThanZero().SetDisplay("Slow Length", "MACD slow period", "MACD")
self._signal_length = self.Param("SignalLength", 9).SetGreaterThanZero().SetDisplay("Signal Length", "MACD signal period", "MACD")
self._cross_score = self.Param("CrossScore", 10.0).SetNotNegative().SetDisplay("Cross Score", "Score of a MACD/signal cross", "Score")
self._indicator_score = self.Param("IndicatorScore", 8.0).SetNotNegative().SetDisplay("Indicator Score", "Score of the MACD line side of zero", "Score")
self._histogram_score = self.Param("HistogramScore", 2.0).SetNotNegative().SetDisplay("Histogram Score", "Score of the histogram direction", "Score")
self._stop_loss_factor = self.Param("StopLossFactor", 1.2).SetGreaterThanZero().SetDisplay("Stop Loss Factor", "ATR multiplier of the trailing stop line", "Risk")
self._stop_loss_period = self.Param("StopLossPeriod", 10).SetGreaterThanZero().SetDisplay("Stop Loss Period", "ATR period of the trailing stop line", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._prev_macd = None
self._prev_signal = None
self._prev_score = None
self._stop_line = None
def OnReseted(self):
super(macd_enhanced_mtf_with_stop_loss_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(macd_enhanced_mtf_with_stop_loss_strategy, self).OnStarted2(time)
self._reset_state()
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = self._fast_length.Value
macd.Macd.LongMa.Length = self._slow_length.Value
macd.SignalMa.Length = self._signal_length.Value
atr = AverageTrueRange()
atr.Length = self._stop_loss_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(macd, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, macd)
def _process_candle(self, candle, macd_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not macd_value.IsFormed or not atr_value.IsFormed:
return
macd_line = macd_value.Macd
signal_line = macd_value.Signal
if macd_line is None or signal_line is None:
return
prev_macd = self._prev_macd
prev_signal = self._prev_signal
self._prev_macd = macd_line
self._prev_signal = signal_line
if prev_macd is None or prev_signal is None:
return
zero = Decimal(0)
cross_score = Decimal(self._cross_score.Value)
indicator_score = Decimal(self._indicator_score.Value)
histogram_score = Decimal(self._histogram_score.Value)
if prev_macd <= prev_signal and macd_line > signal_line:
cross_part = cross_score
elif prev_macd >= prev_signal and macd_line < signal_line:
cross_part = -cross_score
else:
cross_part = zero
if macd_line > zero:
indicator_part = indicator_score
elif macd_line < zero:
indicator_part = -indicator_score
else:
indicator_part = zero
histogram = macd_line - signal_line
prev_histogram = prev_macd - prev_signal
if histogram > prev_histogram:
histogram_part = histogram_score
elif histogram < prev_histogram:
histogram_part = -histogram_score
else:
histogram_part = zero
score = cross_part + indicator_part + histogram_part
prev_score = self._prev_score
self._prev_score = score
if not self.IsFormedAndOnlineAndAllowTrading():
return
close = candle.ClosePrice
offset = atr_value.GetValue[Decimal](None) * Decimal(self._stop_loss_factor.Value)
if self.Position > 0 and self._stop_line is not None:
if close < self._stop_line:
self.SellMarket(self.Position)
self._stop_line = None
return
self._stop_line = max(self._stop_line, close - offset)
elif self.Position < 0 and self._stop_line is not None:
if close > self._stop_line:
self.BuyMarket(-self.Position)
self._stop_line = None
return
self._stop_line = min(self._stop_line, close + offset)
if prev_score is None:
return
if prev_score <= zero and score > zero and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._stop_line = close - offset
elif prev_score >= zero and score < zero and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._stop_line = close + offset
def CreateClone(self):
return macd_enhanced_mtf_with_stop_loss_strategy()