Die Rich Kohonen Map Strategy ist eine Umsetzung des MetaTrader 4 Expert Advisors „Rich.mq4“. Das ursprüngliche System erstellt eine selbstorganisierende Karte (Kohonen-Netzwerk) über Merkmalsvektoren, die aus Pivot-Berechnungen von Tom DeMark abgeleitet wurden, und klassifiziert den nächsten Balken als Kauf-, Verkaufs- oder Haltegelegenheit. Der StockSharp-Port behält den Lernansatz bei und integriert sich gleichzeitig in die High-Level-Strategie API, wobei er ausschließlich auf abgeschlossene Kerzen und Marktaufträge angewendet wird.
Marktdaten
Instrument – konfiguriert über das verknüpfte Security in der Hostanwendung.
Kerzentyp – Parameter CandleType (Standard: 1-stündiger Zeitrahmen). Die Strategie erfordert mindestens sieben fertige Kerzen, bevor Signale erzeugt werden, damit sowohl aktuelle als auch vorherige Merkmalsvektoren zusammengesetzt werden können.
Handelslogik
Behalten Sie ein fortlaufendes Fenster der letzten sieben abgeschlossenen Kerzen bei.
Bauen Sie auf jeder fertigen Kerze zwei Sieben-Elemente-Vektoren auf:
Der aktuelle Vektor verwendet die letzte Eröffnung zusammen mit Tom DeMarks Pivot-Prognosen, die aus den vorherigen fünf Kerzen berechnet wurden.
Der vorherige Vektor verschiebt das Fenster um einen Balken und stellt den Balken dar, der gerade geschlossen wurde. Dieser Vektor wird für das Training verwendet.
Vergleichen Sie den aktuellen Vektor mit drei Kohonen-Karten (Kaufen, Verkaufen, Halten) und zeichnen Sie die euklidische Distanz zu jeder am besten passenden Einheit auf.
Wählen Sie die Aktion mit dem kleinsten Abstand aus und legen Sie die Zielposition fest:
Kaufen → Long Exposure in Höhe des berechneten Volumens.
Verkaufen → Short-Engagement in gleicher Größenordnung.
Halten → keine Position.
Die Strategie sendet Marktaufträge für die Differenz zwischen der aktuellen und der Zielposition, sodass das endgültige Engagement mit der Entscheidung übereinstimmt.
Berechnen Sie die Bewegung von Eröffnung zu Eröffnung (in Pips) zwischen den letzten beiden Kerzen und trainieren Sie die Karte:
Positive Bewegung innerhalb von [MinPips, MaxPips] → Füge den vorherigen Vektor zur Kaufkarte hinzu.
Negative Bewegung innerhalb von [-MaxPips, -MinPips] → Füge den vorherigen Vektor zur Verkaufskarte hinzu.
Andernfalls → den Vektor in der Hold-Map speichern.
Die Positionsgröße wird dynamisch aus dem Portfoliosaldo bestimmt: floor(balance / 50) / 10. Wenn dies Null ergibt, wird stattdessen der Fallback-Parameter Lots verwendet.
Parameter
MinPips – Untergrenze (in Pips) für die Berücksichtigung einer positiven Bewegung von Eröffnung zu Eröffnung als Beispiel für ein Kauftraining.
MaxPips – Obergrenze (in Pips) für Kauf-/Verkaufstrainingsbeispiele.
TakeProfit, StopLoss – vom MQL-Experten zu Dokumentationszwecken aufbewahrt. Die High-Level-Implementierung schließt oder kehrt Positionen über Marktaufträge statt durch Anbringen von Stopps um.
Lots – Fallback-Volumen, das angewendet wird, wenn die saldobasierte Formel Null ergibt.
Slippage – reserviert für die manuelle Auftragsoptimierung (wird nicht direkt von den übergeordneten API-Helfern verwendet).
MapPath – Binärdateipfad, der zum Beibehalten der drei Kohonen-Karten zwischen den Läufen verwendet wird.
EAName – optionaler Kommentar, der als Referenz gespeichert wird.
CandleType – Kerzenabonnement, das zur Feature-Extraktion verwendet wird.
Permanente Kartenspeicherung
Die Strategie speichert die trainierte Karte in einer durch MapPath definierten Binärdatei (Standard: rl.bin im Arbeitsverzeichnis). Die Datei enthält nacheinander die Kauf-, Verkaufs- und Haltematrizen. Beim Start werden die Matrizen geladen und die Strategie zählt die nicht leeren Zeilen, um das Training ab dem vorherigen Status fortzusetzen. Fehlende Dateien werden ignoriert, was dazu führt, dass die Karten mit einem mit Null gefüllten Speicher beginnen.
Unterschiede zum ursprünglichen MQL-Experten
Aufträge werden über StockSharp-Helfer (BuyMarket / SellMarket) erteilt und zielen auf das endgültige gewünschte Engagement ab, anstatt bei jedem Balken einen vollständigen Abschluss und eine erneute Eröffnung zu erzwingen. Dadurch bleibt das effektive Verhalten erhalten und gleichzeitig werden doppelte Transaktionen in der verwalteten Umgebung reduziert.
Stop-Loss- und Take-Profit-Level bleiben als Parameter für die Dokumentation bestehen, werden jedoch nicht als separate Orders registriert. Positionsausstiege erfolgen, wenn der Klassifikator die Gegenseite oder die Halteaktion auswählt.
Für die Dateiverarbeitung werden .NET-I/O-Hilfsprogramme verwendet. Das Kartenformat bleibt kompatibel (Werte mit doppelter Genauigkeit werden identisch angeordnet).
Nutzungshinweise
Stellen Sie sicher, dass die ausgewählte Sicherheit einen gültigen PriceStep bereitstellt, damit Pip-Differenzen korrekt berechnet werden. Wenn der Schritt fehlt oder Null ist, greift die Strategie auf einen Einheitsschritt zurück.
Die Kohonen-Karten können groß werden (bis zu 10.000 Kauf-/Verkaufseinträge und 25.000 Halteeinträge). Behalten Sie den Standardpfad auf einem Speichergerät mit ausreichender Kapazität bei (~2,5 MB, wenn es voll ist).
Da der Algorithmus kontinuierlich trainiert, hilft die Ausführung der Strategie anhand historischer Daten vor der Live-Bereitstellung dabei, die Karte mit repräsentativen Stichproben zu füllen.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Rich Kohonen Map: EMA crossover with ATR stops.
/// </summary>
public class RichKohonenMapStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _slowEmaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
public RichKohonenMapStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_fastEmaLength = Param(nameof(FastEmaLength), 10)
.SetDisplay("Fast EMA Length", "Fast EMA period.", "Indicators");
_slowEmaLength = Param(nameof(SlowEmaLength), 30)
.SetDisplay("Slow EMA Length", "Slow EMA period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int FastEmaLength { get => _fastEmaLength.Value; set => _fastEmaLength.Value = value; }
public int SlowEmaLength { get => _slowEmaLength.Value; set => _slowEmaLength.Value = value; }
public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = 0; _prevSlow = 0; _entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = 0; _prevSlow = 0; _entryPrice = 0;
var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fastEma, slowEma, atr, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, fastEma); DrawIndicator(area, slowEma); DrawOwnTrades(area); }
}
private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished) return;
if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0) { _prevFast = fastVal; _prevSlow = slowVal; return; }
var close = candle.ClosePrice;
if (Position > 0)
{
if ((fastVal < slowVal && _prevFast >= _prevSlow) || close <= _entryPrice - atrVal * 2m) { SellMarket(); _entryPrice = 0; }
}
else if (Position < 0)
{
if ((fastVal > slowVal && _prevFast <= _prevSlow) || close >= _entryPrice + atrVal * 2m) { BuyMarket(); _entryPrice = 0; }
}
if (Position == 0)
{
if (fastVal > slowVal && _prevFast <= _prevSlow) { _entryPrice = close; BuyMarket(); }
else if (fastVal < slowVal && _prevFast >= _prevSlow) { _entryPrice = close; SellMarket(); }
}
_prevFast = fastVal; _prevSlow = slowVal;
}
}
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.Strategies import Strategy
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange
class rich_kohonen_map_strategy(Strategy):
def __init__(self):
super(rich_kohonen_map_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._fast_ema_length = self.Param("FastEmaLength", 10) \
.SetDisplay("Fast EMA Length", "Fast EMA period.", "Indicators")
self._slow_ema_length = self.Param("SlowEmaLength", 30) \
.SetDisplay("Slow EMA Length", "Slow EMA period.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastEmaLength(self):
return self._fast_ema_length.Value
@property
def SlowEmaLength(self):
return self._slow_ema_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(rich_kohonen_map_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = self.FastEmaLength
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowEmaLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._fast_ema, self._slow_ema, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, fast_val, slow_val, atr_val):
if candle.State != CandleStates.Finished:
return
fv = float(fast_val)
sv = float(slow_val)
av = float(atr_val)
if self._prev_fast == 0 or self._prev_slow == 0 or av <= 0:
self._prev_fast = fv
self._prev_slow = sv
return
close = float(candle.ClosePrice)
if self.Position > 0:
if (fv < sv and self._prev_fast >= self._prev_slow) or close <= self._entry_price - av * 2.0:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if (fv > sv and self._prev_fast <= self._prev_slow) or close >= self._entry_price + av * 2.0:
self.BuyMarket()
self._entry_price = 0.0
if self.Position == 0:
if fv > sv and self._prev_fast <= self._prev_slow:
self._entry_price = close
self.BuyMarket()
elif fv < sv and self._prev_fast >= self._prev_slow:
self._entry_price = close
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
def OnReseted(self):
super(rich_kohonen_map_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
def CreateClone(self):
return rich_kohonen_map_strategy()