Стратегия Fisher Cyber Cycle
Стратегия применяет преобразование Фишера к индикатору Cyber Cycle Джона Эллерса. Длинная позиция открывается, когда линия Fisher пересекает свою триггерную линию снизу вверх, короткая — при пересечении сверху вниз. Позиция закрывается или разворачивается при обратном пересечении.
Подробности
- Условия входа:
- Long:
Fisher > Trigger&&предыдущий Fisher <= предыдущего Trigger - Short:
Fisher < Trigger&&предыдущий Fisher >= предыдущего Trigger
- Long:
- Условия выхода:
- Обратное пересечение линий Fisher и Trigger
- Стопы: Нет
- Значения по умолчанию:
Alpha= 0.07Length= 8Candle Type= таймфрейм 8 часов
- Фильтры:
- Категория: Следование тренду
- Направление: Long и Short
- Индикаторы: Fisher Transform, Cyber Cycle
- Стопы: Нет
- Сложность: Средняя
- Таймфрейм: Среднесрочный
- Сезонность: Нет
- Нейросети: Нет
- Дивергенция: Нет
- Уровень риска: Средний
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Fisher Cyber Cycle crossover strategy.
/// Buys when Fisher line crosses above its trigger and sells on cross below.
/// </summary>
public class FisherCyberCycleStrategy : Strategy
{
private readonly StrategyParam<decimal> _alpha;
private readonly StrategyParam<int> _length;
private readonly StrategyParam<int> _cooldownBars;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevFisher;
private decimal _prevTrigger;
private bool _initialized;
// Cyber cycle state
private readonly decimal[] _price = new decimal[4];
private readonly decimal[] _smooth = new decimal[4];
private readonly decimal[] _cycle = new decimal[3];
private decimal _prevFish;
private int _count;
private int _barsSinceTrade;
/// <summary>
/// Smoothing factor for cycle calculation.
/// </summary>
public decimal Alpha
{
get => _alpha.Value;
set => _alpha.Value = value;
}
/// <summary>
/// Normalization window length.
/// </summary>
public int Length
{
get => _length.Value;
set => _length.Value = value;
}
/// <summary>
/// Bars to wait after a completed trade.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Type of candles to use for processing.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="FisherCyberCycleStrategy"/>.
/// </summary>
public FisherCyberCycleStrategy()
{
_alpha = Param(nameof(Alpha), 0.07m)
.SetDisplay("Alpha", "Smoothing factor", "Indicators")
.SetRange(0.01m, 0.5m);
_length = Param(nameof(Length), 8)
.SetGreaterThanZero()
.SetDisplay("Length", "Normalization window", "Indicators")
.SetOptimize(5, 20, 1);
_cooldownBars = Param(nameof(CooldownBars), 1)
.SetDisplay("Cooldown Bars", "Bars to wait after a completed trade", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFisher = 0m;
_prevTrigger = 0m;
_initialized = false;
_prevFish = 0m;
_count = 0;
_barsSinceTrade = CooldownBars;
Array.Clear(_price, 0, _price.Length);
Array.Clear(_smooth, 0, _smooth.Length);
Array.Clear(_cycle, 0, _cycle.Length);
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
OnReseted();
var highest = new Highest { Length = Length };
var lowest = new Lowest { Length = Length };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_barsSinceTrade < CooldownBars)
_barsSinceTrade++;
var price = (candle.HighPrice + candle.LowPrice) / 2m;
var t = candle.OpenTime;
// Shift stored values
_price[3] = _price[2];
_price[2] = _price[1];
_price[1] = _price[0];
_price[0] = price;
_smooth[3] = _smooth[2];
_smooth[2] = _smooth[1];
_smooth[1] = _smooth[0];
_smooth[0] = (_price[0] + 2m * _price[1] + 2m * _price[2] + _price[3]) / 6m;
_cycle[2] = _cycle[1];
_cycle[1] = _cycle[0];
if (_count < 3)
_cycle[0] = (_price[0] + 2m * _price[1] + _price[2]) / 4m;
else
{
var k0 = (decimal)Math.Pow((double)(1m - 0.5m * Alpha), 2);
var k1 = 2m;
var k2 = 2m * (1m - Alpha);
var k3 = (decimal)Math.Pow((double)(1m - Alpha), 2);
_cycle[0] = k0 * (_smooth[0] - k1 * _smooth[1] + _smooth[2]) + k2 * _cycle[1] - k3 * _cycle[2];
}
_count++;
var hhResult = highest.Process(new DecimalIndicatorValue(highest, _cycle[0], t) { IsFinal = true });
var llResult = lowest.Process(new DecimalIndicatorValue(lowest, _cycle[0], t) { IsFinal = true });
if (!highest.IsFormed || !lowest.IsFormed)
return;
var hh = hhResult.ToDecimal();
var ll = llResult.ToDecimal();
var value1 = hh != ll ? (_cycle[0] - ll) / (hh - ll) : 0m;
// Clamp to avoid log domain error
var normalized = 1.98m * (value1 - 0.5m);
if (normalized >= 0.999m) normalized = 0.999m;
if (normalized <= -0.999m) normalized = -0.999m;
var fish = 0.5m * (decimal)Math.Log((double)((1m + normalized) / (1m - normalized)));
var trigger = _prevFish;
_prevFish = fish;
if (!_initialized)
{
_prevFisher = fish;
_prevTrigger = trigger;
_initialized = true;
return;
}
var crossUp = _prevFisher <= _prevTrigger && fish > trigger;
var crossDown = _prevFisher >= _prevTrigger && fish < trigger;
if (_barsSinceTrade >= CooldownBars)
{
if (crossUp && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_barsSinceTrade = 0;
}
else if (crossDown && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_barsSinceTrade = 0;
}
}
_prevFisher = fish;
_prevTrigger = trigger;
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
import math
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class fisher_cyber_cycle_strategy(Strategy):
def __init__(self):
super(fisher_cyber_cycle_strategy, self).__init__()
self._alpha = self.Param("Alpha", 0.07) \
.SetDisplay("Alpha", "Smoothing factor", "Indicators")
self._length = self.Param("Length", 8) \
.SetDisplay("Length", "Normalization window", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 1) \
.SetDisplay("Cooldown Bars", "Bars to wait after a completed trade", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._prev_fisher = 0.0
self._prev_trigger = 0.0
self._initialized = False
self._prev_fish = 0.0
self._count = 0
self._bars_since_trade = 0
self._price = [0.0, 0.0, 0.0, 0.0]
self._smooth = [0.0, 0.0, 0.0, 0.0]
self._cycle = [0.0, 0.0, 0.0]
self._highest = None
self._lowest = None
@property
def Alpha(self):
return self._alpha.Value
@Alpha.setter
def Alpha(self, value):
self._alpha.Value = value
@property
def Length(self):
return self._length.Value
@Length.setter
def Length(self, value):
self._length.Value = value
@property
def CooldownBars(self):
return self._cooldown_bars.Value
@CooldownBars.setter
def CooldownBars(self, value):
self._cooldown_bars.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(fisher_cyber_cycle_strategy, self).OnStarted2(time)
self._prev_fisher = 0.0
self._prev_trigger = 0.0
self._initialized = False
self._prev_fish = 0.0
self._count = 0
self._bars_since_trade = self.CooldownBars
self._price = [0.0, 0.0, 0.0, 0.0]
self._smooth = [0.0, 0.0, 0.0, 0.0]
self._cycle = [0.0, 0.0, 0.0]
self._highest = Highest()
self._highest.Length = self.Length
self._lowest = Lowest()
self._lowest.Length = self.Length
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._bars_since_trade < self.CooldownBars:
self._bars_since_trade += 1
price = (float(candle.HighPrice) + float(candle.LowPrice)) / 2.0
t = candle.OpenTime
alpha = float(self.Alpha)
self._price[3] = self._price[2]
self._price[2] = self._price[1]
self._price[1] = self._price[0]
self._price[0] = price
self._smooth[3] = self._smooth[2]
self._smooth[2] = self._smooth[1]
self._smooth[1] = self._smooth[0]
self._smooth[0] = (self._price[0] + 2.0 * self._price[1] + 2.0 * self._price[2] + self._price[3]) / 6.0
self._cycle[2] = self._cycle[1]
self._cycle[1] = self._cycle[0]
if self._count < 3:
self._cycle[0] = (self._price[0] + 2.0 * self._price[1] + self._price[2]) / 4.0
else:
k0 = (1.0 - 0.5 * alpha) ** 2
k1 = 2.0
k2 = 2.0 * (1.0 - alpha)
k3 = (1.0 - alpha) ** 2
self._cycle[0] = k0 * (self._smooth[0] - k1 * self._smooth[1] + self._smooth[2]) + k2 * self._cycle[1] - k3 * self._cycle[2]
self._count += 1
hh_result = process_float(self._highest, self._cycle[0], t, True)
ll_result = process_float(self._lowest, self._cycle[0], t, True)
if not self._highest.IsFormed or not self._lowest.IsFormed:
return
hh = float(hh_result)
ll = float(ll_result)
if hh != ll:
value1 = (self._cycle[0] - ll) / (hh - ll)
else:
value1 = 0.0
normalized = 1.98 * (value1 - 0.5)
if normalized >= 0.999:
normalized = 0.999
if normalized <= -0.999:
normalized = -0.999
fish = 0.5 * math.log((1.0 + normalized) / (1.0 - normalized))
trigger = self._prev_fish
self._prev_fish = fish
if not self._initialized:
self._prev_fisher = fish
self._prev_trigger = trigger
self._initialized = True
return
cross_up = self._prev_fisher <= self._prev_trigger and fish > trigger
cross_down = self._prev_fisher >= self._prev_trigger and fish < trigger
if self._bars_since_trade >= self.CooldownBars:
pos = self.Position
if cross_up and pos <= 0:
self.BuyMarket(self.Volume + abs(pos))
self._bars_since_trade = 0
elif cross_down and pos >= 0:
self.SellMarket(self.Volume + abs(pos))
self._bars_since_trade = 0
self._prev_fisher = fish
self._prev_trigger = trigger
def OnReseted(self):
super(fisher_cyber_cycle_strategy, self).OnReseted()
self._prev_fisher = 0.0
self._prev_trigger = 0.0
self._initialized = False
self._prev_fish = 0.0
self._count = 0
self._bars_since_trade = self.CooldownBars
self._price = [0.0, 0.0, 0.0, 0.0]
self._smooth = [0.0, 0.0, 0.0, 0.0]
self._cycle = [0.0, 0.0, 0.0]
def CreateClone(self):
return fisher_cyber_cycle_strategy()