Diese Strategie handelt gegen Bewegungen weg vom volumengewichteten Durchschnittspreis. ATR wird verwendet, um zu messen, wie weit der Preis vom VWAP abweichen muss, bevor ein Umkehrhandel in Betracht gezogen wird.
Tests zeigen eine durchschnittliche jährliche Rendite von etwa 58%. Er funktioniert am besten auf dem Aktienmarkt.
Eine Long-Position wird eröffnet, wenn der Preis mehr als K mal den ATR unter den VWAP fällt. Eine Short wird eingegangen, wenn der Preis um denselben Betrag über den VWAP steigt. Trades werden beendet, sobald der Preis zur VWAP-Linie zurückkehrt.
Der Ansatz ist für Intraday-Trader konzipiert, die erwarten, dass die Preise um den VWAP oszillieren anstatt stark zu trenden. Stops, die als Vielfaches des ATR dimensioniert sind, helfen, Verluste zu kontrollieren, wenn die Bewegung gegen den Trade weitergeht.
Details
Einstiegskriterien:
Long: Close < VWAP - K * ATR
Short: Close > VWAP + K * ATR
Long/Short: Beide Seiten.
Ausstiegskriterien:
Long: Ausstieg wenn close >= VWAP
Short: Ausstieg wenn close <= VWAP
Stops: Ja, ATR-basierter Stop.
Standardwerte:
K = 2
CandleType = TimeSpan.FromMinutes(5)
AtrPeriod = 14
Der Markt handelt rund um die Uhr, daher beginnt der Sitzungs-VWAP mit jedem UTC-Tag neu und gewichtet den typischen Preis jeder Kerze mit ihrem Volumen. Der Stop liegt K mal die ATR über AtrPeriod vom Einstiegsschluss entfernt und wird bei Kerzenschluss geprüft. Ein Einstiegssignal gegen eine offene Position dreht sie.
Filter:
Kategorie: Mean Reversion
Richtung: Beide
Indikatoren: VWAP, ATR
Stops: Ja
Komplexität: Mittel
Zeitrahmen: Intraday
Saisonalität: Nein
Neuronale Netze: Nein
Divergenz: Nein
Risikolevel: Mittel
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>
/// VWAP Mean Reversion strategy.
/// The market trades around the clock, so the session VWAP restarts with each UTC day and weighs each candle's typical price by its volume.
/// A close more than K times the AtrPeriod ATR below VWAP goes long and one that far above it goes short, reversing an opposite position.
/// A long closes once the close is back at or above VWAP and a short once it is back at or below it. The stop lies K ATR from the entry
/// close and is checked on candle closes.
/// </summary>
public class VwapMeanReversionStrategy : Strategy
{
private readonly StrategyParam<decimal> _k;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<DataType> _candleType;
private DateTime? _day;
private decimal _cumulativePriceVolume;
private decimal _cumulativeVolume;
private decimal _stopPrice;
/// <summary>
/// ATR multiplier for the entry distance and the stop.
/// </summary>
public decimal K
{
get => _k.Value;
set => _k.Value = value;
}
/// <summary>
/// Period of the ATR.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public VwapMeanReversionStrategy()
{
_k = Param(nameof(K), 2m)
.SetGreaterThanZero()
.SetDisplay("K", "ATR multiplier for the entry distance and the stop", "Parameters");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "Period of the ATR", "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();
_day = null;
_cumulativePriceVolume = 0;
_cumulativeVolume = 0;
_stopPrice = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_day = null;
_cumulativePriceVolume = 0;
_cumulativeVolume = 0;
_stopPrice = default;
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
{
DrawIndicator(oscillators, atr);
}
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
var day = candle.OpenTime.Date;
if (_day != day)
{
_day = day;
_cumulativePriceVolume = 0;
_cumulativeVolume = 0;
}
var typicalPrice = (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3;
_cumulativePriceVolume += typicalPrice * candle.TotalVolume;
_cumulativeVolume += candle.TotalVolume;
if (!atrValue.IsFormed || _cumulativeVolume <= 0)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var vwap = _cumulativePriceVolume / _cumulativeVolume;
var distance = K * atrValue.GetValue<decimal>();
var close = candle.ClosePrice;
if (close < vwap - distance && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_stopPrice = close - distance;
}
else if (close > vwap + distance && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_stopPrice = close + distance;
}
else if (Position > 0 && (close >= vwap || close <= _stopPrice))
{
SellMarket(Position);
}
else if (Position < 0 && (close <= vwap || 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 AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class vwap_mean_reversion_strategy(Strategy):
"""
VWAP Mean Reversion strategy.
The market trades around the clock, so the session VWAP restarts with each UTC day and weighs each candle's typical price by its volume.
A close more than K times the AtrPeriod ATR below VWAP goes long and one that far above it goes short, reversing an opposite position.
A long closes once the close is back at or above VWAP and a short once it is back at or below it. The stop lies K ATR from the entry
close and is checked on candle closes.
"""
def __init__(self):
super(vwap_mean_reversion_strategy, self).__init__()
self._k = self.Param("K", 2.0).SetGreaterThanZero().SetDisplay("K", "ATR multiplier for the entry distance and the stop", "Parameters")
self._atr_period = self.Param("AtrPeriod", 14).SetGreaterThanZero().SetDisplay("ATR Period", "Period of the ATR", "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._day = None
self._cumulative_price_volume = Decimal(0)
self._cumulative_volume = Decimal(0)
self._stop_price = Decimal(0)
def OnReseted(self):
super(vwap_mean_reversion_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(vwap_mean_reversion_strategy, self).OnStarted2(time)
self._reset_state()
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, atr)
def _process_candle(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
day = candle.OpenTime.Date
if self._day is None or self._day != day:
self._day = day
self._cumulative_price_volume = Decimal(0)
self._cumulative_volume = Decimal(0)
typical_price = (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / Decimal(3)
self._cumulative_price_volume += typical_price * candle.TotalVolume
self._cumulative_volume += candle.TotalVolume
if not atr_value.IsFormed or self._cumulative_volume <= 0:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
vwap = self._cumulative_price_volume / self._cumulative_volume
distance = Decimal(self._k.Value) * atr_value.GetValue[Decimal](None)
close = candle.ClosePrice
if close < vwap - distance and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._stop_price = close - distance
elif close > vwap + distance and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
self._stop_price = close + distance
elif self.Position > 0 and (close >= vwap or close <= self._stop_price):
self.SellMarket(self.Position)
elif self.Position < 0 and (close <= vwap or close >= self._stop_price):
self.BuyMarket(-self.Position)
def CreateClone(self):
return vwap_mean_reversion_strategy()