Gold ブレイクアウト RR4戦略
Gold Breakout RR4は、ボリュームとLWTIトレンドフィルターを使用して金のDonchianチャンネルブレイクアウトを取引します。指定されたセッション内で1日1トレードのみ行い、固定の4:1リスク/リワードを使用します。
詳細
- エントリー条件: セッション内でDonchianチャンネルを平均以上のボリュームとLWTI確認でブレイクアウト
- ロング/ショート: 両方
- エグジット条件: リスク/リワードに基づく固定ストップと目標
- ストップ: はい
- デフォルト値:
DonchianLength= 96MaVolumeLength= 30LwtiLength= 25LwtiSmooth= 5StartHour= 20EndHour= 8RiskReward= 4
- フィルター:
- カテゴリ: ブレイクアウト
- 方向: 両方
- インジケーター: Donchian Channel, SMA, WMA
- ストップ: はい
- 複雑さ: 中級
- 時間軸: イントラデイ
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
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>
/// Gold breakout RR4 strategy.
/// The Donchian channel spans the previous DonchianLength candles. A close above its upper band with volume above the MaVolumeLength
/// volume SMA and the Larry Williams Large Trade Index above 50 goes long; a close below the lower band with high volume and LWTI below
/// 50 goes short. LWTI is the LwtiLength WMA of the close change over LwtiLength candles divided by the ATR, scaled around 50 and smoothed
/// with an SMA of LwtiSmooth. Entries are allowed only from StartHour to EndHour (UTC, wrapping over midnight) and once per day. The stop
/// sits at the channel middle and the target at RiskReward times that risk.
/// </summary>
public class GoldBreakoutRr4Strategy : Strategy
{
private readonly StrategyParam<int> _donchianLength;
private readonly StrategyParam<int> _maVolumeLength;
private readonly StrategyParam<int> _lwtiLength;
private readonly StrategyParam<int> _lwtiSmooth;
private readonly StrategyParam<int> _startHour;
private readonly StrategyParam<int> _endHour;
private readonly StrategyParam<decimal> _riskReward;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _volumeMa;
private WeightedMovingAverage _changeWma;
private SimpleMovingAverage _lwtiSma;
private readonly List<decimal> _closes = [];
private decimal? _prevUpper;
private decimal? _prevLower;
private DateTime? _lastTradeDay;
private decimal _stopPrice;
private decimal _takePrice;
/// <summary>
/// Donchian channel length.
/// </summary>
public int DonchianLength
{
get => _donchianLength.Value;
set => _donchianLength.Value = value;
}
/// <summary>
/// Length of the volume average.
/// </summary>
public int MaVolumeLength
{
get => _maVolumeLength.Value;
set => _maVolumeLength.Value = value;
}
/// <summary>
/// LWTI length.
/// </summary>
public int LwtiLength
{
get => _lwtiLength.Value;
set => _lwtiLength.Value = value;
}
/// <summary>
/// LWTI smoothing length.
/// </summary>
public int LwtiSmooth
{
get => _lwtiSmooth.Value;
set => _lwtiSmooth.Value = value;
}
/// <summary>
/// Session start hour (UTC).
/// </summary>
public int StartHour
{
get => _startHour.Value;
set => _startHour.Value = value;
}
/// <summary>
/// Session end hour (UTC).
/// </summary>
public int EndHour
{
get => _endHour.Value;
set => _endHour.Value = value;
}
/// <summary>
/// Target distance as a multiple of the stop distance.
/// </summary>
public decimal RiskReward
{
get => _riskReward.Value;
set => _riskReward.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public GoldBreakoutRr4Strategy()
{
_donchianLength = Param(nameof(DonchianLength), 96)
.SetGreaterThanZero()
.SetDisplay("Donchian Length", "Donchian channel length", "Indicators");
_maVolumeLength = Param(nameof(MaVolumeLength), 30)
.SetGreaterThanZero()
.SetDisplay("Volume MA Length", "Length of the volume average", "Indicators");
_lwtiLength = Param(nameof(LwtiLength), 25)
.SetGreaterThanZero()
.SetDisplay("LWTI Length", "LWTI length", "Indicators");
_lwtiSmooth = Param(nameof(LwtiSmooth), 5)
.SetGreaterThanZero()
.SetDisplay("LWTI Smooth", "LWTI smoothing length", "Indicators");
_startHour = Param(nameof(StartHour), 20)
.SetRange(0, 23)
.SetDisplay("Start Hour", "Session start hour (UTC)", "Session");
_endHour = Param(nameof(EndHour), 8)
.SetRange(0, 23)
.SetDisplay("End Hour", "Session end hour (UTC)", "Session");
_riskReward = Param(nameof(RiskReward), 4m)
.SetGreaterThanZero()
.SetDisplay("Risk Reward", "Target distance as a multiple of the stop distance", "Risk");
_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()
{
_closes.Clear();
_prevUpper = null;
_prevLower = null;
_lastTradeDay = null;
_stopPrice = 0m;
_takePrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var donchian = new DonchianChannels { Length = DonchianLength };
var atr = new AverageTrueRange { Length = LwtiLength };
_volumeMa = new SimpleMovingAverage { Length = MaVolumeLength };
_changeWma = new WeightedMovingAverage { Length = LwtiLength };
_lwtiSma = new SimpleMovingAverage { Length = LwtiSmooth };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(donchian, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, donchian);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue donchianValue, IIndicatorValue atrValue)
{
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 u, LowerBand: decimal l })
{
_prevUpper = u;
_prevLower = l;
}
var volumeAvg = _volumeMa.Process(new DecimalIndicatorValue(_volumeMa, candle.TotalVolume, candle.OpenTime) { IsFinal = true });
var lwti = ProcessLwti(candle, atrValue);
if (ManagePosition(candle))
return;
if (upper is not decimal channelHigh || lower is not decimal channelLow || lwti is not decimal lwtiValue || !_volumeMa.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (Position != 0 || !InSession(candle.OpenTime.Hour) || _lastTradeDay == candle.OpenTime.Date)
return;
var close = candle.ClosePrice;
var highVolume = candle.TotalVolume > volumeAvg.GetValue<decimal>();
var middle = (channelHigh + channelLow) / 2;
if (close > channelHigh && highVolume && lwtiValue > 50m && close > middle)
{
BuyMarket(Volume);
_stopPrice = middle;
_takePrice = close + (close - middle) * RiskReward;
_lastTradeDay = candle.OpenTime.Date;
}
else if (close < channelLow && highVolume && lwtiValue < 50m && close < middle)
{
SellMarket(Volume);
_stopPrice = middle;
_takePrice = close - (middle - close) * RiskReward;
_lastTradeDay = candle.OpenTime.Date;
}
}
private decimal? ProcessLwti(ICandleMessage candle, IIndicatorValue atrValue)
{
_closes.Add(candle.ClosePrice);
if (_closes.Count > LwtiLength + 1)
_closes.RemoveAt(0);
if (_closes.Count <= LwtiLength)
return null;
var change = candle.ClosePrice - _closes[0];
var wma = _changeWma.Process(new DecimalIndicatorValue(_changeWma, change, candle.OpenTime) { IsFinal = true });
if (!_changeWma.IsFormed || !atrValue.IsFormed)
return null;
var atr = atrValue.GetValue<decimal>();
if (atr <= 0)
return null;
var raw = wma.GetValue<decimal>() / atr * 50m + 50m;
var smooth = _lwtiSma.Process(new DecimalIndicatorValue(_lwtiSma, raw, candle.OpenTime) { IsFinal = true });
return _lwtiSma.IsFormed ? smooth.GetValue<decimal>() : null;
}
private bool InSession(int hour)
{
return StartHour <= EndHour
? hour >= StartHour && hour < EndHour
: hour >= StartHour || hour < EndHour;
}
// Returns true when the position was closed on this candle.
private bool ManagePosition(ICandleMessage candle)
{
if (Position > 0 && _stopPrice > 0)
{
if (candle.LowPrice <= _stopPrice || candle.HighPrice >= _takePrice)
{
SellMarket(Position);
return true;
}
}
else if (Position < 0 && _stopPrice > 0)
{
if (candle.HighPrice >= _stopPrice || candle.LowPrice <= _takePrice)
{
BuyMarket(Math.Abs(Position));
return true;
}
}
return false;
}
}
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
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import DonchianChannels, AverageTrueRange, SimpleMovingAverage, WeightedMovingAverage, DecimalIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class gold_breakout_rr4_strategy(Strategy):
"""
Gold breakout RR4 strategy.
The Donchian channel spans the previous DonchianLength candles. A close above its upper band with volume above the MaVolumeLength
volume SMA and the Larry Williams Large Trade Index above 50 goes long; a close below the lower band with high volume and LWTI below
50 goes short. LWTI is the LwtiLength WMA of the close change over LwtiLength candles divided by the ATR, scaled around 50 and smoothed
with an SMA of LwtiSmooth. Entries are allowed only from StartHour to EndHour (UTC, wrapping over midnight) and once per day. The stop
sits at the channel middle and the target at RiskReward times that risk.
"""
def __init__(self):
super(gold_breakout_rr4_strategy, self).__init__()
self._donchian_length = self.Param("DonchianLength", 96).SetGreaterThanZero().SetDisplay("Donchian Length", "Donchian channel length", "Indicators")
self._ma_volume_length = self.Param("MaVolumeLength", 30).SetGreaterThanZero().SetDisplay("Volume MA Length", "Length of the volume average", "Indicators")
self._lwti_length = self.Param("LwtiLength", 25).SetGreaterThanZero().SetDisplay("LWTI Length", "LWTI length", "Indicators")
self._lwti_smooth = self.Param("LwtiSmooth", 5).SetGreaterThanZero().SetDisplay("LWTI Smooth", "LWTI smoothing length", "Indicators")
self._start_hour = self.Param("StartHour", 20).SetRange(0, 23).SetDisplay("Start Hour", "Session start hour (UTC)", "Session")
self._end_hour = self.Param("EndHour", 8).SetRange(0, 23).SetDisplay("End Hour", "Session end hour (UTC)", "Session")
self._risk_reward = self.Param("RiskReward", 4.0).SetGreaterThanZero().SetDisplay("Risk Reward", "Target distance as a multiple of the stop distance", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._volume_ma = None
self._change_wma = None
self._lwti_sma = None
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._closes = []
self._prev_upper = None
self._prev_lower = None
self._last_trade_day = None
self._stop_price = Decimal(0)
self._take_price = Decimal(0)
def OnReseted(self):
super(gold_breakout_rr4_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(gold_breakout_rr4_strategy, self).OnStarted2(time)
self._reset_state()
donchian = DonchianChannels()
donchian.Length = self._donchian_length.Value
atr = AverageTrueRange()
atr.Length = self._lwti_length.Value
self._volume_ma = SimpleMovingAverage()
self._volume_ma.Length = self._ma_volume_length.Value
self._change_wma = WeightedMovingAverage()
self._change_wma.Length = self._lwti_length.Value
self._lwti_sma = SimpleMovingAverage()
self._lwti_sma.Length = self._lwti_smooth.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(donchian, atr, 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_value(self, indicator, value, time):
indicator_input = DecimalIndicatorValue(indicator, value, time)
indicator_input.IsFinal = True
return indicator.Process(indicator_input)
def _process_candle(self, candle, donchian_value, atr_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
volume_avg = self._process_value(self._volume_ma, candle.TotalVolume, candle.OpenTime)
lwti = self._process_lwti(candle, atr_value)
if self._manage_position(candle):
return
if upper is None or lower is None or lwti is None or not self._volume_ma.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
day = candle.OpenTime.Date
if self.Position != 0 or not self._in_session(candle.OpenTime.Hour) or self._last_trade_day == day:
return
close = candle.ClosePrice
high_volume = candle.TotalVolume > volume_avg.GetValue[Decimal](None)
middle = (upper + lower) / Decimal(2)
rr = Decimal(self._risk_reward.Value)
if close > upper and high_volume and lwti > 50 and close > middle:
self.BuyMarket(self.Volume)
self._stop_price = middle
self._take_price = close + (close - middle) * rr
self._last_trade_day = day
elif close < lower and high_volume and lwti < 50 and close < middle:
self.SellMarket(self.Volume)
self._stop_price = middle
self._take_price = close - (middle - close) * rr
self._last_trade_day = day
def _process_lwti(self, candle, atr_value):
length = self._lwti_length.Value
self._closes.append(candle.ClosePrice)
if len(self._closes) > length + 1:
self._closes.pop(0)
if len(self._closes) <= length:
return None
change = candle.ClosePrice - self._closes[0]
wma = self._process_value(self._change_wma, change, candle.OpenTime)
if not self._change_wma.IsFormed or not atr_value.IsFormed:
return None
atr = atr_value.GetValue[Decimal](None)
if atr <= 0:
return None
raw = wma.GetValue[Decimal](None) / atr * Decimal(50) + Decimal(50)
smooth = self._process_value(self._lwti_sma, raw, candle.OpenTime)
return smooth.GetValue[Decimal](None) if self._lwti_sma.IsFormed else None
def _in_session(self, hour):
start = self._start_hour.Value
end = self._end_hour.Value
if start <= end:
return start <= hour < end
return hour >= start or hour < end
def _manage_position(self, candle):
if self.Position > 0 and self._stop_price > 0:
if candle.LowPrice <= self._stop_price or candle.HighPrice >= self._take_price:
self.SellMarket(self.Position)
return True
elif self.Position < 0 and self._stop_price > 0:
if candle.HighPrice >= self._stop_price or candle.LowPrice <= self._take_price:
self.BuyMarket(abs(self.Position))
return True
return False
def CreateClone(self):
return gold_breakout_rr4_strategy()