Estrategia de Oscilador de Zona de Volumen Suavizado por Fourier WFSVZ0
Estrategia que utiliza un Oscilador de Zona de Volumen suavizado por Fourier. Abre largo cuando el oscilador sube por encima del umbral y corto cuando cae por debajo del umbral negativo. Opcionalmente cierra posiciones abiertas cuando no hay señal.
Detalles
- Criterios de entrada: Oscilador sube por encima del umbral / cae por debajo del umbral negativo.
- Largo/Corto: Ambos.
- Criterios de salida: Señal opuesta o cierre opcional de todo.
- Stops: Ninguno.
- Valores predeterminados:
VzoLength= 2SmoothLength= 2Threshold= 0mCloseAllPositions= trueCandleType= TimeSpan.FromMinutes(5)
- Filtros:
- Categoría: Volumen
- Dirección: Ambos
- Indicadores: Volume Zone Oscillator
- Stops: Ninguno
- Complejidad: Intermedio
- Marco temporal: Intradía (5m)
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: No
- Nivel de riesgo: Medio
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Fourier Smoothed Volume Zone Oscillator strategy.
/// The Volume Zone Oscillator is 100 times the EMA of volume signed by the close-to-close direction divided by the EMA of volume,
/// both over VzoLength candles. It is smoothed by rebuilding its latest value from the mean and the fundamental harmonic of a
/// discrete Fourier transform over the last SmoothLength values. Above Threshold the strategy is long, below -Threshold short,
/// reversing an opposite position; between them the position is closed when CloseAllPositions is set.
/// </summary>
public class FourierSmoothedVzoStrategy : Strategy
{
private readonly StrategyParam<int> _vzoLength;
private readonly StrategyParam<int> _smoothLength;
private readonly StrategyParam<decimal> _threshold;
private readonly StrategyParam<bool> _closeAllPositions;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _signedVolumeEma;
private ExponentialMovingAverage _volumeEma;
private readonly List<decimal> _vzoValues = [];
private decimal? _prevClose;
/// <summary>
/// EMA length of the oscillator.
/// </summary>
public int VzoLength
{
get => _vzoLength.Value;
set => _vzoLength.Value = value;
}
/// <summary>
/// Window of the Fourier smoothing.
/// </summary>
public int SmoothLength
{
get => _smoothLength.Value;
set => _smoothLength.Value = value;
}
/// <summary>
/// Oscillator level for signals; its negative is the short level.
/// </summary>
public decimal Threshold
{
get => _threshold.Value;
set => _threshold.Value = value;
}
/// <summary>
/// Close the position when there is no signal.
/// </summary>
public bool CloseAllPositions
{
get => _closeAllPositions.Value;
set => _closeAllPositions.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public FourierSmoothedVzoStrategy()
{
_vzoLength = Param(nameof(VzoLength), 2)
.SetGreaterThanZero()
.SetDisplay("VZO Length", "EMA length of the oscillator", "Indicators");
_smoothLength = Param(nameof(SmoothLength), 2)
.SetGreaterThanZero()
.SetDisplay("Smooth Length", "Window of the Fourier smoothing", "Indicators");
_threshold = Param(nameof(Threshold), 0m)
.SetNotNegative()
.SetDisplay("Threshold", "Oscillator level for signals", "Signals");
_closeAllPositions = Param(nameof(CloseAllPositions), true)
.SetDisplay("Close All Positions", "Close the position when there is no signal", "Signals");
_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();
_signedVolumeEma = null;
_volumeEma = null;
_vzoValues.Clear();
_prevClose = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_signedVolumeEma = new ExponentialMovingAverage { Length = VzoLength };
_volumeEma = new ExponentialMovingAverage { Length = VzoLength };
_vzoValues.Clear();
_prevClose = null;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var prevClose = _prevClose;
_prevClose = candle.ClosePrice;
if (prevClose is not decimal lastClose)
return;
var volume = candle.TotalVolume;
var direction = Math.Sign(candle.ClosePrice - lastClose);
var signedEma = _signedVolumeEma.Process(direction * volume, candle.OpenTime, true);
var volumeEma = _volumeEma.Process(volume, candle.OpenTime, true);
if (!_signedVolumeEma.IsFormed || !_volumeEma.IsFormed)
return;
var total = volumeEma.ToDecimal();
if (total == 0)
return;
_vzoValues.Add(100m * signedEma.ToDecimal() / total);
if (_vzoValues.Count > SmoothLength)
_vzoValues.RemoveAt(0);
if (_vzoValues.Count < SmoothLength)
return;
var oscillator = FourierSmooth(_vzoValues);
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (oscillator > Threshold)
{
if (Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
}
else if (oscillator < -Threshold)
{
if (Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
else if (CloseAllPositions)
{
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(-Position);
}
}
private static decimal FourierSmooth(List<decimal> values)
{
var n = values.Count;
var mean = 0.0;
var cosSum = 0.0;
var sinSum = 0.0;
for (var i = 0; i < n; i++)
{
var x = (double)values[i];
var angle = 2 * Math.PI * i / n;
mean += x;
cosSum += x * Math.Cos(angle);
sinSum += x * Math.Sin(angle);
}
mean /= n;
if (n < 2)
return (decimal)mean;
// The Nyquist harmonic of an even window has no sine part and counts once, not twice.
var scale = n == 2 ? 1.0 / n : 2.0 / n;
var last = 2 * Math.PI * (n - 1) / n;
return (decimal)(mean + scale * (cosSum * Math.Cos(last) + sinSum * Math.Sin(last)));
}
}
import clr
import math
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class fourier_smoothed_vzo_strategy(Strategy):
"""
Fourier Smoothed Volume Zone Oscillator strategy.
The Volume Zone Oscillator is 100 times the EMA of volume signed by the close-to-close direction divided by the EMA of volume,
both over VzoLength candles. It is smoothed by rebuilding its latest value from the mean and the fundamental harmonic of a
discrete Fourier transform over the last SmoothLength values. Above Threshold the strategy is long, below -Threshold short,
reversing an opposite position; between them the position is closed when CloseAllPositions is set.
"""
def __init__(self):
super(fourier_smoothed_vzo_strategy, self).__init__()
self._vzo_length = self.Param("VzoLength", 2).SetGreaterThanZero().SetDisplay("VZO Length", "EMA length of the oscillator", "Indicators")
self._smooth_length = self.Param("SmoothLength", 2).SetGreaterThanZero().SetDisplay("Smooth Length", "Window of the Fourier smoothing", "Indicators")
self._threshold = self.Param("Threshold", 0.0).SetNotNegative().SetDisplay("Threshold", "Oscillator level for signals", "Signals")
self._close_all_positions = self.Param("CloseAllPositions", True).SetDisplay("Close All Positions", "Close the position when there is no signal", "Signals")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._signed_volume_ema = None
self._volume_ema = None
self._vzo_values = []
self._prev_close = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(fourier_smoothed_vzo_strategy, self).OnReseted()
self._signed_volume_ema = None
self._volume_ema = None
self._vzo_values = []
self._prev_close = None
def OnStarted2(self, time):
super(fourier_smoothed_vzo_strategy, self).OnStarted2(time)
self._signed_volume_ema = ExponentialMovingAverage()
self._signed_volume_ema.Length = self._vzo_length.Value
self._volume_ema = ExponentialMovingAverage()
self._volume_ema.Length = self._vzo_length.Value
self._vzo_values = []
self._prev_close = None
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
last_close = self._prev_close
self._prev_close = close
if last_close is None:
return
volume = float(candle.TotalVolume)
direction = (close > last_close) - (close < last_close)
signed_ema = process_float(self._signed_volume_ema, Decimal(direction * volume), candle.OpenTime, True)
volume_ema = process_float(self._volume_ema, Decimal(volume), candle.OpenTime, True)
if not self._signed_volume_ema.IsFormed or not self._volume_ema.IsFormed:
return
total = float(volume_ema)
if total == 0:
return
smooth_length = self._smooth_length.Value
self._vzo_values.append(100.0 * float(signed_ema) / total)
if len(self._vzo_values) > smooth_length:
del self._vzo_values[0]
if len(self._vzo_values) < smooth_length:
return
oscillator = self._fourier_smooth(self._vzo_values)
if not self.IsFormedAndOnlineAndAllowTrading():
return
threshold = float(self._threshold.Value)
if oscillator > threshold:
if self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif oscillator < -threshold:
if self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self._close_all_positions.Value:
if self.Position > 0:
self.SellMarket(self.Position)
elif self.Position < 0:
self.BuyMarket(-self.Position)
def _fourier_smooth(self, values):
n = len(values)
mean = 0.0
cos_sum = 0.0
sin_sum = 0.0
for i, x in enumerate(values):
angle = 2.0 * math.pi * i / n
mean += x
cos_sum += x * math.cos(angle)
sin_sum += x * math.sin(angle)
mean /= n
if n < 2:
return mean
# The Nyquist harmonic of an even window has no sine part and counts once, not twice.
scale = 1.0 / n if n == 2 else 2.0 / n
last = 2.0 * math.pi * (n - 1) / n
return mean + scale * (cos_sum * math.cos(last) + sin_sum * math.sin(last))
def CreateClone(self):
return fourier_smoothed_vzo_strategy()