Estrategia Fisher Cyber Cycle
Esta estrategia aplica la Transformada de Fisher al indicador Cyber Cycle de Ehlers. Se abre una posición larga cuando la línea Fisher cruza por encima de su línea de disparo, mientras que se abre una posición corta en un cruce descendente. Las posiciones se cierran o revierten en el cruce opuesto.
Detalles
- Criterios de entrada:
- Largo:
Fisher > Trigger&&Fisher anterior <= Trigger anterior - Corto:
Fisher < Trigger&&Fisher anterior >= Trigger anterior
- Largo:
- Criterios de salida:
- Cruce opuesto de Fisher y Trigger
- Stops: Ninguno
- Valores predeterminados:
Alpha= 0.07Length= 8Candle Type= marco temporal de 8 horas
- Filtros:
- Categoría: Seguimiento de tendencia
- Dirección: Largo y Corto
- Indicadores: Fisher Transform, Cyber Cycle
- Stops: Ninguno
- Complejidad: Moderado
- Marco temporal: Medio plazo
- Estacionalidad: No
- Redes neuronales: No
- Divergencia: No
- Nivel de riesgo: Medio
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()