ATR Expansion Breakout 戦略
この戦略はAverage True Rangeを使用してボラティリティの急増に追従します。ATRが前のバーと比較して上昇し、価格が移動平均に対して取引される場合、ブレイクアウトに乗ることを目指します。
テストでは年間平均リターン約145%が示されています。暗号資産市場で最も優れたパフォーマンスを発揮します。
ATRの拡大は強い動きが進行中であることを示します。エントリーは移動平均に対する価格の方向に合わせ、ボラティリティの収縮がエグジットを引き起こします。
ストップは高ボラティリティ時に取引に余裕を持たせるためATRの倍数で設定されます。
詳細
- エントリー条件: ATRが上昇し、価格がMAの上/下。
- ロング/ショート: 両方向。
- エグジット条件: ATRが収縮するかストップに達する。
- ストップ: はい。
- デフォルト値:
AtrPeriod= 14MAPeriod= 20AtrMultiplier= 2.0mCandleType= TimeSpan.FromMinutes(5)
- フィルター:
- カテゴリ: ブレイクアウト
- 方向: 両方
- インジケーター: ATR, MA
- ストップ: はい
- 複雑さ: 基本
- 時間軸: イントラデイ
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
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>
/// Strategy that trades on volatility expansion as measured by ATR.
/// Enters on any one-bar increase in formed ATR in the price/SMA direction,
/// exits when volatility contracts.
/// </summary>
public class AtrExpansionStrategy : Strategy
{
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevAtr;
private bool _hasPrev;
private Order _pendingOrder;
private Unit _stopDistance;
private bool _protectionStarted;
/// <summary>
/// Period for ATR calculation.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Period for Moving Average calculation.
/// </summary>
public int MAPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Type of candles used for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize the ATR Expansion strategy.
/// </summary>
public AtrExpansionStrategy()
{
_atrPeriod = Param(nameof(AtrPeriod), 14).SetGreaterThanZero()
.SetDisplay("ATR Period", "Period for ATR calculation", "Indicators")
.SetOptimize(7, 21, 7);
_maPeriod = Param(nameof(MAPeriod), 20).SetGreaterThanZero()
.SetDisplay("MA Period", "Period for MA calculation", "Indicators")
.SetOptimize(10, 50, 5);
_atrMultiplier = Param(nameof(AtrMultiplier), 2m).SetNotNegative()
.SetDisplay("ATR Stop Multiplier", "Frozen entry ATR stop distance; zero disables it", "Protection");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
OrderRegistering += order => _pendingOrder = order;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType), (Security, DataType.Level1)];
}
public decimal AtrMultiplier { get => _atrMultiplier.Value; set => _atrMultiplier.Value = value; }
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevAtr = default;
_hasPrev = default;
_pendingOrder = null;
_stopDistance = null;
_protectionStarted = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevAtr = 0;
_hasPrev = false;
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 atr = new AverageTrueRange { Length = AtrPeriod };
var sma = new SimpleMovingAverage { Length = MAPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(atr, sma, ProcessCandle, false)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, sma);
DrawOwnTrades(area);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// Native protection runs before this callback, including between finished candles.
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue atrValue, IIndicatorValue smaValue)
{
if (candle.State != CandleStates.Finished || !atrValue.Indicator.IsFormed || !smaValue.Indicator.IsFormed
|| !IsFormedAndOnlineAndAllowTrading())
return;
var atr = atrValue.GetValue<decimal>();
var mean = smaValue.GetValue<decimal>();
if (!_hasPrev)
{
_hasPrev = true;
_prevAtr = atr;
return;
}
var previous = _prevAtr;
_prevAtr = atr;
if (_pendingOrder is not null && _pendingOrder.State is not (OrderStates.Done or OrderStates.Failed))
return;
// Any contraction closes, including a zero ATR; the MA direction is entry-only.
if (Position != 0m && atr < previous)
{
if (Position > 0m) SellMarket(Position);
else BuyMarket(Math.Abs(Position));
}
else if (Position == 0m && atr > previous)
{
if (candle.ClosePrice > mean) Enter(Sides.Buy, atr);
else if (candle.ClosePrice < mean) Enter(Sides.Sell, atr);
}
}
private void Enter(Sides side, decimal atr)
{
var distance = atr * AtrMultiplier;
_stopDistance ??= new Unit(distance);
// Keep the Unit reference held by native cached protection controllers.
_stopDistance.Value = distance;
if (!_protectionStarted && distance > 0m)
{
StartProtection(new Unit(), _stopDistance, useMarketOrders: true, isLocalStop: true);
_protectionStarted = true;
}
RegisterOrder(new Order
{
Security = Security,
Portfolio = Portfolio,
Type = OrderTypes.Market,
Side = side,
Volume = Volume,
Comment = "ATR expansion entry",
});
}
}
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, Math
from StockSharp.Messages import DataType, CandleStates, Unit, OrderStates, OrderTypes, Sides, Level1Fields
from StockSharp.BusinessEntities import Order, Subscription
from StockSharp.Algo.Indicators import AverageTrueRange, SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class atr_expansion_strategy(Strategy):
"""
Strategy that trades on volatility expansion as measured by ATR.
Enters on any one-bar increase in formed ATR in the price/SMA direction,
exits when volatility contracts.
"""
def __init__(self):
super(atr_expansion_strategy, self).__init__()
self._atr_period = self.Param("AtrPeriod", 14).SetGreaterThanZero().SetDisplay("ATR Period", "Period for ATR calculation", "Indicators")
self._ma_period = self.Param("MAPeriod", 20).SetGreaterThanZero().SetDisplay("MA Period", "Period for MA calculation", "Indicators")
self._atr_multiplier = self.Param("AtrMultiplier", 2.0).SetNotNegative().SetDisplay("ATR Stop Multiplier", "Frozen entry ATR stop distance; zero disables it", "Protection")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._prev_atr = Decimal.Zero
self._has_prev = False
self._pending_order = None
self._stop_distance = None
self._protection_started = False
self.OrderRegistering += self._track_pending
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type), (self.Security, DataType.Level1)]
def _track_pending(self, order):
self._pending_order = order
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(atr_expansion_strategy, self).OnReseted()
self._prev_atr = Decimal.Zero
self._has_prev = False
self._pending_order = None
self._stop_distance = None
self._protection_started = False
def OnStarted2(self, time):
super(atr_expansion_strategy, self).OnStarted2(time)
self._prev_atr = Decimal.Zero
self._has_prev = False
self._pending_order = None
self._stop_distance = None
self._protection_started = False
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()
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
sma = SimpleMovingAverage()
sma.Length = self._ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(atr, sma, self._process_candle, False).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, sma)
self.DrawOwnTrades(area)
def _observe_protection_quote(self, quote):
# Native protection runs before this callback, including between finished candles.
pass
def _process_candle(self, candle, atr_value, sma_value):
if candle.State != CandleStates.Finished or not atr_value.Indicator.IsFormed or not sma_value.Indicator.IsFormed or not self.IsFormedAndOnlineAndAllowTrading():
return
atr = atr_value.GetValue[Decimal](None)
mean = sma_value.GetValue[Decimal](None)
if not self._has_prev:
self._has_prev = True
self._prev_atr = atr
return
previous = self._prev_atr
self._prev_atr = atr
if self._pending_order is not None and self._pending_order.State not in (OrderStates.Done, OrderStates.Failed):
return
# Any contraction closes, including zero ATR; price/MA is only an entry filter.
if self.Position != 0 and atr < previous:
if self.Position > 0:
self.SellMarket(self.Position)
else:
self.BuyMarket(Math.Abs(self.Position))
elif self.Position == 0 and atr > previous:
if candle.ClosePrice > mean:
self._enter(Sides.Buy, atr)
elif candle.ClosePrice < mean:
self._enter(Sides.Sell, atr)
def _enter(self, side, atr):
distance = atr * Decimal(self._atr_multiplier.Value)
if self._stop_distance is None:
self._stop_distance = Unit(distance)
# Keep the same Unit held by native cached protection controllers.
self._stop_distance.Value = distance
if not self._protection_started and distance > 0:
self.StartProtection(Unit(), self._stop_distance, useMarketOrders=True, isLocalStop=True)
self._protection_started = True
order = Order()
order.Security = self.Security
order.Portfolio = self.Portfolio
order.Type = OrderTypes.Market
order.Side = side
order.Volume = self.Volume
order.Comment = "ATR expansion entry"
self.RegisterOrder(order)
def CreateClone(self):
return atr_expansion_strategy()