VWAP 策略
使用 VWAP 及其入场通道并提供多种退出模式。当价格收于下方通道上方时做多,收于上方通道下方时做空。支持按 VWAP 或偏差通道退出,并可在连续反向柱后触发安全退出。
参数
- StopPoints:信号柱高/低点缓冲。
- ExitModeLong:多头退出模式。
- ExitModeShort:空头退出模式。
- TargetLongDeviation:多头目标偏差倍数。
- TargetShortDeviation:空头目标偏差倍数。
- EnableSafetyExit:启用安全退出。
- NumOpposingBars:安全退出所需的反向柱数。
- AllowLongs:允许多头。
- AllowShorts:允许空头。
- MinStrength:最小信号强度。
- CandleType:蜡烛类型。
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// VWAP strategy with entry bands, signal-based stops and multiple exit modes.
/// </summary>
public class VwapStrategy : Strategy
{
public enum ExitModes
{
Vwap,
Deviation,
None
}
private readonly StrategyParam<decimal> _stopPoints;
private readonly StrategyParam<ExitModes> _exitModeLong;
private readonly StrategyParam<ExitModes> _exitModeShort;
private readonly StrategyParam<decimal> _targetLongDeviation;
private readonly StrategyParam<decimal> _targetShortDeviation;
private readonly StrategyParam<bool> _enableSafetyExit;
private readonly StrategyParam<int> _numOpposingBars;
private readonly StrategyParam<bool> _allowLongs;
private readonly StrategyParam<bool> _allowShorts;
private readonly StrategyParam<decimal> _minStrength;
private readonly StrategyParam<DataType> _candleType;
private DateTime _sessionDate;
private decimal _sumSrc;
private decimal _sumVol;
private decimal _sumSrcSqVol;
private decimal? _signalLow;
private decimal? _signalHigh;
private int _bullCount;
private int _bearCount;
/// <summary>
/// Stop buffer in price points.
/// </summary>
public decimal StopPoints { get => _stopPoints.Value; set => _stopPoints.Value = value; }
/// <summary>
/// Long exit mode.
/// </summary>
public ExitModes ExitModeLong { get => _exitModeLong.Value; set => _exitModeLong.Value = value; }
/// <summary>
/// Short exit mode.
/// </summary>
public ExitModes ExitModeShort { get => _exitModeShort.Value; set => _exitModeShort.Value = value; }
/// <summary>
/// Deviation multiplier for long targets.
/// </summary>
public decimal TargetLongDeviation { get => _targetLongDeviation.Value; set => _targetLongDeviation.Value = value; }
/// <summary>
/// Deviation multiplier for short targets.
/// </summary>
public decimal TargetShortDeviation { get => _targetShortDeviation.Value; set => _targetShortDeviation.Value = value; }
/// <summary>
/// Enable safety exit.
/// </summary>
public bool EnableSafetyExit { get => _enableSafetyExit.Value; set => _enableSafetyExit.Value = value; }
/// <summary>
/// Number of opposing bars for safety exit.
/// </summary>
public int NumOpposingBars { get => _numOpposingBars.Value; set => _numOpposingBars.Value = value; }
/// <summary>
/// Allow long trades.
/// </summary>
public bool AllowLongs { get => _allowLongs.Value; set => _allowLongs.Value = value; }
/// <summary>
/// Allow short trades.
/// </summary>
public bool AllowShorts { get => _allowShorts.Value; set => _allowShorts.Value = value; }
/// <summary>
/// Minimum signal strength.
/// </summary>
public decimal MinStrength { get => _minStrength.Value; set => _minStrength.Value = value; }
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public VwapStrategy()
{
_stopPoints = Param(nameof(StopPoints), 20m)
.SetGreaterThanZero()
.SetDisplay("Stop Points", "Stop buffer from signal bar", "Parameters");
_exitModeLong = Param(nameof(ExitModeLong), ExitModes.Vwap)
.SetDisplay("Long Exit Mode", string.Empty, "Parameters");
_exitModeShort = Param(nameof(ExitModeShort), ExitModes.Vwap)
.SetDisplay("Short Exit Mode", string.Empty, "Parameters");
_targetLongDeviation = Param(nameof(TargetLongDeviation), 2m)
.SetGreaterThanZero()
.SetDisplay("Long Target Deviation", string.Empty, "Parameters");
_targetShortDeviation = Param(nameof(TargetShortDeviation), 2m)
.SetGreaterThanZero()
.SetDisplay("Short Target Deviation", string.Empty, "Parameters");
_enableSafetyExit = Param(nameof(EnableSafetyExit), true)
.SetDisplay("Enable Safety Exit", string.Empty, "Parameters");
_numOpposingBars = Param(nameof(NumOpposingBars), 3)
.SetGreaterThanZero()
.SetDisplay("Opposing Bars", string.Empty, "Parameters");
_allowLongs = Param(nameof(AllowLongs), true)
.SetDisplay("Allow Longs", string.Empty, "Parameters");
_allowShorts = Param(nameof(AllowShorts), true)
.SetDisplay("Allow Shorts", string.Empty, "Parameters");
_minStrength = Param(nameof(MinStrength), 0.7m)
.SetGreaterThanZero()
.SetDisplay("Min Strength", string.Empty, "Parameters");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "Parameters");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_sessionDate = default;
_sumSrc = 0m;
_sumVol = 0m;
_sumSrcSqVol = 0m;
_signalLow = null;
_signalHigh = null;
_bullCount = 0;
_bearCount = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var sma = new SimpleMovingAverage { Length = 2 };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(sma, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal _dummy)
{
if (candle.State != CandleStates.Finished)
return;
var date = candle.OpenTime.Date;
var vol = candle.TotalVolume;
var src = (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m;
if (date != _sessionDate)
{
_sessionDate = date;
_sumSrc = src * vol;
_sumVol = vol;
_sumSrcSqVol = src * src * vol;
}
else
{
_sumSrc += src * vol;
_sumVol += vol;
_sumSrcSqVol += src * src * vol;
}
if (_sumVol == 0m)
return;
var vwap = _sumSrc / _sumVol;
var variance = _sumSrcSqVol / _sumVol - vwap * vwap;
var stdev = (decimal)Math.Sqrt((double)Math.Max(variance, 0m));
var entryUpper = vwap + stdev * 2m;
var entryLower = vwap - stdev * 2m;
var targetUpperLong = vwap + stdev * TargetLongDeviation;
var targetLowerShort = vwap - stdev * TargetShortDeviation;
var barRange = candle.HighPrice - candle.LowPrice;
var bullStrength = barRange > 0m ? (candle.ClosePrice - candle.LowPrice) / barRange : 0m;
var bearStrength = barRange > 0m ? (candle.HighPrice - candle.ClosePrice) / barRange : 0m;
if (candle.ClosePrice > candle.OpenPrice)
{
_bullCount++;
_bearCount = 0;
}
else if (candle.ClosePrice < candle.OpenPrice)
{
_bearCount++;
_bullCount = 0;
}
else
{
_bullCount = 0;
_bearCount = 0;
}
var longCondition = AllowLongs && candle.OpenPrice < entryLower && candle.ClosePrice > entryLower && bullStrength >= MinStrength && Position == 0;
var shortCondition = AllowShorts && candle.OpenPrice > entryUpper && candle.ClosePrice < entryUpper && bearStrength >= MinStrength && Position == 0;
if (longCondition)
{
BuyMarket();
_signalLow = candle.LowPrice;
_signalHigh = null;
}
else if (shortCondition)
{
SellMarket();
_signalHigh = candle.HighPrice;
_signalLow = null;
}
if (Position == 0)
{
_signalLow = null;
_signalHigh = null;
}
if (Position > 0 && _signalLow.HasValue)
{
var stop = _signalLow.Value - StopPoints;
var exitVwap = ExitModeLong == ExitModes.Vwap && candle.HighPrice >= vwap;
var exitDev = ExitModeLong == ExitModes.Deviation && candle.HighPrice >= targetUpperLong;
if (candle.LowPrice <= stop || (ExitModeLong != ExitModes.None && (exitVwap || exitDev)))
SellMarket();
else if (EnableSafetyExit && _bearCount >= NumOpposingBars)
SellMarket();
}
else if (Position < 0 && _signalHigh.HasValue)
{
var stop = _signalHigh.Value + StopPoints;
var exitVwap = ExitModeShort == ExitModes.Vwap && candle.LowPrice <= vwap;
var exitDev = ExitModeShort == ExitModes.Deviation && candle.LowPrice <= targetLowerShort;
if (candle.HighPrice >= stop || (ExitModeShort != ExitModes.None && (exitVwap || exitDev)))
BuyMarket();
else if (EnableSafetyExit && _bullCount >= NumOpposingBars)
BuyMarket();
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class vwap_strategy(Strategy):
EXIT_VWAP = 0
EXIT_DEVIATION = 1
EXIT_NONE = 2
def __init__(self):
super(vwap_strategy, self).__init__()
self._stop_points = self.Param("StopPoints", 20.0) \
.SetDisplay("Stop Points", "Stop buffer from signal bar", "Parameters")
self._exit_mode_long = self.Param("ExitModeLong", 0) \
.SetDisplay("Long Exit Mode", "", "Parameters")
self._exit_mode_short = self.Param("ExitModeShort", 0) \
.SetDisplay("Short Exit Mode", "", "Parameters")
self._target_long_deviation = self.Param("TargetLongDeviation", 2.0) \
.SetDisplay("Long Target Deviation", "", "Parameters")
self._target_short_deviation = self.Param("TargetShortDeviation", 2.0) \
.SetDisplay("Short Target Deviation", "", "Parameters")
self._enable_safety_exit = self.Param("EnableSafetyExit", True) \
.SetDisplay("Enable Safety Exit", "", "Parameters")
self._num_opposing_bars = self.Param("NumOpposingBars", 3) \
.SetDisplay("Opposing Bars", "", "Parameters")
self._allow_longs = self.Param("AllowLongs", True) \
.SetDisplay("Allow Longs", "", "Parameters")
self._allow_shorts = self.Param("AllowShorts", True) \
.SetDisplay("Allow Shorts", "", "Parameters")
self._min_strength = self.Param("MinStrength", 0.7) \
.SetDisplay("Min Strength", "", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles", "Parameters")
self._session_date = None
self._sum_src = 0.0
self._sum_vol = 0.0
self._sum_src_sq_vol = 0.0
self._signal_low = None
self._signal_high = None
self._bull_count = 0
self._bear_count = 0
@property
def stop_points(self):
return self._stop_points.Value
@property
def exit_mode_long(self):
return self._exit_mode_long.Value
@property
def exit_mode_short(self):
return self._exit_mode_short.Value
@property
def target_long_deviation(self):
return self._target_long_deviation.Value
@property
def target_short_deviation(self):
return self._target_short_deviation.Value
@property
def enable_safety_exit(self):
return self._enable_safety_exit.Value
@property
def num_opposing_bars(self):
return self._num_opposing_bars.Value
@property
def allow_longs(self):
return self._allow_longs.Value
@property
def allow_shorts(self):
return self._allow_shorts.Value
@property
def min_strength(self):
return self._min_strength.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(vwap_strategy, self).OnReseted()
self._session_date = None
self._sum_src = 0.0
self._sum_vol = 0.0
self._sum_src_sq_vol = 0.0
self._signal_low = None
self._signal_high = None
self._bull_count = 0
self._bear_count = 0
def OnStarted2(self, time):
super(vwap_strategy, self).OnStarted2(time)
sma = SimpleMovingAverage()
sma.Length = 2
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def on_process(self, candle, dummy):
if candle.State != CandleStates.Finished:
return
date = candle.OpenTime.Date
vol = float(candle.TotalVolume)
src = (float(candle.HighPrice) + float(candle.LowPrice) + float(candle.ClosePrice)) / 3.0
if self._session_date is None or date != self._session_date:
self._session_date = date
self._sum_src = src * vol
self._sum_vol = vol
self._sum_src_sq_vol = src * src * vol
else:
self._sum_src += src * vol
self._sum_vol += vol
self._sum_src_sq_vol += src * src * vol
if self._sum_vol == 0:
return
vwap = self._sum_src / self._sum_vol
variance = self._sum_src_sq_vol / self._sum_vol - vwap * vwap
stdev = Math.Sqrt(float(max(variance, 0)))
entry_upper = vwap + stdev * 2.0
entry_lower = vwap - stdev * 2.0
target_upper_long = vwap + stdev * self.target_long_deviation
target_lower_short = vwap - stdev * self.target_short_deviation
bar_range = float(candle.HighPrice) - float(candle.LowPrice)
bull_strength = (float(candle.ClosePrice) - float(candle.LowPrice)) / bar_range if bar_range > 0 else 0.0
bear_strength = (float(candle.HighPrice) - float(candle.ClosePrice)) / bar_range if bar_range > 0 else 0.0
if candle.ClosePrice > candle.OpenPrice:
self._bull_count += 1
self._bear_count = 0
elif candle.ClosePrice < candle.OpenPrice:
self._bear_count += 1
self._bull_count = 0
else:
self._bull_count = 0
self._bear_count = 0
close = float(candle.ClosePrice)
opn = float(candle.OpenPrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
long_condition = self.allow_longs and opn < entry_lower and close > entry_lower and bull_strength >= self.min_strength and self.Position == 0
short_condition = self.allow_shorts and opn > entry_upper and close < entry_upper and bear_strength >= self.min_strength and self.Position == 0
if long_condition:
self.BuyMarket()
self._signal_low = low
self._signal_high = None
elif short_condition:
self.SellMarket()
self._signal_high = high
self._signal_low = None
if self.Position == 0:
self._signal_low = None
self._signal_high = None
if self.Position > 0 and self._signal_low is not None:
stop = self._signal_low - self.stop_points
exit_vwap = self.exit_mode_long == self.EXIT_VWAP and high >= vwap
exit_dev = self.exit_mode_long == self.EXIT_DEVIATION and high >= target_upper_long
if low <= stop or (self.exit_mode_long != self.EXIT_NONE and (exit_vwap or exit_dev)):
self.SellMarket()
elif self.enable_safety_exit and self._bear_count >= self.num_opposing_bars:
self.SellMarket()
elif self.Position < 0 and self._signal_high is not None:
stop = self._signal_high + self.stop_points
exit_vwap = self.exit_mode_short == self.EXIT_VWAP and low <= vwap
exit_dev = self.exit_mode_short == self.EXIT_DEVIATION and low <= target_lower_short
if high >= stop or (self.exit_mode_short != self.EXIT_NONE and (exit_vwap or exit_dev)):
self.BuyMarket()
elif self.enable_safety_exit and self._bull_count >= self.num_opposing_bars:
self.BuyMarket()
def CreateClone(self):
return vwap_strategy()