BONK Long Volatility 策略
该策略仅做多,结合均线、波动性和成交量过滤。当市场上行、波动扩大并且动量指标确认时买入。离场通过固定止盈、止损以及基于ATR的追踪止损完成。
详情
- 入场条件: 快速均线高于慢速均线,K线范围大于 ATR *
AtrMultiplier,RSI 在RsiOversold与RsiOverbought之间,MACD 线高于信号线且大于0,成交量高于均量 *VolumeThreshold,收盘价高于快速均线,K线时间在最近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)
- 过滤器:
- 类别: Trend
- 方向: Long
- 指标: SMA, ATR, RSI, MACD, Volume
- 止损: 有
- 复杂度: 中等
- 时间框架: 日内
- 季节性: 无
- 神经网络: 无
- 背离: 无
- 风险等级: 中等
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()