Cyberia Trader KI-Strategie
Diese Strategie ist eine StockSharp-Konvertierung des Expert Advisors CyberiaTrader.mq4 (Build 8553). Das ursprüngliche MQL-Programm mischt a Wahrscheinlichkeits-Engine mit einer Sammlung optionaler Trendfilter. Der C#-Port behält die gleiche Struktur bei: Ein Wahrscheinlichkeitsmodell sucht für den zuverlässigsten Stichprobenzeitraum und dann können optionale MACD-, EMA- und Umkehrfilter Trades ablehnen.
Indikatoren und internes Modell
- Probability Engine – iteriert Kandidaten-Stichprobenzeiträume (
MaxPeriod) und wertetSamplesPerPeriodhistorische Segmente aus. Für jede Periode berechnet die Engine:- Entscheidungsrichtung (Kauf/Verkauf/Flat) basierend auf aufeinanderfolgenden bullischen/bärischen Ein-Minuten-Kerzen im Abstand des Abtastzeitraums.
- Durchschnittliche „Möglichkeits“-Amplituden für Kauf, Verkauf und undefinierte Ergebnisse sowie der Anteil der oben genannten erfolgreichen Ergebnisse
SpreadThreshold. - Erfolgsquoten, die den Zeitraum mit der besten Leistung auswählen.
- EMA Trendfilter – optionaler exponentieller gleitender Durchschnitt (
EnableMa), der Trades gegen die aktuelle Steigung blockiert. - MACD-Filter – optionale Konvergenz/Divergenz des gleitenden Durchschnitts (
EnableMacd), die den Handel gegen das Momentum verbietet. - Reversal Detector – optionaler Spike-Detektor (
EnableReversalDetector), der Berechtigungen umkehrt, wenn die Wahrscheinlichkeiten darüber steigenReversalFactorVielfache ihrer Durchschnittswerte.
Parameter
| Name | Beschreibung |
|---|---|
MaxPeriod |
Größter Stichprobenschritt, der von der Wahrscheinlichkeits-Engine überprüft wird. |
SamplesPerPeriod |
Anzahl der pro Periodenkandidaten verarbeiteten Segmente (spiegelt MQL ValuesPeriodCount wider). |
SpreadThreshold |
Minimale Amplitude, die als erfolgreiches Wahrscheinlichkeitsergebnis gilt. |
EnableCyberiaLogic |
Aktiviert die Cyberia-Wahrscheinlichkeitsschalter, die Käufe oder Verkäufe deaktivieren können. |
EnableMacd |
Aktiviert den Momentumfilter MACD. |
EnableMa |
Aktiviert den Steigungsfilter EMA. |
EnableReversalDetector |
Aktiviert das Umschalten der Berechtigungen des Umkehrdetektors bei extremen Spitzen. |
MaPeriod |
EMA Länge, die vom Trendfilter verwendet wird. |
MacdFast / MacdSlow / MacdSignal |
MACD schneller EMA, langsamer EMA und Signalperioden. |
ReversalFactor |
Multiplikator, der den Umkehrdetektor auslöst. |
CandleType |
Vom Modell verarbeiteter Kerzendatentyp (Standard 1 Minute). |
TakeProfitPercent |
Optionaler schützender Take-Profit, ausgedrückt in Prozent. |
StopLossPercent |
Optionaler schützender Stop-Loss, ausgedrückt in Prozent. |
Handelslogik
- Jede abgeschlossene Kerze aktualisiert die lokale Verlaufswarteschlange und berechnet die Wahrscheinlichkeitsstatistik für jeden Zeitraum von 1 bis neu
MaxPeriod. Der Zeitraum mit der höchsten Erfolgsquote wird zur aktiven Konfiguration. - Die Cyberia-Logik setzt
DisableBuy/DisableSell-Flags unter Verwendung derselben Vergleiche wie der MQL-Code:- Vergleicht durchschnittliche Kauf-/Verkaufsmöglichkeiten und ihre erfolgsgewichteten Varianten, wenn der Zeitraum zunimmt oder abnimmt.
- Deaktiviert Einträge, wenn neue Möglichkeiten das Doppelte ihres Erfolgsdurchschnitts überschreiten.
- Optionale Filter werden in der Reihenfolge angewendet: MACD, EMA Steigung, dann der Umkehrdetektor.
- Wenn keine Position offen ist, kommt die Strategie zum Tragen, wenn die aktuelle Entscheidung Kauf (oder Verkauf) ist und die entsprechende Möglichkeit größer ist sein erfolgreicher Durchschnitt, während die Gegenrichtung deaktiviert ist.
- Während eine Position vorhanden ist, prüft der Code die gleichen Bedingungen, um sie zu schließen, wenn die Wahrscheinlichkeitsmaschine umkippt oder wenn Filter dies verbieten aktuelle Richtung.
StartProtectionreproduziert die ursprünglichen Geldverwaltungsblöcke, wenn Risikoparameter ungleich Null angegeben werden.
Hinweise zur Konvertierung
- Der Port behält die statistischen Berechnungen bei, ersetzt jedoch die Tick-basierte Spread-Prüfung durch den konfigurierbaren
SpreadThreshold. - Die automatische Losgrößen- und Bilanzdiagnose aus dem MQL-Skript ist nicht implementiert. Die Lautstärke von StockSharp wird über
Volumegesteuert. - Die Module MoneyTrain und Pipsator sind in der oben beschriebenen einheitlichen Ein-/Ausstiegslogik zusammengefasst, um der Verwendung von API auf hoher Ebene gerecht zu werden.
- Die Strategie fügt Diagrammzeichnungen für Kerzen, EMA und MACD hinzu, um die Validierung im Designer zu erleichtern.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
using StockSharp.Algo;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// StockSharp port of the CyberiaTrader (build 8553) expert advisor.
/// Recreates the probability driven decision engine together with optional MA, MACD, and reversal filters.
/// </summary>
public class CyberiaTraderAiStrategy : Strategy
{
private readonly StrategyParam<int> _maxPeriod;
private readonly StrategyParam<int> _samplesPerPeriod;
private readonly StrategyParam<decimal> _spreadThreshold;
private readonly StrategyParam<bool> _enableCyberiaLogic;
private readonly StrategyParam<bool> _enableMacd;
private readonly StrategyParam<bool> _enableMa;
private readonly StrategyParam<bool> _enableReversalDetector;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _macdFast;
private readonly StrategyParam<int> _macdSlow;
private readonly StrategyParam<int> _macdSignal;
private readonly StrategyParam<decimal> _reversalFactor;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _takeProfitPercent;
private readonly StrategyParam<decimal> _stopLossPercent;
private MovingAverageConvergenceDivergenceSignal _macd;
private ExponentialMovingAverage _ema;
private readonly Queue<CandleSnapshot> _history = new();
private decimal? _previousEma;
private int? _previousPeriod;
private ModelStats _currentStats;
/// <summary>
/// Maximum sampling period evaluated by the probability model.
/// </summary>
public int MaxPeriod
{
get => _maxPeriod.Value;
set => _maxPeriod.Value = value;
}
/// <summary>
/// Number of segments (per period) used for statistical evaluation.
/// </summary>
public int SamplesPerPeriod
{
get => _samplesPerPeriod.Value;
set => _samplesPerPeriod.Value = value;
}
/// <summary>
/// Minimal absolute move that qualifies as a successful probability outcome.
/// </summary>
public decimal SpreadThreshold
{
get => _spreadThreshold.Value;
set => _spreadThreshold.Value = value;
}
/// <summary>
/// Enables the Cyberia probability filter.
/// </summary>
public bool EnableCyberiaLogic
{
get => _enableCyberiaLogic.Value;
set => _enableCyberiaLogic.Value = value;
}
/// <summary>
/// Enables the MACD trend filter.
/// </summary>
public bool EnableMacd
{
get => _enableMacd.Value;
set => _enableMacd.Value = value;
}
/// <summary>
/// Enables the EMA slope filter.
/// </summary>
public bool EnableMa
{
get => _enableMa.Value;
set => _enableMa.Value = value;
}
/// <summary>
/// Enables the reversal detector that flips permissions when extreme spikes appear.
/// </summary>
public bool EnableReversalDetector
{
get => _enableReversalDetector.Value;
set => _enableReversalDetector.Value = value;
}
/// <summary>
/// Length of the EMA trend filter.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Fast period of the MACD module.
/// </summary>
public int MacdFast
{
get => _macdFast.Value;
set => _macdFast.Value = value;
}
/// <summary>
/// Slow period of the MACD module.
/// </summary>
public int MacdSlow
{
get => _macdSlow.Value;
set => _macdSlow.Value = value;
}
/// <summary>
/// Signal period of the MACD module.
/// </summary>
public int MacdSignal
{
get => _macdSignal.Value;
set => _macdSignal.Value = value;
}
/// <summary>
/// Multiplier used by the reversal detector.
/// </summary>
public decimal ReversalFactor
{
get => _reversalFactor.Value;
set => _reversalFactor.Value = value;
}
/// <summary>
/// Candle type processed by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Optional take profit distance in percent.
/// </summary>
public decimal TakeProfitPercent
{
get => _takeProfitPercent.Value;
set => _takeProfitPercent.Value = value;
}
/// <summary>
/// Optional stop loss distance in percent.
/// </summary>
public decimal StopLossPercent
{
get => _stopLossPercent.Value;
set => _stopLossPercent.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="CyberiaTraderAiStrategy"/>.
/// </summary>
public CyberiaTraderAiStrategy()
{
_maxPeriod = Param(nameof(MaxPeriod), 23)
.SetGreaterThanZero()
.SetDisplay("Max Period", "Largest sampling stride tested by the probability engine", "Model");
_samplesPerPeriod = Param(nameof(SamplesPerPeriod), 5)
.SetGreaterThanZero()
.SetDisplay("Segments Per Period", "Number of historical segments processed for every period candidate", "Model");
_spreadThreshold = Param(nameof(SpreadThreshold), 0m)
.SetNotNegative()
.SetDisplay("Spread Threshold", "Minimal absolute move to count a probability as successful", "Model");
_enableCyberiaLogic = Param(nameof(EnableCyberiaLogic), true)
.SetDisplay("Enable Cyberia Logic", "Use the probability based disable/allow switches", "Filters");
_enableMacd = Param(nameof(EnableMacd), false)
.SetDisplay("Enable MACD", "Use MACD to block trading against momentum", "Filters");
_enableMa = Param(nameof(EnableMa), false)
.SetDisplay("Enable EMA", "Use EMA slope to forbid trades against the trend", "Filters");
_enableReversalDetector = Param(nameof(EnableReversalDetector), false)
.SetDisplay("Enable Reversal Detector", "Flip permissions on extreme probability spikes", "Filters");
_maPeriod = Param(nameof(MaPeriod), 23)
.SetGreaterThanZero()
.SetDisplay("EMA Period", "Length of the EMA used in the trend filter", "Indicators");
_macdFast = Param(nameof(MacdFast), 12)
.SetGreaterThanZero()
.SetDisplay("MACD Fast", "Fast EMA length for MACD", "Indicators");
_macdSlow = Param(nameof(MacdSlow), 26)
.SetGreaterThanZero()
.SetDisplay("MACD Slow", "Slow EMA length for MACD", "Indicators");
_macdSignal = Param(nameof(MacdSignal), 9)
.SetGreaterThanZero()
.SetDisplay("MACD Signal", "Signal EMA length for MACD", "Indicators");
_reversalFactor = Param(nameof(ReversalFactor), 3m)
.SetGreaterThanZero()
.SetDisplay("Reversal Factor", "Threshold multiplier that triggers the reversal detector", "Filters");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe processed by the model", "General");
_takeProfitPercent = Param(nameof(TakeProfitPercent), 0m)
.SetNotNegative()
.SetDisplay("Take Profit %", "Optional take profit distance expressed in percent", "Risk");
_stopLossPercent = Param(nameof(StopLossPercent), 0m)
.SetNotNegative()
.SetDisplay("Stop Loss %", "Optional stop loss distance expressed in percent", "Risk");
Volume = 1m;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_history.Clear();
_previousEma = null;
_previousPeriod = null;
_currentStats = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Prepare indicator instances used by the optional filters.
_macd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = MacdFast },
LongMa = { Length = MacdSlow },
},
SignalMa = { Length = MacdSignal }
};
_ema = new EMA { Length = MaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_macd, _ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ema);
DrawIndicator(area, _macd);
DrawOwnTrades(area);
}
var takeProfit = TakeProfitPercent > 0m ? new Unit(TakeProfitPercent / 100m, UnitTypes.Percent) : new Unit();
var stopLoss = StopLossPercent > 0m ? new Unit(StopLossPercent / 100m, UnitTypes.Percent) : new Unit();
StartProtection(takeProfit, stopLoss);
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue, IIndicatorValue emaValue)
{
// Operate only on completed candles.
if (candle.State != CandleStates.Finished)
{
return;
}
// Respect indicator readiness when the corresponding filter is enabled.
MovingAverageConvergenceDivergenceSignalValue macdSignal = null;
if (macdValue.IsFinal)
{
if (macdValue is MovingAverageConvergenceDivergenceSignalValue macdData)
{
macdSignal = macdData;
}
}
else if (EnableMacd)
{
return;
}
decimal? emaSnapshot = null;
if (emaValue.IsFinal)
{
emaSnapshot = emaValue.ToDecimal();
}
else if (EnableMa)
{
return;
}
// Store the candle in the local history used by the probability model.
UpdateHistory(candle);
var candles = _history.ToArray();
_currentStats = FindBestStats(candles);
// Always capture the latest EMA value for slope calculations.
if (emaSnapshot is decimal emaValueDecimal)
{
if (_previousEma == null)
{
_previousEma = emaValueDecimal;
}
}
// Avoid trading before the strategy is fully initialized.
if (!IsFormedAndOnlineAndAllowTrading())
{
if (emaSnapshot is decimal emaValueUnformed)
{
_previousEma = emaValueUnformed;
}
return;
}
if (!_currentStats.IsValid)
{
if (emaSnapshot is decimal emaValueInvalid)
{
_previousEma = emaValueInvalid;
}
return;
}
var flags = CalculateDirection(emaSnapshot, macdSignal);
HandlePositions(flags);
_previousPeriod = _currentStats.Period;
}
private void HandlePositions(DirectionFlags flags)
{
var stats = _currentStats;
// No trades without a valid statistical snapshot.
if (!stats.IsValid)
{
return;
}
// Manage existing positions first to mirror the MQL behaviour.
if (Position > 0)
{
var shouldExitLong = (stats.CurrentDecision == TradeDecisions.Sell &&
stats.SellPossibility >= stats.SellSucPossibilityMid &&
stats.SellSucPossibilityMid > 0m) ||
(flags.DisableBuy && stats.CurrentDecision != TradeDecisions.Buy);
if (shouldExitLong)
{
SellMarket(Position);
return;
}
}
else if (Position < 0)
{
var shouldExitShort = (stats.CurrentDecision == TradeDecisions.Buy &&
stats.BuyPossibility >= stats.BuySucPossibilityMid &&
stats.BuySucPossibilityMid > 0m) ||
(flags.DisableSell && stats.CurrentDecision != TradeDecisions.Sell);
if (shouldExitShort)
{
BuyMarket(-Position);
return;
}
}
// Evaluate fresh entries only when the probability module allows it.
if (stats.CurrentDecision == TradeDecisions.Buy &&
!flags.DisableBuy &&
stats.BuyPossibility >= stats.BuySucPossibilityMid &&
stats.BuySucPossibilityMid > 0m &&
Position <= 0)
{
var volume = Volume + (Position < 0 ? -Position : 0m);
BuyMarket(volume);
return;
}
if (stats.CurrentDecision == TradeDecisions.Sell &&
!flags.DisableSell &&
stats.SellPossibility >= stats.SellSucPossibilityMid &&
stats.SellSucPossibilityMid > 0m &&
Position >= 0)
{
var volume = Volume + (Position > 0 ? Position : 0m);
SellMarket(volume);
}
}
private DirectionFlags CalculateDirection(decimal? emaValue, MovingAverageConvergenceDivergenceSignalValue macdValue)
{
var stats = _currentStats;
var disableBuy = false;
var disableSell = false;
var disablePipsator = false;
var disableBuyPips = false;
var disableSellPips = false;
if (EnableCyberiaLogic)
{
var buyScore = stats.BuyPossibilityMid * stats.BuyPossibilityQuality;
var sellScore = stats.SellPossibilityMid * stats.SellPossibilityQuality;
if (_previousPeriod is int previousPeriodValue)
{
if (stats.Period > previousPeriodValue)
{
if (sellScore > buyScore)
{
disableSell = false;
disableBuy = true;
disableBuyPips = true;
if (stats.SellSucPossibilityMid * stats.SellSucPossibilityQuality >
stats.BuySucPossibilityMid * stats.BuySucPossibilityQuality)
{
disableSell = true;
}
}
else if (sellScore < buyScore)
{
disableSell = true;
disableBuy = false;
disableSellPips = true;
if (stats.SellSucPossibilityMid * stats.SellSucPossibilityQuality <
stats.BuySucPossibilityMid * stats.BuySucPossibilityQuality)
{
disableBuy = true;
}
}
}
else if (stats.Period < previousPeriodValue)
{
disableSell = true;
disableBuy = true;
}
}
if (sellScore == buyScore)
{
disableSell = true;
disableBuy = true;
disablePipsator = false;
}
if (stats.SellPossibility > stats.SellSucPossibilityMid * 2m && stats.SellSucPossibilityMid > 0m)
{
disableSell = true;
disableSellPips = true;
}
if (stats.BuyPossibility > stats.BuySucPossibilityMid * 2m && stats.BuySucPossibilityMid > 0m)
{
disableBuy = true;
disableBuyPips = true;
}
}
if (EnableMa && emaValue is decimal emaDecimal)
{
if (_previousEma is decimal previousEma)
{
if (emaDecimal > previousEma)
{
disableSell = true;
disableSellPips = true;
}
else if (emaDecimal < previousEma)
{
disableBuy = true;
disableBuyPips = true;
}
}
_previousEma = emaDecimal;
}
else if (emaValue is decimal emaSnapshot)
{
_previousEma = emaSnapshot;
}
if (EnableMacd && macdValue != null)
{
var macdMain = macdValue.Macd;
var macdSignal = macdValue.Signal;
if (macdMain > macdSignal)
{
disableSell = true;
}
else if (macdMain < macdSignal)
{
disableBuy = true;
}
}
if (EnableReversalDetector)
{
var trigger = false;
if (stats.BuyPossibilityMid > 0m && stats.BuyPossibility > stats.BuyPossibilityMid * ReversalFactor)
{
trigger = true;
}
if (stats.SellPossibilityMid > 0m && stats.SellPossibility > stats.SellPossibilityMid * ReversalFactor)
{
trigger = true;
}
if (trigger)
{
disableSell = !disableSell;
disableBuy = !disableBuy;
disableSellPips = !disableSellPips;
disableBuyPips = !disableBuyPips;
disablePipsator = !disablePipsator;
}
}
return new DirectionFlags
{
DisableBuy = disableBuy,
DisableSell = disableSell,
DisablePipsator = disablePipsator,
DisableBuyPipsator = disableBuyPips,
DisableSellPipsator = disableSellPips,
};
}
private void UpdateHistory(ICandleMessage candle)
{
var snapshot = new CandleSnapshot(candle.OpenPrice, candle.HighPrice, candle.LowPrice, candle.ClosePrice);
_history.Enqueue(snapshot);
var maxHistory = MaxPeriod * (MaxPeriod * SamplesPerPeriod + 2);
while (_history.Count > maxHistory)
{
_history.Dequeue();
}
}
private ModelStats FindBestStats(CandleSnapshot[] candles)
{
var bestStats = default(ModelStats);
var bestQuality = decimal.MinValue;
var maxPeriod = MaxPeriod;
var segments = SamplesPerPeriod;
var spread = SpreadThreshold;
for (var period = 1; period <= maxPeriod; period++)
{
var modelingBars = period * segments;
var required = period * modelingBars + 1;
if (candles.Length < required)
{
continue;
}
var stats = CalculateStats(candles, period, modelingBars, spread);
if (!stats.IsValid)
{
continue;
}
if (stats.PossibilitySuccessRatio > bestQuality)
{
bestQuality = stats.PossibilitySuccessRatio;
bestStats = stats;
}
}
return bestStats;
}
private ModelStats CalculateStats(CandleSnapshot[] candles, int period, int modelingBars, decimal spreadThreshold)
{
var stats = new ModelStats { Period = period };
var buyQuality = 0;
var sellQuality = 0;
var undefinedQuality = 0;
var buySum = 0m;
var sellSum = 0m;
var undefinedSum = 0m;
var buySuccessSum = 0m;
var sellSuccessSum = 0m;
var undefinedSuccessSum = 0m;
for (var shift = 0; shift < modelingBars; shift++)
{
var currentIndex = candles.Length - 1 - period * shift;
var previousIndex = currentIndex - period;
if (previousIndex < 0)
{
return default;
}
var current = candles[currentIndex];
var previous = candles[previousIndex];
var decisionValue = current.Close - current.Open;
var previousValue = previous.Close - previous.Open;
var buyPossibility = 0m;
var sellPossibility = 0m;
var undefinedPossibility = 0m;
var decision = TradeDecisions.Unknown;
if (decisionValue > 0m)
{
if (previousValue < 0m)
{
decision = TradeDecisions.Sell;
sellPossibility = decisionValue;
}
else
{
undefinedPossibility = decisionValue;
}
}
else if (decisionValue < 0m)
{
if (previousValue > 0m)
{
decision = TradeDecisions.Buy;
buyPossibility = -decisionValue;
}
else
{
undefinedPossibility = -decisionValue;
}
}
if (shift == 0)
{
stats.CurrentDecision = decision;
stats.BuyPossibility = buyPossibility;
stats.SellPossibility = sellPossibility;
stats.UndefinedPossibility = undefinedPossibility;
}
switch (decision)
{
case TradeDecisions.Buy:
buyQuality++;
buySum += buyPossibility;
if (buyPossibility > spreadThreshold)
{
buySuccessSum += buyPossibility;
stats.BuySucPossibilityQuality++;
}
break;
case TradeDecisions.Sell:
sellQuality++;
sellSum += sellPossibility;
if (sellPossibility > spreadThreshold)
{
sellSuccessSum += sellPossibility;
stats.SellSucPossibilityQuality++;
}
break;
default:
undefinedQuality++;
undefinedSum += undefinedPossibility;
if (undefinedPossibility > spreadThreshold)
{
undefinedSuccessSum += undefinedPossibility;
stats.UndefinedSucPossibilityQuality++;
}
break;
}
}
stats.BuyPossibilityQuality = buyQuality;
stats.SellPossibilityQuality = sellQuality;
stats.UndefinedPossibilityQuality = undefinedQuality;
stats.BuyPossibilityMid = buyQuality > 0 ? buySum / buyQuality : 0m;
stats.SellPossibilityMid = sellQuality > 0 ? sellSum / sellQuality : 0m;
stats.UndefinedPossibilityMid = undefinedQuality > 0 ? undefinedSum / undefinedQuality : 0m;
var buySuccessCount = stats.BuySucPossibilityQuality;
var sellSuccessCount = stats.SellSucPossibilityQuality;
var undefinedSuccessCount = stats.UndefinedSucPossibilityQuality;
stats.BuySucPossibilityMid = buySuccessCount > 0 ? buySuccessSum / buySuccessCount : 0m;
stats.SellSucPossibilityMid = sellSuccessCount > 0 ? sellSuccessSum / sellSuccessCount : 0m;
stats.UndefinedSucPossibilityMid = undefinedSuccessCount > 0 ? undefinedSuccessSum / undefinedSuccessCount : 0m;
var successTotal = buySuccessCount + sellSuccessCount + undefinedSuccessCount;
if (successTotal > 0)
{
stats.PossibilitySuccessRatio = (buySuccessCount + sellSuccessCount) / (decimal)successTotal;
}
else
{
stats.PossibilitySuccessRatio = 0m;
}
stats.IsValid = buyQuality + sellQuality + undefinedQuality > 0;
return stats;
}
private readonly struct CandleSnapshot
{
public CandleSnapshot(decimal open, decimal high, decimal low, decimal close)
{
Open = open;
High = high;
Low = low;
Close = close;
}
public decimal Open { get; }
public decimal High { get; }
public decimal Low { get; }
public decimal Close { get; }
}
private struct DirectionFlags
{
public bool DisableBuy;
public bool DisableSell;
public bool DisablePipsator;
public bool DisableBuyPipsator;
public bool DisableSellPipsator;
}
private struct ModelStats
{
public bool IsValid;
public int Period;
public TradeDecisions CurrentDecision;
public decimal BuyPossibility;
public decimal SellPossibility;
public decimal UndefinedPossibility;
public int BuyPossibilityQuality;
public int SellPossibilityQuality;
public int UndefinedPossibilityQuality;
public decimal BuyPossibilityMid;
public decimal SellPossibilityMid;
public decimal UndefinedPossibilityMid;
public decimal BuySucPossibilityMid;
public decimal SellSucPossibilityMid;
public decimal UndefinedSucPossibilityMid;
public int BuySucPossibilityQuality;
public int SellSucPossibilityQuality;
public int UndefinedSucPossibilityQuality;
public decimal PossibilitySuccessRatio;
}
private enum TradeDecisions
{
Unknown,
Buy,
Sell,
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from collections import deque
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import (
MovingAverageConvergenceDivergenceSignal,
ExponentialMovingAverage,
)
# Trade decision constants
_DECISION_UNKNOWN = 0
_DECISION_BUY = 1
_DECISION_SELL = 2
class cyberia_trader_ai_strategy(Strategy):
def __init__(self):
super(cyberia_trader_ai_strategy, self).__init__()
self._max_period = self.Param("MaxPeriod", 23) \
.SetDisplay("Max Period", "Largest sampling stride tested by the probability engine", "Model")
self._samples_per_period = self.Param("SamplesPerPeriod", 5) \
.SetDisplay("Segments Per Period", "Number of historical segments processed for every period candidate", "Model")
self._spread_threshold = self.Param("SpreadThreshold", 0.0) \
.SetDisplay("Spread Threshold", "Minimal absolute move to count a probability as successful", "Model")
self._enable_cyberia_logic = self.Param("EnableCyberiaLogic", True) \
.SetDisplay("Enable Cyberia Logic", "Use the probability based disable/allow switches", "Filters")
self._enable_macd = self.Param("EnableMacd", False) \
.SetDisplay("Enable MACD", "Use MACD to block trading against momentum", "Filters")
self._enable_ma = self.Param("EnableMa", False) \
.SetDisplay("Enable EMA", "Use EMA slope to forbid trades against the trend", "Filters")
self._enable_reversal_detector = self.Param("EnableReversalDetector", False) \
.SetDisplay("Enable Reversal Detector", "Flip permissions on extreme probability spikes", "Filters")
self._ma_period = self.Param("MaPeriod", 23) \
.SetDisplay("EMA Period", "Length of the EMA used in the trend filter", "Indicators")
self._macd_fast = self.Param("MacdFast", 12) \
.SetDisplay("MACD Fast", "Fast EMA length for MACD", "Indicators")
self._macd_slow = self.Param("MacdSlow", 26) \
.SetDisplay("MACD Slow", "Slow EMA length for MACD", "Indicators")
self._macd_signal = self.Param("MacdSignal", 9) \
.SetDisplay("MACD Signal", "Signal EMA length for MACD", "Indicators")
self._reversal_factor = self.Param("ReversalFactor", 3.0) \
.SetDisplay("Reversal Factor", "Threshold multiplier that triggers the reversal detector", "Filters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(2))) \
.SetDisplay("Candle Type", "Primary timeframe processed by the model", "General")
self._take_profit_percent = self.Param("TakeProfitPercent", 0.0) \
.SetDisplay("Take Profit %", "Optional take profit distance expressed in percent", "Risk")
self._stop_loss_percent = self.Param("StopLossPercent", 0.0) \
.SetDisplay("Stop Loss %", "Optional stop loss distance expressed in percent", "Risk")
self._history = deque()
self._previous_ema = None
self._previous_period = None
self._current_stats = None
@property
def MaxPeriod(self):
return self._max_period.Value
@property
def SamplesPerPeriod(self):
return self._samples_per_period.Value
@property
def SpreadThreshold(self):
return self._spread_threshold.Value
@property
def EnableCyberiaLogic(self):
return self._enable_cyberia_logic.Value
@property
def EnableMacd(self):
return self._enable_macd.Value
@property
def EnableMa(self):
return self._enable_ma.Value
@property
def EnableReversalDetector(self):
return self._enable_reversal_detector.Value
@property
def MaPeriod(self):
return self._ma_period.Value
@property
def MacdFast(self):
return self._macd_fast.Value
@property
def MacdSlow(self):
return self._macd_slow.Value
@property
def MacdSignal(self):
return self._macd_signal.Value
@property
def ReversalFactor(self):
return self._reversal_factor.Value
@property
def CandleType(self):
return self._candle_type.Value
@property
def TakeProfitPercent(self):
return self._take_profit_percent.Value
@property
def StopLossPercent(self):
return self._stop_loss_percent.Value
def OnStarted2(self, time):
super(cyberia_trader_ai_strategy, self).OnStarted2(time)
self._macd_indicator = MovingAverageConvergenceDivergenceSignal()
self._macd_indicator.Macd.ShortMa.Length = self.MacdFast
self._macd_indicator.Macd.LongMa.Length = self.MacdSlow
self._macd_indicator.SignalMa.Length = self.MacdSignal
self._ema = ExponentialMovingAverage()
self._ema.Length = self.MaPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(self._macd_indicator, self._ema, self.ProcessCandle).Start()
tp = float(self.TakeProfitPercent)
sl = float(self.StopLossPercent)
take_profit = Unit(tp / 100.0, UnitTypes.Percent) if tp > 0 else Unit()
stop_loss = Unit(sl / 100.0, UnitTypes.Percent) if sl > 0 else Unit()
self.StartProtection(take_profit, stop_loss)
def ProcessCandle(self, candle, macd_value, ema_value):
if candle.State != CandleStates.Finished:
return
macd_main_val = None
macd_signal_val = None
if macd_value.IsFinal:
if hasattr(macd_value, 'Macd') and hasattr(macd_value, 'Signal'):
m = macd_value.Macd
s = macd_value.Signal
if m is not None:
macd_main_val = float(m)
if s is not None:
macd_signal_val = float(s)
elif self.EnableMacd:
return
ema_snapshot = None
if ema_value.IsFinal:
v = ema_value.Value if hasattr(ema_value, 'Value') else None
if v is not None:
ema_snapshot = float(v)
elif self.EnableMa:
return
self._update_history(candle)
candles = list(self._history)
self._current_stats = self._find_best_stats(candles)
if ema_snapshot is not None:
if self._previous_ema is None:
self._previous_ema = ema_snapshot
if not self.IsFormedAndOnlineAndAllowTrading():
if ema_snapshot is not None:
self._previous_ema = ema_snapshot
return
if self._current_stats is None or not self._current_stats['is_valid']:
if ema_snapshot is not None:
self._previous_ema = ema_snapshot
return
flags = self._calculate_direction(ema_snapshot, macd_main_val, macd_signal_val)
self._handle_positions(flags)
self._previous_period = self._current_stats['period']
def _handle_positions(self, flags):
stats = self._current_stats
if stats is None or not stats['is_valid']:
return
if self.Position > 0:
should_exit = (stats['current_decision'] == _DECISION_SELL and
stats['sell_possibility'] >= stats['sell_suc_possibility_mid'] and
stats['sell_suc_possibility_mid'] > 0) or \
(flags['disable_buy'] and stats['current_decision'] != _DECISION_BUY)
if should_exit:
self.SellMarket(Math.Abs(self.Position))
return
elif self.Position < 0:
should_exit = (stats['current_decision'] == _DECISION_BUY and
stats['buy_possibility'] >= stats['buy_suc_possibility_mid'] and
stats['buy_suc_possibility_mid'] > 0) or \
(flags['disable_sell'] and stats['current_decision'] != _DECISION_SELL)
if should_exit:
self.BuyMarket(Math.Abs(self.Position))
return
if (stats['current_decision'] == _DECISION_BUY and
not flags['disable_buy'] and
stats['buy_possibility'] >= stats['buy_suc_possibility_mid'] and
stats['buy_suc_possibility_mid'] > 0 and
self.Position <= 0):
volume = self.Volume + (Math.Abs(self.Position) if self.Position < 0 else 0)
self.BuyMarket(volume)
return
if (stats['current_decision'] == _DECISION_SELL and
not flags['disable_sell'] and
stats['sell_possibility'] >= stats['sell_suc_possibility_mid'] and
stats['sell_suc_possibility_mid'] > 0 and
self.Position >= 0):
volume = self.Volume + (Math.Abs(self.Position) if self.Position > 0 else 0)
self.SellMarket(volume)
def _calculate_direction(self, ema_val, macd_main, macd_signal):
stats = self._current_stats
disable_buy = False
disable_sell = False
disable_pipsator = False
disable_buy_pips = False
disable_sell_pips = False
if self.EnableCyberiaLogic:
buy_score = stats['buy_possibility_mid'] * stats['buy_possibility_quality']
sell_score = stats['sell_possibility_mid'] * stats['sell_possibility_quality']
if self._previous_period is not None:
if stats['period'] > self._previous_period:
if sell_score > buy_score:
disable_sell = False
disable_buy = True
disable_buy_pips = True
if (stats['sell_suc_possibility_mid'] * stats['sell_suc_possibility_quality'] >
stats['buy_suc_possibility_mid'] * stats['buy_suc_possibility_quality']):
disable_sell = True
elif sell_score < buy_score:
disable_sell = True
disable_buy = False
disable_sell_pips = True
if (stats['sell_suc_possibility_mid'] * stats['sell_suc_possibility_quality'] <
stats['buy_suc_possibility_mid'] * stats['buy_suc_possibility_quality']):
disable_buy = True
elif stats['period'] < self._previous_period:
disable_sell = True
disable_buy = True
if sell_score == buy_score:
disable_sell = True
disable_buy = True
disable_pipsator = False
if stats['sell_suc_possibility_mid'] > 0 and stats['sell_possibility'] > stats['sell_suc_possibility_mid'] * 2:
disable_sell = True
disable_sell_pips = True
if stats['buy_suc_possibility_mid'] > 0 and stats['buy_possibility'] > stats['buy_suc_possibility_mid'] * 2:
disable_buy = True
disable_buy_pips = True
if self.EnableMa and ema_val is not None:
if self._previous_ema is not None:
if ema_val > self._previous_ema:
disable_sell = True
disable_sell_pips = True
elif ema_val < self._previous_ema:
disable_buy = True
disable_buy_pips = True
self._previous_ema = ema_val
elif ema_val is not None:
self._previous_ema = ema_val
if self.EnableMacd and macd_main is not None and macd_signal is not None:
if macd_main > macd_signal:
disable_sell = True
elif macd_main < macd_signal:
disable_buy = True
if self.EnableReversalDetector:
trigger = False
rev_factor = float(self.ReversalFactor)
if stats['buy_possibility_mid'] > 0 and stats['buy_possibility'] > stats['buy_possibility_mid'] * rev_factor:
trigger = True
if stats['sell_possibility_mid'] > 0 and stats['sell_possibility'] > stats['sell_possibility_mid'] * rev_factor:
trigger = True
if trigger:
disable_sell = not disable_sell
disable_buy = not disable_buy
disable_sell_pips = not disable_sell_pips
disable_buy_pips = not disable_buy_pips
disable_pipsator = not disable_pipsator
return {
'disable_buy': disable_buy,
'disable_sell': disable_sell,
'disable_pipsator': disable_pipsator,
'disable_buy_pips': disable_buy_pips,
'disable_sell_pips': disable_sell_pips,
}
def _update_history(self, candle):
snapshot = (float(candle.OpenPrice), float(candle.HighPrice), float(candle.LowPrice), float(candle.ClosePrice))
self._history.append(snapshot)
max_period = self.MaxPeriod
samples = self.SamplesPerPeriod
max_history = max_period * (max_period * samples + 2)
while len(self._history) > max_history:
self._history.popleft()
def _find_best_stats(self, candles):
best_stats = None
best_quality = -1e18
max_period = self.MaxPeriod
segments = self.SamplesPerPeriod
spread = float(self.SpreadThreshold)
for period in range(1, max_period + 1):
modeling_bars = period * segments
required = period * modeling_bars + 1
if len(candles) < required:
continue
stats = self._calculate_stats(candles, period, modeling_bars, spread)
if stats is None or not stats['is_valid']:
continue
if stats['possibility_success_ratio'] > best_quality:
best_quality = stats['possibility_success_ratio']
best_stats = stats
return best_stats
def _calculate_stats(self, candles, period, modeling_bars, spread_threshold):
stats = {
'is_valid': False,
'period': period,
'current_decision': _DECISION_UNKNOWN,
'buy_possibility': 0.0,
'sell_possibility': 0.0,
'undefined_possibility': 0.0,
'buy_possibility_quality': 0,
'sell_possibility_quality': 0,
'undefined_possibility_quality': 0,
'buy_possibility_mid': 0.0,
'sell_possibility_mid': 0.0,
'undefined_possibility_mid': 0.0,
'buy_suc_possibility_mid': 0.0,
'sell_suc_possibility_mid': 0.0,
'undefined_suc_possibility_mid': 0.0,
'buy_suc_possibility_quality': 0,
'sell_suc_possibility_quality': 0,
'undefined_suc_possibility_quality': 0,
'possibility_success_ratio': 0.0,
}
buy_quality = 0
sell_quality = 0
undefined_quality = 0
buy_sum = 0.0
sell_sum = 0.0
undefined_sum = 0.0
buy_success_sum = 0.0
sell_success_sum = 0.0
undefined_success_sum = 0.0
for shift in range(modeling_bars):
current_index = len(candles) - 1 - period * shift
previous_index = current_index - period
if previous_index < 0:
return None
current = candles[current_index]
previous = candles[previous_index]
decision_value = current[3] - current[0] # close - open
previous_value = previous[3] - previous[0]
buy_poss = 0.0
sell_poss = 0.0
undef_poss = 0.0
decision = _DECISION_UNKNOWN
if decision_value > 0:
if previous_value < 0:
decision = _DECISION_SELL
sell_poss = decision_value
else:
undef_poss = decision_value
elif decision_value < 0:
if previous_value > 0:
decision = _DECISION_BUY
buy_poss = -decision_value
else:
undef_poss = -decision_value
if shift == 0:
stats['current_decision'] = decision
stats['buy_possibility'] = buy_poss
stats['sell_possibility'] = sell_poss
stats['undefined_possibility'] = undef_poss
if decision == _DECISION_BUY:
buy_quality += 1
buy_sum += buy_poss
if buy_poss > spread_threshold:
buy_success_sum += buy_poss
stats['buy_suc_possibility_quality'] += 1
elif decision == _DECISION_SELL:
sell_quality += 1
sell_sum += sell_poss
if sell_poss > spread_threshold:
sell_success_sum += sell_poss
stats['sell_suc_possibility_quality'] += 1
else:
undefined_quality += 1
undefined_sum += undef_poss
if undef_poss > spread_threshold:
undefined_success_sum += undef_poss
stats['undefined_suc_possibility_quality'] += 1
stats['buy_possibility_quality'] = buy_quality
stats['sell_possibility_quality'] = sell_quality
stats['undefined_possibility_quality'] = undefined_quality
stats['buy_possibility_mid'] = buy_sum / buy_quality if buy_quality > 0 else 0.0
stats['sell_possibility_mid'] = sell_sum / sell_quality if sell_quality > 0 else 0.0
stats['undefined_possibility_mid'] = undefined_sum / undefined_quality if undefined_quality > 0 else 0.0
bsc = stats['buy_suc_possibility_quality']
ssc = stats['sell_suc_possibility_quality']
usc = stats['undefined_suc_possibility_quality']
stats['buy_suc_possibility_mid'] = buy_success_sum / bsc if bsc > 0 else 0.0
stats['sell_suc_possibility_mid'] = sell_success_sum / ssc if ssc > 0 else 0.0
stats['undefined_suc_possibility_mid'] = undefined_success_sum / usc if usc > 0 else 0.0
success_total = bsc + ssc + usc
if success_total > 0:
stats['possibility_success_ratio'] = (bsc + ssc) / float(success_total)
else:
stats['possibility_success_ratio'] = 0.0
stats['is_valid'] = (buy_quality + sell_quality + undefined_quality) > 0
return stats
def OnReseted(self):
super(cyberia_trader_ai_strategy, self).OnReseted()
self._history = deque()
self._previous_ema = None
self._previous_period = None
self._current_stats = None
def CreateClone(self):
return cyberia_trader_ai_strategy()