Blau Ergodic-Strategie
Diese Strategie portiert den Expert Advisor Exp_BlauErgodic von MQL5 auf StockSharp. Sie rekonstruiert den Blau Ergodic-Oszillator durch dreifaches Glätten des Momentums und seines absoluten Wertes mit EMA-Filtern, erzeugt einen normalisierten Oszillator und eine Signallinie, und bietet drei verschiedene Signalmodi, die den ursprünglichen EA widerspiegeln.
Die Standardkonfiguration wertet abgeschlossene 4-Stunden-Kerzen aus. Sie können den angewendeten Preis (Schluss, Eröffnung, hoch/tief-basierte
Durchschnitte), jede Glättungstiefe und den Balkenindex (SignalBar) ändern, der zum Lesen von Signalen verwendet wird. Trades werden über die
Volume-Eigenschaft der Strategie dimensioniert; Long/Short-Einstiege oder -Ausstiege können individuell durch boolesche Parameter deaktiviert werden. Schutz-Stop-Loss- und
Take-Profit-Niveaus werden in Punkten definiert und über Security.PriceStep in absolute Preise umgerechnet.
Signalmodi
- Breakdown – reagiert auf das Kreuzen der Nulllinie durch den Oszillator. Longs öffnen bei negativen-zu-positiven Flips und Shorts bei positiven-zu-negativen Flips. Positionen werden geschlossen, wenn der Oszillator auf der gegenüberliegenden Seite von null verbleibt.
- Twist – sucht nach Steigungsumkehrungen. Ein Long-Setup erscheint, wenn der Oszillator auf dem vorherigen Balken fiel, aber auf dem neuesten Balken steigt; ein Short-Setup erfordert das umgekehrte Muster.
- CloudTwist – überwacht das Kreuzen der Signallinie durch den Oszillator. Longs werden ausgelöst, wenn der Oszillator durch die Signalwolke steigt, und Shorts wenn er wieder darunter fällt.
Alle Modi lesen Indikatorwerte vom Balken, der durch SignalBar angegeben wird (Standard 1, also der letzte abgeschlossene Balken) und stützen sich auf
ältere Werte zur Bestätigung. Setzen Sie SignalBar auf mindestens 1, da die Konvertierung nur abgeschlossene Kerzen verarbeitet.
Einstiegs- und Ausstiegsregeln
- Long-Einstiege: aktiviert wenn
AllowBuyEntrywahr ist, keine bestehende Long-Position vorhanden ist (Position <= 0), und der aktive Modus eine Kaufbedingung generiert. Die Strategie kehrt jede Short-Exposition um, indem sieVolume + |Position|kauft. - Short-Einstiege: aktiviert wenn
AllowSellEntrywahr ist, keine bestehende Short-Position vorhanden ist (Position >= 0), und der aktive Modus eine Verkaufsbedingung ausgibt. Sie deckt jede Long-Exposition ab, bevor der Short etabliert wird. - Long-Ausstiege: ausgelöst durch die moduspezifische Bedingung, oder wenn
StopLossPoints/TakeProfitPointserreicht werden. Erzwungene Ausstiege umgehen denAllowBuyExit-Flag, damit Schutz-Stops immer eingehalten werden. - Short-Ausstiege: analog zur Long-Ausstiegslogik mit
AllowSellExitund Stop-Niveaus für Short-Trades.
Parameter
CandleType– Zeitrahmen für Kerzenabonnements (Standard 4-Stunden-Kerzen).Mode– eines vonBreakdown,Twist, oderCloudTwist.MomentumLength– Rückblick für die rohe Momentumdifferenz.First/Second/ThirdSmoothingLength– EMA-Tiefen für die kaskadierten Momentum-Filter.SignalSmoothingLength– EMA-Tiefe für die Signallinie.SignalBar– Index des abgeschlossenen Balkens zum Lesen von Signalen (Minimum1).AppliedPrices– Preisquelle, die den Oszillator speist (Schluss, Eröffnung, Median, Typisch, Gewichtet, etc.).AllowBuyEntry,AllowSellEntry,AllowBuyExit,AllowSellExit– bestimmte Operationen aktivieren oder deaktivieren.StopLossPoints,TakeProfitPoints– Schutzabstände in Punkten (konvertiert überSecurity.PriceStep).
Die Konvertierung behält das Verhalten des MQL5-Experten bei und nutzt die StockSharp-High-Level-API (SubscribeCandles,
Bind) unter Einhaltung der StockSharp-Strategie-Konventionen mit Tabulatoreinrückung und englischen Kommentaren.
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;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Blau Ergodic oscillator strategy with multiple signal modes.
/// </summary>
public class BlauErgodicStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<BlauErgodicModes> _mode;
private readonly StrategyParam<int> _momentumLength;
private readonly StrategyParam<int> _firstSmoothingLength;
private readonly StrategyParam<int> _secondSmoothingLength;
private readonly StrategyParam<int> _thirdSmoothingLength;
private readonly StrategyParam<int> _signalSmoothingLength;
private readonly StrategyParam<int> _signalBar;
private readonly StrategyParam<AppliedPrices> _appliedPrice;
private readonly StrategyParam<bool> _allowBuyEntry;
private readonly StrategyParam<bool> _allowSellEntry;
private readonly StrategyParam<bool> _allowBuyExit;
private readonly StrategyParam<bool> _allowSellExit;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private ExponentialMovingAverage _momEma1 = null!;
private ExponentialMovingAverage _momEma2 = null!;
private ExponentialMovingAverage _momEma3 = null!;
private ExponentialMovingAverage _absMomEma1 = null!;
private ExponentialMovingAverage _absMomEma2 = null!;
private ExponentialMovingAverage _absMomEma3 = null!;
private ExponentialMovingAverage _signal = null!;
private readonly List<decimal> _priceHistory = new();
private readonly List<decimal> _mainHistory = new();
private readonly List<decimal?> _signalHistory = new();
private decimal _entryPrice;
/// <summary>
/// Trading candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Mode that defines signal detection.
/// </summary>
public BlauErgodicModes Mode
{
get => _mode.Value;
set => _mode.Value = value;
}
/// <summary>
/// Momentum lookback length.
/// </summary>
public int MomentumLength
{
get => _momentumLength.Value;
set => _momentumLength.Value = value;
}
/// <summary>
/// First EMA smoothing length for momentum streams.
/// </summary>
public int FirstSmoothingLength
{
get => _firstSmoothingLength.Value;
set => _firstSmoothingLength.Value = value;
}
/// <summary>
/// Second EMA smoothing length for momentum streams.
/// </summary>
public int SecondSmoothingLength
{
get => _secondSmoothingLength.Value;
set => _secondSmoothingLength.Value = value;
}
/// <summary>
/// Third EMA smoothing length for momentum streams.
/// </summary>
public int ThirdSmoothingLength
{
get => _thirdSmoothingLength.Value;
set => _thirdSmoothingLength.Value = value;
}
/// <summary>
/// EMA length applied to the signal line.
/// </summary>
public int SignalSmoothingLength
{
get => _signalSmoothingLength.Value;
set => _signalSmoothingLength.Value = value;
}
/// <summary>
/// Number of closed candles used to read signals.
/// </summary>
public int SignalBar
{
get => _signalBar.Value;
set => _signalBar.Value = value;
}
/// <summary>
/// Price source used inside the indicator.
/// </summary>
public AppliedPrices AppliedPrice
{
get => _appliedPrice.Value;
set => _appliedPrice.Value = value;
}
/// <summary>
/// Allows opening long positions.
/// </summary>
public bool AllowBuyEntry
{
get => _allowBuyEntry.Value;
set => _allowBuyEntry.Value = value;
}
/// <summary>
/// Allows opening short positions.
/// </summary>
public bool AllowSellEntry
{
get => _allowSellEntry.Value;
set => _allowSellEntry.Value = value;
}
/// <summary>
/// Allows closing long positions on indicator signals.
/// </summary>
public bool AllowBuyExit
{
get => _allowBuyExit.Value;
set => _allowBuyExit.Value = value;
}
/// <summary>
/// Allows closing short positions on indicator signals.
/// </summary>
public bool AllowSellExit
{
get => _allowSellExit.Value;
set => _allowSellExit.Value = value;
}
/// <summary>
/// Stop loss distance in points.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit distance in points.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public BlauErgodicStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for calculations", "General");
_mode = Param(nameof(Mode), BlauErgodicModes.Twist)
.SetDisplay("Mode", "Signal interpretation mode", "Trading");
_momentumLength = Param(nameof(MomentumLength), 2)
.SetGreaterThanZero()
.SetDisplay("Momentum Length", "Momentum lookback for Blau Ergodic", "Indicator");
_firstSmoothingLength = Param(nameof(FirstSmoothingLength), 20)
.SetGreaterThanZero()
.SetDisplay("First Smoothing", "First EMA smoothing length", "Indicator");
_secondSmoothingLength = Param(nameof(SecondSmoothingLength), 5)
.SetGreaterThanZero()
.SetDisplay("Second Smoothing", "Second EMA smoothing length", "Indicator");
_thirdSmoothingLength = Param(nameof(ThirdSmoothingLength), 3)
.SetGreaterThanZero()
.SetDisplay("Third Smoothing", "Third EMA smoothing length", "Indicator");
_signalSmoothingLength = Param(nameof(SignalSmoothingLength), 3)
.SetGreaterThanZero()
.SetDisplay("Signal Smoothing", "EMA length for signal line", "Indicator");
_signalBar = Param(nameof(SignalBar), 1)
.SetGreaterThanZero()
.SetDisplay("Signal Bar", "Completed bars back to evaluate", "Trading");
_appliedPrice = Param(nameof(AppliedPrices), AppliedPrices.Close)
.SetDisplay("Applied Price", "Price source for calculations", "Indicator");
_allowBuyEntry = Param(nameof(AllowBuyEntry), true)
.SetDisplay("Allow Buy Entry", "Allow opening long positions", "Trading");
_allowSellEntry = Param(nameof(AllowSellEntry), true)
.SetDisplay("Allow Sell Entry", "Allow opening short positions", "Trading");
_allowBuyExit = Param(nameof(AllowBuyExit), true)
.SetDisplay("Allow Buy Exit", "Allow closing long positions", "Trading");
_allowSellExit = Param(nameof(AllowSellExit), true)
.SetDisplay("Allow Sell Exit", "Allow closing short positions", "Trading");
_stopLossPoints = Param(nameof(StopLossPoints), 1000)
.SetDisplay("Stop Loss", "Protective stop loss distance", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000)
.SetDisplay("Take Profit", "Profit target distance", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_priceHistory.Clear();
_mainHistory.Clear();
_signalHistory.Clear();
_entryPrice = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
// Initialize EMA cascades for momentum and absolute momentum streams.
_momEma1 = new ExponentialMovingAverage { Length = FirstSmoothingLength };
_momEma2 = new ExponentialMovingAverage { Length = SecondSmoothingLength };
_momEma3 = new ExponentialMovingAverage { Length = ThirdSmoothingLength };
_absMomEma1 = new ExponentialMovingAverage { Length = FirstSmoothingLength };
_absMomEma2 = new ExponentialMovingAverage { Length = SecondSmoothingLength };
_absMomEma3 = new ExponentialMovingAverage { Length = ThirdSmoothingLength };
_signal = new ExponentialMovingAverage { Length = SignalSmoothingLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// Store price history for momentum calculation.
var price = GetAppliedPrice(candle);
_priceHistory.Add(price);
TrimHistory(_priceHistory, MomentumLength + SignalBar + 10);
if (MomentumLength <= 0)
return;
var backShift = MomentumLength - 1;
if (_priceHistory.Count <= backShift)
return;
var referenceIndex = _priceHistory.Count - 1 - backShift;
var referencePrice = _priceHistory[referenceIndex];
var momentum = price - referencePrice;
var absMomentum = Math.Abs(momentum);
// Process cascaded EMA filters for momentum and absolute momentum.
var time = candle.ServerTime;
var mom1 = _momEma1.Process(new DecimalIndicatorValue(_momEma1, momentum, time) { IsFinal = true });
var abs1 = _absMomEma1.Process(new DecimalIndicatorValue(_absMomEma1, absMomentum, time) { IsFinal = true });
if (mom1.IsEmpty || abs1.IsEmpty)
return;
var mom2 = _momEma2.Process(new DecimalIndicatorValue(_momEma2, mom1.ToDecimal(), time) { IsFinal = true });
var abs2 = _absMomEma2.Process(new DecimalIndicatorValue(_absMomEma2, abs1.ToDecimal(), time) { IsFinal = true });
if (mom2.IsEmpty || abs2.IsEmpty)
return;
var mom3 = _momEma3.Process(new DecimalIndicatorValue(_momEma3, mom2.ToDecimal(), time) { IsFinal = true });
var abs3 = _absMomEma3.Process(new DecimalIndicatorValue(_absMomEma3, abs2.ToDecimal(), time) { IsFinal = true });
if (mom3.IsEmpty || abs3.IsEmpty)
return;
var smoothedMomentum = mom3.ToDecimal();
var smoothedAbsMomentum = abs3.ToDecimal();
var main = smoothedAbsMomentum == 0m ? 0m : 100m * smoothedMomentum / smoothedAbsMomentum;
var signalValue = _signal.Process(new DecimalIndicatorValue(_signal, main, time) { IsFinal = true });
decimal? signal = null;
if (!signalValue.IsEmpty)
signal = signalValue.ToDecimal();
AppendIndicatorHistory(main, signal);
EvaluateSignals(candle);
}
private void EvaluateSignals(ICandleMessage candle)
{
var currentIndex = SignalBar - 1;
if (currentIndex < 0)
return;
if (!TryGetMainValue(currentIndex, out var currentMain))
return;
var buyOpen = false;
var sellOpen = false;
var buyClose = false;
var sellClose = false;
switch (Mode)
{
case BlauErgodicModes.Breakdown:
{
if (!TryGetMainValue(currentIndex + 1, out var previousMain))
return;
// Close shorts when histogram stays above zero and longs when it stays below zero.
if (AllowSellExit && currentMain > 0m)
sellClose = true;
if (AllowBuyExit && currentMain < 0m)
buyClose = true;
if (AllowBuyEntry && previousMain <= 0m && currentMain > 0m)
buyOpen = true;
if (AllowSellEntry && previousMain >= 0m && currentMain < 0m)
sellOpen = true;
break;
}
case BlauErgodicModes.Twist:
{
if (!TryGetMainValue(currentIndex + 1, out var previousMain) ||
!TryGetMainValue(currentIndex + 2, out var olderMain))
return;
// Detect turning points by comparing slope changes.
if (AllowSellExit && previousMain < currentMain)
sellClose = true;
if (AllowBuyExit && previousMain > currentMain)
buyClose = true;
if (AllowBuyEntry && olderMain > previousMain && previousMain < currentMain)
buyOpen = true;
if (AllowSellEntry && olderMain < previousMain && previousMain > currentMain)
sellOpen = true;
break;
}
case BlauErgodicModes.CloudTwist:
{
if (!TryGetMainValue(currentIndex + 1, out var previousMain) ||
!TryGetSignalValue(currentIndex, out var currentSignal) ||
!TryGetSignalValue(currentIndex + 1, out var previousSignal))
return;
// Close when main line crosses the signal line.
if (AllowSellExit && currentMain > currentSignal)
sellClose = true;
if (AllowBuyExit && currentMain < currentSignal)
buyClose = true;
if (AllowBuyEntry && previousMain <= previousSignal && currentMain > currentSignal)
buyOpen = true;
if (AllowSellEntry && previousMain >= previousSignal && currentMain < currentSignal)
sellOpen = true;
break;
}
}
var (closeLongByStops, closeShortByStops) = EvaluateStops(candle);
var forceBuyClose = closeLongByStops;
var forceSellClose = closeShortByStops;
if (closeLongByStops)
buyClose = true;
if (closeShortByStops)
sellClose = true;
ExecuteOrders(candle, buyOpen, sellOpen, buyClose, sellClose, forceBuyClose, forceSellClose);
}
private (bool closeLong, bool closeShort) EvaluateStops(ICandleMessage candle)
{
var closeLong = false;
var closeShort = false;
var priceStep = Security?.PriceStep ?? 0m;
var stopLossDistance = priceStep > 0m && StopLossPoints > 0 ? StopLossPoints * priceStep : 0m;
var takeProfitDistance = priceStep > 0m && TakeProfitPoints > 0 ? TakeProfitPoints * priceStep : 0m;
// Evaluate protective levels against the current candle range.
if (Position > 0)
{
if (stopLossDistance > 0m && candle.LowPrice <= _entryPrice - stopLossDistance)
closeLong = true;
if (takeProfitDistance > 0m && candle.HighPrice >= _entryPrice + takeProfitDistance)
closeLong = true;
}
else if (Position < 0)
{
if (stopLossDistance > 0m && candle.HighPrice >= _entryPrice + stopLossDistance)
closeShort = true;
if (takeProfitDistance > 0m && candle.LowPrice <= _entryPrice - takeProfitDistance)
closeShort = true;
}
return (closeLong, closeShort);
}
private void ExecuteOrders(ICandleMessage candle, bool buyOpen, bool sellOpen, bool buyClose, bool sellClose, bool forceBuyClose, bool forceSellClose)
{
if (((buyClose && AllowBuyExit) || forceBuyClose) && Position > 0)
{
// Close existing long position.
SellMarket(Position);
_entryPrice = 0m;
}
if (((sellClose && AllowSellExit) || forceSellClose) && Position < 0)
{
// Close existing short position.
BuyMarket(-Position);
_entryPrice = 0m;
}
if (buyOpen && AllowBuyEntry && Position <= 0)
{
// Reverse any short exposure and open a new long.
var volume = Volume + Math.Abs(Position);
BuyMarket(volume);
_entryPrice = candle.ClosePrice;
}
if (sellOpen && AllowSellEntry && Position >= 0)
{
// Reverse any long exposure and open a new short.
var volume = Volume + Math.Abs(Position);
SellMarket(volume);
_entryPrice = candle.ClosePrice;
}
}
private decimal GetAppliedPrice(ICandleMessage candle)
{
return AppliedPrice switch
{
AppliedPrices.Open => candle.OpenPrice,
AppliedPrices.High => candle.HighPrice,
AppliedPrices.Low => candle.LowPrice,
AppliedPrices.Median => (candle.HighPrice + candle.LowPrice) / 2m,
AppliedPrices.Typical => (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m,
AppliedPrices.Weighted => (candle.HighPrice + candle.LowPrice + candle.ClosePrice + candle.ClosePrice) / 4m,
AppliedPrices.Simple => (candle.OpenPrice + candle.ClosePrice) / 2m,
AppliedPrices.Quarter => (candle.HighPrice + candle.LowPrice + candle.OpenPrice + candle.ClosePrice) / 4m,
_ => candle.ClosePrice,
};
}
private void AppendIndicatorHistory(decimal main, decimal? signal)
{
_mainHistory.Add(main);
_signalHistory.Add(signal);
var maxSize = Math.Max(SignalBar + 5, 10);
TrimHistory(_mainHistory, maxSize);
TrimHistory(_signalHistory, maxSize);
}
private static void TrimHistory<T>(IList<T> values, int maxSize)
{
while (values.Count > maxSize)
values.RemoveAt(0);
}
private bool TryGetMainValue(int shift, out decimal value)
{
value = default;
var index = _mainHistory.Count - 1 - shift;
if (index < 0 || index >= _mainHistory.Count)
return false;
value = _mainHistory[index];
return true;
}
private bool TryGetSignalValue(int shift, out decimal value)
{
value = default;
var index = _signalHistory.Count - 1 - shift;
if (index < 0 || index >= _signalHistory.Count)
return false;
var raw = _signalHistory[index];
if (raw is null)
return false;
value = raw.Value;
return true;
}
/// <summary>
/// Trading modes supported by the strategy.
/// </summary>
public enum BlauErgodicModes
{
Breakdown,
Twist,
CloudTwist,
}
/// <summary>
/// Price types available for indicator calculation.
/// </summary>
public enum AppliedPrices
{
Close,
Open,
High,
Low,
Median,
Typical,
Weighted,
Simple,
Quarter,
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Messages import DataType, CandleStates
from System import TimeSpan, Math, Decimal
from indicator_extensions import *
class blau_ergodic_strategy(Strategy):
def __init__(self):
super(blau_ergodic_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8)))
self._mode = self.Param("Mode", 1)
self._momentum_length = self.Param("MomentumLength", 2)
self._first_smoothing_length = self.Param("FirstSmoothingLength", 20)
self._second_smoothing_length = self.Param("SecondSmoothingLength", 5)
self._third_smoothing_length = self.Param("ThirdSmoothingLength", 3)
self._signal_smoothing_length = self.Param("SignalSmoothingLength", 3)
self._signal_bar = self.Param("SignalBar", 1)
self._allow_buy_entry = self.Param("AllowBuyEntry", True)
self._allow_sell_entry = self.Param("AllowSellEntry", True)
self._allow_buy_exit = self.Param("AllowBuyExit", True)
self._allow_sell_exit = self.Param("AllowSellExit", True)
self._stop_loss_points = self.Param("StopLossPoints", 1000)
self._take_profit_points = self.Param("TakeProfitPoints", 2000)
self._mom_ema1 = None
self._mom_ema2 = None
self._mom_ema3 = None
self._abs_mom_ema1 = None
self._abs_mom_ema2 = None
self._abs_mom_ema3 = None
self._signal_ema = None
self._price_history = []
self._main_history = []
self._signal_history = []
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(blau_ergodic_strategy, self).OnStarted2(time)
self._mom_ema1 = ExponentialMovingAverage()
self._mom_ema1.Length = self._first_smoothing_length.Value
self._mom_ema2 = ExponentialMovingAverage()
self._mom_ema2.Length = self._second_smoothing_length.Value
self._mom_ema3 = ExponentialMovingAverage()
self._mom_ema3.Length = self._third_smoothing_length.Value
self._abs_mom_ema1 = ExponentialMovingAverage()
self._abs_mom_ema1.Length = self._first_smoothing_length.Value
self._abs_mom_ema2 = ExponentialMovingAverage()
self._abs_mom_ema2.Length = self._second_smoothing_length.Value
self._abs_mom_ema3 = ExponentialMovingAverage()
self._abs_mom_ema3.Length = self._third_smoothing_length.Value
self._signal_ema = ExponentialMovingAverage()
self._signal_ema.Length = self._signal_smoothing_length.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
price = float(candle.ClosePrice)
self._price_history.append(price)
max_hist = self._momentum_length.Value + self._signal_bar.Value + 10
while len(self._price_history) > max_hist:
self._price_history.pop(0)
if self._momentum_length.Value <= 0:
return
back_shift = self._momentum_length.Value - 1
if len(self._price_history) <= back_shift:
return
ref_index = len(self._price_history) - 1 - back_shift
ref_price = self._price_history[ref_index]
momentum = price - ref_price
abs_momentum = abs(momentum)
t = candle.ServerTime
mom1 = process_float(self._mom_ema1, Decimal(float(momentum)), t, True)
abs1 = process_float(self._abs_mom_ema1, Decimal(float(abs_momentum)), t, True)
if mom1.IsEmpty or abs1.IsEmpty:
return
mom2 = process_float(self._mom_ema2, Decimal(float(mom1.Value)), t, True)
abs2 = process_float(self._abs_mom_ema2, Decimal(float(abs1.Value)), t, True)
if mom2.IsEmpty or abs2.IsEmpty:
return
mom3 = process_float(self._mom_ema3, Decimal(float(mom2.Value)), t, True)
abs3 = process_float(self._abs_mom_ema3, Decimal(float(abs2.Value)), t, True)
if mom3.IsEmpty or abs3.IsEmpty:
return
smoothed_mom = float(mom3.Value)
smoothed_abs = float(abs3.Value)
main = 0.0 if smoothed_abs == 0.0 else 100.0 * smoothed_mom / smoothed_abs
signal_result = process_float(self._signal_ema, Decimal(float(main)), t, True)
signal = float(signal_result.Value) if not signal_result.IsEmpty else None
self._main_history.append(main)
self._signal_history.append(signal)
max_size = max(self._signal_bar.Value + 5, 10)
while len(self._main_history) > max_size:
self._main_history.pop(0)
while len(self._signal_history) > max_size:
self._signal_history.pop(0)
self._evaluate_signals(candle)
def _evaluate_signals(self, candle):
current_index = self._signal_bar.Value - 1
if current_index < 0:
return
current_main = self._try_get_main(current_index)
if current_main is None:
return
buy_open = False
sell_open = False
buy_close = False
sell_close = False
mode = self._mode.Value
if mode == 0:
previous_main = self._try_get_main(current_index + 1)
if previous_main is None:
return
if self._allow_sell_exit.Value and current_main > 0:
sell_close = True
if self._allow_buy_exit.Value and current_main < 0:
buy_close = True
if self._allow_buy_entry.Value and previous_main <= 0 and current_main > 0:
buy_open = True
if self._allow_sell_entry.Value and previous_main >= 0 and current_main < 0:
sell_open = True
elif mode == 1:
previous_main = self._try_get_main(current_index + 1)
older_main = self._try_get_main(current_index + 2)
if previous_main is None or older_main is None:
return
if self._allow_sell_exit.Value and previous_main < current_main:
sell_close = True
if self._allow_buy_exit.Value and previous_main > current_main:
buy_close = True
if self._allow_buy_entry.Value and older_main > previous_main and previous_main < current_main:
buy_open = True
if self._allow_sell_entry.Value and older_main < previous_main and previous_main > current_main:
sell_open = True
elif mode == 2:
previous_main = self._try_get_main(current_index + 1)
current_signal = self._try_get_signal(current_index)
previous_signal = self._try_get_signal(current_index + 1)
if previous_main is None or current_signal is None or previous_signal is None:
return
if self._allow_sell_exit.Value and current_main > current_signal:
sell_close = True
if self._allow_buy_exit.Value and current_main < current_signal:
buy_close = True
if self._allow_buy_entry.Value and previous_main <= previous_signal and current_main > current_signal:
buy_open = True
if self._allow_sell_entry.Value and previous_main >= previous_signal and current_main < current_signal:
sell_open = True
close_long_stops, close_short_stops = self._evaluate_stops(candle)
if close_long_stops:
buy_close = True
if close_short_stops:
sell_close = True
self._execute_orders(candle, buy_open, sell_open, buy_close, sell_close, close_long_stops, close_short_stops)
def _evaluate_stops(self, candle):
close_long = False
close_short = False
price_step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 0.0
sl_dist = self._stop_loss_points.Value * price_step if price_step > 0 and self._stop_loss_points.Value > 0 else 0.0
tp_dist = self._take_profit_points.Value * price_step if price_step > 0 and self._take_profit_points.Value > 0 else 0.0
if self.Position > 0:
if sl_dist > 0 and float(candle.LowPrice) <= self._entry_price - sl_dist:
close_long = True
if tp_dist > 0 and float(candle.HighPrice) >= self._entry_price + tp_dist:
close_long = True
elif self.Position < 0:
if sl_dist > 0 and float(candle.HighPrice) >= self._entry_price + sl_dist:
close_short = True
if tp_dist > 0 and float(candle.LowPrice) <= self._entry_price - tp_dist:
close_short = True
return close_long, close_short
def _execute_orders(self, candle, buy_open, sell_open, buy_close, sell_close, force_buy_close, force_sell_close):
if ((buy_close and self._allow_buy_exit.Value) or force_buy_close) and self.Position > 0:
self.SellMarket(self.Position)
self._entry_price = 0.0
if ((sell_close and self._allow_sell_exit.Value) or force_sell_close) and self.Position < 0:
self.BuyMarket(abs(self.Position))
self._entry_price = 0.0
if buy_open and self._allow_buy_entry.Value and self.Position <= 0:
volume = float(self.Volume) + abs(self.Position)
self.BuyMarket(volume)
self._entry_price = float(candle.ClosePrice)
if sell_open and self._allow_sell_entry.Value and self.Position >= 0:
volume = float(self.Volume) + abs(self.Position)
self.SellMarket(volume)
self._entry_price = float(candle.ClosePrice)
def _try_get_main(self, shift):
index = len(self._main_history) - 1 - shift
if index < 0 or index >= len(self._main_history):
return None
return self._main_history[index]
def _try_get_signal(self, shift):
index = len(self._signal_history) - 1 - shift
if index < 0 or index >= len(self._signal_history):
return None
return self._signal_history[index]
def OnReseted(self):
super(blau_ergodic_strategy, self).OnReseted()
self._mom_ema1 = None
self._mom_ema2 = None
self._mom_ema3 = None
self._abs_mom_ema1 = None
self._abs_mom_ema2 = None
self._abs_mom_ema3 = None
self._signal_ema = None
self._price_history = []
self._main_history = []
self._signal_history = []
self._entry_price = 0.0
def CreateClone(self):
return blau_ergodic_strategy()