VWAP Reversion 戦略
出来高加重平均価格からの乖離を取引するVWAP Reversion戦略
テストでは年平均リターン約127%が示されています。株式市場で最もパフォーマンスが高くなります。
VWAP Reversionは出来高加重平均価格からの乖離を取引します。価格がVWAPより大きく上または下に離れると、戦略はその動きに逆らってトレードし、戻ったときにエグジットします。
VWAPは典型的な取引レベルを反映しているため、極端な乖離はしばしば価格を引き戻します。一部のトレーダーはより高い確率のために、このシグナルをイントラデイのトレンドフィルターと組み合わせます。
詳細
- エントリー条件: RSI、VWAPに基づくシグナル。
- ロング/ショート: 両方。
- エグジット条件: 逆シグナルまたはストップ。
- ストップ: はい。
- デフォルト値:
DeviationPercent= 2.0mStopLossPercent= 2.0mCandleType= TimeSpan.FromMinutes(5)
- フィルター:
- カテゴリ: 平均回帰
- 方向: 両方
- インジケーター: RSI, VWAP
- ストップ: はい
- 複雑さ: 基本
- 時間軸: イントラデイ (5m)
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
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 Reversion strategy that trades on deviations from Volume Weighted Average Price.
/// Opens positions when price deviates from VWAP and exits when price returns.
/// </summary>
public class VwapReversionStrategy : Strategy
{
private readonly StrategyParam<decimal> _deviationPercent;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _rsiOversold;
private readonly StrategyParam<decimal> _rsiOverbought;
private Order _pendingOrder;
private VolumeWeightedAveragePrice _vwap;
private RelativeStrengthIndex _rsi;
private DateTime? _sessionDate;
/// <summary>
/// Deviation percentage from VWAP required for entry.
/// </summary>
public decimal DeviationPercent
{
get => _deviationPercent.Value;
set => _deviationPercent.Value = value;
}
/// <summary>
/// Type of candles used for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// RSI period confirming the entries.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// RSI level below which a long entry is confirmed.
/// </summary>
public decimal RsiOversold
{
get => _rsiOversold.Value;
set => _rsiOversold.Value = value;
}
/// <summary>
/// RSI level above which a short entry is confirmed.
/// </summary>
public decimal RsiOverbought
{
get => _rsiOverbought.Value;
set => _rsiOverbought.Value = value;
}
/// <summary>
/// Initialize the VWAP Reversion strategy.
/// </summary>
public VwapReversionStrategy()
{
_deviationPercent = Param(nameof(DeviationPercent), 2m).SetGreaterThanZero()
.SetDisplay("Deviation %", "Deviation from VWAP for entry", "Entry")
.SetOptimize(0.2m, 2.0m, 0.2m);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_stopLossPercent = Param(nameof(StopLossPercent), 2m).SetNotNegative()
.SetDisplay("Stop Loss (%)", "Actual-fill percent stop; zero disables it.", "Protection");
_rsiPeriod = Param(nameof(RsiPeriod), 14).SetGreaterThanZero()
.SetDisplay("RSI Period", "Period of the RSI confirming entries", "Entry")
.SetOptimize(7, 21, 7);
_rsiOversold = Param(nameof(RsiOversold), 30m).SetRange(0m, 100m)
.SetDisplay("RSI Oversold", "Long entries require RSI below this level", "Entry");
_rsiOverbought = Param(nameof(RsiOverbought), 70m).SetRange(0m, 100m)
.SetDisplay("RSI Overbought", "Short entries require RSI above this level", "Entry");
OrderRegistering += order => _pendingOrder = order;
}
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType), (Security, DataType.Level1)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_pendingOrder = null;
_sessionDate = null;
_vwap = null;
_rsi = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_vwap = new VolumeWeightedAveragePrice();
Indicators.Add(_vwap);
_rsi = new RelativeStrengthIndex { Length = RsiPeriod };
Indicators.Add(_rsi);
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
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 subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _vwap);
DrawIndicator(area, _rsi);
DrawOwnTrades(area);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// Native protection runs before the callback, including between finished candles.
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// RSI runs over every finished candle and, unlike VWAP, is never reset per session.
var rsiValue = _rsi.Process(candle);
var date = candle.OpenTime.ToUniversalTime().Date;
if (_sessionDate != date)
{
_sessionDate = date;
// Reset BEFORE processing the first final bar of the new explicit UTC session.
_vwap.Reset();
}
var value = _vwap.Process(candle);
if (value.IsEmpty || !value.IsFormed || !IsFormedAndOnlineAndAllowTrading())
return;
if (_pendingOrder is not null && _pendingOrder.State is not (OrderStates.Done or OrderStates.Failed))
return;
var vwap = value.GetValue<decimal>();
if (vwap <= 0m)
return;
var deviation = 100m * (candle.ClosePrice - vwap) / vwap;
if (Position > 0m && candle.ClosePrice >= vwap)
SellMarket(Position);
else if (Position < 0m && candle.ClosePrice <= vwap)
BuyMarket(Math.Abs(Position));
else if (Position == 0m && rsiValue.IsFormed && deviation < -DeviationPercent && rsiValue.GetValue<decimal>() < RsiOversold)
BuyMarket(Volume);
else if (Position == 0m && rsiValue.IsFormed && deviation > DeviationPercent && rsiValue.GetValue<decimal>() > RsiOverbought)
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, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, OrderStates, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import VolumeWeightedAveragePrice, RelativeStrengthIndex, CandleIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class vwap_reversion_strategy(Strategy):
"""
VWAP Reversion strategy.
Trades on deviations from VWAP, exits when price returns.
"""
def __init__(self):
super(vwap_reversion_strategy, self).__init__()
self._deviation_percent = self.Param("DeviationPercent", 2.0).SetGreaterThanZero().SetDisplay("Deviation %", "Deviation from VWAP for entry", "Entry")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative() \
.SetDisplay("Stop Loss (%)", "Actual-fill percent stop; zero disables it.", "Protection")
self._rsi_period = self.Param("RsiPeriod", 14).SetGreaterThanZero() \
.SetDisplay("RSI Period", "Period of the RSI confirming entries", "Entry")
self._rsi_oversold = self.Param("RsiOversold", 30.0).SetRange(0.0, 100.0) \
.SetDisplay("RSI Oversold", "Long entries require RSI below this level", "Entry")
self._rsi_overbought = self.Param("RsiOverbought", 70.0).SetRange(0.0, 100.0) \
.SetDisplay("RSI Overbought", "Short entries require RSI above this level", "Entry")
self._pending_order = None
self._vwap = None
self._rsi = None
self._session_date = None
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(vwap_reversion_strategy, self).OnReseted()
self._pending_order = None
self._vwap = None
self._rsi = None
self._session_date = None
def OnStarted2(self, time):
super(vwap_reversion_strategy, self).OnStarted2(time)
self._vwap = VolumeWeightedAveragePrice()
self.Indicators.Add(self._vwap)
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self._rsi_period.Value
self.Indicators.Add(self._rsi)
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
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()
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._vwap)
self.DrawIndicator(area, self._rsi)
self.DrawOwnTrades(area)
def _observe_protection_quote(self, quote):
# Native protection runs before this callback, including between signal bars.
pass
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
# RSI runs over every finished candle and, unlike VWAP, is never reset per session.
rsi_value = self._rsi.Process(CandleIndicatorValue(self._rsi, candle))
date = candle.OpenTime.ToUniversalTime().Date
if self._session_date != date:
self._session_date = date
# Reset before the first finished candle of the explicit UTC session.
self._vwap.Reset()
value = self._vwap.Process(CandleIndicatorValue(self._vwap, candle))
if value.IsEmpty or not value.IsFormed or not self.IsFormedAndOnlineAndAllowTrading():
return
if self._pending_order is not None and self._pending_order.State not in (OrderStates.Done, OrderStates.Failed):
return
vwap = value.GetValue[Decimal](None)
if vwap <= 0:
return
close = candle.ClosePrice
deviation = Decimal(100) * (close - vwap) / vwap
threshold = Decimal(self._deviation_percent.Value)
if self.Position > 0 and close >= vwap:
self.SellMarket(self.Position)
elif self.Position < 0 and close <= vwap:
self.BuyMarket(Math.Abs(self.Position))
elif self.Position == 0 and rsi_value.IsFormed and deviation < -threshold \
and rsi_value.GetValue[Decimal](None) < Decimal(self._rsi_oversold.Value):
self.BuyMarket(self.Volume)
elif self.Position == 0 and rsi_value.IsFormed and deviation > threshold \
and rsi_value.GetValue[Decimal](None) > Decimal(self._rsi_overbought.Value):
self.SellMarket(self.Volume)
def CreateClone(self):
return vwap_reversion_strategy()