EMAプルバック速度戦略
EMAプルバック速度戦略は、価格の加速度に適応するダイナミックEMAを使用します。上昇トレンド中に強気の反転と十分な上昇速度を伴いながら価格がダイナミックEMAに戻ったときにロングポジションを建てます。逆の条件でショートポジションを建てます。エグジットにはATRベースのストップロスと固定パーセンテージのテイクプロフィットを使用します。
詳細
- エントリー条件:
- ロング: 価格がダイナミックEMAより上、強気の反転、価格がEMAに戻る、正の速度、短期EMAが長期EMAより上、速度 ≥
LongSpeedMin。 - ショート: 価格がダイナミックEMAより下、弱気の反転、価格がEMAに戻る、負の速度、短期EMAが長期EMAより下、速度 ≤
ShortSpeedMax。
- ロング: 価格がダイナミックEMAより上、強気の反転、価格がEMAに戻る、正の速度、短期EMAが長期EMAより上、速度 ≥
- ロング/ショート: 両方向。
- エグジット条件: ATRストップロスと固定パーセンテージのテイクプロフィット。
- ストップ: ストップロス
AtrMultiplier×ATR、テイクプロフィットFixedTpPct%。 - デフォルト値:
MaxLength= 50AccelMultiplier= 3ReturnThreshold= 5AtrLength= 14AtrMultiplier= 4FixedTpPct= 1.5ShortEmaLength= 21LongEmaLength= 50LongSpeedMin= 1000ShortSpeedMax= -1000
- フィルター:
- カテゴリ: トレンドフォロー
- 方向: 両方
- インジケーター: EMA, ATR
- ストップ: ATRストップロス、固定テイクプロフィット
- 複雑さ: 中
- 時間軸: 5m
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// EMA pullback speed strategy.
/// A dynamic EMA changes its length between 5 and MaxLength with the candle body relative to the largest recent body and
/// speeds up with price acceleration scaled by AccelMultiplier. Speed accumulates candle bodies while the dynamic EMA keeps its
/// direction and restarts when it turns. A long needs the close above the dynamic EMA after the low came back within
/// ReturnThreshold percent of it, a bullish reversal candle, the short EMA above the long EMA and speed of at least LongSpeedMin;
/// a short mirrors it with speed of at most ShortSpeedMax. The stop is AtrMultiplier ATR from entry, the take profit FixedTpPct percent.
/// </summary>
public class EmaPullbackSpeedStrategy : Strategy
{
private const int _minLength = 5;
private const int _lookback = 200;
private readonly StrategyParam<int> _maxLength;
private readonly StrategyParam<decimal> _accelMultiplier;
private readonly StrategyParam<decimal> _returnThreshold;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<decimal> _fixedTpPct;
private readonly StrategyParam<int> _shortEmaLength;
private readonly StrategyParam<int> _longEmaLength;
private readonly StrategyParam<decimal> _longSpeedMin;
private readonly StrategyParam<decimal> _shortSpeedMax;
private readonly StrategyParam<DataType> _candleType;
private readonly Queue<decimal> _bodies = new();
private readonly Queue<decimal> _deltas = new();
private decimal? _dynEma;
private decimal? _prevClose;
private decimal? _prevOpen;
private int _direction;
private decimal _speed;
private decimal _stopPrice;
private decimal _takePrice;
/// <summary>
/// Maximum length of the dynamic EMA.
/// </summary>
public int MaxLength
{
get => _maxLength.Value;
set => _maxLength.Value = value;
}
/// <summary>
/// Weight of price acceleration in the dynamic EMA.
/// </summary>
public decimal AccelMultiplier
{
get => _accelMultiplier.Value;
set => _accelMultiplier.Value = value;
}
/// <summary>
/// Maximum distance in percent between the low (high) and the dynamic EMA for a pullback.
/// </summary>
public decimal ReturnThreshold
{
get => _returnThreshold.Value;
set => _returnThreshold.Value = value;
}
/// <summary>
/// ATR length.
/// </summary>
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <summary>
/// Stop loss distance in ATR.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Take profit percentage.
/// </summary>
public decimal FixedTpPct
{
get => _fixedTpPct.Value;
set => _fixedTpPct.Value = value;
}
/// <summary>
/// Short EMA length.
/// </summary>
public int ShortEmaLength
{
get => _shortEmaLength.Value;
set => _shortEmaLength.Value = value;
}
/// <summary>
/// Long EMA length.
/// </summary>
public int LongEmaLength
{
get => _longEmaLength.Value;
set => _longEmaLength.Value = value;
}
/// <summary>
/// Minimum speed for a long.
/// </summary>
public decimal LongSpeedMin
{
get => _longSpeedMin.Value;
set => _longSpeedMin.Value = value;
}
/// <summary>
/// Maximum speed for a short.
/// </summary>
public decimal ShortSpeedMax
{
get => _shortSpeedMax.Value;
set => _shortSpeedMax.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public EmaPullbackSpeedStrategy()
{
_maxLength = Param(nameof(MaxLength), 50)
.SetGreaterThanZero()
.SetDisplay("Max Length", "Maximum length of the dynamic EMA", "Dynamic EMA");
_accelMultiplier = Param(nameof(AccelMultiplier), 3m)
.SetNotNegative()
.SetDisplay("Accel Multiplier", "Weight of price acceleration", "Dynamic EMA");
_returnThreshold = Param(nameof(ReturnThreshold), 5m)
.SetNotNegative()
.SetDisplay("Return Threshold %", "Maximum pullback distance from the dynamic EMA", "Dynamic EMA");
_atrLength = Param(nameof(AtrLength), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR length", "Risk");
_atrMultiplier = Param(nameof(AtrMultiplier), 4m)
.SetNotNegative()
.SetDisplay("ATR Multiplier", "Stop loss distance in ATR", "Risk");
_fixedTpPct = Param(nameof(FixedTpPct), 1.5m)
.SetNotNegative()
.SetDisplay("Take Profit %", "Take profit percentage", "Risk");
_shortEmaLength = Param(nameof(ShortEmaLength), 21)
.SetGreaterThanZero()
.SetDisplay("Short EMA", "Short EMA length", "Trend");
_longEmaLength = Param(nameof(LongEmaLength), 50)
.SetGreaterThanZero()
.SetDisplay("Long EMA", "Long EMA length", "Trend");
_longSpeedMin = Param(nameof(LongSpeedMin), 1000m)
.SetDisplay("Long Speed Min", "Minimum speed for a long", "Speed");
_shortSpeedMax = Param(nameof(ShortSpeedMax), -1000m)
.SetDisplay("Short Speed Max", "Maximum speed for a short", "Speed");
_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()
{
_bodies.Clear();
_deltas.Clear();
_dynEma = null;
_prevClose = null;
_prevOpen = null;
_direction = 0;
_speed = 0m;
_stopPrice = 0m;
_takePrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var shortEma = new ExponentialMovingAverage { Length = ShortEmaLength };
var longEma = new ExponentialMovingAverage { Length = LongEmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(shortEma, longEma, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, shortEma);
DrawIndicator(area, longEma);
DrawOwnTrades(area);
}
}
private static void Push(Queue<decimal> queue, decimal value)
{
queue.Enqueue(value);
if (queue.Count > _lookback)
queue.Dequeue();
}
private void ProcessCandle(ICandleMessage candle, decimal shortEma, decimal longEma, decimal atr)
{
if (candle.State != CandleStates.Finished)
return;
var open = candle.OpenPrice;
var close = candle.ClosePrice;
var body = close - open;
var prevClose = _prevClose;
var prevOpen = _prevOpen;
_prevClose = close;
_prevOpen = open;
Push(_bodies, Math.Abs(body));
Push(_deltas, prevClose is decimal pc ? Math.Abs(close - pc) : 0m);
var maxBody = _bodies.Max();
var maxDelta = _deltas.Max();
// Larger bullish bodies lengthen the EMA, bearish ones shorten it, and acceleration speeds it up.
var norm = maxBody > 0m ? (body + maxBody) / (2m * maxBody) : 0.5m;
var dynLength = _minLength + norm * (MaxLength - _minLength);
var accel = maxDelta > 0m ? _deltas.Last() / maxDelta : 0m;
var alpha = Math.Min(1m, 2m / (dynLength + 1m) * (1m + accel * AccelMultiplier));
var prevDyn = _dynEma;
var dyn = prevDyn is decimal pd ? alpha * close + (1m - alpha) * pd : close;
_dynEma = dyn;
if (prevDyn is decimal previous)
{
var direction = Math.Sign(dyn - previous);
if (direction != 0 && direction != _direction)
{
_direction = direction;
_speed = 0m;
}
_speed += body;
}
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (Position > 0)
{
if ((AtrMultiplier > 0m && candle.LowPrice <= _stopPrice) || (FixedTpPct > 0m && candle.HighPrice >= _takePrice))
SellMarket(Position);
return;
}
if (Position < 0)
{
if ((AtrMultiplier > 0m && candle.HighPrice >= _stopPrice) || (FixedTpPct > 0m && candle.LowPrice <= _takePrice))
BuyMarket(-Position);
return;
}
if (prevOpen is not decimal po || prevClose is not decimal pclose || prevDyn is null || dyn <= 0m)
return;
var threshold = ReturnThreshold / 100m;
var bullishReversal = close > open && pclose < po;
var bearishReversal = close < open && pclose > po;
var returnedLong = candle.LowPrice <= dyn * (1m + threshold);
var returnedShort = candle.HighPrice >= dyn * (1m - threshold);
if (close > dyn && bullishReversal && returnedLong && _speed > 0m && _speed >= LongSpeedMin && shortEma > longEma)
{
_stopPrice = close - atr * AtrMultiplier;
_takePrice = close * (1m + FixedTpPct / 100m);
BuyMarket(Volume);
}
else if (close < dyn && bearishReversal && returnedShort && _speed < 0m && _speed <= ShortSpeedMax && shortEma < longEma)
{
_stopPrice = close + atr * AtrMultiplier;
_takePrice = close * (1m - FixedTpPct / 100m);
SellMarket(Volume);
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
MIN_LENGTH = 5
LOOKBACK = 200
class ema_pullback_speed_strategy(Strategy):
"""
EMA pullback speed strategy.
A dynamic EMA changes its length between 5 and MaxLength with the candle body relative to the largest recent body and
speeds up with price acceleration scaled by AccelMultiplier. Speed accumulates candle bodies while the dynamic EMA keeps its
direction and restarts when it turns. A long needs the close above the dynamic EMA after the low came back within
ReturnThreshold percent of it, a bullish reversal candle, the short EMA above the long EMA and speed of at least LongSpeedMin;
a short mirrors it with speed of at most ShortSpeedMax. The stop is AtrMultiplier ATR from entry, the take profit FixedTpPct percent.
"""
def __init__(self):
super(ema_pullback_speed_strategy, self).__init__()
self._max_length = self.Param("MaxLength", 50).SetGreaterThanZero().SetDisplay("Max Length", "Maximum length of the dynamic EMA", "Dynamic EMA")
self._accel_multiplier = self.Param("AccelMultiplier", 3.0).SetNotNegative().SetDisplay("Accel Multiplier", "Weight of price acceleration", "Dynamic EMA")
self._return_threshold = self.Param("ReturnThreshold", 5.0).SetNotNegative().SetDisplay("Return Threshold %", "Maximum pullback distance from the dynamic EMA", "Dynamic EMA")
self._atr_length = self.Param("AtrLength", 14).SetGreaterThanZero().SetDisplay("ATR Length", "ATR length", "Risk")
self._atr_multiplier = self.Param("AtrMultiplier", 4.0).SetNotNegative().SetDisplay("ATR Multiplier", "Stop loss distance in ATR", "Risk")
self._fixed_tp_pct = self.Param("FixedTpPct", 1.5).SetNotNegative().SetDisplay("Take Profit %", "Take profit percentage", "Risk")
self._short_ema_length = self.Param("ShortEmaLength", 21).SetGreaterThanZero().SetDisplay("Short EMA", "Short EMA length", "Trend")
self._long_ema_length = self.Param("LongEmaLength", 50).SetGreaterThanZero().SetDisplay("Long EMA", "Long EMA length", "Trend")
self._long_speed_min = self.Param("LongSpeedMin", 1000.0).SetDisplay("Long Speed Min", "Minimum speed for a long", "Speed")
self._short_speed_max = self.Param("ShortSpeedMax", -1000.0).SetDisplay("Short Speed Max", "Maximum speed for a short", "Speed")
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._bodies = []
self._deltas = []
self._dyn_ema = None
self._prev_close = None
self._prev_open = None
self._direction = 0
self._speed = 0.0
self._stop_price = 0.0
self._take_price = 0.0
def OnReseted(self):
super(ema_pullback_speed_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(ema_pullback_speed_strategy, self).OnStarted2(time)
self._reset_state()
short_ema = ExponentialMovingAverage()
short_ema.Length = self._short_ema_length.Value
long_ema = ExponentialMovingAverage()
long_ema.Length = self._long_ema_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(short_ema, long_ema, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, short_ema)
self.DrawIndicator(area, long_ema)
self.DrawOwnTrades(area)
@staticmethod
def _push(items, value):
items.append(value)
if len(items) > LOOKBACK:
items.pop(0)
def _process_candle(self, candle, short_value, long_value, atr_value):
if candle.State != CandleStates.Finished:
return
short_ema = float(short_value)
long_ema = float(long_value)
atr = float(atr_value)
open_price = float(candle.OpenPrice)
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
body = close - open_price
prev_close = self._prev_close
prev_open = self._prev_open
self._prev_close = close
self._prev_open = open_price
self._push(self._bodies, abs(body))
self._push(self._deltas, abs(close - prev_close) if prev_close is not None else 0.0)
max_body = max(self._bodies)
max_delta = max(self._deltas)
# Larger bullish bodies lengthen the EMA, bearish ones shorten it, and acceleration speeds it up.
max_length = self._max_length.Value
norm = (body + max_body) / (2.0 * max_body) if max_body > 0 else 0.5
dyn_length = MIN_LENGTH + norm * (max_length - MIN_LENGTH)
accel = self._deltas[-1] / max_delta if max_delta > 0 else 0.0
alpha = min(1.0, 2.0 / (dyn_length + 1.0) * (1.0 + accel * float(self._accel_multiplier.Value)))
prev_dyn = self._dyn_ema
dyn = alpha * close + (1.0 - alpha) * prev_dyn if prev_dyn is not None else close
self._dyn_ema = dyn
if prev_dyn is not None:
diff = dyn - prev_dyn
direction = 1 if diff > 0 else (-1 if diff < 0 else 0)
if direction != 0 and direction != self._direction:
self._direction = direction
self._speed = 0.0
self._speed += body
if not self.IsFormedAndOnlineAndAllowTrading():
return
atr_multiplier = float(self._atr_multiplier.Value)
tp_pct = float(self._fixed_tp_pct.Value)
if self.Position > 0:
if (atr_multiplier > 0 and low <= self._stop_price) or (tp_pct > 0 and high >= self._take_price):
self.SellMarket(self.Position)
return
if self.Position < 0:
if (atr_multiplier > 0 and high >= self._stop_price) or (tp_pct > 0 and low <= self._take_price):
self.BuyMarket(-self.Position)
return
if prev_open is None or prev_close is None or prev_dyn is None or dyn <= 0:
return
threshold = float(self._return_threshold.Value) / 100.0
bullish_reversal = close > open_price and prev_close < prev_open
bearish_reversal = close < open_price and prev_close > prev_open
returned_long = low <= dyn * (1.0 + threshold)
returned_short = high >= dyn * (1.0 - threshold)
if close > dyn and bullish_reversal and returned_long and self._speed > 0 and self._speed >= float(self._long_speed_min.Value) and short_ema > long_ema:
self._stop_price = close - atr * atr_multiplier
self._take_price = close * (1.0 + tp_pct / 100.0)
self.BuyMarket(self.Volume)
elif close < dyn and bearish_reversal and returned_short and self._speed < 0 and self._speed <= float(self._short_speed_max.Value) and short_ema < long_ema:
self._stop_price = close + atr * atr_multiplier
self._take_price = close * (1.0 - tp_pct / 100.0)
self.SellMarket(self.Volume)
def CreateClone(self):
return ema_pullback_speed_strategy()