VWAP Behavioral Bias Filter
VWAP Behavioral Bias Filter 策略基于 VWAP Behavioral Bias Filter。
测试表明年均收益约为 124%,该策略在外汇市场表现最佳。
当 Behavioral confirms filtered entries 在日内(5m)数据上得到确认时触发信号,适合积极交易者。
止损依赖于 ATR 倍数以及 BiasThreshold, BiasWindowSize 等参数,可根据需要调整以平衡风险与收益。
详情
- 入场条件:参见指标条件实现.
- 多空方向:双向.
- 退出条件:反向信号或止损逻辑.
- 止损:是,基于指标计算.
- 默认值:
BiasThreshold = 0.5mBiasWindowSize = 20StopLoss = 2mCandleType = TimeSpan.FromMinutes(5).TimeFrame()
- 过滤器:
- 分类: 趋势跟随
- 方向: 双向
- 指标: Behavioral, Bias
- 止损: 是
- 复杂度: 中等
- 时间框架: 日内 (5m)
- 季节性: 否
- 神经网络: 否
- 背离: 否
- 风险等级: 中等
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 with Behavioral Bias Filter strategy.
/// Entry condition:
/// Long: Price < VWAP && Bias_Score < -Threshold (oversold with panic)
/// Short: Price > VWAP && Bias_Score > Threshold (overbought with euphoria)
/// Exit condition:
/// Long: Price > VWAP
/// Short: Price < VWAP
/// </summary>
public class VwapWithBehavioralBiasFilterStrategy : Strategy
{
private static readonly object _biasSync = new();
private readonly StrategyParam<decimal> _biasThreshold;
private readonly StrategyParam<int> _biasWindowSize;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<DataType> _candleType;
private VolumeWeightedMovingAverage _vwap;
private decimal _currentBiasScore;
// Tracks recent price movements for bias calculation
private readonly Queue<decimal> _recentPriceMovements = [];
// Flags to track positions
private bool _isLong;
private bool _isShort;
/// <summary>
/// Behavioral bias threshold for entry signal.
/// </summary>
public decimal BiasThreshold
{
get => _biasThreshold.Value;
set => _biasThreshold.Value = value;
}
/// <summary>
/// Window size for behavioral bias calculation.
/// </summary>
public int BiasWindowSize
{
get => _biasWindowSize.Value;
set => _biasWindowSize.Value = value;
}
/// <summary>
/// Stop loss percentage.
/// </summary>
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Type of candles to use.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor with default parameters.
/// </summary>
public VwapWithBehavioralBiasFilterStrategy()
{
_biasThreshold = Param(nameof(BiasThreshold), 0.5m)
.SetGreaterThanZero()
.SetDisplay("Bias Threshold", "Threshold for behavioral bias", "Behavioral Settings")
.SetOptimize(0.3m, 0.7m, 0.1m);
_biasWindowSize = Param(nameof(BiasWindowSize), 20)
.SetGreaterThanZero()
.SetDisplay("Bias Window Size", "Window size for behavioral bias calculation", "Behavioral Settings")
.SetOptimize(10, 30, 5);
_stopLoss = Param(nameof(StopLoss), 2m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss (%)", "Stop Loss percentage from entry price", "Risk Management")
.SetOptimize(1m, 3m, 0.5m);
_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();
_isLong = false;
_isShort = false;
_currentBiasScore = 0;
_recentPriceMovements.Clear();
_vwap = default;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Initialize VWAP indicator
_vwap = new();
// Subscribe to candles and bind indicator
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_vwap, ProcessCandle)
.Start();
// Create chart visualization if available
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _vwap);
DrawOwnTrades(area);
}
// Enable position protection with stop-loss
StartProtection(
new Unit(0), // No take profit
new Unit(StopLoss, UnitTypes.Percent) // Stop-loss as percentage
);
}
/// <summary>
/// Process each candle and VWAP value.
/// </summary>
private void ProcessCandle(ICandleMessage candle, decimal vwapValue)
{
// Skip unfinished candles
if (candle.State != CandleStates.Finished)
return;
// Check if strategy is ready to trade
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Update behavioral bias score
UpdateBehavioralBias(candle);
var price = candle.ClosePrice;
var priceBelowVwap = price < vwapValue;
var priceAboveVwap = price > vwapValue;
// Trading logic
// Entry conditions
// Long entry: Price below VWAP and negative bias score (panic)
if (priceBelowVwap && _currentBiasScore < -BiasThreshold && !_isLong && Position <= 0)
{
LogInfo($"Long signal: Price {price} < VWAP {vwapValue}, Bias {_currentBiasScore} < -Threshold {-BiasThreshold}");
BuyMarket(Volume);
_isLong = true;
_isShort = false;
}
// Short entry: Price above VWAP and positive bias score (euphoria)
else if (priceAboveVwap && _currentBiasScore > BiasThreshold && !_isShort && Position >= 0)
{
LogInfo($"Short signal: Price {price} > VWAP {vwapValue}, Bias {_currentBiasScore} > Threshold {BiasThreshold}");
SellMarket(Volume);
_isShort = true;
_isLong = false;
}
// Exit conditions
// Exit long: Price rises above VWAP
if (_isLong && priceAboveVwap && Position > 0)
{
LogInfo($"Exit long: Price {price} > VWAP {vwapValue}");
SellMarket(Math.Abs(Position));
_isLong = false;
}
// Exit short: Price falls below VWAP
else if (_isShort && priceBelowVwap && Position < 0)
{
LogInfo($"Exit short: Price {price} < VWAP {vwapValue}");
BuyMarket(Math.Abs(Position));
_isShort = false;
}
}
/// <summary>
/// Update behavioral bias score based on recent price movements.
/// This is a simplified model of behavioral biases in markets.
/// </summary>
private void UpdateBehavioralBias(ICandleMessage candle)
{
lock (_biasSync)
{
// Calculate price movement %
decimal priceChange = 0;
if (candle.OpenPrice != 0)
{
priceChange = (candle.ClosePrice - candle.OpenPrice) / candle.OpenPrice * 100;
}
// Add to queue
_recentPriceMovements.Enqueue(priceChange);
// Maintain window size
while (_recentPriceMovements.Count > BiasWindowSize)
{
_recentPriceMovements.Dequeue();
}
// Not enough data yet
if (_recentPriceMovements.Count < 5)
{
_currentBiasScore = 0;
return;
}
var movements = _recentPriceMovements.ToArray();
// Calculate various components of bias score
decimal recentMovement = 0;
for (var i = Math.Max(0, movements.Length - 5); i < movements.Length; i++)
recentMovement += movements[i];
decimal sum = 0;
decimal sumSquared = 0;
foreach (var movement in movements)
{
sum += movement;
sumSquared += movement * movement;
}
var avg = sum / movements.Length;
var variance = (sumSquared / movements.Length) - (avg * avg);
var volatility = (decimal)Math.Sqrt((double)Math.Max(0, variance));
decimal previousMove = 0;
int consecutiveSameDirection = 0;
int maxConsecutive = 0;
foreach (var movement in movements)
{
if (previousMove != 0 && Math.Sign(movement) == Math.Sign(previousMove))
{
consecutiveSameDirection++;
maxConsecutive = Math.Max(maxConsecutive, consecutiveSameDirection);
}
else
{
consecutiveSameDirection = 0;
}
previousMove = movement;
}
decimal bodySize = Math.Abs(candle.ClosePrice - candle.OpenPrice);
decimal totalSize = candle.HighPrice - candle.LowPrice;
decimal bodyRatio = totalSize > 0 ? bodySize / totalSize : 0;
_currentBiasScore = 0;
_currentBiasScore += Math.Min(0.5m, Math.Max(-0.5m, recentMovement / 2));
_currentBiasScore += Math.Sign(recentMovement) * Math.Min(0.3m, volatility / 10);
_currentBiasScore += Math.Sign(recentMovement) * Math.Min(0.2m, maxConsecutive / 10.0m);
if (candle.ClosePrice > candle.OpenPrice)
_currentBiasScore += bodyRatio * 0.2m;
else
_currentBiasScore -= bodyRatio * 0.2m;
_currentBiasScore = Math.Max(-1.0m, Math.Min(1.0m, _currentBiasScore));
LogInfo($"Behavioral Bias: {_currentBiasScore}, Components: Momentum={recentMovement/2}, Volatility={volatility/10}, Herding={maxConsecutive/10.0m}, Candle={bodyRatio*0.2m}");
}
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Indicators import VolumeWeightedMovingAverage
from StockSharp.Algo.Strategies import Strategy
class vwap_with_behavioral_bias_filter_strategy(Strategy):
"""
VWAP with Behavioral Bias Filter strategy.
"""
def __init__(self):
super(vwap_with_behavioral_bias_filter_strategy, self).__init__()
self._bias_threshold = self.Param("BiasThreshold", 0.5) \
.SetGreaterThanZero() \
.SetDisplay("Bias Threshold", "Threshold for behavioral bias", "Behavioral Settings")
self._bias_window_size = self.Param("BiasWindowSize", 20) \
.SetGreaterThanZero() \
.SetDisplay("Bias Window Size", "Window size for behavioral bias calculation", "Behavioral Settings")
self._stop_loss = self.Param("StopLoss", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss (%)", "Stop Loss percentage from entry price", "Risk Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._vwap = None
self._current_bias_score = 0.0
self._recent_price_movements = []
self._is_long = False
self._is_short = False
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type)]
def OnReseted(self):
super(vwap_with_behavioral_bias_filter_strategy, self).OnReseted()
self._is_long = False
self._is_short = False
self._current_bias_score = 0.0
self._recent_price_movements = []
self._vwap = None
def OnStarted2(self, time):
super(vwap_with_behavioral_bias_filter_strategy, self).OnStarted2(time)
self._vwap = VolumeWeightedMovingAverage()
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._vwap, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._vwap)
self.DrawOwnTrades(area)
self.StartProtection(
Unit(0),
Unit(float(self._stop_loss.Value), UnitTypes.Percent)
)
def ProcessCandle(self, candle, vwap_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
self.UpdateBehavioralBias(candle)
price = float(candle.ClosePrice)
vwap = float(vwap_value)
price_below_vwap = price < vwap
price_above_vwap = price > vwap
threshold = float(self._bias_threshold.Value)
if price_below_vwap and self._current_bias_score < -threshold and not self._is_long and self.Position <= 0:
self.BuyMarket(self.Volume)
self._is_long = True
self._is_short = False
elif price_above_vwap and self._current_bias_score > threshold and not self._is_short and self.Position >= 0:
self.SellMarket(self.Volume)
self._is_short = True
self._is_long = False
if self._is_long and price_above_vwap and self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
self._is_long = False
elif self._is_short and price_below_vwap and self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self._is_short = False
def UpdateBehavioralBias(self, candle):
price_change = 0.0
if candle.OpenPrice != 0:
price_change = float((candle.ClosePrice - candle.OpenPrice) / candle.OpenPrice * 100)
self._recent_price_movements.append(price_change)
window = int(self._bias_window_size.Value)
while len(self._recent_price_movements) > window:
self._recent_price_movements.pop(0)
if len(self._recent_price_movements) < 5:
self._current_bias_score = 0.0
return
movements = self._recent_price_movements
recent_movement = 0.0
start = max(0, len(movements) - 5)
for i in range(start, len(movements)):
recent_movement += movements[i]
total = 0.0
total_sq = 0.0
for m in movements:
total += m
total_sq += m * m
avg = total / len(movements)
variance = (total_sq / len(movements)) - (avg * avg)
volatility = Math.Sqrt(max(0.0, variance))
previous_move = 0.0
consecutive_same = 0
max_consecutive = 0
for m in movements:
if previous_move != 0 and Math.Sign(m) == Math.Sign(previous_move):
consecutive_same += 1
max_consecutive = max(max_consecutive, consecutive_same)
else:
consecutive_same = 0
previous_move = m
body_size = float(abs(candle.ClosePrice - candle.OpenPrice))
total_size = float(candle.HighPrice - candle.LowPrice)
body_ratio = body_size / total_size if total_size > 0 else 0.0
self._current_bias_score = 0.0
self._current_bias_score += min(0.5, max(-0.5, recent_movement / 2.0))
self._current_bias_score += Math.Sign(recent_movement) * min(0.3, volatility / 10.0)
self._current_bias_score += Math.Sign(recent_movement) * min(0.2, max_consecutive / 10.0)
if candle.ClosePrice > candle.OpenPrice:
self._current_bias_score += body_ratio * 0.2
else:
self._current_bias_score -= body_ratio * 0.2
self._current_bias_score = max(-1.0, min(1.0, self._current_bias_score))
def CreateClone(self):
return vwap_with_behavioral_bias_filter_strategy()