Estrategia de Oscilador de Pronóstico
La estrategia adapta el indicador clásico Forecast Oscillator a StockSharp. Combina una línea base de regresión lineal con suavizado Tillson T3 para resaltar reversiones de tendencia. Una señal de compra aparece cuando el oscilador cruza hacia arriba su línea suavizada mientras la línea suavizada permanece por debajo de cero. Una señal de venta se produce en las condiciones opuestas.
El algoritmo sigue la implementación MQL original y admite habilitar o deshabilitar la apertura y el cierre de posiciones por separado.
Detalles
- Criterios de entrada:
- Largo: El oscilador cruza hacia arriba el T3 y el T3 es negativo.
- Corto: El oscilador cruza hacia abajo el T3 y el T3 es positivo.
- Largo/Corto: Ambas direcciones son compatibles.
- Criterios de salida:
- Señales opuestas si las opciones de cierre correspondientes están habilitadas.
- Stops: Ninguno.
- Filtros: Ninguno.
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>
/// Strategy based on the Forecast Oscillator indicator.
/// Uses linear regression forecast with T3 smoothing for signal generation.
/// </summary>
public class ForecastOscillatorStrategy : Strategy
{
private readonly StrategyParam<int> _length;
private readonly StrategyParam<int> _t3Period;
private readonly StrategyParam<decimal> _bFactor;
private readonly StrategyParam<DataType> _candleType;
private LinearRegression _linReg;
private decimal _b2, _b3, _c1, _c2, _c3, _c4, _w1, _w2;
private decimal _e1, _e2, _e3, _e4, _e5, _e6;
private decimal? _forecastPrev1, _forecastPrev2;
private decimal? _sigPrev1, _sigPrev2, _sigPrev3;
public int Length { get => _length.Value; set => _length.Value = value; }
public int T3Period { get => _t3Period.Value; set => _t3Period.Value = value; }
public decimal BFactor { get => _bFactor.Value; set => _bFactor.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public ForecastOscillatorStrategy()
{
_length = Param(nameof(Length), 15)
.SetGreaterThanZero()
.SetDisplay("Length", "Regression length", "Indicators");
_t3Period = Param(nameof(T3Period), 3)
.SetGreaterThanZero()
.SetDisplay("T3 Period", "T3 smoothing period", "Indicators");
_bFactor = Param(nameof(BFactor), 0.7m)
.SetDisplay("T3 Factor", "T3 smoothing factor", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_b2 = default; _b3 = default; _c1 = default; _c2 = default; _c3 = default; _c4 = default;
_w1 = default; _w2 = default;
_e1 = default; _e2 = default; _e3 = default; _e4 = default; _e5 = default; _e6 = default;
_forecastPrev1 = default; _forecastPrev2 = default;
_sigPrev1 = default; _sigPrev2 = default; _sigPrev3 = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var linReg = new LinearRegression { Length = Length };
_linReg = linReg;
// Pre-calculate T3 constants
var b = BFactor;
_b2 = b * b;
_b3 = _b2 * b;
_c1 = -_b3;
_c2 = 3m * (_b2 + _b3);
_c3 = -3m * (2m * _b2 + b + _b3);
_c4 = 1m + 3m * b + _b3 + 3m * _b2;
var n = 1m + 0.5m * ((decimal)T3Period - 1m);
_w1 = 2m / (n + 1m);
_w2 = 1m - _w1;
_e1 = _e2 = _e3 = _e4 = _e5 = _e6 = 0;
_forecastPrev1 = _forecastPrev2 = null;
_sigPrev1 = _sigPrev2 = _sigPrev3 = null;
Indicators.Add(linReg);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var price = candle.ClosePrice;
var lrResult = _linReg.Process(price, candle.OpenTime, true);
if (!lrResult.IsFormed)
return;
var lrValue = (LinearRegressionValue)lrResult;
if (lrValue.LinearReg is not decimal regValue || regValue == 0)
return;
var forecast = (price - regValue) / regValue * 100m;
// T3 smoothing
_e1 = _w1 * forecast + _w2 * _e1;
_e2 = _w1 * _e1 + _w2 * _e2;
_e3 = _w1 * _e2 + _w2 * _e3;
_e4 = _w1 * _e3 + _w2 * _e4;
_e5 = _w1 * _e4 + _w2 * _e5;
_e6 = _w1 * _e5 + _w2 * _e6;
var t3 = _c1 * _e6 + _c2 * _e5 + _c3 * _e4 + _c4 * _e3;
// Cross detection: forecast crosses signal line
if (_forecastPrev1 != null && _forecastPrev2 != null && _sigPrev1 != null && _sigPrev2 != null && _sigPrev3 != null)
{
var buySignal = _forecastPrev1 > _sigPrev2 && _forecastPrev2 <= _sigPrev3 && _sigPrev1 < 0;
var sellSignal = _forecastPrev1 < _sigPrev2 && _forecastPrev2 >= _sigPrev3 && _sigPrev1 > 0;
if (buySignal && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
}
else if (sellSignal && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
}
}
// Shift previous values
_forecastPrev2 = _forecastPrev1;
_forecastPrev1 = forecast;
_sigPrev3 = _sigPrev2;
_sigPrev2 = _sigPrev1;
_sigPrev1 = t3;
}
}
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 LinearRegression
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class forecast_oscillator_strategy(Strategy):
def __init__(self):
super(forecast_oscillator_strategy, self).__init__()
self._length = self.Param("Length", 15) \
.SetDisplay("Length", "Regression length", "Indicators")
self._t3_period = self.Param("T3Period", 3) \
.SetDisplay("T3 Period", "T3 smoothing period", "Indicators")
self._b_factor = self.Param("BFactor", 0.7) \
.SetDisplay("T3 Factor", "T3 smoothing factor", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._lin_reg = None
self._b2 = 0.0
self._b3 = 0.0
self._c1 = 0.0
self._c2 = 0.0
self._c3 = 0.0
self._c4 = 0.0
self._w1 = 0.0
self._w2 = 0.0
self._e1 = 0.0
self._e2 = 0.0
self._e3 = 0.0
self._e4 = 0.0
self._e5 = 0.0
self._e6 = 0.0
self._forecast_prev1 = None
self._forecast_prev2 = None
self._sig_prev1 = None
self._sig_prev2 = None
self._sig_prev3 = None
@property
def Length(self):
return self._length.Value
@Length.setter
def Length(self, value):
self._length.Value = value
@property
def T3Period(self):
return self._t3_period.Value
@T3Period.setter
def T3Period(self, value):
self._t3_period.Value = value
@property
def BFactor(self):
return self._b_factor.Value
@BFactor.setter
def BFactor(self, value):
self._b_factor.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(forecast_oscillator_strategy, self).OnStarted2(time)
self._lin_reg = LinearRegression()
self._lin_reg.Length = self.Length
b = float(self.BFactor)
self._b2 = b * b
self._b3 = self._b2 * b
self._c1 = -self._b3
self._c2 = 3.0 * (self._b2 + self._b3)
self._c3 = -3.0 * (2.0 * self._b2 + b + self._b3)
self._c4 = 1.0 + 3.0 * b + self._b3 + 3.0 * self._b2
n = 1.0 + 0.5 * (float(self.T3Period) - 1.0)
self._w1 = 2.0 / (n + 1.0)
self._w2 = 1.0 - self._w1
self._e1 = self._e2 = self._e3 = self._e4 = self._e5 = self._e6 = 0.0
self._forecast_prev1 = None
self._forecast_prev2 = None
self._sig_prev1 = None
self._sig_prev2 = None
self._sig_prev3 = None
self.SubscribeCandles(self.CandleType) \
.Bind(self.ProcessCandle) \
.Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
price = float(candle.ClosePrice)
t = candle.OpenTime
lr_result = process_float(self._lin_reg, price, t, True)
if not lr_result.IsFormed:
return
lr_val = lr_result.LinearReg
if lr_val is None:
return
reg_value = float(lr_val)
if reg_value == 0:
return
forecast = (price - reg_value) / reg_value * 100.0
self._e1 = self._w1 * forecast + self._w2 * self._e1
self._e2 = self._w1 * self._e1 + self._w2 * self._e2
self._e3 = self._w1 * self._e2 + self._w2 * self._e3
self._e4 = self._w1 * self._e3 + self._w2 * self._e4
self._e5 = self._w1 * self._e4 + self._w2 * self._e5
self._e6 = self._w1 * self._e5 + self._w2 * self._e6
t3 = self._c1 * self._e6 + self._c2 * self._e5 + self._c3 * self._e4 + self._c4 * self._e3
if self._forecast_prev1 is not None and self._forecast_prev2 is not None and \
self._sig_prev1 is not None and self._sig_prev2 is not None and self._sig_prev3 is not None:
buy_signal = self._forecast_prev1 > self._sig_prev2 and self._forecast_prev2 <= self._sig_prev3 and self._sig_prev1 < 0
sell_signal = self._forecast_prev1 < self._sig_prev2 and self._forecast_prev2 >= self._sig_prev3 and self._sig_prev1 > 0
if buy_signal and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif sell_signal and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._forecast_prev2 = self._forecast_prev1
self._forecast_prev1 = forecast
self._sig_prev3 = self._sig_prev2
self._sig_prev2 = self._sig_prev1
self._sig_prev1 = t3
def OnReseted(self):
super(forecast_oscillator_strategy, self).OnReseted()
self._lin_reg = None
self._b2 = 0.0
self._b3 = 0.0
self._c1 = 0.0
self._c2 = 0.0
self._c3 = 0.0
self._c4 = 0.0
self._w1 = 0.0
self._w2 = 0.0
self._e1 = 0.0
self._e2 = 0.0
self._e3 = 0.0
self._e4 = 0.0
self._e5 = 0.0
self._e6 = 0.0
self._forecast_prev1 = None
self._forecast_prev2 = None
self._sig_prev1 = None
self._sig_prev2 = None
self._sig_prev3 = None
def CreateClone(self):
return forecast_oscillator_strategy()