Prognose-Oszillator-Strategie
Die Strategie adaptiert den klassischen Forecast-Oszillator-Indikator für StockSharp. Sie kombiniert eine lineare Regressionsbasis mit Tillson-T3-Glättung, um Trendumkehrungen hervorzuheben. Ein Kaufsignal erscheint, wenn der Oszillator seine geglättete Linie von unten kreuzt, während die geglättete Linie unter null verbleibt. Ein Verkaufssignal wird bei den entgegengesetzten Bedingungen erzeugt.
Der Algorithmus folgt der ursprünglichen MQL-Implementierung und unterstützt das separate Aktivieren oder Deaktivieren der Positionseröffnung und -schließung.
Details
- Einstiegskriterien:
- Long: Oszillator kreuzt T3 von unten und T3 ist negativ.
- Short: Oszillator kreuzt T3 von oben und T3 ist positiv.
- Long/Short: Beide Richtungen werden unterstützt.
- Ausstiegskriterien:
- Entgegengesetzte Signale, wenn die entsprechenden Schließungsoptionen aktiviert sind.
- Stops: Keine.
- Filter: Keine.
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()