ATR 枯渇ストラテジー
Average True Rangeの急激な上昇は、急速に収束しうるボラティリティの拡大を示します。この戦略は、設定可能な乗数で移動平均を上回るATR値を探します。リバーサルローソク足と組み合わせることで、その後の収縮を捉えることを目指します。
テストでは年平均リターン約139%を示しています。株式市場で最も優れたパフォーマンスを発揮します。
各バーがATRとその独自の平均を更新します。ATRが乗数分だけ平均を超え、ローソク足が前の動きとは逆方向に終値をつけた場合、トレードが開かれます。ストップは価格に対するトレーリングの割合で、ボラティリティの急騰が収まるにつれてポジションに追従します。
ポジションは通常ストップによる決済に依存し、ボラティリティの急騰が収まった後の素早い押し戻しを狙います。
詳細
- エントリー条件: 平均を上回るATRスパイクとリバーサルローソク足。
- ロング/ショート: 両方。
- エグジット条件: ストップロス。
- ストップ: はい、トレーリングのパーセント。
- デフォルト値:
AtrPeriod= 14AtrAvgPeriod= 20AtrMultiplier= 1.5MaPeriod= 20StopLossPercent= 2CandleType= 5 minute
- フィルター:
- カテゴリ: リバーサル
- 方向: 両方
- インジケーター: 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>
/// ATR Exhaustion strategy.
/// An ATR spike is an ATR above AtrMultiplier times its own AtrAvgPeriod moving average. While flat, a bullish candle on a spike
/// buys when the price moving average has been falling and a bearish one sells when it has been rising.
/// The only exit is a trailing percent stop.
/// </summary>
public class AtrExhaustionStrategy : Strategy
{
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<int> _atrAvgPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _atrAverage;
private decimal? _prevMa;
/// <summary>
/// ATR period.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Period of the moving average of ATR.
/// </summary>
public int AtrAvgPeriod
{
get => _atrAvgPeriod.Value;
set => _atrAvgPeriod.Value = value;
}
/// <summary>
/// How many times its average ATR must exceed to count as a spike.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Period of the price moving average that defines the prior move.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Trailing stop-loss percentage.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public AtrExhaustionStrategy()
{
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period for ATR", "Indicators");
_atrAvgPeriod = Param(nameof(AtrAvgPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("ATR Average Period", "Period of the moving average of ATR", "Indicators");
_atrMultiplier = Param(nameof(AtrMultiplier), 1.5m)
.SetGreaterThanZero()
.SetDisplay("ATR Multiplier", "How many times its average ATR must exceed", "Indicators");
_maPeriod = Param(nameof(MaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Price moving average that defines the prior move", "Indicators");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Trailing stop loss percentage", "Risk");
_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();
_atrAverage = null;
_prevMa = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_atrAverage = new SimpleMovingAverage { Length = AtrAvgPeriod };
_prevMa = null;
var sma = new SimpleMovingAverage { Length = MaPeriod };
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(sma, atr, ProcessCandle)
.Start();
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), isStopTrailing: true, 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, sma);
DrawIndicator(area, atr);
DrawOwnTrades(area);
}
}
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 smaValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished || !atrValue.IsFormed)
return;
var atr = atrValue.GetValue<decimal>();
var atrAverage = _atrAverage.Process(atr, candle.OpenTime, true);
if (!smaValue.IsFormed)
return;
var ma = smaValue.GetValue<decimal>();
var prevMa = _prevMa;
_prevMa = ma;
if (!_atrAverage.IsFormed || prevMa is not decimal lastMa || Position != 0 || !IsFormedAndOnlineAndAllowTrading())
return;
if (atr <= atrAverage.GetValue<decimal>() * AtrMultiplier)
return;
var close = candle.ClosePrice;
if (close > candle.OpenPrice && ma < lastMa)
BuyMarket(Volume);
else if (close < candle.OpenPrice && ma > lastMa)
SellMarket(Volume);
}
}
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
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class atr_exhaustion_strategy(Strategy):
"""
ATR Exhaustion strategy.
An ATR spike is an ATR above AtrMultiplier times its own AtrAvgPeriod moving average. While flat, a bullish candle on a spike
buys when the price moving average has been falling and a bearish one sells when it has been rising.
The only exit is a trailing percent stop.
"""
def __init__(self):
super(atr_exhaustion_strategy, self).__init__()
self._atr_period = self.Param("AtrPeriod", 14).SetGreaterThanZero().SetDisplay("ATR Period", "Period for ATR", "Indicators")
self._atr_avg_period = self.Param("AtrAvgPeriod", 20).SetGreaterThanZero().SetDisplay("ATR Average Period", "Period of the moving average of ATR", "Indicators")
self._atr_multiplier = self.Param("AtrMultiplier", 1.5).SetGreaterThanZero().SetDisplay("ATR Multiplier", "How many times its average ATR must exceed", "Indicators")
self._ma_period = self.Param("MaPeriod", 20).SetGreaterThanZero().SetDisplay("MA Period", "Price moving average that defines the prior move", "Indicators")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss %", "Trailing stop loss percentage", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._atr_average = None
self._prev_ma = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(atr_exhaustion_strategy, self).OnReseted()
self._atr_average = None
self._prev_ma = None
def OnStarted2(self, time):
super(atr_exhaustion_strategy, self).OnStarted2(time)
self._atr_average = SimpleMovingAverage()
self._atr_average.Length = self._atr_avg_period.Value
self._prev_ma = None
sma = SimpleMovingAverage()
sma.Length = self._ma_period.Value
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(sma, atr, self._process_candle).Start()
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), isStopTrailing=True, 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, sma)
self.DrawIndicator(area, atr)
self.DrawOwnTrades(area)
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, sma_value, atr_value):
if candle.State != CandleStates.Finished or not atr_value.IsFormed:
return
atr = atr_value.GetValue[Decimal](None)
atr_average = process_value(self._atr_average, atr, candle.OpenTime, True)
if not sma_value.IsFormed:
return
ma = sma_value.GetValue[Decimal](None)
prev_ma = self._prev_ma
self._prev_ma = ma
if not self._atr_average.IsFormed or prev_ma is None or self.Position != 0 or not self.IsFormedAndOnlineAndAllowTrading():
return
if atr <= to_decimal(atr_average) * Decimal(self._atr_multiplier.Value):
return
close = candle.ClosePrice
if close > candle.OpenPrice and ma < prev_ma:
self.BuyMarket(self.Volume)
elif close < candle.OpenPrice and ma > prev_ma:
self.SellMarket(self.Volume)
def CreateClone(self):
return atr_exhaustion_strategy()