VWAP Volume Strategie
Strategie, die VWAP und Volumen-Indikatoren kombiniert. Kauft/verkauft bei VWAP-Ausbrüchen, die durch überdurchschnittliches Volumen bestätigt werden.
Tests zeigen eine durchschnittliche jährliche Rendite von etwa 52%. Am besten geeignet für den Kryptomarkt.
Diese Strategie nutzt den VWAP zur Bewertung des fairen Wertes und erfordert eine Volumenbestätigung vor dem Trade. Die Idee ist, sich Bewegungen anzuschließen, die durch starke Marktteilnahme unterstützt werden.
Intraday-Trader, die sich auf Volumenkennzahlen konzentrieren, können diese Methode anwenden. Verluste werden durch einen prozentualen Stop begrenzt.
Details
- Einstiegskriterien:
- Long:
Close < VWAP && Volume > AvgVolume * VolumeThreshold - Short:
Close > VWAP && Volume > AvgVolume * VolumeThreshold
- Long:
- Long/Short: Beide
- Ausstiegskriterien:
- Preis kreuzt zurück durch den VWAP
- Stops: Prozentbasiert mit
StopLossPercent - Standardwerte:
VolumePeriod= 20VolumeThreshold= 1.5mStopLossPercent= 2.0mCandleType= TimeSpan.FromMinutes(5).TimeFrame() 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. AvgVolume ist das durchschnittliche Volumen der vorherigen VolumePeriod Kerzen. Ein Einstiegssignal gegen eine offene Position dreht sie.
- Filter:
- Kategorie: Mean Reversion
- Richtung: Beide
- Indikatoren: VWAP, Volumen
- Stops: Ja
- Komplexität: Mittel
- Zeitrahmen: Mittelfristig
- Saisonalität: Nein
- Neuronale Netze: Nein
- Divergenz: Nein
- Risikolevel: Mittel
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// VWAP Volume 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 below VWAP on volume above VolumeThreshold times the average of the previous VolumePeriod candles goes long and a close above
/// VWAP on such volume goes short, reversing an opposite position. The position closes once price crosses back through VWAP,
/// and a percent stop limits the loss.
/// </summary>
public class VwapVolumeStrategy : Strategy
{
private readonly StrategyParam<int> _volumePeriod;
private readonly StrategyParam<decimal> _volumeThreshold;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _volumes = [];
private DateTime? _day;
private decimal _cumulativePriceVolume;
private decimal _cumulativeVolume;
/// <summary>
/// Previous candles the volume is averaged over.
/// </summary>
public int VolumePeriod
{
get => _volumePeriod.Value;
set => _volumePeriod.Value = value;
}
/// <summary>
/// How many times the average volume a candle must exceed.
/// </summary>
public decimal VolumeThreshold
{
get => _volumeThreshold.Value;
set => _volumeThreshold.Value = value;
}
/// <summary>
/// Stop loss percentage from entry price.
/// </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 VwapVolumeStrategy()
{
_volumePeriod = Param(nameof(VolumePeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Volume Period", "Previous candles the volume is averaged over", "Volume");
_volumeThreshold = Param(nameof(VolumeThreshold), 1.5m)
.SetGreaterThanZero()
.SetDisplay("Volume Threshold", "How many times the average volume a candle must exceed", "Volume");
_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();
_volumes.Clear();
_day = null;
_cumulativePriceVolume = 0;
_cumulativeVolume = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_volumes.Clear();
_day = null;
_cumulativePriceVolume = 0;
_cumulativeVolume = 0;
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;
// Volume is compared with the candles before this one.
var average = _volumes.Count == VolumePeriod ? _volumes.Average() : (decimal?)null;
_volumes.Add(candle.TotalVolume);
if (_volumes.Count > VolumePeriod)
_volumes.RemoveAt(0);
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 (average is not decimal avgVolume || _cumulativeVolume <= 0)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var vwap = _cumulativePriceVolume / _cumulativeVolume;
var close = candle.ClosePrice;
var surge = candle.TotalVolume > avgVolume * VolumeThreshold;
if (close < vwap && surge && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (close > vwap && surge && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0 && close > vwap)
SellMarket(Position);
else if (Position < 0 && close < vwap)
BuyMarket(-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, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Strategies import Strategy
class vwap_volume_strategy(Strategy):
"""
VWAP Volume 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 below VWAP on volume above VolumeThreshold times the average of the previous VolumePeriod candles goes long and a close above
VWAP on such volume goes short, reversing an opposite position. The position closes once price crosses back through VWAP,
and a percent stop limits the loss.
"""
def __init__(self):
super(vwap_volume_strategy, self).__init__()
self._volume_period = self.Param("VolumePeriod", 20).SetGreaterThanZero().SetDisplay("Volume Period", "Previous candles the volume is averaged over", "Volume")
self._volume_threshold = self.Param("VolumeThreshold", 1.5).SetGreaterThanZero().SetDisplay("Volume Threshold", "How many times the average volume a candle must exceed", "Volume")
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._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._volumes = []
self._day = None
self._cumulative_price_volume = Decimal(0)
self._cumulative_volume = Decimal(0)
def OnReseted(self):
super(vwap_volume_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(vwap_volume_strategy, self).OnStarted2(time)
self._reset_state()
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
# Volume is compared with the candles before this one.
period = self._volume_period.Value
average = None
if len(self._volumes) == period:
total = Decimal(0)
for volume in self._volumes:
total += volume
average = total / Decimal(period)
self._volumes.append(candle.TotalVolume)
if len(self._volumes) > period:
self._volumes.pop(0)
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 average is None or self._cumulative_volume <= 0:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
vwap = self._cumulative_price_volume / self._cumulative_volume
close = candle.ClosePrice
surge = candle.TotalVolume > average * Decimal(self._volume_threshold.Value)
if close < vwap and surge and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif close > vwap and surge and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0 and close > vwap:
self.SellMarket(self.Position)
elif self.Position < 0 and close < vwap:
self.BuyMarket(-self.Position)
def CreateClone(self):
return vwap_volume_strategy()