Digital Filter T01 Strategy
Summary
DigitalF-T01 filter compared with a shifted trigger line. The strategy opens a long position when the digital filter crosses below the trigger and a short position when it crosses above. Stop-loss and take-profit protection are applied in points.
Parameters
- Half Channel - half channel distance added to the trigger line.
- Stop Loss - stop-loss value in points.
- Take Profit - take-profit value in points.
- Candle Type - candle type used for calculations.
using System;
using System.Collections.Generic;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy based on a digital FIR filter crossing a trigger line.
/// Buys when the digital filter crosses below the trigger (reversal expected up).
/// Sells when the filter crosses above the trigger (reversal expected down).
/// </summary>
public class DigitalFilterT01Strategy : Strategy
{
private static readonly decimal[] _coeffs =
{
0.24470985659780m, 0.23139774006970m, 0.20613796947320m, 0.17166230340640m,
0.13146907903600m, 0.08950387549560m, 0.04960091651250m, 0.01502270569607m,
-0.01188033734430m, -0.02989873856137m, -0.03898967104900m, -0.04014113626390m,
-0.03511968085800m, -0.02611613850342m, -0.01539056955666m, -0.00495353651394m,
0.00368588764825m, 0.00963614049782m, 0.01265138888314m, 0.01307496106868m,
0.01169702291063m, 0.00974841844086m, 0.00898900012545m, -0.00649745721156m
};
private readonly StrategyParam<decimal> _halfChannel;
private readonly StrategyParam<DataType> _candleType;
private readonly Queue<decimal> _prices = new();
private decimal _prevDigital;
private decimal _prevTrigger;
private bool _hasPrev;
public decimal HalfChannel
{
get => _halfChannel.Value;
set => _halfChannel.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public DigitalFilterT01Strategy()
{
_halfChannel = Param(nameof(HalfChannel), 50m)
.SetDisplay("Half Channel", "Half channel distance for trigger", "Parameters");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle type used for the strategy", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prices.Clear();
_prevDigital = 0;
_prevTrigger = 0;
_hasPrev = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prices.Clear();
_prevDigital = 0;
_prevTrigger = 0;
_hasPrev = false;
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;
_prices.Enqueue(candle.ClosePrice);
if (_prices.Count > _coeffs.Length)
_prices.Dequeue();
if (_prices.Count < _coeffs.Length)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var arr = new decimal[_coeffs.Length];
_prices.CopyTo(arr, 0);
decimal digital = 0;
for (var i = 0; i < _coeffs.Length; i++)
digital += _coeffs[i] * arr[_coeffs.Length - 1 - i];
var prevClose = arr[^2];
var trigger = digital >= prevClose ? prevClose + HalfChannel : prevClose - HalfChannel;
if (!_hasPrev)
{
_prevDigital = digital;
_prevTrigger = trigger;
_hasPrev = true;
return;
}
// Cross detection
if (_prevDigital > _prevTrigger && digital < trigger)
{
// Filter crossed below trigger - buy signal
if (Position <= 0)
BuyMarket();
}
else if (_prevDigital < _prevTrigger && digital > trigger)
{
// Filter crossed above trigger - sell signal
if (Position >= 0)
SellMarket();
}
_prevDigital = digital;
_prevTrigger = trigger;
}
}
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 System.Collections.Generic import Queue
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class digital_filter_t01_strategy(Strategy):
_coeffs = [
0.24470985659780, 0.23139774006970, 0.20613796947320, 0.17166230340640,
0.13146907903600, 0.08950387549560, 0.04960091651250, 0.01502270569607,
-0.01188033734430, -0.02989873856137, -0.03898967104900, -0.04014113626390,
-0.03511968085800, -0.02611613850342, -0.01539056955666, -0.00495353651394,
0.00368588764825, 0.00963614049782, 0.01265138888314, 0.01307496106868,
0.01169702291063, 0.00974841844086, 0.00898900012545, -0.00649745721156
]
def __init__(self):
super(digital_filter_t01_strategy, self).__init__()
self._half_channel = self.Param("HalfChannel", 50.0) \
.SetDisplay("Half Channel", "Half channel distance for trigger", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle type used for the strategy", "General")
self._prices = []
self._prev_digital = 0.0
self._prev_trigger = 0.0
self._has_prev = False
@property
def half_channel(self):
return self._half_channel.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(digital_filter_t01_strategy, self).OnReseted()
self._prices = []
self._prev_digital = 0.0
self._prev_trigger = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(digital_filter_t01_strategy, self).OnStarted2(time)
self._prices = []
self._prev_digital = 0.0
self._prev_trigger = 0.0
self._has_prev = False
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)
self._prices.append(close)
num_coeffs = len(self._coeffs)
if len(self._prices) > num_coeffs:
self._prices.pop(0)
if len(self._prices) < num_coeffs:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
digital = 0.0
for i in range(num_coeffs):
digital += self._coeffs[i] * self._prices[num_coeffs - 1 - i]
prev_close = self._prices[-2]
half_ch = float(self.half_channel)
if digital >= prev_close:
trigger = prev_close + half_ch
else:
trigger = prev_close - half_ch
if not self._has_prev:
self._prev_digital = digital
self._prev_trigger = trigger
self._has_prev = True
return
# Cross detection
if self._prev_digital > self._prev_trigger and digital < trigger:
if self.Position <= 0:
self.BuyMarket()
elif self._prev_digital < self._prev_trigger and digital > trigger:
if self.Position >= 0:
self.SellMarket()
self._prev_digital = digital
self._prev_trigger = trigger
def CreateClone(self):
return digital_filter_t01_strategy()