Crunchsterのタートル・アンド・トレンド・システム戦略
高速/低速EMAトレンドフィルターとDonchianチャネルブレイクアウトエントリー、ATRベースのストップ管理を組み合わせた戦略。モメンタムが反転した際にはトレーリングDonchianチャネルでポジションを決済する。
詳細
- エントリー条件: EMA差分クロスまたはDonchianチャネルブレイクアウト。
- ロング/ショート: 両方。
- エグジット条件: トレーリングチャネルまたはATRストップ。
- ストップ: あり、ATRベース。
- デフォルト値:
CandleType= 1時間FastEmaPeriod= 10BreakoutPeriod= 20TrailPeriod= 1000StopAtrMultiple= 20OrderPercent= 10TrendEnabled= trueBreakoutEnabled= false
- フィルター:
- カテゴリ: トレンド
- 方向: ロング/ショート
- インジケーター: EMA、Donchian、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>
/// Crunchster's Turtle and Trend System strategy.
/// The trend signal is the difference between a FastEmaPeriod EMA and an EMA five times slower: crossing above zero goes long and
/// crossing below zero goes short (TrendEnabled). The breakout signal goes long on a close above the highest high of the previous
/// BreakoutPeriod candles and short on a close below the lowest low (BreakoutEnabled). An opposite signal reverses the position.
/// A long closes below the lowest low of the previous TrailPeriod candles, a short above the highest high, and a stop
/// StopAtrMultiple ATRs from the entry limits the loss.
/// </summary>
public class CrunchstersTurtleAndTrendSystemStrategy : Strategy
{
private const int _atrLength = 14;
private const int _slowEmaFactor = 5;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastEmaPeriod;
private readonly StrategyParam<int> _breakoutPeriod;
private readonly StrategyParam<int> _trailPeriod;
private readonly StrategyParam<decimal> _stopAtrMultiple;
private readonly StrategyParam<decimal> _orderPercent;
private readonly StrategyParam<bool> _trendEnabled;
private readonly StrategyParam<bool> _breakoutEnabled;
private Highest _breakoutHigh;
private Lowest _breakoutLow;
private Highest _trailHigh;
private Lowest _trailLow;
private decimal? _prevDiff;
private decimal? _prevBreakoutHigh;
private decimal? _prevBreakoutLow;
private decimal? _prevTrailHigh;
private decimal? _prevTrailLow;
private decimal? _stopPrice;
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Fast EMA period; the slow EMA is five times longer.
/// </summary>
public int FastEmaPeriod
{
get => _fastEmaPeriod.Value;
set => _fastEmaPeriod.Value = value;
}
/// <summary>
/// Donchian breakout period.
/// </summary>
public int BreakoutPeriod
{
get => _breakoutPeriod.Value;
set => _breakoutPeriod.Value = value;
}
/// <summary>
/// Trailing Donchian exit period.
/// </summary>
public int TrailPeriod
{
get => _trailPeriod.Value;
set => _trailPeriod.Value = value;
}
/// <summary>
/// ATR multiple for the stop.
/// </summary>
public decimal StopAtrMultiple
{
get => _stopAtrMultiple.Value;
set => _stopAtrMultiple.Value = value;
}
/// <summary>
/// Percent of equity per order (informational; the strategy trades Volume).
/// </summary>
public decimal OrderPercent
{
get => _orderPercent.Value;
set => _orderPercent.Value = value;
}
/// <summary>
/// Trade the EMA trend signal.
/// </summary>
public bool TrendEnabled
{
get => _trendEnabled.Value;
set => _trendEnabled.Value = value;
}
/// <summary>
/// Trade the Donchian breakout signal.
/// </summary>
public bool BreakoutEnabled
{
get => _breakoutEnabled.Value;
set => _breakoutEnabled.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public CrunchstersTurtleAndTrendSystemStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_fastEmaPeriod = Param(nameof(FastEmaPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA period; the slow EMA is five times longer", "Trend");
_breakoutPeriod = Param(nameof(BreakoutPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Breakout Period", "Donchian breakout period", "Breakout");
_trailPeriod = Param(nameof(TrailPeriod), 1000)
.SetGreaterThanZero()
.SetDisplay("Trail Period", "Trailing Donchian exit period", "Risk");
_stopAtrMultiple = Param(nameof(StopAtrMultiple), 20m)
.SetNotNegative()
.SetDisplay("Stop ATR Multiple", "ATR multiple for the stop", "Risk");
_orderPercent = Param(nameof(OrderPercent), 10m)
.SetGreaterThanZero()
.SetDisplay("Order %", "Percent of equity per order", "Risk");
_trendEnabled = Param(nameof(TrendEnabled), true)
.SetDisplay("Trend Enabled", "Trade the EMA trend signal", "Trend");
_breakoutEnabled = Param(nameof(BreakoutEnabled), false)
.SetDisplay("Breakout Enabled", "Trade the Donchian breakout signal", "Breakout");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
_breakoutHigh = null;
_breakoutLow = null;
_trailHigh = null;
_trailLow = null;
}
private void ResetState()
{
_prevDiff = null;
_prevBreakoutHigh = null;
_prevBreakoutLow = null;
_prevTrailHigh = null;
_prevTrailLow = null;
_stopPrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var fastEma = new ExponentialMovingAverage { Length = FastEmaPeriod };
var slowEma = new ExponentialMovingAverage { Length = FastEmaPeriod * _slowEmaFactor };
var atr = new AverageTrueRange { Length = _atrLength };
_breakoutHigh = new Highest { Length = BreakoutPeriod };
_breakoutLow = new Lowest { Length = BreakoutPeriod };
_trailHigh = new Highest { Length = TrailPeriod };
_trailLow = new Lowest { Length = TrailPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(fastEma, slowEma, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastEma);
DrawIndicator(area, slowEma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue fastValue, IIndicatorValue slowValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
// Channels are measured on the candles before this one.
var breakoutHigh = _prevBreakoutHigh;
var breakoutLow = _prevBreakoutLow;
var trailHigh = _prevTrailHigh;
var trailLow = _prevTrailLow;
_prevBreakoutHigh = Track(_breakoutHigh, candle.HighPrice, candle.OpenTime);
_prevBreakoutLow = Track(_breakoutLow, candle.LowPrice, candle.OpenTime);
_prevTrailHigh = Track(_trailHigh, candle.HighPrice, candle.OpenTime);
_prevTrailLow = Track(_trailLow, candle.LowPrice, candle.OpenTime);
if (!fastValue.IsFormed || !slowValue.IsFormed)
return;
var diff = fastValue.ToDecimal() - slowValue.ToDecimal();
var prevDiff = _prevDiff;
_prevDiff = diff;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
var longSignal = false;
var shortSignal = false;
if (TrendEnabled && prevDiff is decimal pd)
{
longSignal |= pd <= 0 && diff > 0;
shortSignal |= pd >= 0 && diff < 0;
}
if (BreakoutEnabled)
{
longSignal |= breakoutHigh is decimal bh && close > bh;
shortSignal |= breakoutLow is decimal bl && close < bl;
}
var stopDistance = atrValue.IsFormed && StopAtrMultiple > 0 ? atrValue.ToDecimal() * StopAtrMultiple : (decimal?)null;
if (longSignal && !shortSignal && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_stopPrice = close - stopDistance;
return;
}
if (shortSignal && !longSignal && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_stopPrice = close + stopDistance;
return;
}
if (Position > 0)
{
if ((_stopPrice is decimal stop && candle.LowPrice <= stop) || (trailLow is decimal tl && close < tl))
{
SellMarket(Position);
_stopPrice = null;
}
}
else if (Position < 0)
{
if ((_stopPrice is decimal stop && candle.HighPrice >= stop) || (trailHigh is decimal th && close > th))
{
BuyMarket(-Position);
_stopPrice = null;
}
}
}
private static decimal? Track(IIndicator indicator, decimal value, DateTime time)
{
var result = indicator.Process(value, time, true);
return result.IsFormed ? result.ToDecimal() : null;
}
}
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, AverageTrueRange, Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class crunchsters_turtle_and_trend_system_strategy(Strategy):
"""
Crunchster's Turtle and Trend System strategy.
The trend signal is the difference between a FastEmaPeriod EMA and an EMA five times slower: crossing above zero goes long and
crossing below zero goes short (TrendEnabled). The breakout signal goes long on a close above the highest high of the previous
BreakoutPeriod candles and short on a close below the lowest low (BreakoutEnabled). An opposite signal reverses the position.
A long closes below the lowest low of the previous TrailPeriod candles, a short above the highest high, and a stop
StopAtrMultiple ATRs from the entry limits the loss.
"""
_atr_length = 14
_slow_ema_factor = 5
def __init__(self):
super(crunchsters_turtle_and_trend_system_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._fast_ema_period = self.Param("FastEmaPeriod", 10).SetGreaterThanZero().SetDisplay("Fast EMA", "Fast EMA period; the slow EMA is five times longer", "Trend")
self._breakout_period = self.Param("BreakoutPeriod", 20).SetGreaterThanZero().SetDisplay("Breakout Period", "Donchian breakout period", "Breakout")
self._trail_period = self.Param("TrailPeriod", 1000).SetGreaterThanZero().SetDisplay("Trail Period", "Trailing Donchian exit period", "Risk")
self._stop_atr_multiple = self.Param("StopAtrMultiple", 20.0).SetNotNegative().SetDisplay("Stop ATR Multiple", "ATR multiple for the stop", "Risk")
self._order_percent = self.Param("OrderPercent", 10.0).SetGreaterThanZero().SetDisplay("Order %", "Percent of equity per order", "Risk")
self._trend_enabled = self.Param("TrendEnabled", True).SetDisplay("Trend Enabled", "Trade the EMA trend signal", "Trend")
self._breakout_enabled = self.Param("BreakoutEnabled", False).SetDisplay("Breakout Enabled", "Trade the Donchian breakout signal", "Breakout")
self._breakout_high = None
self._breakout_low = None
self._trail_high = None
self._trail_low = None
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._prev_diff = None
self._prev_breakout_high = None
self._prev_breakout_low = None
self._prev_trail_high = None
self._prev_trail_low = None
self._stop_price = None
def OnReseted(self):
super(crunchsters_turtle_and_trend_system_strategy, self).OnReseted()
self._reset_state()
self._breakout_high = None
self._breakout_low = None
self._trail_high = None
self._trail_low = None
def OnStarted2(self, time):
super(crunchsters_turtle_and_trend_system_strategy, self).OnStarted2(time)
self._reset_state()
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self._fast_ema_period.Value
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self._fast_ema_period.Value * self._slow_ema_factor
atr = AverageTrueRange()
atr.Length = self._atr_length
self._breakout_high = Highest()
self._breakout_high.Length = self._breakout_period.Value
self._breakout_low = Lowest()
self._breakout_low.Length = self._breakout_period.Value
self._trail_high = Highest()
self._trail_high.Length = self._trail_period.Value
self._trail_low = Lowest()
self._trail_low.Length = self._trail_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(fast_ema, slow_ema, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_ema)
self.DrawIndicator(area, slow_ema)
self.DrawOwnTrades(area)
@staticmethod
def _track(indicator, value, time):
result = process_value(indicator, value, time, True)
return result.GetValue[Decimal](None) if result.IsFormed else None
def _process_candle(self, candle, fast_value, slow_value, atr_value):
if candle.State != CandleStates.Finished:
return
# Channels are measured on the candles before this one.
breakout_high = self._prev_breakout_high
breakout_low = self._prev_breakout_low
trail_high = self._prev_trail_high
trail_low = self._prev_trail_low
self._prev_breakout_high = self._track(self._breakout_high, candle.HighPrice, candle.OpenTime)
self._prev_breakout_low = self._track(self._breakout_low, candle.LowPrice, candle.OpenTime)
self._prev_trail_high = self._track(self._trail_high, candle.HighPrice, candle.OpenTime)
self._prev_trail_low = self._track(self._trail_low, candle.LowPrice, candle.OpenTime)
if not fast_value.IsFormed or not slow_value.IsFormed:
return
diff = fast_value.GetValue[Decimal](None) - slow_value.GetValue[Decimal](None)
prev_diff = self._prev_diff
self._prev_diff = diff
if not self.IsFormedAndOnlineAndAllowTrading():
return
close = candle.ClosePrice
long_signal = False
short_signal = False
if self._trend_enabled.Value and prev_diff is not None:
long_signal = long_signal or (prev_diff <= 0 and diff > 0)
short_signal = short_signal or (prev_diff >= 0 and diff < 0)
if self._breakout_enabled.Value:
long_signal = long_signal or (breakout_high is not None and close > breakout_high)
short_signal = short_signal or (breakout_low is not None and close < breakout_low)
multiple = Decimal(self._stop_atr_multiple.Value)
stop_distance = atr_value.GetValue[Decimal](None) * multiple if atr_value.IsFormed and multiple > 0 else None
if long_signal and not short_signal and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._stop_price = close - stop_distance if stop_distance is not None else None
return
if short_signal and not long_signal and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._stop_price = close + stop_distance if stop_distance is not None else None
return
if self.Position > 0:
if (self._stop_price is not None and candle.LowPrice <= self._stop_price) or (trail_low is not None and close < trail_low):
self.SellMarket(self.Position)
self._stop_price = None
elif self.Position < 0:
if (self._stop_price is not None and candle.HighPrice >= self._stop_price) or (trail_high is not None and close > trail_high):
self.BuyMarket(-self.Position)
self._stop_price = None
def CreateClone(self):
return crunchsters_turtle_and_trend_system_strategy()