Esta estrategia opera contra los movimientos que se alejan del precio promedio ponderado por volumen. El ATR se utiliza para medir cuánto debe desviarse el precio del VWAP antes de considerar una operación de reversión.
Las pruebas indican un retorno anual promedio de aproximadamente 58%. Funciona mejor en el mercado de acciones.
Se abre una posición larga cuando el precio cae por debajo del VWAP en más de K veces el ATR. Se toma una posición corta cuando el precio sube por encima del VWAP en la misma cantidad. Las operaciones se cierran en cuanto el precio regresa a la línea VWAP.
El enfoque está diseñado para traders intradía que esperan que los precios oscilen alrededor del VWAP en lugar de tendencias fuertes. Los stops dimensionados como múltiplo del ATR ayudan a mantener las pérdidas controladas si el movimiento continúa contra la operación.
Detalles
Criterios de entrada:
Largo: Close < VWAP - K * ATR
Corto: Close > VWAP + K * ATR
Largo/Corto: Ambos lados.
Criterios de salida:
Largo: Salir cuando close >= VWAP
Corto: Salir cuando close <= VWAP
Stops: Sí, stop basado en ATR.
Valores predeterminados:
K = 2
CandleType = TimeSpan.FromMinutes(5)
AtrPeriod = 14
El mercado opera las 24 horas, por lo que el VWAP de la sesión se reinicia cada día UTC y pondera el precio típico de cada vela por su volumen. El stop se sitúa a K veces el ATR de AtrPeriod del cierre de entrada y se comprueba al cierre de las velas. Una señal de entrada contra una posición abierta la invierte.
Filtros:
Categoría: Mean reversion
Dirección: Ambos
Indicadores: VWAP, ATR
Stops: Sí
Complejidad: Intermedio
Marco temporal: Intradía
Estacionalidad: No
Redes neuronales: No
Divergencia: No
Nivel de riesgo: Medio
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()