Diese Strategie portiert den Exp_FisherCGOscillator MetaTrader 5-Expertenberater auf die High-Level-API von StockSharp. Sie bildet den Fisher Center of Gravity-Oszillator und seine Triggerlinie nach, wertet Signale auf einer konfigurierbaren historischen Bar aus und reproduziert den originalen Stop/Take-Workflow mit StockSharp-Orders und Risikohelferklassen.
Funktionsweise
Indikator-Pipeline – jede abgeschlossene Kerze wird durch den Fisher CG-Oszillator geleitet: Medianpreise speisen eine Center-of-Gravity-Schleife, Werte werden über die letzten Length Bars normalisiert, und eine Fisher-Transformation erzeugt die Oszillatorlinie. Die Triggerlinie ist einfach der um eine Bar verzögerte Oszillator.
Signalextraktion – die Strategie untersucht zwei historische Lesungen, die durch SignalBar definiert werden. Sie eröffnet einen Long, wenn der ältere Oszillatorwert (SignalBar + 1) über seinem Trigger liegt, während der neuere Wert (SignalBar) wieder über den Trigger kreuzt und eine bullische Wende signalisiert. Shorts spiegeln diese Logik auf der bärischen Seite.
Ausstiegsbehandlung – Long-Ausstiege erfolgen, sobald der ältere Oszillator unter seinen Trigger fällt, während Short-Ausstiege ausgelöst werden, wenn er über den Trigger steigt, was den sofortigen Schließindikatoren des EA entspricht. Entgegengesetzte Einstiege schließen die aktive Position vor der Umkehr.
Bar-für-Bar-Verarbeitung – alles läuft auf abgeschlossenen Kerzen aus CandleType; es werden keine Intrabar-Trades generiert, was deterministische Backtests sicherstellt und dem "neue Bar"-Gate des EA entspricht.
Risikomanagement und Positionsgrößenbestimmung
Stops/Ziele – StopLossPoints und TakeProfitPoints werden in Instrumentschritten ausgedrückt und über Security.PriceStep in absolute Preisabstände übersetzt.
Volumenkontrolle – SizingMode = FixedVolume sendet das konstante FixedVolume. SizingMode = PortfolioShare konvertiert DepositShare des aktuellen Portfoliowerts in Kontrakte unter Verwendung des letzten Schlusskurses und VolumeStep.
Einzelposition – die Strategie flacht immer ab, bevor sie auf die entgegengesetzte Seite eintritt, um gleichzeitig gehedgte Positionen zu vermeiden.
Parameter
Parameter
Beschreibung
CandleType
Abonnierter Zeitrahmen für Kerzen und Indikatorberechnungen.
Length
Fisher CG-Oszillatorperiode (auch für das Normalisierungsfenster verwendet).
SignalBar
Anzahl der geschlossenen Kerzen zurück, die zum Lesen von Signalen verwendet werden; 1 entspricht dem EA-Standard.
AllowLongEntry / AllowShortEntry
Long-/Short-Einstiege umschalten.
AllowLongExit / AllowShortExit
Automatische Ausstiege für Long-/Short-Positionen umschalten.
StopLossPoints / TakeProfitPoints
Schutz-Stop- und Zielabstände in Preisschritten. Auf 0 setzen zum Deaktivieren.
FixedVolume
Volumen im festen Größenmodus.
DepositShare
Portfolioanteil pro Trade im PortfolioShare-Modus.
SizingMode
Wählt zwischen festem Volumen und anteilsbasierter Positionsgrößenbestimmung.
Verwendungshinweise
Stimmen Sie CandleType und SignalBar auf den vom ursprünglichen Indikator verwendeten Zeitrahmen ab (standardmäßig H8 und Bar-Verschiebung von 1).
Erlauben Sie eine kurze Aufwärmphase, damit der Oszillator genug Historie aufbauen kann; die Strategie ignoriert Trades, bis der Indikator vollständig initialisiert ist.
Stops und Ziele operieren auf dem Kerzenschluss. Passen Sie Punktwerte an die Tick-Größe Ihres Instruments an.
Wenn PortfolioShare-Sizing ausgewählt ist, stellen Sie sicher, dass die Portfoliobewertung verfügbar ist; andernfalls fällt die Strategie auf das feste Volumen zurück.
Unterschiede vs. originalem EA
Orders werden als Marktorders ohne den Slippage-Parameter Deviation_ gesendet; StockSharp verarbeitet die Ausführung mit seinen eigenen Slippage-Einstellungen.
Geldmanagement ist auf zwei Größenmodi vereinfacht (FixedVolume und PortfolioShare). Die Verlustprozentsatz-Optionen des EA werden absichtlich weggelassen.
Ausstehende Order-Zeitstempel (UpSignalTime/DnSignalTime) werden nicht verwendet. Signale werden sofort auf der verarbeiteten Kerze ausgeführt.
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
using StockSharp.Algo.Candles;
/// <summary>
/// Fisher Center of Gravity oscillator crossover strategy.
/// </summary>
public class ExpFisherCgOscillatorStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _medianPrices = new();
private readonly List<decimal> _cgValues = new();
private readonly decimal[] _valueBuffer = new decimal[4];
private int _valueCount;
private decimal? _previousFisher;
private readonly List<(decimal Main, decimal Trigger)> _oscillatorHistory = new();
private decimal? _entryPrice;
private int _length = 10;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public ExpFisherCgOscillatorStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_medianPrices.Clear();
_cgValues.Clear();
Array.Clear(_valueBuffer);
_valueCount = 0;
_previousFisher = null;
_oscillatorHistory.Clear();
_entryPrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
OnReseted();
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// Calculate Fisher CG oscillator inline
var price = (candle.HighPrice + candle.LowPrice) / 2m;
_medianPrices.Add(price);
while (_medianPrices.Count > _length)
_medianPrices.RemoveAt(0);
if (_medianPrices.Count < _length)
return;
decimal num = 0m;
decimal denom = 0m;
var weight = 1;
for (var index = _medianPrices.Count - 1; index >= 0; index--)
{
var median = _medianPrices[index];
num += weight * median;
denom += median;
weight++;
}
decimal cg;
if (denom != 0m)
cg = -num / denom + (_length + 1m) / 2m;
else
cg = 0m;
_cgValues.Add(cg);
while (_cgValues.Count > _length)
_cgValues.RemoveAt(0);
var high = cg;
var low = cg;
for (var i = 0; i < _cgValues.Count; i++)
{
var v = _cgValues[i];
if (v > high) high = v;
if (v < low) low = v;
}
decimal normalized;
if (high != low)
normalized = (cg - low) / (high - low);
else
normalized = 0m;
var limit = Math.Min(_valueCount, 3);
for (var shift = limit; shift > 0; shift--)
_valueBuffer[shift] = _valueBuffer[shift - 1];
_valueBuffer[0] = normalized;
if (_valueCount < 4)
_valueCount++;
if (_valueCount < 4)
return;
var value2 = (4m * _valueBuffer[0] + 3m * _valueBuffer[1] + 2m * _valueBuffer[2] + _valueBuffer[3]) / 10m;
var x = 1.98m * (value2 - 0.5m);
if (x > 0.999m)
x = 0.999m;
else if (x < -0.999m)
x = -0.999m;
var numerator = 1m + x;
var denominator = 1m - x;
if (denominator == 0m)
denominator = 0.0000001m;
var ratio = numerator / denominator;
if (ratio <= 0m)
ratio = 0.0000001m;
var fisher = 0.5m * (decimal)Math.Log((double)ratio);
var trigger = _previousFisher ?? fisher;
_previousFisher = fisher;
// Store history
_oscillatorHistory.Add((fisher, trigger));
while (_oscillatorHistory.Count > 10)
_oscillatorHistory.RemoveAt(0);
if (_oscillatorHistory.Count < 3)
return;
// Handle risk management
HandleRiskManagement(candle.ClosePrice);
if (!IsFormedAndOnlineAndAllowTrading())
return;
var current = _oscillatorHistory[^1];
var previous = _oscillatorHistory[^2];
var previousAbove = previous.Main > previous.Trigger;
var previousBelow = previous.Main < previous.Trigger;
var buyOpen = previousAbove && current.Main <= current.Trigger;
var sellOpen = previousBelow && current.Main >= current.Trigger;
var buyClose = previousBelow;
var sellClose = previousAbove;
if (sellClose && Position < 0)
{
BuyMarket();
_entryPrice = null;
}
if (buyClose && Position > 0)
{
SellMarket();
_entryPrice = null;
}
if (buyOpen && Position <= 0)
{
if (Position < 0)
{
BuyMarket();
_entryPrice = null;
return;
}
BuyMarket();
_entryPrice = candle.ClosePrice;
}
else if (sellOpen && Position >= 0)
{
if (Position > 0)
{
SellMarket();
_entryPrice = null;
return;
}
SellMarket();
_entryPrice = candle.ClosePrice;
}
}
private void HandleRiskManagement(decimal closePrice)
{
if (_entryPrice is null || Position == 0)
return;
var step = Security?.PriceStep ?? 1m;
if (step <= 0m) step = 1m;
var stopDistance = 1000 * step;
var takeDistance = 2000 * step;
if (Position > 0)
{
if (closePrice <= _entryPrice.Value - stopDistance)
{
SellMarket();
_entryPrice = null;
return;
}
if (closePrice >= _entryPrice.Value + takeDistance)
{
SellMarket();
_entryPrice = null;
}
}
else if (Position < 0)
{
if (closePrice >= _entryPrice.Value + stopDistance)
{
BuyMarket();
_entryPrice = null;
return;
}
if (closePrice <= _entryPrice.Value - takeDistance)
{
BuyMarket();
_entryPrice = null;
}
}
}
}
import clr
import math
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.Strategies import Strategy
class exp_fisher_cg_oscillator_strategy(Strategy):
def __init__(self):
super(exp_fisher_cg_oscillator_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._median_prices = []
self._cg_values = []
self._value_buffer = [0.0, 0.0, 0.0, 0.0]
self._value_count = 0
self._previous_fisher = None
self._oscillator_history = []
self._entry_price = None
self._length = 10
@property
def CandleType(self):
return self._candle_type.Value
def OnReseted(self):
super(exp_fisher_cg_oscillator_strategy, self).OnReseted()
self._median_prices = []
self._cg_values = []
self._value_buffer = [0.0, 0.0, 0.0, 0.0]
self._value_count = 0
self._previous_fisher = None
self._oscillator_history = []
self._entry_price = None
def OnStarted2(self, time):
super(exp_fisher_cg_oscillator_strategy, self).OnStarted2(time)
self._median_prices = []
self._cg_values = []
self._value_buffer = [0.0, 0.0, 0.0, 0.0]
self._value_count = 0
self._previous_fisher = None
self._oscillator_history = []
self._entry_price = None
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._on_process).Start()
def _on_process(self, candle):
if candle.State != CandleStates.Finished:
return
price = (float(candle.HighPrice) + float(candle.LowPrice)) / 2.0
self._median_prices.append(price)
while len(self._median_prices) > self._length:
self._median_prices.pop(0)
if len(self._median_prices) < self._length:
return
num = 0.0
denom = 0.0
weight = 1
for i in range(len(self._median_prices) - 1, -1, -1):
median = self._median_prices[i]
num += weight * median
denom += median
weight += 1
if denom != 0.0:
cg = -num / denom + (self._length + 1.0) / 2.0
else:
cg = 0.0
self._cg_values.append(cg)
while len(self._cg_values) > self._length:
self._cg_values.pop(0)
high = cg
low = cg
for v in self._cg_values:
if v > high:
high = v
if v < low:
low = v
if high != low:
normalized = (cg - low) / (high - low)
else:
normalized = 0.0
limit = min(self._value_count, 3)
shift = limit
while shift > 0:
self._value_buffer[shift] = self._value_buffer[shift - 1]
shift -= 1
self._value_buffer[0] = normalized
if self._value_count < 4:
self._value_count += 1
if self._value_count < 4:
return
value2 = (4.0 * self._value_buffer[0] + 3.0 * self._value_buffer[1] + 2.0 * self._value_buffer[2] + self._value_buffer[3]) / 10.0
x = 1.98 * (value2 - 0.5)
if x > 0.999:
x = 0.999
elif x < -0.999:
x = -0.999
numerator = 1.0 + x
denominator = 1.0 - x
if denominator == 0.0:
denominator = 0.0000001
ratio = numerator / denominator
if ratio <= 0.0:
ratio = 0.0000001
fisher = 0.5 * math.log(ratio)
trigger = self._previous_fisher if self._previous_fisher is not None else fisher
self._previous_fisher = fisher
self._oscillator_history.append((fisher, trigger))
while len(self._oscillator_history) > 10:
self._oscillator_history.pop(0)
if len(self._oscillator_history) < 3:
return
self._handle_risk_management(float(candle.ClosePrice))
current = self._oscillator_history[-1]
previous = self._oscillator_history[-2]
previous_above = previous[0] > previous[1]
previous_below = previous[0] < previous[1]
buy_open = previous_above and current[0] <= current[1]
sell_open = previous_below and current[0] >= current[1]
if previous_above and self.Position < 0:
self.BuyMarket()
self._entry_price = None
if previous_below and self.Position > 0:
self.SellMarket()
self._entry_price = None
if buy_open and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self._entry_price = None
return
self.BuyMarket()
self._entry_price = float(candle.ClosePrice)
elif sell_open and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self._entry_price = None
return
self.SellMarket()
self._entry_price = float(candle.ClosePrice)
def _handle_risk_management(self, close_price):
if self._entry_price is None or self.Position == 0:
return
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 1.0
if step <= 0.0:
step = 1.0
stop_distance = 1000 * step
take_distance = 2000 * step
if self.Position > 0:
if close_price <= self._entry_price - stop_distance:
self.SellMarket()
self._entry_price = None
return
if close_price >= self._entry_price + take_distance:
self.SellMarket()
self._entry_price = None
elif self.Position < 0:
if close_price >= self._entry_price + stop_distance:
self.BuyMarket()
self._entry_price = None
return
if close_price <= self._entry_price - take_distance:
self.BuyMarket()
self._entry_price = None
def CreateClone(self):
return exp_fisher_cg_oscillator_strategy()