VWAP Behavioral Bias Filter
The VWAP Behavioral Bias Filter strategy is built around VWAP Behavioral Bias Filter.
Testing indicates an average annual return of about 124%. It performs best in the forex market.
Signals trigger when Behavioral confirms filtered entries on intraday (5m) data. This makes the method suitable for active traders.
Stops rely on ATR multiples and factors like BiasThreshold, BiasWindowSize. Adjust these defaults to balance risk and reward.
Details
- Entry Criteria: see implementation for indicator conditions.
- Long/Short: Both directions.
- Exit Criteria: opposite signal or stop logic.
- Stops: Yes, using indicator-based calculations.
- Default Values:
BiasThreshold = 0.5mBiasWindowSize = 20StopLoss = 2mCandleType = TimeSpan.FromMinutes(5).TimeFrame()
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: Behavioral, Bias
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday (5m)
- Seasonality: No
- Neural Networks: No
- Divergence: No
- Risk Level: Medium
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()