Chande Krollトレンド戦略
Chande KrollストップとトレンドフィルターとしてのSMAを組み合わせた戦略です。終値が下方ストップを上抜けしSMAを上回るとロングポジションを建てます。終値が上方ストップを下抜けするとポジションを決済します。ポジションサイズは1560バーにわたる最安値終値とリスク乗数に基づいて算出します。
詳細
- エントリー条件:
- ロング:
previous close <= previous low stop && Close > low stop && Close > SMA
- ロング:
- ロング/ショート: ロングのみ
- エグジット条件:
- ロング:
Close < high stop
- ロング:
- ストップ: Chande Krollストップ(Donchianの極値 ± ATR)
- デフォルト値:
CalcMode= CalcMode.ExponentialRiskMultiplier= 5mAtrPeriod= 10AtrMultiplier= 3mStopLength= 21SmaLength= 21CandleType= TimeSpan.FromHours(1).TimeFrame()
- フィルター:
- カテゴリ: トレンド
- 方向: ロングのみ
- インジケーター: ATR, Donchian, SMA, Lowest
- ストップ: はい
- 複雑さ: 初心者
- 時間軸: 中期
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
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>
/// Chande Kroll Trend strategy.
/// The high stop is the highest high of the last StopLength candles minus AtrMultiplier times ATR(AtrPeriod), the low stop the lowest
/// low plus the same distance. Long only: buys when the close crosses above the low stop while above SMA(SmaLength), and closes the
/// long when the close falls below the high stop. The order size is RiskMultiplier percent of the capital divided by the lowest close
/// of the last 1560 candles; in Exponential mode the capital is the current equity, in Linear mode the starting capital.
/// </summary>
public class ChandeKrollTrendStrategy : Strategy
{
/// <summary>
/// Position sizing mode.
/// </summary>
public enum CalcModes
{
/// <summary>
/// Size from the starting capital.
/// </summary>
Linear,
/// <summary>
/// Size from the current equity.
/// </summary>
Exponential,
}
private const int _sizingLookback = 1560;
private readonly StrategyParam<CalcModes> _calcMode;
private readonly StrategyParam<decimal> _riskMultiplier;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<int> _stopLength;
private readonly StrategyParam<int> _smaLength;
private readonly StrategyParam<DataType> _candleType;
private Lowest _lowestClose;
private decimal? _prevClose;
private decimal? _prevLowStop;
private decimal _initialCapital;
/// <summary>
/// Position sizing mode.
/// </summary>
public CalcModes CalcMode
{
get => _calcMode.Value;
set => _calcMode.Value = value;
}
/// <summary>
/// Percent of the capital committed to a position.
/// </summary>
public decimal RiskMultiplier
{
get => _riskMultiplier.Value;
set => _riskMultiplier.Value = value;
}
/// <summary>
/// ATR period.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// ATR multiplier of the stops.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Candles of the Donchian extremes.
/// </summary>
public int StopLength
{
get => _stopLength.Value;
set => _stopLength.Value = value;
}
/// <summary>
/// Period of the trend SMA.
/// </summary>
public int SmaLength
{
get => _smaLength.Value;
set => _smaLength.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public ChandeKrollTrendStrategy()
{
_calcMode = Param(nameof(CalcMode), CalcModes.Exponential)
.SetDisplay("Calc Mode", "Position sizing mode", "Sizing");
_riskMultiplier = Param(nameof(RiskMultiplier), 5m)
.SetGreaterThanZero()
.SetDisplay("Risk Multiplier", "Percent of the capital committed to a position", "Sizing");
_atrPeriod = Param(nameof(AtrPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR period", "Stops");
_atrMultiplier = Param(nameof(AtrMultiplier), 3m)
.SetGreaterThanZero()
.SetDisplay("ATR Multiplier", "ATR multiplier of the stops", "Stops");
_stopLength = Param(nameof(StopLength), 21)
.SetGreaterThanZero()
.SetDisplay("Stop Length", "Candles of the Donchian extremes", "Stops");
_smaLength = Param(nameof(SmaLength), 21)
.SetGreaterThanZero()
.SetDisplay("SMA Length", "Period of the trend SMA", "Trend");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(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();
_lowestClose = null;
_prevClose = null;
_prevLowStop = null;
_initialCapital = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevClose = null;
_prevLowStop = null;
_initialCapital = Portfolio?.BeginValue is decimal begin && begin > 0 ? begin : Portfolio?.CurrentValue ?? 0m;
_lowestClose = new Lowest { Length = _sizingLookback };
var atr = new AverageTrueRange { Length = AtrPeriod };
var highest = new Highest { Length = StopLength };
var lowest = new Lowest { Length = StopLength };
var sma = new SimpleMovingAverage { Length = SmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(atr, highest, lowest, sma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private decimal GetOrderVolume(decimal lowestClose)
{
var capital = CalcMode == CalcModes.Exponential ? _initialCapital + PnL : _initialCapital;
if (capital <= 0 || lowestClose <= 0)
return Volume;
var volume = capital * RiskMultiplier / 100m / lowestClose;
var step = Security?.VolumeStep ?? 1m;
if (step > 0)
volume = Math.Floor(volume / step) * step;
return volume > 0 ? volume : Volume;
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue atrValue, IIndicatorValue highestValue, IIndicatorValue lowestValue, IIndicatorValue smaValue)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
// The sizing low uses whatever history is available until the full lookback is reached.
var lowestClose = _lowestClose.Process(new DecimalIndicatorValue(_lowestClose, close, candle.OpenTime) { IsFinal = true }).GetValue<decimal>();
if (!atrValue.IsFormed || !highestValue.IsFormed || !lowestValue.IsFormed)
return;
var distance = AtrMultiplier * atrValue.GetValue<decimal>();
var highStop = highestValue.GetValue<decimal>() - distance;
var lowStop = lowestValue.GetValue<decimal>() + distance;
var prevClose = _prevClose;
var prevLowStop = _prevLowStop;
_prevClose = close;
_prevLowStop = lowStop;
if (!smaValue.IsFormed || prevClose is not decimal pc || prevLowStop is not decimal pl)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (Position > 0)
{
if (close < highStop)
SellMarket(Position);
return;
}
if (Position == 0 && pc <= pl && close > lowStop && close > smaValue.GetValue<decimal>())
BuyMarket(GetOrderVolume(lowestClose));
}
}
import clr
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, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import AverageTrueRange, Highest, Lowest, SimpleMovingAverage, DecimalIndicatorValue
from StockSharp.Algo.Strategies import Strategy
CALC_LINEAR = 0
CALC_EXPONENTIAL = 1
SIZING_LOOKBACK = 1560
class chande_kroll_trend_strategy(Strategy):
"""
Chande Kroll Trend strategy.
The high stop is the highest high of the last StopLength candles minus AtrMultiplier times ATR(AtrPeriod), the low stop the lowest
low plus the same distance. Long only: buys when the close crosses above the low stop while above SMA(SmaLength), and closes the
long when the close falls below the high stop. The order size is RiskMultiplier percent of the capital divided by the lowest close
of the last 1560 candles; in Exponential mode the capital is the current equity, in Linear mode the starting capital.
"""
def __init__(self):
super(chande_kroll_trend_strategy, self).__init__()
self._calc_mode = self.Param("CalcMode", CALC_EXPONENTIAL).SetDisplay("Calc Mode", "Position sizing mode (0 = Linear, 1 = Exponential)", "Sizing")
self._risk_multiplier = self.Param("RiskMultiplier", 5.0).SetGreaterThanZero().SetDisplay("Risk Multiplier", "Percent of the capital committed to a position", "Sizing")
self._atr_period = self.Param("AtrPeriod", 10).SetGreaterThanZero().SetDisplay("ATR Period", "ATR period", "Stops")
self._atr_multiplier = self.Param("AtrMultiplier", 3.0).SetGreaterThanZero().SetDisplay("ATR Multiplier", "ATR multiplier of the stops", "Stops")
self._stop_length = self.Param("StopLength", 21).SetGreaterThanZero().SetDisplay("Stop Length", "Candles of the Donchian extremes", "Stops")
self._sma_length = self.Param("SmaLength", 21).SetGreaterThanZero().SetDisplay("SMA Length", "Period of the trend SMA", "Trend")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._lowest_close = None
self._prev_close = None
self._prev_low_stop = None
self._initial_capital = Decimal(0)
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(chande_kroll_trend_strategy, self).OnReseted()
self._lowest_close = None
self._prev_close = None
self._prev_low_stop = None
self._initial_capital = Decimal(0)
def OnStarted2(self, time):
super(chande_kroll_trend_strategy, self).OnStarted2(time)
self._prev_close = None
self._prev_low_stop = None
self._initial_capital = Decimal(0)
portfolio = self.Portfolio
if portfolio is not None:
begin = portfolio.BeginValue
if begin is not None and begin > 0:
self._initial_capital = begin
elif portfolio.CurrentValue is not None:
self._initial_capital = portfolio.CurrentValue
self._lowest_close = Lowest()
self._lowest_close.Length = SIZING_LOOKBACK
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
highest = Highest()
highest.Length = self._stop_length.Value
lowest = Lowest()
lowest.Length = self._stop_length.Value
sma = SimpleMovingAverage()
sma.Length = self._sma_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(atr, highest, lowest, sma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _get_order_volume(self, lowest_close):
if self._calc_mode.Value == CALC_EXPONENTIAL:
capital = self._initial_capital + self.PnL
else:
capital = self._initial_capital
if capital <= 0 or lowest_close <= 0:
return self.Volume
volume = capital * Decimal(self._risk_multiplier.Value) / Decimal(100) / lowest_close
step = self.Security.VolumeStep if self.Security is not None and self.Security.VolumeStep is not None else Decimal(1)
if step > 0:
volume = Math.Floor(volume / step) * step
return volume if volume > 0 else self.Volume
def _process_candle(self, candle, atr_value, highest_value, lowest_value, sma_value):
if candle.State != CandleStates.Finished:
return
close = candle.ClosePrice
# The sizing low uses whatever history is available until the full lookback is reached.
lowest_input = DecimalIndicatorValue(self._lowest_close, close, candle.OpenTime)
lowest_input.IsFinal = True
lowest_close = self._lowest_close.Process(lowest_input).GetValue[Decimal](None)
if not atr_value.IsFormed or not highest_value.IsFormed or not lowest_value.IsFormed:
return
distance = Decimal(self._atr_multiplier.Value) * atr_value.GetValue[Decimal](None)
high_stop = highest_value.GetValue[Decimal](None) - distance
low_stop = lowest_value.GetValue[Decimal](None) + distance
pc = self._prev_close
pl = self._prev_low_stop
self._prev_close = close
self._prev_low_stop = low_stop
if not sma_value.IsFormed or pc is None or pl is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self.Position > 0:
if close < high_stop:
self.SellMarket(self.Position)
return
if self.Position == 0 and pc <= pl and close > low_stop and close > sma_value.GetValue[Decimal](None):
self.BuyMarket(self._get_order_volume(lowest_close))
def CreateClone(self):
return chande_kroll_trend_strategy()