Стратегия Three Signal Directional Trend
Стратегия Three Signal Directional Trend объединяет три индикатора: MACD, стохастик и скорость скользящей средней (ROC). Каждый индикатор подает сигнал на покупку или продажу. Позиция открывается, когда хотя бы два индикатора совпадают по направлению. Такой подход помогает отфильтровать шум и следовать основному тренду.
Подробности
- Условия входа:
- Совпадение минимум двух сигналов.
- Long: MACD растет, стохастик в зоне перепроданности, MA ROC положительный.
- Short: MACD падает, стохастик в зоне перекупленности, MA ROC отрицательный.
- Long/Short: обе стороны.
- Условия выхода:
- Противоположный сигнал.
- Стопы: отсутствуют.
- Значения по умолчанию:
AvgLength= 50RocLength= 1AvgRocLength= 10StochLength= 14SmoothK= 3Overbought= 80Oversold= 20MacdFastLength= 12MacdSlowLength= 26MacdAvgLength= 9
- Фильтры:
- Категория: следование тренду
- Направление: обе стороны
- Индикаторы: MACD, Stochastic, SMA, ROC
- Стопы: нет
- Сложность: низкая
- Таймфрейм: любой
- Сезонность: нет
- Нейросети: нет
- Дивергенция: нет
- Уровень риска: средний
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Three Signal Directional Trend Strategy.
/// Three indicators vote: the MACD signal line rising or falling, the Stochastic %K (StochLength, smoothed over SmoothK) below
/// Oversold or above Overbought, and the rate of change over RocLength bars of the AvgLength SMA, averaged over AvgRocLength
/// bars, above or below zero. At least two long votes open a long and at least two short votes open a short, reversing
/// an opposite position.
/// </summary>
public class ThreeSignalDirectionalTrendStrategy : Strategy
{
private readonly StrategyParam<int> _avgLength;
private readonly StrategyParam<int> _rocLength;
private readonly StrategyParam<int> _avgRocLength;
private readonly StrategyParam<int> _stochLength;
private readonly StrategyParam<int> _smoothK;
private readonly StrategyParam<decimal> _overbought;
private readonly StrategyParam<decimal> _oversold;
private readonly StrategyParam<int> _macdFastLength;
private readonly StrategyParam<int> _macdSlowLength;
private readonly StrategyParam<int> _macdAvgLength;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _kSmooth;
private SimpleMovingAverage _rocAverage;
private readonly List<decimal> _maHistory = [];
private decimal? _prevSignal;
/// <summary>
/// SMA period of the rate of change.
/// </summary>
public int AvgLength
{
get => _avgLength.Value;
set => _avgLength.Value = value;
}
/// <summary>
/// Bars of the rate of change.
/// </summary>
public int RocLength
{
get => _rocLength.Value;
set => _rocLength.Value = value;
}
/// <summary>
/// Averaging period of the rate of change.
/// </summary>
public int AvgRocLength
{
get => _avgRocLength.Value;
set => _avgRocLength.Value = value;
}
/// <summary>
/// Stochastic lookback.
/// </summary>
public int StochLength
{
get => _stochLength.Value;
set => _stochLength.Value = value;
}
/// <summary>
/// Smoothing of %K.
/// </summary>
public int SmoothK
{
get => _smoothK.Value;
set => _smoothK.Value = value;
}
/// <summary>
/// Stochastic overbought level.
/// </summary>
public decimal Overbought
{
get => _overbought.Value;
set => _overbought.Value = value;
}
/// <summary>
/// Stochastic oversold level.
/// </summary>
public decimal Oversold
{
get => _oversold.Value;
set => _oversold.Value = value;
}
/// <summary>
/// MACD fast EMA period.
/// </summary>
public int MacdFastLength
{
get => _macdFastLength.Value;
set => _macdFastLength.Value = value;
}
/// <summary>
/// MACD slow EMA period.
/// </summary>
public int MacdSlowLength
{
get => _macdSlowLength.Value;
set => _macdSlowLength.Value = value;
}
/// <summary>
/// MACD signal line period.
/// </summary>
public int MacdAvgLength
{
get => _macdAvgLength.Value;
set => _macdAvgLength.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public ThreeSignalDirectionalTrendStrategy()
{
_avgLength = Param(nameof(AvgLength), 50)
.SetGreaterThanZero()
.SetDisplay("Average Length", "SMA period of the rate of change", "ROC");
_rocLength = Param(nameof(RocLength), 1)
.SetGreaterThanZero()
.SetDisplay("ROC Length", "Bars of the rate of change", "ROC");
_avgRocLength = Param(nameof(AvgRocLength), 10)
.SetGreaterThanZero()
.SetDisplay("Average ROC Length", "Averaging period of the rate of change", "ROC");
_stochLength = Param(nameof(StochLength), 14)
.SetGreaterThanZero()
.SetDisplay("Stochastic Length", "Stochastic lookback", "Stochastic");
_smoothK = Param(nameof(SmoothK), 3)
.SetGreaterThanZero()
.SetDisplay("Smooth K", "Smoothing of %K", "Stochastic");
_overbought = Param(nameof(Overbought), 80m)
.SetDisplay("Overbought", "Stochastic overbought level", "Stochastic");
_oversold = Param(nameof(Oversold), 20m)
.SetDisplay("Oversold", "Stochastic oversold level", "Stochastic");
_macdFastLength = Param(nameof(MacdFastLength), 12)
.SetGreaterThanZero()
.SetDisplay("MACD Fast", "MACD fast EMA period", "MACD");
_macdSlowLength = Param(nameof(MacdSlowLength), 26)
.SetGreaterThanZero()
.SetDisplay("MACD Slow", "MACD slow EMA period", "MACD");
_macdAvgLength = Param(nameof(MacdAvgLength), 9)
.SetGreaterThanZero()
.SetDisplay("MACD Signal", "MACD signal line period", "MACD");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_maHistory.Clear();
_prevSignal = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_maHistory.Clear();
_prevSignal = null;
var macd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = MacdFastLength },
LongMa = { Length = MacdSlowLength },
},
SignalMa = { Length = MacdAvgLength },
};
var stochK = new StochasticK { Length = StochLength };
var sma = new SimpleMovingAverage { Length = AvgLength };
_kSmooth = new SimpleMovingAverage { Length = SmoothK };
_rocAverage = new SimpleMovingAverage { Length = AvgRocLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(macd, stochK, sma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
DrawIndicator(oscillators, macd);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue, IIndicatorValue stochValue, IIndicatorValue smaValue)
{
if (candle.State != CandleStates.Finished)
return;
var time = candle.OpenTime;
decimal? k = null;
if (stochValue.IsFormed)
{
var smoothed = _kSmooth.Process(stochValue.GetValue<decimal>(), time, true);
if (_kSmooth.IsFormed)
k = smoothed.ToDecimal();
}
decimal? avgRoc = null;
if (smaValue.IsFormed)
{
var ma = smaValue.GetValue<decimal>();
_maHistory.Add(ma);
if (_maHistory.Count > RocLength + 1)
_maHistory.RemoveAt(0);
if (_maHistory.Count == RocLength + 1 && _maHistory[0] != 0m)
{
var roc = (ma - _maHistory[0]) / _maHistory[0] * 100m;
var averaged = _rocAverage.Process(roc, time, true);
if (_rocAverage.IsFormed)
avgRoc = averaged.ToDecimal();
}
}
decimal? signal = null;
if (macdValue.IsFormed && macdValue is IMovingAverageConvergenceDivergenceSignalValue { Signal: decimal s })
signal = s;
var prevSignal = _prevSignal;
if (signal is not null)
_prevSignal = signal;
if (k is not decimal kValue || avgRoc is not decimal rocValue || signal is not decimal signalValue || prevSignal is not decimal previous)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var longVotes = (signalValue > previous ? 1 : 0) + (kValue < Oversold ? 1 : 0) + (rocValue > 0m ? 1 : 0);
var shortVotes = (signalValue < previous ? 1 : 0) + (kValue > Overbought ? 1 : 0) + (rocValue < 0m ? 1 : 0);
if (longVotes >= 2 && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (shortVotes >= 2 && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
}
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, StochasticK, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class three_signal_directional_trend_strategy(Strategy):
"""
Three Signal Directional Trend Strategy.
Three indicators vote: the MACD signal line rising or falling, the Stochastic %K (StochLength, smoothed over SmoothK) below
Oversold or above Overbought, and the rate of change over RocLength bars of the AvgLength SMA, averaged over AvgRocLength
bars, above or below zero. At least two long votes open a long and at least two short votes open a short, reversing
an opposite position.
"""
def __init__(self):
super(three_signal_directional_trend_strategy, self).__init__()
self._avg_length = self.Param("AvgLength", 50).SetGreaterThanZero().SetDisplay("Average Length", "SMA period of the rate of change", "ROC")
self._roc_length = self.Param("RocLength", 1).SetGreaterThanZero().SetDisplay("ROC Length", "Bars of the rate of change", "ROC")
self._avg_roc_length = self.Param("AvgRocLength", 10).SetGreaterThanZero().SetDisplay("Average ROC Length", "Averaging period of the rate of change", "ROC")
self._stoch_length = self.Param("StochLength", 14).SetGreaterThanZero().SetDisplay("Stochastic Length", "Stochastic lookback", "Stochastic")
self._smooth_k = self.Param("SmoothK", 3).SetGreaterThanZero().SetDisplay("Smooth K", "Smoothing of %K", "Stochastic")
self._overbought = self.Param("Overbought", 80.0).SetDisplay("Overbought", "Stochastic overbought level", "Stochastic")
self._oversold = self.Param("Oversold", 20.0).SetDisplay("Oversold", "Stochastic oversold level", "Stochastic")
self._macd_fast_length = self.Param("MacdFastLength", 12).SetGreaterThanZero().SetDisplay("MACD Fast", "MACD fast EMA period", "MACD")
self._macd_slow_length = self.Param("MacdSlowLength", 26).SetGreaterThanZero().SetDisplay("MACD Slow", "MACD slow EMA period", "MACD")
self._macd_avg_length = self.Param("MacdAvgLength", 9).SetGreaterThanZero().SetDisplay("MACD Signal", "MACD signal line period", "MACD")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._k_smooth = None
self._roc_average = None
self._ma_history = []
self._prev_signal = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(three_signal_directional_trend_strategy, self).OnReseted()
self._ma_history = []
self._prev_signal = None
def OnStarted2(self, time):
super(three_signal_directional_trend_strategy, self).OnStarted2(time)
self._ma_history = []
self._prev_signal = None
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = self._macd_fast_length.Value
macd.Macd.LongMa.Length = self._macd_slow_length.Value
macd.SignalMa.Length = self._macd_avg_length.Value
stoch_k = StochasticK()
stoch_k.Length = self._stoch_length.Value
sma = SimpleMovingAverage()
sma.Length = self._avg_length.Value
self._k_smooth = SimpleMovingAverage()
self._k_smooth.Length = self._smooth_k.Value
self._roc_average = SimpleMovingAverage()
self._roc_average.Length = self._avg_roc_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(macd, stoch_k, sma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, macd)
def _process_candle(self, candle, macd_value, stoch_value, sma_value):
if candle.State != CandleStates.Finished:
return
time = candle.OpenTime
k = None
if stoch_value.IsFormed:
smoothed = process_float(self._k_smooth, stoch_value.GetValue[Decimal](None), time, True)
if self._k_smooth.IsFormed:
k = float(smoothed.GetValue[Decimal](None))
avg_roc = None
if sma_value.IsFormed:
ma = float(sma_value.GetValue[Decimal](None))
roc_length = self._roc_length.Value
self._ma_history.append(ma)
if len(self._ma_history) > roc_length + 1:
self._ma_history.pop(0)
if len(self._ma_history) == roc_length + 1 and self._ma_history[0] != 0:
roc = (ma - self._ma_history[0]) / self._ma_history[0] * 100.0
averaged = process_float(self._roc_average, roc, time, True)
if self._roc_average.IsFormed:
avg_roc = float(averaged.GetValue[Decimal](None))
signal = None
if macd_value.IsFormed and macd_value.Signal is not None:
signal = float(macd_value.Signal)
previous = self._prev_signal
if signal is not None:
self._prev_signal = signal
if k is None or avg_roc is None or signal is None or previous is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
oversold = float(self._oversold.Value)
overbought = float(self._overbought.Value)
long_votes = (1 if signal > previous else 0) + (1 if k < oversold else 0) + (1 if avg_roc > 0 else 0)
short_votes = (1 if signal < previous else 0) + (1 if k > overbought else 0) + (1 if avg_roc < 0 else 0)
if long_votes >= 2 and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif short_votes >= 2 and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
def CreateClone(self):
return three_signal_directional_trend_strategy()