EMAクロスオーバーと出来高+段階的TP&トレーリングSL戦略
この戦略は出来高でフィルタリングされたEMAクロスオーバーを取引します。ATRに基づく2つの利益目標を設定し、価格が有利に動いた後は残りのポジションにトレーリングストップを適用します。
詳細
- エントリー条件:
- 短期EMAが長期EMAを上抜け/下抜け。
- 出来高 > 平均出来高 *
VolumeMultiplier。
- ロング/ショート: ロングおよびショート。
- エグジット条件:
- 最初の利食い:
TP1Multiplier * ATR(ポジションの33%)。 - 2番目の利食い:
TP2Multiplier * ATR(さらに33%)。 - トレーリングストップは価格が
TrailTriggerMultiplier * ATR動いた後に有効化され、TrailOffsetMultiplier * ATRでトレールします。
- 最初の利食い:
- ストップ: トレーリングストップのみ。
- デフォルト値:
FastLength= 21SlowLength= 55VolumeMultiplier= 1.2AtrLength= 14Tp1Multiplier= 1.5Tp2Multiplier= 2.5TrailOffsetMultiplier= 1.5TrailTriggerMultiplier= 1.5CandleType= 5m
- フィルター:
- カテゴリ: トレンドフォロー
- 方向: ロング/ショート
- インジケーター: EMA, 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>
/// EMA crossover with volume, stacked take profits and trailing stop strategy.
/// The fast EMA crossing the slow EMA opens a position in the crossing direction, reversing an opposite one, when the candle
/// volume exceeds its average times VolumeMultiplier. A third of the position is taken at Tp1Multiplier ATR and another third at
/// Tp2Multiplier ATR from the entry. After price has moved TrailTriggerMultiplier ATR in favour a trailing stop follows the best
/// price at TrailOffsetMultiplier ATR. The ATR is frozen at entry.
/// </summary>
public class EmaCrossoverVolumeStackedTpTrailingSlStrategy : Strategy
{
// The README does not give the volume average length.
private const int _volumeAverageLength = 20;
private const decimal _partialShare = 0.33m;
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<decimal> _volumeMultiplier;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _tp1Multiplier;
private readonly StrategyParam<decimal> _tp2Multiplier;
private readonly StrategyParam<decimal> _trailOffsetMultiplier;
private readonly StrategyParam<decimal> _trailTriggerMultiplier;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _volumeAverage;
private decimal? _prevFast;
private decimal? _prevSlow;
private decimal _entryPrice;
private decimal _entryAtr;
private decimal _entryVolume;
private decimal _bestPrice;
private bool _tp1Done;
private bool _tp2Done;
private bool _trailActive;
/// <summary>
/// Fast EMA length.
/// </summary>
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
/// <summary>
/// Slow EMA length.
/// </summary>
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.Value = value;
}
/// <summary>
/// Multiplier of the average volume the candle volume must exceed.
/// </summary>
public decimal VolumeMultiplier
{
get => _volumeMultiplier.Value;
set => _volumeMultiplier.Value = value;
}
/// <summary>
/// ATR length.
/// </summary>
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <summary>
/// First take profit in ATR.
/// </summary>
public decimal Tp1Multiplier
{
get => _tp1Multiplier.Value;
set => _tp1Multiplier.Value = value;
}
/// <summary>
/// Second take profit in ATR.
/// </summary>
public decimal Tp2Multiplier
{
get => _tp2Multiplier.Value;
set => _tp2Multiplier.Value = value;
}
/// <summary>
/// Trailing distance in ATR.
/// </summary>
public decimal TrailOffsetMultiplier
{
get => _trailOffsetMultiplier.Value;
set => _trailOffsetMultiplier.Value = value;
}
/// <summary>
/// Favourable move in ATR that activates the trailing stop.
/// </summary>
public decimal TrailTriggerMultiplier
{
get => _trailTriggerMultiplier.Value;
set => _trailTriggerMultiplier.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public EmaCrossoverVolumeStackedTpTrailingSlStrategy()
{
_fastLength = Param(nameof(FastLength), 21)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA length", "Indicators");
_slowLength = Param(nameof(SlowLength), 55)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA length", "Indicators");
_volumeMultiplier = Param(nameof(VolumeMultiplier), 1.2m)
.SetNotNegative()
.SetDisplay("Volume Multiplier", "Multiplier of the average volume", "Filters");
_atrLength = Param(nameof(AtrLength), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR length", "Indicators");
_tp1Multiplier = Param(nameof(Tp1Multiplier), 1.5m)
.SetGreaterThanZero()
.SetDisplay("TP1 ATR", "First take profit in ATR", "Risk");
_tp2Multiplier = Param(nameof(Tp2Multiplier), 2.5m)
.SetGreaterThanZero()
.SetDisplay("TP2 ATR", "Second take profit in ATR", "Risk");
_trailOffsetMultiplier = Param(nameof(TrailOffsetMultiplier), 1.5m)
.SetGreaterThanZero()
.SetDisplay("Trail Offset ATR", "Trailing distance in ATR", "Risk");
_trailTriggerMultiplier = Param(nameof(TrailTriggerMultiplier), 1.5m)
.SetNotNegative()
.SetDisplay("Trail Trigger ATR", "Favourable move in ATR that activates trailing", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).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()
{
_prevFast = null;
_prevSlow = null;
_entryPrice = 0m;
_entryAtr = 0m;
_entryVolume = 0m;
_bestPrice = 0m;
_tp1Done = false;
_tp2Done = false;
_trailActive = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var fast = new ExponentialMovingAverage { Length = FastLength };
var slow = new ExponentialMovingAverage { Length = SlowLength };
var atr = new AverageTrueRange { Length = AtrLength };
_volumeAverage = new SimpleMovingAverage { Length = _volumeAverageLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fast, slow, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fast);
DrawIndicator(area, slow);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow, decimal atr)
{
if (candle.State != CandleStates.Finished)
return;
var volumeAverageValue = _volumeAverage.Process(new DecimalIndicatorValue(_volumeAverage, candle.TotalVolume, candle.OpenTime) { IsFinal = true });
var prevFast = _prevFast;
var prevSlow = _prevSlow;
_prevFast = fast;
_prevSlow = slow;
if (!IsFormedAndOnlineAndAllowTrading())
return;
// An exit order sent on this candle is not reflected in Position yet.
if (Position != 0 && ManagePosition(candle))
return;
if (!volumeAverageValue.IsFormed || prevFast is not decimal pf || prevSlow is not decimal ps || atr <= 0m)
return;
var volumeOk = candle.TotalVolume > volumeAverageValue.GetValue<decimal>() * VolumeMultiplier;
if (!volumeOk)
return;
if (pf <= ps && fast > slow && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
OpenPosition(candle.ClosePrice, atr);
}
else if (pf >= ps && fast < slow && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
OpenPosition(candle.ClosePrice, atr);
}
}
private void OpenPosition(decimal price, decimal atr)
{
_entryPrice = price;
_entryAtr = atr;
_entryVolume = Volume;
_bestPrice = price;
_tp1Done = false;
_tp2Done = false;
_trailActive = false;
}
private decimal PartialVolume()
{
var step = Security?.VolumeStep ?? 0m;
var part = _entryVolume * _partialShare;
if (step > 0m)
part = Math.Floor(part / step) * step;
return Math.Min(part, Math.Abs(Position));
}
private bool ManagePosition(ICandleMessage candle)
{
var trailDistance = TrailOffsetMultiplier * _entryAtr;
if (Position > 0)
{
if (_trailActive && candle.LowPrice <= _bestPrice - trailDistance)
{
SellMarket(Position);
return true;
}
_bestPrice = Math.Max(_bestPrice, candle.HighPrice);
if (!_trailActive && _bestPrice - _entryPrice >= TrailTriggerMultiplier * _entryAtr)
_trailActive = true;
if (!_tp1Done && candle.HighPrice >= _entryPrice + Tp1Multiplier * _entryAtr)
{
_tp1Done = true;
var part = PartialVolume();
if (part > 0m)
{
SellMarket(part);
return true;
}
}
else if (_tp1Done && !_tp2Done && candle.HighPrice >= _entryPrice + Tp2Multiplier * _entryAtr)
{
_tp2Done = true;
var part = PartialVolume();
if (part > 0m)
{
SellMarket(part);
return true;
}
}
}
else
{
if (_trailActive && candle.HighPrice >= _bestPrice + trailDistance)
{
BuyMarket(-Position);
return true;
}
_bestPrice = Math.Min(_bestPrice, candle.LowPrice);
if (!_trailActive && _entryPrice - _bestPrice >= TrailTriggerMultiplier * _entryAtr)
_trailActive = true;
if (!_tp1Done && candle.LowPrice <= _entryPrice - Tp1Multiplier * _entryAtr)
{
_tp1Done = true;
var part = PartialVolume();
if (part > 0m)
{
BuyMarket(part);
return true;
}
}
else if (_tp1Done && !_tp2Done && candle.LowPrice <= _entryPrice - Tp2Multiplier * _entryAtr)
{
_tp2Done = true;
var part = PartialVolume();
if (part > 0m)
{
BuyMarket(part);
return true;
}
}
}
return false;
}
}
import clr
import math
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange, SimpleMovingAverage, DecimalIndicatorValue
from StockSharp.Algo.Strategies import Strategy
# The README does not give the volume average length.
VOLUME_AVERAGE_LENGTH = 20
PARTIAL_SHARE = 0.33
class ema_crossover_volume_stacked_tp_trailing_sl_strategy(Strategy):
"""
EMA crossover with volume, stacked take profits and trailing stop strategy.
The fast EMA crossing the slow EMA opens a position in the crossing direction, reversing an opposite one, when the candle
volume exceeds its average times VolumeMultiplier. A third of the position is taken at Tp1Multiplier ATR and another third at
Tp2Multiplier ATR from the entry. After price has moved TrailTriggerMultiplier ATR in favour a trailing stop follows the best
price at TrailOffsetMultiplier ATR. The ATR is frozen at entry.
"""
def __init__(self):
super(ema_crossover_volume_stacked_tp_trailing_sl_strategy, self).__init__()
self._fast_length = self.Param("FastLength", 21).SetGreaterThanZero().SetDisplay("Fast EMA", "Fast EMA length", "Indicators")
self._slow_length = self.Param("SlowLength", 55).SetGreaterThanZero().SetDisplay("Slow EMA", "Slow EMA length", "Indicators")
self._volume_multiplier = self.Param("VolumeMultiplier", 1.2).SetNotNegative().SetDisplay("Volume Multiplier", "Multiplier of the average volume", "Filters")
self._atr_length = self.Param("AtrLength", 14).SetGreaterThanZero().SetDisplay("ATR Length", "ATR length", "Indicators")
self._tp1_multiplier = self.Param("Tp1Multiplier", 1.5).SetGreaterThanZero().SetDisplay("TP1 ATR", "First take profit in ATR", "Risk")
self._tp2_multiplier = self.Param("Tp2Multiplier", 2.5).SetGreaterThanZero().SetDisplay("TP2 ATR", "Second take profit in ATR", "Risk")
self._trail_offset_multiplier = self.Param("TrailOffsetMultiplier", 1.5).SetGreaterThanZero().SetDisplay("Trail Offset ATR", "Trailing distance in ATR", "Risk")
self._trail_trigger_multiplier = self.Param("TrailTriggerMultiplier", 1.5).SetNotNegative().SetDisplay("Trail Trigger ATR", "Favourable move in ATR that activates trailing", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._volume_average = 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._entry_price = 0.0
self._entry_atr = 0.0
self._entry_volume = 0.0
self._best_price = 0.0
self._tp1_done = False
self._tp2_done = False
self._trail_active = False
def OnReseted(self):
super(ema_crossover_volume_stacked_tp_trailing_sl_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(ema_crossover_volume_stacked_tp_trailing_sl_strategy, self).OnStarted2(time)
self._reset_state()
fast = ExponentialMovingAverage()
fast.Length = self._fast_length.Value
slow = ExponentialMovingAverage()
slow.Length = self._slow_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
self._volume_average = SimpleMovingAverage()
self._volume_average.Length = VOLUME_AVERAGE_LENGTH
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(fast, slow, 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)
def _process_candle(self, candle, fast_value, slow_value, atr_value):
if candle.State != CandleStates.Finished:
return
volume_input = DecimalIndicatorValue(self._volume_average, candle.TotalVolume, candle.OpenTime)
volume_input.IsFinal = True
volume_average_value = self._volume_average.Process(volume_input)
fast = float(fast_value)
slow = float(slow_value)
atr = float(atr_value)
prev_fast = self._prev_fast
prev_slow = self._prev_slow
self._prev_fast = fast
self._prev_slow = slow
if not self.IsFormedAndOnlineAndAllowTrading():
return
# An exit order sent on this candle is not reflected in Position yet.
if self.Position != 0 and self._manage_position(candle):
return
if not volume_average_value.IsFormed or prev_fast is None or prev_slow is None or atr <= 0:
return
volume_average = float(volume_average_value.GetValue[Decimal](None))
if float(candle.TotalVolume) <= volume_average * float(self._volume_multiplier.Value):
return
close = float(candle.ClosePrice)
if prev_fast <= prev_slow and fast > slow and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._open_position(close, atr)
elif prev_fast >= prev_slow and fast < slow and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._open_position(close, atr)
def _open_position(self, price, atr):
self._entry_price = price
self._entry_atr = atr
self._entry_volume = float(self.Volume)
self._best_price = price
self._tp1_done = False
self._tp2_done = False
self._trail_active = False
def _partial_volume(self):
step = float(self.Security.VolumeStep) if self.Security is not None and self.Security.VolumeStep is not None else 0.0
part = self._entry_volume * PARTIAL_SHARE
if step > 0:
part = math.floor(part / step + 1e-9) * step
return min(part, abs(float(self.Position)))
def _manage_position(self, candle):
high = float(candle.HighPrice)
low = float(candle.LowPrice)
atr = self._entry_atr
trail_distance = float(self._trail_offset_multiplier.Value) * atr
trigger = float(self._trail_trigger_multiplier.Value) * atr
tp1 = float(self._tp1_multiplier.Value) * atr
tp2 = float(self._tp2_multiplier.Value) * atr
if self.Position > 0:
if self._trail_active and low <= self._best_price - trail_distance:
self.SellMarket(self.Position)
return True
self._best_price = max(self._best_price, high)
if not self._trail_active and self._best_price - self._entry_price >= trigger:
self._trail_active = True
if not self._tp1_done and high >= self._entry_price + tp1:
self._tp1_done = True
part = self._partial_volume()
if part > 0:
self.SellMarket(Decimal(part))
return True
elif self._tp1_done and not self._tp2_done and high >= self._entry_price + tp2:
self._tp2_done = True
part = self._partial_volume()
if part > 0:
self.SellMarket(Decimal(part))
return True
else:
if self._trail_active and high >= self._best_price + trail_distance:
self.BuyMarket(-self.Position)
return True
self._best_price = min(self._best_price, low)
if not self._trail_active and self._entry_price - self._best_price >= trigger:
self._trail_active = True
if not self._tp1_done and low <= self._entry_price - tp1:
self._tp1_done = True
part = self._partial_volume()
if part > 0:
self.BuyMarket(Decimal(part))
return True
elif self._tp1_done and not self._tp2_done and low <= self._entry_price - tp2:
self._tp2_done = True
part = self._partial_volume()
if part > 0:
self.BuyMarket(Decimal(part))
return True
return False
def CreateClone(self):
return ema_crossover_volume_stacked_tp_trailing_sl_strategy()