Estrategia VWAP con Filtro de Sesgo Conductual
La estrategia VWAP Behavioral Bias Filter está construida en torno al filtro de sesgo conductual de VWAP.
Las pruebas indican un retorno anual promedio de aproximadamente 124%. Funciona mejor en el mercado de divisas.
Las señales se activan cuando el filtro Behavioral confirma entradas filtradas en datos intradía (5m). Esto hace que el método sea adecuado para traders activos.
Los stops se basan en múltiplos de ATR y factores como BiasThreshold, BiasWindowSize. Ajuste estos valores predeterminados para equilibrar el riesgo y la recompensa.
Detalles
- Criterios de entrada: ver implementación para condiciones de indicadores.
- Largo/Corto: Ambos direcciones.
- Criterios de salida: señal opuesta o lógica de stop.
- Stops: Sí, usando cálculos basados en indicadores.
- Valores predeterminados:
BiasThreshold = 0.5mBiasWindowSize = 20StopLoss = 2mCandleType = TimeSpan.FromMinutes(5).TimeFrame()
- Filtros:
- Categoría: Seguimiento de tendencia
- Dirección: Ambos
- Indicadores: Behavioral, Bias
- Stops: Sí
- Complejidad: Intermedio
- Marco temporal: Intradía (5m)
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: No
- Nivel de riesgo: Medio
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()