トレンド確認と独立ストップロス付きCBC戦略
この戦略はカラーバーチェンジ(CBC)の状態を使用して、価格が前のローソク足の高値または安値を突破した際の転換点を検出します。エントリーはEMAとVWAPによるトレンド確認を必要とし、取引セッション時間帯に限定されます。エグジットはATRベースの利益目標を適用し、前のローソク足の極値をストップロスレベルとして使用します。
詳細
- エントリー条件: CBCの転換、オプションの強い転換フィルター、VWAPに対する低速EMA、取引時間内。
- ロング/ショート: 両方。
- エグジット条件: ATR乗数によるテイクプロフィット、前のローソク足の高値/安値のストップロス。
- ストップ: はい。
- デフォルト値:
AtrLength= 14ProfitTargetMultiplier= 1.0mStrongFlipsOnly= trueEntryStartHour= 10EntryEndHour= 15CandleType= TimeSpan.FromMinutes(5)
- フィルター:
- カテゴリ: トレンド
- 方向: 両方
- インジケーター: EMA, VWAP, 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>
/// CBC with Trend Confirmation and Separate Stop Loss strategy.
/// The color bar change (CBC) state turns bullish when the close breaks above the previous candle's high and bearish when it breaks
/// below the previous candle's low. A flip to bullish goes long when the slow EMA is above the daily VWAP, a flip to bearish goes short
/// when it is below, reversing an opposite position; with StrongFlipsOnly the flip candle also has to close in its direction. Entries
/// are taken only between EntryStartHour and EntryEndHour (UTC). Each entry freezes a target ProfitTargetMultiplier ATRs away and a
/// stop at the previous candle's low (long) or high (short).
/// </summary>
public class CbcWithTrendConfirmationAndSeparateStopLossStrategy : Strategy
{
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _profitTargetMultiplier;
private readonly StrategyParam<bool> _strongFlipsOnly;
private readonly StrategyParam<int> _entryStartHour;
private readonly StrategyParam<int> _entryEndHour;
private readonly StrategyParam<int> _slowEmaLength;
private readonly StrategyParam<DataType> _candleType;
private ICandleMessage _prevCandle;
private bool? _cbcBullish;
private DateTime _vwapDay;
private decimal _vwapPriceVolume;
private decimal _vwapVolume;
private decimal? _stopPrice;
private decimal? _targetPrice;
/// <summary>
/// ATR period.
/// </summary>
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <summary>
/// ATR multiple of the profit target.
/// </summary>
public decimal ProfitTargetMultiplier
{
get => _profitTargetMultiplier.Value;
set => _profitTargetMultiplier.Value = value;
}
/// <summary>
/// Trade only flips whose candle closes in the flip direction.
/// </summary>
public bool StrongFlipsOnly
{
get => _strongFlipsOnly.Value;
set => _strongFlipsOnly.Value = value;
}
/// <summary>
/// First entry hour (UTC).
/// </summary>
public int EntryStartHour
{
get => _entryStartHour.Value;
set => _entryStartHour.Value = value;
}
/// <summary>
/// Hour entries stop (UTC, exclusive).
/// </summary>
public int EntryEndHour
{
get => _entryEndHour.Value;
set => _entryEndHour.Value = value;
}
/// <summary>
/// Period of the slow trend EMA.
/// </summary>
public int SlowEmaLength
{
get => _slowEmaLength.Value;
set => _slowEmaLength.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public CbcWithTrendConfirmationAndSeparateStopLossStrategy()
{
_atrLength = Param(nameof(AtrLength), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR period", "Risk");
_profitTargetMultiplier = Param(nameof(ProfitTargetMultiplier), 1.0m)
.SetGreaterThanZero()
.SetDisplay("Profit Target Multiplier", "ATR multiple of the profit target", "Risk");
_strongFlipsOnly = Param(nameof(StrongFlipsOnly), true)
.SetDisplay("Strong Flips Only", "Trade only flips whose candle closes in the flip direction", "Signals");
_entryStartHour = Param(nameof(EntryStartHour), 10)
.SetRange(0, 23)
.SetDisplay("Entry Start Hour", "First entry hour (UTC)", "Session");
_entryEndHour = Param(nameof(EntryEndHour), 15)
.SetRange(1, 24)
.SetDisplay("Entry End Hour", "Hour entries stop (UTC, exclusive)", "Session");
_slowEmaLength = Param(nameof(SlowEmaLength), 21)
.SetGreaterThanZero()
.SetDisplay("Slow EMA Length", "Period of the slow trend EMA", "Trend");
_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()
{
_prevCandle = null;
_cbcBullish = null;
_vwapDay = default;
_vwapPriceVolume = 0m;
_vwapVolume = 0m;
_stopPrice = null;
_targetPrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var ema = new ExponentialMovingAverage { Length = SlowEmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(ema, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue emaValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
// Session VWAP restarts every UTC day.
var day = candle.OpenTime.Date;
if (day != _vwapDay)
{
_vwapDay = day;
_vwapPriceVolume = 0m;
_vwapVolume = 0m;
}
var typical = (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m;
_vwapPriceVolume += typical * candle.TotalVolume;
_vwapVolume += candle.TotalVolume;
var prev = _prevCandle;
_prevCandle = candle;
if (prev == null)
return;
var previousState = _cbcBullish;
if (candle.ClosePrice > prev.HighPrice)
_cbcBullish = true;
else if (candle.ClosePrice < prev.LowPrice)
_cbcBullish = false;
if (!emaValue.IsFormed || !atrValue.IsFormed || _vwapVolume <= 0)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Stop and target frozen at entry.
if (Position > 0 && _stopPrice is decimal longStop && _targetPrice is decimal longTarget && (candle.LowPrice <= longStop || candle.HighPrice >= longTarget))
{
SellMarket(Position);
_stopPrice = null;
_targetPrice = null;
return;
}
if (Position < 0 && _stopPrice is decimal shortStop && _targetPrice is decimal shortTarget && (candle.HighPrice >= shortStop || candle.LowPrice <= shortTarget))
{
BuyMarket(-Position);
_stopPrice = null;
_targetPrice = null;
return;
}
if (previousState is not bool wasBullish || _cbcBullish is not bool isBullish || wasBullish == isBullish)
return;
var hour = candle.OpenTime.Hour;
if (hour < EntryStartHour || hour >= EntryEndHour)
return;
var ema = emaValue.GetValue<decimal>();
var vwap = _vwapPriceVolume / _vwapVolume;
var atr = atrValue.GetValue<decimal>();
var close = candle.ClosePrice;
if (isBullish && (!StrongFlipsOnly || close > candle.OpenPrice) && ema > vwap && Position <= 0 && prev.LowPrice < close)
{
BuyMarket(Volume + Math.Abs(Position));
_stopPrice = prev.LowPrice;
_targetPrice = close + atr * ProfitTargetMultiplier;
}
else if (!isBullish && (!StrongFlipsOnly || close < candle.OpenPrice) && ema < vwap && Position >= 0 && prev.HighPrice > close)
{
SellMarket(Volume + Math.Abs(Position));
_stopPrice = prev.HighPrice;
_targetPrice = close - atr * ProfitTargetMultiplier;
}
}
}
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
from StockSharp.Algo.Strategies import Strategy
class cbc_with_trend_confirmation_and_separate_stop_loss_strategy(Strategy):
"""
CBC with Trend Confirmation and Separate Stop Loss strategy.
The color bar change (CBC) state turns bullish when the close breaks above the previous candle's high and bearish when it breaks
below the previous candle's low. A flip to bullish goes long when the slow EMA is above the daily VWAP, a flip to bearish goes short
when it is below, reversing an opposite position; with StrongFlipsOnly the flip candle also has to close in its direction. Entries
are taken only between EntryStartHour and EntryEndHour (UTC). Each entry freezes a target ProfitTargetMultiplier ATRs away and a
stop at the previous candle's low (long) or high (short).
"""
def __init__(self):
super(cbc_with_trend_confirmation_and_separate_stop_loss_strategy, self).__init__()
self._atr_length = self.Param("AtrLength", 14).SetGreaterThanZero().SetDisplay("ATR Length", "ATR period", "Risk")
self._profit_target_multiplier = self.Param("ProfitTargetMultiplier", 1.0).SetGreaterThanZero().SetDisplay("Profit Target Multiplier", "ATR multiple of the profit target", "Risk")
self._strong_flips_only = self.Param("StrongFlipsOnly", True).SetDisplay("Strong Flips Only", "Trade only flips whose candle closes in the flip direction", "Signals")
self._entry_start_hour = self.Param("EntryStartHour", 10).SetRange(0, 23).SetDisplay("Entry Start Hour", "First entry hour (UTC)", "Session")
self._entry_end_hour = self.Param("EntryEndHour", 15).SetRange(1, 24).SetDisplay("Entry End Hour", "Hour entries stop (UTC, exclusive)", "Session")
self._slow_ema_length = self.Param("SlowEmaLength", 21).SetGreaterThanZero().SetDisplay("Slow EMA Length", "Period of the slow trend EMA", "Trend")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).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_candle = None
self._cbc_bullish = None
self._vwap_day = None
self._vwap_price_volume = Decimal(0)
self._vwap_volume = Decimal(0)
self._stop_price = None
self._target_price = None
def OnReseted(self):
super(cbc_with_trend_confirmation_and_separate_stop_loss_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(cbc_with_trend_confirmation_and_separate_stop_loss_strategy, self).OnStarted2(time)
self._reset_state()
ema = ExponentialMovingAverage()
ema.Length = self._slow_ema_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(ema, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ema_value, atr_value):
if candle.State != CandleStates.Finished:
return
# Session VWAP restarts every UTC day.
day = candle.OpenTime.Date
if day != self._vwap_day:
self._vwap_day = day
self._vwap_price_volume = Decimal(0)
self._vwap_volume = Decimal(0)
typical = (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / Decimal(3)
self._vwap_price_volume += typical * candle.TotalVolume
self._vwap_volume += candle.TotalVolume
prev = self._prev_candle
self._prev_candle = candle
if prev is None:
return
previous_state = self._cbc_bullish
if candle.ClosePrice > prev.HighPrice:
self._cbc_bullish = True
elif candle.ClosePrice < prev.LowPrice:
self._cbc_bullish = False
if not ema_value.IsFormed or not atr_value.IsFormed or self._vwap_volume <= 0:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
# Stop and target frozen at entry.
if self.Position > 0 and self._stop_price is not None and self._target_price is not None and (candle.LowPrice <= self._stop_price or candle.HighPrice >= self._target_price):
self.SellMarket(self.Position)
self._stop_price = None
self._target_price = None
return
if self.Position < 0 and self._stop_price is not None and self._target_price is not None and (candle.HighPrice >= self._stop_price or candle.LowPrice <= self._target_price):
self.BuyMarket(-self.Position)
self._stop_price = None
self._target_price = None
return
is_bullish = self._cbc_bullish
if previous_state is None or is_bullish is None or previous_state == is_bullish:
return
hour = candle.OpenTime.Hour
if hour < self._entry_start_hour.Value or hour >= self._entry_end_hour.Value:
return
ema = ema_value.GetValue[Decimal](None)
vwap = self._vwap_price_volume / self._vwap_volume
atr = atr_value.GetValue[Decimal](None)
close = candle.ClosePrice
strong_only = self._strong_flips_only.Value
multiplier = Decimal(self._profit_target_multiplier.Value)
if is_bullish and (not strong_only or close > candle.OpenPrice) and ema > vwap and self.Position <= 0 and prev.LowPrice < close:
self.BuyMarket(self.Volume + abs(self.Position))
self._stop_price = prev.LowPrice
self._target_price = close + atr * multiplier
elif not is_bullish and (not strong_only or close < candle.OpenPrice) and ema < vwap and self.Position >= 0 and prev.HighPrice > close:
self.SellMarket(self.Volume + abs(self.Position))
self._stop_price = prev.HighPrice
self._target_price = close - atr * multiplier
def CreateClone(self):
return cbc_with_trend_confirmation_and_separate_stop_loss_strategy()