VWAPバウンス戦略
出来高加重平均価格(VWAP)は人気のあるイントラデイのベンチマークです。価格がVWAPから大幅に乖離し、その後VWAPへ向かうローソク足を印刷すると、しばしば短い反転の動きが続きます。この戦略はそのバウンスを取引します。
テストでは年平均リターンが約130%であることが示されています。株式市場で最も良いパフォーマンスを発揮します。
各バーごとに現在のVWAPが計算されます。強気のローソク足がVWAPの下で終値を付けるとシステムはロングに入り、弱気のローソク足がVWAPの上で終値を付けるとショートに入ります。固定のストップロスのパーセントがリスクを管理し、ポジションは通常、反対のシグナルが形成されるかストップに達するまでのみ保持されます。
イントラデイの極値に逆張りするため、この手法は強いトレンドよりもレンジ相場で最もよく機能します。
詳細
- エントリー条件: 強気のローソク足でVWAPの下での終値、または弱気のローソク足でVWAPの上での終値。
- ロング/ショート: 両方。
- エグジット条件: 反対のシグナルまたはストップロス。
- ストップ: はい、パーセントベース。
- デフォルト値:
CandleType= 5 minuteStopLossPercent= 2 VWAPは毎UTC日の始めにリセットされ、各ローソク足の典型価格を出来高で加重します。
- フィルター:
- カテゴリ: 平均回帰
- 方向: 両方
- インジケーター: VWAP
- ストップ: はい
- 複雑さ: 基本
- 時間軸: イントラデイ
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
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 Bounce strategy.
/// The VWAP restarts every UTC day from the typical price of each candle weighted by its volume. A bullish candle closing below
/// the VWAP turns the position long and a bearish candle closing above it turns it short; a percent stop limits the loss.
/// </summary>
public class VwapBounceStrategy : Strategy
{
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private DateTime? _day;
private decimal _priceVolume;
private decimal _volume;
/// <summary>
/// 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 VwapBounceStrategy()
{
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "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();
_day = null;
_priceVolume = default;
_volume = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_day = null;
_priceVolume = default;
_volume = default;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), 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);
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)
{
if (candle.State != CandleStates.Finished)
return;
var day = candle.OpenTime.Date;
if (_day != day)
{
_day = day;
_priceVolume = 0;
_volume = 0;
}
var typical = (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m;
_priceVolume += typical * candle.TotalVolume;
_volume += candle.TotalVolume;
if (_volume <= 0 || !IsFormedAndOnlineAndAllowTrading())
return;
var vwap = _priceVolume / _volume;
var close = candle.ClosePrice;
if (close > candle.OpenPrice && close < vwap && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (close < candle.OpenPrice && close > vwap && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
}
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, Math, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Strategies import Strategy
class vwap_bounce_strategy(Strategy):
"""
VWAP Bounce strategy.
The VWAP restarts every UTC day from the typical price of each candle weighted by its volume. A bullish candle closing below
the VWAP turns the position long and a bearish candle closing above it turns it short; a percent stop limits the loss.
"""
def __init__(self):
super(vwap_bounce_strategy, self).__init__()
self._stop_loss_percent = self.Param("StopLossPercent", 2.0).SetNotNegative().SetDisplay("Stop Loss %", "Stop loss percentage from entry price", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._day = None
self._price_volume = Decimal(0)
self._volume = Decimal(0)
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(vwap_bounce_strategy, self).OnReseted()
self._day = None
self._price_volume = Decimal(0)
self._volume = Decimal(0)
def OnStarted2(self, time):
super(vwap_bounce_strategy, self).OnStarted2(time)
self._day = None
self._price_volume = Decimal(0)
self._volume = Decimal(0)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), 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.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):
if candle.State != CandleStates.Finished:
return
day = candle.OpenTime.Date
if self._day is None or self._day != day:
self._day = day
self._price_volume = Decimal(0)
self._volume = Decimal(0)
typical = (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / Decimal(3)
self._price_volume += typical * candle.TotalVolume
self._volume += candle.TotalVolume
if self._volume <= 0 or not self.IsFormedAndOnlineAndAllowTrading():
return
vwap = self._price_volume / self._volume
close = candle.ClosePrice
if close > candle.OpenPrice and close < vwap and self.Position <= 0:
self.BuyMarket(self.Volume + Math.Abs(self.Position))
elif close < candle.OpenPrice and close > vwap and self.Position >= 0:
self.SellMarket(self.Volume + Math.Abs(self.Position))
def CreateClone(self):
return vwap_bounce_strategy()