BONK ロング・ボラティリティ戦略
この純ロング戦略は、移動平均、ボラティリティ、出来高フィルターを組み合わせた強い強気条件でエントリーします。市場が上昇トレンドにあり、ボラティリティが拡大し、モメンタム指標が強さを確認したときに買います。決済には固定テイクプロフィット、ストップロス、ATRベースのトレーリングストップを使用します。
詳細
- エントリー条件: 速いMAが遅いMAを上回る、価格レンジがATR *
AtrMultiplierを超える、RSIがRsiOversoldとRsiOverboughtの間、MACDラインがシグナルとゼロを上回る、出来高がSMA *VolumeThresholdを上回る、終値が速いMAを上回る、ローソク足が最後のLookbackDays以内。 - ロング/ショート: ロングのみ。
- エグジット条件: テイクプロフィット、ストップロス、またはATRトレーリングストップ。
- ストップ: はい。
- デフォルト値:
ProfitTargetPercent= 5.0mStopLossPercent= 3.0mAtrLength= 10AtrMultiplier= 1.5mRsiLength= 14RsiOverbought= 65RsiOversold= 35MacdFast= 12MacdSlow= 26MacdSignal= 9VolumeSmaLength= 20VolumeThreshold= 1.5mMaFastLength= 5MaSlowLength= 13LookbackDays= 30CandleType= TimeSpan.FromHours(1)
- フィルター:
- カテゴリ: トレンド
- 方向: ロング
- インジケーター: SMA, ATR, RSI, MACD, 出来高
- ストップ: はい
- 複雑さ: 中級
- 時間軸: イントラデイ
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
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>
/// BONK Long Volatility strategy.
/// Long only: buys when SMA(MaFastLength) is above SMA(MaSlowLength), the candle range exceeds AtrMultiplier times ATR(AtrLength),
/// RSI lies between RsiOversold and RsiOverbought, the MACD line is above its signal and above zero, volume exceeds VolumeThreshold
/// times its VolumeSmaLength average, the close is above the fast SMA and the candle is no older than LookbackDays. The long closes on
/// the percent take profit or stop loss, or when the close falls below a trailing stop AtrMultiplier ATRs under the highest close
/// since entry.
/// </summary>
public class BonkLongVolatilityStrategy : Strategy
{
private readonly StrategyParam<decimal> _profitTargetPercent;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<decimal> _rsiOverbought;
private readonly StrategyParam<decimal> _rsiOversold;
private readonly StrategyParam<int> _macdFast;
private readonly StrategyParam<int> _macdSlow;
private readonly StrategyParam<int> _macdSignal;
private readonly StrategyParam<int> _volumeSmaLength;
private readonly StrategyParam<decimal> _volumeThreshold;
private readonly StrategyParam<int> _maFastLength;
private readonly StrategyParam<int> _maSlowLength;
private readonly StrategyParam<int> _lookbackDays;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _volumeSma;
private decimal? _highestClose;
/// <summary>
/// Take profit percentage from entry price.
/// </summary>
public decimal ProfitTargetPercent
{
get => _profitTargetPercent.Value;
set => _profitTargetPercent.Value = value;
}
/// <summary>
/// Stop loss percentage from entry price.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// ATR period.
/// </summary>
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <summary>
/// ATR multiple of the range filter and the trailing stop.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// RSI period.
/// </summary>
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <summary>
/// Upper RSI bound for entries.
/// </summary>
public decimal RsiOverbought
{
get => _rsiOverbought.Value;
set => _rsiOverbought.Value = value;
}
/// <summary>
/// Lower RSI bound for entries.
/// </summary>
public decimal RsiOversold
{
get => _rsiOversold.Value;
set => _rsiOversold.Value = value;
}
/// <summary>
/// Fast EMA period of MACD.
/// </summary>
public int MacdFast
{
get => _macdFast.Value;
set => _macdFast.Value = value;
}
/// <summary>
/// Slow EMA period of MACD.
/// </summary>
public int MacdSlow
{
get => _macdSlow.Value;
set => _macdSlow.Value = value;
}
/// <summary>
/// Signal line period of MACD.
/// </summary>
public int MacdSignal
{
get => _macdSignal.Value;
set => _macdSignal.Value = value;
}
/// <summary>
/// Candles of the average volume.
/// </summary>
public int VolumeSmaLength
{
get => _volumeSmaLength.Value;
set => _volumeSmaLength.Value = value;
}
/// <summary>
/// Multiple of the average volume the candle volume has to exceed.
/// </summary>
public decimal VolumeThreshold
{
get => _volumeThreshold.Value;
set => _volumeThreshold.Value = value;
}
/// <summary>
/// Fast SMA period.
/// </summary>
public int MaFastLength
{
get => _maFastLength.Value;
set => _maFastLength.Value = value;
}
/// <summary>
/// Slow SMA period.
/// </summary>
public int MaSlowLength
{
get => _maSlowLength.Value;
set => _maSlowLength.Value = value;
}
/// <summary>
/// Maximum age of a signal candle in days.
/// </summary>
public int LookbackDays
{
get => _lookbackDays.Value;
set => _lookbackDays.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public BonkLongVolatilityStrategy()
{
_profitTargetPercent = Param(nameof(ProfitTargetPercent), 5.0m)
.SetNotNegative()
.SetDisplay("Profit Target %", "Take profit percentage from entry price", "Risk");
_stopLossPercent = Param(nameof(StopLossPercent), 3.0m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk");
_atrLength = Param(nameof(AtrLength), 10)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR period", "Volatility");
_atrMultiplier = Param(nameof(AtrMultiplier), 1.5m)
.SetGreaterThanZero()
.SetDisplay("ATR Multiplier", "ATR multiple of the range filter and the trailing stop", "Volatility");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "Momentum");
_rsiOverbought = Param(nameof(RsiOverbought), 65m)
.SetDisplay("RSI Overbought", "Upper RSI bound for entries", "Momentum");
_rsiOversold = Param(nameof(RsiOversold), 35m)
.SetDisplay("RSI Oversold", "Lower RSI bound for entries", "Momentum");
_macdFast = Param(nameof(MacdFast), 12)
.SetGreaterThanZero()
.SetDisplay("MACD Fast", "Fast EMA period of MACD", "Momentum");
_macdSlow = Param(nameof(MacdSlow), 26)
.SetGreaterThanZero()
.SetDisplay("MACD Slow", "Slow EMA period of MACD", "Momentum");
_macdSignal = Param(nameof(MacdSignal), 9)
.SetGreaterThanZero()
.SetDisplay("MACD Signal", "Signal line period of MACD", "Momentum");
_volumeSmaLength = Param(nameof(VolumeSmaLength), 20)
.SetGreaterThanZero()
.SetDisplay("Volume SMA Length", "Candles of the average volume", "Volume");
_volumeThreshold = Param(nameof(VolumeThreshold), 1.5m)
.SetNotNegative()
.SetDisplay("Volume Threshold", "Multiple of the average volume the candle volume has to exceed", "Volume");
_maFastLength = Param(nameof(MaFastLength), 5)
.SetGreaterThanZero()
.SetDisplay("Fast MA Length", "Fast SMA period", "Trend");
_maSlowLength = Param(nameof(MaSlowLength), 13)
.SetGreaterThanZero()
.SetDisplay("Slow MA Length", "Slow SMA period", "Trend");
_lookbackDays = Param(nameof(LookbackDays), 30)
.SetGreaterThanZero()
.SetDisplay("Lookback Days", "Maximum age of a signal candle in days", "General");
_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();
_volumeSma = null;
_highestClose = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_highestClose = null;
_volumeSma = new SimpleMovingAverage { Length = VolumeSmaLength };
var fastMa = new SimpleMovingAverage { Length = MaFastLength };
var slowMa = new SimpleMovingAverage { Length = MaSlowLength };
var atr = new AverageTrueRange { Length = AtrLength };
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var macd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = MacdFast },
LongMa = { Length = MacdSlow },
},
SignalMa = { Length = MacdSignal }
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(fastMa, slowMa, atr, rsi, macd, ProcessCandle)
.Start();
StartProtection(new Unit(ProfitTargetPercent, UnitTypes.Percent), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
// The stop has to see prices between candles, not only at their close.
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fastMa);
DrawIndicator(area, slowMa);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, rsi);
DrawIndicator(oscillators, macd);
}
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// The high-level handler activates native protection before this callback, also between signal bars.
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue fastValue, IIndicatorValue slowValue, IIndicatorValue atrValue, IIndicatorValue rsiValue, IIndicatorValue macdValue)
{
if (candle.State != CandleStates.Finished)
return;
var volumeValue = _volumeSma.Process(new DecimalIndicatorValue(_volumeSma, candle.TotalVolume, candle.OpenTime) { IsFinal = true });
if (!fastValue.IsFormed || !slowValue.IsFormed || !atrValue.IsFormed || !rsiValue.IsFormed || !macdValue.IsFormed || !volumeValue.IsFormed)
return;
var macdTyped = (MovingAverageConvergenceDivergenceSignalValue)macdValue;
if (macdTyped.Macd is not decimal macd || macdTyped.Signal is not decimal signal)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
var atr = atrValue.GetValue<decimal>();
if (Position > 0)
{
_highestClose = _highestClose is decimal h ? Math.Max(h, close) : close;
if (close < _highestClose.Value - atr * AtrMultiplier)
{
SellMarket(Position);
_highestClose = null;
}
return;
}
_highestClose = null;
var fast = fastValue.GetValue<decimal>();
var slow = slowValue.GetValue<decimal>();
var rsi = rsiValue.GetValue<decimal>();
var recent = candle.OpenTime >= CurrentTime.AddDays(-LookbackDays);
if (Position == 0
&& fast > slow
&& candle.HighPrice - candle.LowPrice > atr * AtrMultiplier
&& rsi > RsiOversold && rsi < RsiOverbought
&& macd > signal && macd > 0
&& candle.TotalVolume > volumeValue.GetValue<decimal>() * VolumeThreshold
&& close > fast
&& recent)
{
BuyMarket(Volume);
_highestClose = close;
}
}
}
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, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import (SimpleMovingAverage, AverageTrueRange, RelativeStrengthIndex,
MovingAverageConvergenceDivergenceSignal, DecimalIndicatorValue)
from StockSharp.Algo.Strategies import Strategy
class bonk_long_volatility_strategy(Strategy):
"""
BONK Long Volatility strategy.
Long only: buys when SMA(MaFastLength) is above SMA(MaSlowLength), the candle range exceeds AtrMultiplier times ATR(AtrLength),
RSI lies between RsiOversold and RsiOverbought, the MACD line is above its signal and above zero, volume exceeds VolumeThreshold
times its VolumeSmaLength average, the close is above the fast SMA and the candle is no older than LookbackDays. The long closes on
the percent take profit or stop loss, or when the close falls below a trailing stop AtrMultiplier ATRs under the highest close
since entry.
"""
def __init__(self):
super(bonk_long_volatility_strategy, self).__init__()
self._profit_target_percent = self.Param("ProfitTargetPercent", 5.0).SetNotNegative().SetDisplay("Profit Target %", "Take profit percentage from entry price", "Risk")
self._stop_loss_percent = self.Param("StopLossPercent", 3.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._atr_length = self.Param("AtrLength", 10).SetGreaterThanZero().SetDisplay("ATR Length", "ATR period", "Volatility")
self._atr_multiplier = self.Param("AtrMultiplier", 1.5).SetGreaterThanZero().SetDisplay("ATR Multiplier", "ATR multiple of the range filter and the trailing stop", "Volatility")
self._rsi_length = self.Param("RsiLength", 14).SetGreaterThanZero().SetDisplay("RSI Length", "RSI period", "Momentum")
self._rsi_overbought = self.Param("RsiOverbought", 65.0).SetDisplay("RSI Overbought", "Upper RSI bound for entries", "Momentum")
self._rsi_oversold = self.Param("RsiOversold", 35.0).SetDisplay("RSI Oversold", "Lower RSI bound for entries", "Momentum")
self._macd_fast = self.Param("MacdFast", 12).SetGreaterThanZero().SetDisplay("MACD Fast", "Fast EMA period of MACD", "Momentum")
self._macd_slow = self.Param("MacdSlow", 26).SetGreaterThanZero().SetDisplay("MACD Slow", "Slow EMA period of MACD", "Momentum")
self._macd_signal = self.Param("MacdSignal", 9).SetGreaterThanZero().SetDisplay("MACD Signal", "Signal line period of MACD", "Momentum")
self._volume_sma_length = self.Param("VolumeSmaLength", 20).SetGreaterThanZero().SetDisplay("Volume SMA Length", "Candles of the average volume", "Volume")
self._volume_threshold = self.Param("VolumeThreshold", 1.5).SetNotNegative().SetDisplay("Volume Threshold", "Multiple of the average volume the candle volume has to exceed", "Volume")
self._ma_fast_length = self.Param("MaFastLength", 5).SetGreaterThanZero().SetDisplay("Fast MA Length", "Fast SMA period", "Trend")
self._ma_slow_length = self.Param("MaSlowLength", 13).SetGreaterThanZero().SetDisplay("Slow MA Length", "Slow SMA period", "Trend")
self._lookback_days = self.Param("LookbackDays", 30).SetGreaterThanZero().SetDisplay("Lookback Days", "Maximum age of a signal candle in days", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._volume_sma = None
self._highest_close = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(bonk_long_volatility_strategy, self).OnReseted()
self._volume_sma = None
self._highest_close = None
def OnStarted2(self, time):
super(bonk_long_volatility_strategy, self).OnStarted2(time)
self._highest_close = None
self._volume_sma = SimpleMovingAverage()
self._volume_sma.Length = self._volume_sma_length.Value
fast_ma = SimpleMovingAverage()
fast_ma.Length = self._ma_fast_length.Value
slow_ma = SimpleMovingAverage()
slow_ma.Length = self._ma_slow_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_length.Value
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = self._macd_fast.Value
macd.Macd.LongMa.Length = self._macd_slow.Value
macd.SignalMa.Length = self._macd_signal.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(fast_ma, slow_ma, atr, rsi, macd, self._process_candle).Start()
self.StartProtection(Unit(Decimal(self._profit_target_percent.Value), UnitTypes.Percent), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
# The stop has to see prices between candles, not only at their close.
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast_ma)
self.DrawIndicator(area, slow_ma)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, rsi)
self.DrawIndicator(oscillators, macd)
def _observe_protection_quote(self, quote):
# The high-level handler activates native protection before the callback, including between bars.
pass
def _process_candle(self, candle, fast_value, slow_value, atr_value, rsi_value, macd_value):
if candle.State != CandleStates.Finished:
return
volume_input = DecimalIndicatorValue(self._volume_sma, candle.TotalVolume, candle.OpenTime)
volume_input.IsFinal = True
volume_value = self._volume_sma.Process(volume_input)
if not fast_value.IsFormed or not slow_value.IsFormed or not atr_value.IsFormed or not rsi_value.IsFormed or not macd_value.IsFormed or not volume_value.IsFormed:
return
macd = macd_value.Macd
signal = macd_value.Signal
if macd is None or signal is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
close = candle.ClosePrice
atr = atr_value.GetValue[Decimal](None)
multiplier = Decimal(self._atr_multiplier.Value)
if self.Position > 0:
if self._highest_close is None or close > self._highest_close:
self._highest_close = close
if close < self._highest_close - atr * multiplier:
self.SellMarket(self.Position)
self._highest_close = None
return
self._highest_close = None
fast = fast_value.GetValue[Decimal](None)
slow = slow_value.GetValue[Decimal](None)
rsi = rsi_value.GetValue[Decimal](None)
recent = candle.OpenTime >= self.CurrentTime.AddDays(-self._lookback_days.Value)
if (self.Position == 0
and fast > slow
and candle.HighPrice - candle.LowPrice > atr * multiplier
and rsi > Decimal(self._rsi_oversold.Value) and rsi < Decimal(self._rsi_overbought.Value)
and macd > signal and macd > 0
and candle.TotalVolume > volume_value.GetValue[Decimal](None) * Decimal(self._volume_threshold.Value)
and close > fast
and recent):
self.BuyMarket(self.Volume)
self._highest_close = close
def CreateClone(self):
return bonk_long_volatility_strategy()