マルチインジケーター・トレンドフォロー戦略
RSIとボリューム確認付きのEMAクロスオーバー戦略。ATRベースのストップロスとテイクプロフィットを使用します。
詳細
- エントリー条件: 速いEMAが遅いEMAを上抜け/下抜けし、RSIフィルターと高出来高が確認される
- ロング/ショート: 両方
- エグジット条件: ATRベースのストップロスとテイクプロフィット
- ストップ: はい、ATRベース
- デフォルト値:
CandleType= 5 minuteFastMaLength= 10SlowMaLength= 30RsiLength= 14VolumeMaLength= 20VolumeMultiplier= 1.5AtrPeriod= 14StopLossAtrMultiplier= 2TakeProfitAtrMultiplier= 3
- フィルター:
- カテゴリ: トレンドフォロー
- 方向: 両方
- インジケーター: EMA, RSI, ATR, Volume
- ストップ: はい
- 複雑さ: 中級
- 時間軸: イントラデイ
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
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>
/// Multi indicator trend following strategy.
/// Goes long when the fast EMA crosses above the slow EMA with RSI above 50 and volume above VolumeMultiplier times its average,
/// and short on the mirrored crossover with RSI below 50, reversing an opposite position. Positions are closed by a stop loss and a
/// take profit placed StopLossAtrMultiplier and TakeProfitAtrMultiplier ATRs away from the entry price.
/// </summary>
public class MultiIndicatorTrendFollowingStrategy : Strategy
{
private const decimal _rsiMiddle = 50m;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastMaLength;
private readonly StrategyParam<int> _slowMaLength;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _volumeMaLength;
private readonly StrategyParam<decimal> _volumeMultiplier;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _stopLossAtrMultiplier;
private readonly StrategyParam<decimal> _takeProfitAtrMultiplier;
private SimpleMovingAverage _volumeMa;
private decimal? _prevFast;
private decimal? _prevSlow;
private decimal? _stopPrice;
private decimal? _takePrice;
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Fast EMA length.
/// </summary>
public int FastMaLength
{
get => _fastMaLength.Value;
set => _fastMaLength.Value = value;
}
/// <summary>
/// Slow EMA length.
/// </summary>
public int SlowMaLength
{
get => _slowMaLength.Value;
set => _slowMaLength.Value = value;
}
/// <summary>
/// RSI period.
/// </summary>
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <summary>
/// Volume moving average length.
/// </summary>
public int VolumeMaLength
{
get => _volumeMaLength.Value;
set => _volumeMaLength.Value = value;
}
/// <summary>
/// Volume must exceed its average times this multiplier.
/// </summary>
public decimal VolumeMultiplier
{
get => _volumeMultiplier.Value;
set => _volumeMultiplier.Value = value;
}
/// <summary>
/// ATR period.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// ATR multiple for the stop loss distance.
/// </summary>
public decimal StopLossAtrMultiplier
{
get => _stopLossAtrMultiplier.Value;
set => _stopLossAtrMultiplier.Value = value;
}
/// <summary>
/// ATR multiple for the take profit distance.
/// </summary>
public decimal TakeProfitAtrMultiplier
{
get => _takeProfitAtrMultiplier.Value;
set => _takeProfitAtrMultiplier.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public MultiIndicatorTrendFollowingStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_fastMaLength = Param(nameof(FastMaLength), 10)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA length", "Indicators");
_slowMaLength = Param(nameof(SlowMaLength), 30)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA length", "Indicators");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "Indicators");
_volumeMaLength = Param(nameof(VolumeMaLength), 20)
.SetGreaterThanZero()
.SetDisplay("Volume MA Length", "Volume moving average length", "Volume");
_volumeMultiplier = Param(nameof(VolumeMultiplier), 1.5m)
.SetNotNegative()
.SetDisplay("Volume Multiplier", "Volume must exceed its average times this", "Volume");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR period", "Risk");
_stopLossAtrMultiplier = Param(nameof(StopLossAtrMultiplier), 2m)
.SetNotNegative()
.SetDisplay("Stop ATR Mult", "ATR multiple for the stop loss", "Risk");
_takeProfitAtrMultiplier = Param(nameof(TakeProfitAtrMultiplier), 3m)
.SetNotNegative()
.SetDisplay("Take ATR Mult", "ATR multiple for the take profit", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var fast = new ExponentialMovingAverage { Length = FastMaLength };
var slow = new ExponentialMovingAverage { Length = SlowMaLength };
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var atr = new AverageTrueRange { Length = AtrPeriod };
_volumeMa = new SimpleMovingAverage { Length = VolumeMaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(fast, slow, rsi, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fast);
DrawIndicator(area, slow);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
DrawIndicator(oscillators, rsi);
}
}
private void ResetState()
{
_prevFast = null;
_prevSlow = null;
_stopPrice = null;
_takePrice = null;
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue fastValue, IIndicatorValue slowValue, IIndicatorValue rsiValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
var volumeAverage = _volumeMa.Process(candle.TotalVolume, candle.OpenTime, true);
var exited = false;
// The ATR levels are checked against the candle range, so exits happen before new signals.
if (Position > 0 && _stopPrice is decimal longStop && _takePrice is decimal longTake)
{
if (candle.LowPrice <= longStop || candle.HighPrice >= longTake)
{
SellMarket(Position);
_stopPrice = null;
_takePrice = null;
exited = true;
}
}
else if (Position < 0 && _stopPrice is decimal shortStop && _takePrice is decimal shortTake)
{
if (candle.HighPrice >= shortStop || candle.LowPrice <= shortTake)
{
BuyMarket(-Position);
_stopPrice = null;
_takePrice = null;
exited = true;
}
}
if (!fastValue.IsFormed || !slowValue.IsFormed || !rsiValue.IsFormed || !atrValue.IsFormed || !_volumeMa.IsFormed)
return;
var fast = fastValue.GetValue<decimal>();
var slow = slowValue.GetValue<decimal>();
var prevFast = _prevFast;
var prevSlow = _prevSlow;
_prevFast = fast;
_prevSlow = slow;
if (exited || prevFast is not decimal pf || prevSlow is not decimal ps)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var rsi = rsiValue.GetValue<decimal>();
var atr = atrValue.GetValue<decimal>();
var highVolume = candle.TotalVolume > volumeAverage.GetValue<decimal>() * VolumeMultiplier;
var crossUp = pf <= ps && fast > slow;
var crossDown = pf >= ps && fast < slow;
if (crossUp && rsi > _rsiMiddle && highVolume && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
SetLevels(candle.ClosePrice, atr, true);
}
else if (crossDown && rsi < _rsiMiddle && highVolume && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
SetLevels(candle.ClosePrice, atr, false);
}
}
private void SetLevels(decimal entry, decimal atr, bool isLong)
{
var stop = atr * StopLossAtrMultiplier;
var take = atr * TakeProfitAtrMultiplier;
// A zero multiplier disables that level.
_stopPrice = StopLossAtrMultiplier > 0 ? (isLong ? entry - stop : entry + stop) : (isLong ? decimal.MinValue : decimal.MaxValue);
_takePrice = TakeProfitAtrMultiplier > 0 ? (isLong ? entry + take : entry - take) : (isLong ? decimal.MaxValue : decimal.MinValue);
}
}
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 ExponentialMovingAverage, RelativeStrengthIndex, AverageTrueRange, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
RSI_MIDDLE = 50
class multi_indicator_trend_following_strategy(Strategy):
"""
Multi indicator trend following strategy.
Goes long when the fast EMA crosses above the slow EMA with RSI above 50 and volume above VolumeMultiplier times its average,
and short on the mirrored crossover with RSI below 50, reversing an opposite position. Positions are closed by a stop loss and a
take profit placed StopLossAtrMultiplier and TakeProfitAtrMultiplier ATRs away from the entry price.
"""
def __init__(self):
super(multi_indicator_trend_following_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._fast_ma_length = self.Param("FastMaLength", 10).SetGreaterThanZero().SetDisplay("Fast EMA", "Fast EMA length", "Indicators")
self._slow_ma_length = self.Param("SlowMaLength", 30).SetGreaterThanZero().SetDisplay("Slow EMA", "Slow EMA length", "Indicators")
self._rsi_length = self.Param("RsiLength", 14).SetGreaterThanZero().SetDisplay("RSI Length", "RSI period", "Indicators")
self._volume_ma_length = self.Param("VolumeMaLength", 20).SetGreaterThanZero().SetDisplay("Volume MA Length", "Volume moving average length", "Volume")
self._volume_multiplier = self.Param("VolumeMultiplier", 1.5).SetNotNegative().SetDisplay("Volume Multiplier", "Volume must exceed its average times this", "Volume")
self._atr_period = self.Param("AtrPeriod", 14).SetGreaterThanZero().SetDisplay("ATR Period", "ATR period", "Risk")
self._stop_loss_atr_multiplier = self.Param("StopLossAtrMultiplier", 2.0).SetNotNegative().SetDisplay("Stop ATR Mult", "ATR multiple for the stop loss", "Risk")
self._take_profit_atr_multiplier = self.Param("TakeProfitAtrMultiplier", 3.0).SetNotNegative().SetDisplay("Take ATR Mult", "ATR multiple for the take profit", "Risk")
self._volume_ma = None
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._prev_fast = None
self._prev_slow = None
self._stop_price = None
self._take_price = None
def OnReseted(self):
super(multi_indicator_trend_following_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(multi_indicator_trend_following_strategy, self).OnStarted2(time)
self._reset_state()
fast = ExponentialMovingAverage()
fast.Length = self._fast_ma_length.Value
slow = ExponentialMovingAverage()
slow.Length = self._slow_ma_length.Value
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
self._volume_ma = SimpleMovingAverage()
self._volume_ma.Length = self._volume_ma_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(fast, slow, rsi, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, slow)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, rsi)
def _process_candle(self, candle, fast_value, slow_value, rsi_value, atr_value):
if candle.State != CandleStates.Finished:
return
volume_average = process_value(self._volume_ma, candle.TotalVolume, candle.OpenTime, True)
exited = False
# The ATR levels are checked against the candle range, so exits happen before new signals.
if self.Position > 0 and self._stop_price is not None and self._take_price is not None:
if candle.LowPrice <= self._stop_price or candle.HighPrice >= self._take_price:
self.SellMarket(self.Position)
self._stop_price = None
self._take_price = None
exited = True
elif self.Position < 0 and self._stop_price is not None and self._take_price is not None:
if candle.HighPrice >= self._stop_price or candle.LowPrice <= self._take_price:
self.BuyMarket(-self.Position)
self._stop_price = None
self._take_price = None
exited = True
if not fast_value.IsFormed or not slow_value.IsFormed or not rsi_value.IsFormed or not atr_value.IsFormed or not self._volume_ma.IsFormed:
return
fast = fast_value.GetValue[Decimal](None)
slow = slow_value.GetValue[Decimal](None)
prev_fast = self._prev_fast
prev_slow = self._prev_slow
self._prev_fast = fast
self._prev_slow = slow
if exited or prev_fast is None or prev_slow is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
rsi = rsi_value.GetValue[Decimal](None)
atr = atr_value.GetValue[Decimal](None)
high_volume = candle.TotalVolume > volume_average.GetValue[Decimal](None) * Decimal(self._volume_multiplier.Value)
cross_up = prev_fast <= prev_slow and fast > slow
cross_down = prev_fast >= prev_slow and fast < slow
if cross_up and rsi > Decimal(RSI_MIDDLE) and high_volume and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._set_levels(candle.ClosePrice, atr, True)
elif cross_down and rsi < Decimal(RSI_MIDDLE) and high_volume and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._set_levels(candle.ClosePrice, atr, False)
def _set_levels(self, entry, atr, is_long):
stop_mult = Decimal(self._stop_loss_atr_multiplier.Value)
take_mult = Decimal(self._take_profit_atr_multiplier.Value)
stop = atr * stop_mult
take = atr * take_mult
# A zero multiplier disables that level.
if stop_mult > Decimal(0):
self._stop_price = entry - stop if is_long else entry + stop
else:
self._stop_price = Decimal.MinValue if is_long else Decimal.MaxValue
if take_mult > Decimal(0):
self._take_price = entry + take if is_long else entry - take
else:
self._take_price = Decimal.MaxValue if is_long else Decimal.MinValue
def CreateClone(self):
return multi_indicator_trend_following_strategy()