Volatility Breakout 策略
该策略在价格脱离平均区间时捕捉强势走势。通过平均真实波幅(ATR)衡量价格与简单移动平均线的距离,并据此设定随波动变化的突破阈值。
测试表明年均收益约为 97%,该策略在加密市场表现最佳。
当收盘价高于SMA超过Multiplier*ATR时买入;当收盘价低于SMA相同距离时卖出。持仓直到出现反向突破或触发止损。
此方法适合在日内动量爆发中获利的交易者。ATR阈值有助于过滤噪声,仅在显著波动时进场。
细节
- 入场条件:
- 多头:
Close > SMA + Multiplier*ATR - 空头:
Close < SMA - Multiplier*ATR
- 多头:
- 多/空: 双向
- 离场条件:
- 多头: 反向突破或止损触发
- 空头: 反向突破或止损触发
- 止损:
Multiplier*ATR - 默认值:
Period= 20Multiplier= 2 止损距入场收盘价Multiplier倍Period周期ATR,按K线收盘检查。与持仓方向相反的入场信号会反转持仓。CandleType= TimeSpan.FromMinutes(5)
- 过滤器:
- 类别: Breakout
- 方向: 双向
- 指标: SMA, 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>
/// Volatility Breakout strategy.
/// A close more than Multiplier times the Period ATR above the Period simple moving average goes long and one that far below it goes short,
/// reversing an opposite position. The position stays open until the opposite breakout, or until the stop Multiplier ATR from the entry
/// close is hit, checked on candle closes.
/// </summary>
public class VolatilityBreakoutStrategy : Strategy
{
private readonly StrategyParam<int> _period;
private readonly StrategyParam<decimal> _multiplier;
private readonly StrategyParam<DataType> _candleType;
private decimal _stopPrice;
/// <summary>
/// Period for SMA and ATR.
/// </summary>
public int Period
{
get => _period.Value;
set => _period.Value = value;
}
/// <summary>
/// ATR multiplier for the breakout threshold and the stop.
/// </summary>
public decimal Multiplier
{
get => _multiplier.Value;
set => _multiplier.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public VolatilityBreakoutStrategy()
{
_period = Param(nameof(Period), 20)
.SetGreaterThanZero()
.SetDisplay("Period", "Period for SMA and ATR", "Parameters");
_multiplier = Param(nameof(Multiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("Multiplier", "ATR multiplier for the breakout threshold and the stop", "Parameters");
_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();
_stopPrice = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_stopPrice = default;
var sma = new SimpleMovingAverage { Length = Period };
var atr = new AverageTrueRange { Length = Period };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(sma, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, atr);
}
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue smaValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!smaValue.IsFormed || !atrValue.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var sma = smaValue.GetValue<decimal>();
var distance = Multiplier * atrValue.GetValue<decimal>();
var close = candle.ClosePrice;
if (close > sma + distance && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_stopPrice = close - distance;
}
else if (close < sma - distance && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_stopPrice = close + distance;
}
else if (Position > 0 && close <= _stopPrice)
{
SellMarket(Position);
}
else if (Position < 0 && close >= _stopPrice)
{
BuyMarket(-Position);
}
}
}
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 SimpleMovingAverage, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class volatility_breakout_strategy(Strategy):
"""
Volatility Breakout strategy.
A close more than Multiplier times the Period ATR above the Period simple moving average goes long and one that far below it goes short,
reversing an opposite position. The position stays open until the opposite breakout, or until the stop Multiplier ATR from the entry
close is hit, checked on candle closes.
"""
def __init__(self):
super(volatility_breakout_strategy, self).__init__()
self._period = self.Param("Period", 20).SetGreaterThanZero().SetDisplay("Period", "Period for SMA and ATR", "Parameters")
self._multiplier = self.Param("Multiplier", 2.0).SetGreaterThanZero().SetDisplay("Multiplier", "ATR multiplier for the breakout threshold and the stop", "Parameters")
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._stop_price = Decimal(0)
def OnReseted(self):
super(volatility_breakout_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(volatility_breakout_strategy, self).OnStarted2(time)
self._reset_state()
sma = SimpleMovingAverage()
sma.Length = self._period.Value
atr = AverageTrueRange()
atr.Length = self._period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(sma, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, atr)
def _process_candle(self, candle, sma_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not sma_value.IsFormed or not atr_value.IsFormed:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
sma = sma_value.GetValue[Decimal](None)
distance = Decimal(self._multiplier.Value) * atr_value.GetValue[Decimal](None)
close = candle.ClosePrice
if close > sma + distance and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._stop_price = close - distance
elif close < sma - distance and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._stop_price = close + distance
elif self.Position > 0 and close <= self._stop_price:
self.SellMarket(self.Position)
elif self.Position < 0 and close >= self._stop_price:
self.BuyMarket(-self.Position)
def CreateClone(self):
return volatility_breakout_strategy()