コントラリアンDC戦略
コントラリアンDC戦略は、ドンチャンチャンネルのブレイクアウトに逆張りします。価格が下限バンドを割り込んだときに買い、価格が上限バンドに触れたときに売ります。ストップロス発動後は、同方向へのエントリーが指定した本数のローソク足の間、一時停止されます。リスク管理には、リスク/リワード比率に基づく対称的なストップロスとテイクプロフィットを使用します。
詳細
- エントリー条件:
- ロング: 価格の安値 <= Donchian低値 && 一時停止が満足されている
- ショート: 価格の高値 >= Donchian高値 && 一時停止が満足されている
- ロング/ショート: 両方。
- エグジット条件:
- ストップ: パーセンテージ・ストップロス
- ターゲット: リスク/リワードに基づくテイクプロフィット
- バンド: 反対側のバンドに達したときにクローズ
- ストップ: あり、パーセンテージベース
- デフォルト値:
DonchianPeriod= 20RiskRewardRatio= 1.7mStopLossPercent= 0.3mPauseCandles= 3CandleType= TimeSpan.FromMinutes(15)
- フィルター:
- カテゴリ: 平均回帰
- 方向: 両方
- インジケーター: Donchian Channel
- ストップ: はい
- 複雑さ: 基本
- 時間軸: イントラデイ
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
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>
/// Contrarian Donchian Channel strategy.
/// The channel spans the previous DonchianPeriod candles. A low at or below the lower band buys and a high at or above the upper
/// band sells short. Each trade has a StopLossPercent stop and a target RiskRewardRatio times farther away, and is also closed when
/// price reaches the opposite band. After a stop-loss, entries in the same direction pause for PauseCandles candles.
/// </summary>
public class ContrarianDcStrategy : Strategy
{
private readonly StrategyParam<int> _donchianPeriod;
private readonly StrategyParam<decimal> _riskRewardRatio;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<int> _pauseCandles;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevUpper;
private decimal? _prevLower;
private decimal? _stopPrice;
private decimal? _takePrice;
private int _longPause;
private int _shortPause;
/// <summary>
/// Previous candles the channel spans.
/// </summary>
public int DonchianPeriod
{
get => _donchianPeriod.Value;
set => _donchianPeriod.Value = value;
}
/// <summary>
/// Target distance as a multiple of the stop distance.
/// </summary>
public decimal RiskRewardRatio
{
get => _riskRewardRatio.Value;
set => _riskRewardRatio.Value = value;
}
/// <summary>
/// Stop loss percentage from entry price.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candles to skip same-direction entries after a stop-loss.
/// </summary>
public int PauseCandles
{
get => _pauseCandles.Value;
set => _pauseCandles.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public ContrarianDcStrategy()
{
_donchianPeriod = Param(nameof(DonchianPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Donchian Period", "Previous candles the channel spans", "Indicators");
_riskRewardRatio = Param(nameof(RiskRewardRatio), 1.7m)
.SetGreaterThanZero()
.SetDisplay("Risk/Reward", "Target distance as a multiple of the stop distance", "Risk");
_stopLossPercent = Param(nameof(StopLossPercent), 0.3m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_pauseCandles = Param(nameof(PauseCandles), 3)
.SetNotNegative()
.SetDisplay("Pause Candles", "Candles to skip same-direction entries after a stop-loss", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).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()
{
_prevUpper = null;
_prevLower = null;
_stopPrice = null;
_takePrice = null;
_longPause = 0;
_shortPause = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var donchian = new DonchianChannels { Length = DonchianPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(donchian, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, donchian);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue donchianValue)
{
if (candle.State != CandleStates.Finished)
return;
// The channel is measured on the candles before this one.
var upper = _prevUpper;
var lower = _prevLower;
if (donchianValue.IsFormed && donchianValue is IDonchianChannelsValue { UpperBand: decimal currentUpper, LowerBand: decimal currentLower })
{
_prevUpper = currentUpper;
_prevLower = currentLower;
}
if (_longPause > 0)
_longPause--;
if (_shortPause > 0)
_shortPause--;
if (upper is not decimal channelHigh || lower is not decimal channelLow)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (Position > 0)
{
if (_stopPrice is decimal stop && candle.LowPrice <= stop)
{
SellMarket(Position);
_longPause = PauseCandles;
ClearLevels();
}
else if ((_takePrice is decimal take && candle.HighPrice >= take) || candle.HighPrice >= channelHigh)
{
SellMarket(Position);
ClearLevels();
}
return;
}
if (Position < 0)
{
if (_stopPrice is decimal stop && candle.HighPrice >= stop)
{
BuyMarket(-Position);
_shortPause = PauseCandles;
ClearLevels();
}
else if ((_takePrice is decimal take && candle.LowPrice <= take) || candle.LowPrice <= channelLow)
{
BuyMarket(-Position);
ClearLevels();
}
return;
}
var close = candle.ClosePrice;
var stopDistance = close * StopLossPercent / 100m;
if (candle.LowPrice <= channelLow && _longPause == 0)
{
BuyMarket(Volume);
_stopPrice = close - stopDistance;
_takePrice = close + stopDistance * RiskRewardRatio;
}
else if (candle.HighPrice >= channelHigh && _shortPause == 0)
{
SellMarket(Volume);
_stopPrice = close + stopDistance;
_takePrice = close - stopDistance * RiskRewardRatio;
}
}
private void ClearLevels()
{
_stopPrice = null;
_takePrice = 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 DonchianChannels
from StockSharp.Algo.Strategies import Strategy
class contrarian_dc_strategy(Strategy):
"""
Contrarian Donchian Channel strategy.
The channel spans the previous DonchianPeriod candles. A low at or below the lower band buys and a high at or above the upper
band sells short. Each trade has a StopLossPercent stop and a target RiskRewardRatio times farther away, and is also closed when
price reaches the opposite band. After a stop-loss, entries in the same direction pause for PauseCandles candles.
"""
def __init__(self):
super(contrarian_dc_strategy, self).__init__()
self._donchian_period = self.Param("DonchianPeriod", 20).SetGreaterThanZero().SetDisplay("Donchian Period", "Previous candles the channel spans", "Indicators")
self._risk_reward_ratio = self.Param("RiskRewardRatio", 1.7).SetGreaterThanZero().SetDisplay("Risk/Reward", "Target distance as a multiple of the stop distance", "Risk")
self._stop_loss_percent = self.Param("StopLossPercent", 0.3).SetGreaterThanZero().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._pause_candles = self.Param("PauseCandles", 3).SetNotNegative().SetDisplay("Pause Candles", "Candles to skip same-direction entries after a stop-loss", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))).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._prev_upper = None
self._prev_lower = None
self._stop_price = None
self._take_price = None
self._long_pause = 0
self._short_pause = 0
def OnReseted(self):
super(contrarian_dc_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(contrarian_dc_strategy, self).OnStarted2(time)
self._reset_state()
donchian = DonchianChannels()
donchian.Length = self._donchian_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(donchian, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, donchian)
self.DrawOwnTrades(area)
def _process_candle(self, candle, donchian_value):
if candle.State != CandleStates.Finished:
return
# The channel is measured on the candles before this one.
upper = self._prev_upper
lower = self._prev_lower
if donchian_value.IsFormed and donchian_value.UpperBand is not None and donchian_value.LowerBand is not None:
self._prev_upper = donchian_value.UpperBand
self._prev_lower = donchian_value.LowerBand
if self._long_pause > 0:
self._long_pause -= 1
if self._short_pause > 0:
self._short_pause -= 1
if upper is None or lower is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self.Position > 0:
if self._stop_price is not None and candle.LowPrice <= self._stop_price:
self.SellMarket(self.Position)
self._long_pause = self._pause_candles.Value
self._clear_levels()
elif (self._take_price is not None and candle.HighPrice >= self._take_price) or candle.HighPrice >= upper:
self.SellMarket(self.Position)
self._clear_levels()
return
if self.Position < 0:
if self._stop_price is not None and candle.HighPrice >= self._stop_price:
self.BuyMarket(-self.Position)
self._short_pause = self._pause_candles.Value
self._clear_levels()
elif (self._take_price is not None and candle.LowPrice <= self._take_price) or candle.LowPrice <= lower:
self.BuyMarket(-self.Position)
self._clear_levels()
return
close = candle.ClosePrice
stop_distance = close * Decimal(self._stop_loss_percent.Value) / Decimal(100)
ratio = Decimal(self._risk_reward_ratio.Value)
if candle.LowPrice <= lower and self._long_pause == 0:
self.BuyMarket(self.Volume)
self._stop_price = close - stop_distance
self._take_price = close + stop_distance * ratio
elif candle.HighPrice >= upper and self._short_pause == 0:
self.SellMarket(self.Volume)
self._stop_price = close + stop_distance
self._take_price = close - stop_distance * ratio
def _clear_levels(self):
self._stop_price = None
self._take_price = None
def CreateClone(self):
return contrarian_dc_strategy()