ZPF-Volumen-Filter
Der ZPF-Volumen-Filter kombiniert zwei gleitende Durchschnitte mit einem Volumendurchschnitt. Der Indikatorwert ist die volumengeglättete Differenz zwischen einem schnellen und einem langsamen gleitenden Durchschnitt. Wenn dieser Wert die Nulllinie nach oben kreuzt, wird bullischer Druck angenommen; ein Kreuzen nach unten signalisiert bärischen Druck.
Die Strategie handelt in beide Richtungen. Einstiege erfolgen, wenn der ZPF-Indikator die Nulllinie kreuzt. Positionen werden bei einem entgegengesetzten Kreuzungssignal geschlossen.
Details
- Einstiegskriterien: ZPF kreuzt die Nulllinie nach oben oder unten.
- Long/Short: Beide Richtungen.
- Ausstiegskriterien: Entgegengesetztes Kreuzungssignal an der Nulllinie.
- Stops: Nein.
- Standardwerte:
Length= 12CandleType= TimeSpan.FromHours(4)
- Filter:
- Kategorie: Trend
- Richtung: Beide
- Indikatoren: Moving Average, Volume
- Stops: Nein
- Komplexität: Grundlegend
- Zeitrahmen: Swing
- Saisonalität: Nein
- Neuronale Netze: Nein
- Divergenz: Nein
- Risikolevel: Mittel
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>
/// ZPF Volume Filter strategy.
/// Opens long positions when the ZPF indicator crosses above zero and
/// short positions when it crosses below.
/// </summary>
public class ZpfStrategy : Strategy
{
private readonly StrategyParam<int> _length;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _fastMa;
private SimpleMovingAverage _slowMa;
private SimpleMovingAverage _volumeMa;
private int _barsSinceTrade;
public int Length { get => _length.Value; set => _length.Value = value; }
public int CooldownBars { get => _cooldownBars.Value; set => _cooldownBars.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public ZpfStrategy()
{
_length = Param(nameof(Length), 12)
.SetRange(5, 50)
.SetDisplay("Length", "Base moving average length", "Indicators");
_cooldownBars = Param(nameof(CooldownBars), 6)
.SetGreaterThanZero()
.SetDisplay("Cooldown Bars", "Bars between trades", "Trading");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).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();
_fastMa = default;
_slowMa = default;
_volumeMa = default;
_barsSinceTrade = CooldownBars;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_barsSinceTrade = CooldownBars;
_fastMa = new SimpleMovingAverage { Length = Length };
_slowMa = new SimpleMovingAverage { Length = Length * 2 };
_volumeMa = new SimpleMovingAverage { Length = Length };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_fastMa, _slowMa, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _fastMa);
DrawIndicator(area, _slowMa);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
{
if (candle.State != CandleStates.Finished)
return;
_barsSinceTrade++;
var volResult = _volumeMa.Process(candle.TotalVolume, candle.OpenTime, true);
if (!volResult.IsFormed)
return;
var volumeAvg = volResult.ToDecimal();
// Calculate ZPF value
var zpf = volumeAvg * (fast - slow) / 2m;
// Detect zero line cross
if (zpf > 0 && Position <= 0 && _barsSinceTrade >= CooldownBars)
{
if (Position < 0) BuyMarket();
BuyMarket();
_barsSinceTrade = 0;
}
else if (zpf < 0 && Position >= 0 && _barsSinceTrade >= CooldownBars)
{
if (Position > 0) SellMarket();
SellMarket();
_barsSinceTrade = 0;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class zpf_strategy(Strategy):
def __init__(self):
super(zpf_strategy, self).__init__()
self._length = self.Param("Length", 12) \
.SetDisplay("Length", "Base moving average length", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 6) \
.SetGreaterThanZero() \
.SetDisplay("Cooldown Bars", "Bars between trades", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._volume_ma = None
self._bars_since_trade = 6
@property
def length(self):
return self._length.Value
@property
def cooldown_bars(self):
return self._cooldown_bars.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(zpf_strategy, self).OnReseted()
self._volume_ma = None
self._bars_since_trade = self.cooldown_bars
def OnStarted2(self, time):
super(zpf_strategy, self).OnStarted2(time)
self._bars_since_trade = self.cooldown_bars
fast_ma = SimpleMovingAverage()
fast_ma.Length = self.length
slow_ma = SimpleMovingAverage()
slow_ma.Length = self.length * 2
self._volume_ma = SimpleMovingAverage()
self._volume_ma.Length = self.length
sub = self.SubscribeCandles(self.candle_type)
sub.Bind(fast_ma, slow_ma, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawIndicator(area, fast_ma)
self.DrawIndicator(area, slow_ma)
self.DrawOwnTrades(area)
def process_candle(self, candle, fast, slow):
if candle.State != CandleStates.Finished:
return
self._bars_since_trade += 1
vol_result = process_float(self._volume_ma, candle.TotalVolume, candle.OpenTime, True)
if not vol_result.IsFormed:
return
volume_avg = float(vol_result)
zpf = volume_avg * (float(fast) - float(slow)) / 2.0
if zpf > 0 and self.Position <= 0 and self._bars_since_trade >= self.cooldown_bars:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._bars_since_trade = 0
elif zpf < 0 and self.Position >= 0 and self._bars_since_trade >= self.cooldown_bars:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._bars_since_trade = 0
def CreateClone(self):
return zpf_strategy()