Blau Ergodic Strategy
This strategy ports the Exp_BlauErgodic expert advisor from MQL5 to StockSharp. It rebuilds the Blau Ergodic oscillator by triple-smoothing the momentum and its absolute value with EMA filters, generates a normalized oscillator and a signal line, and offers three distinct signal modes that mirror the original EA.
The default configuration evaluates completed 4-hour candles. You can change the applied price (close, open, high/low-based
averages), every smoothing depth, and the bar index (SignalBar) used to read signals. Trades are sized via the strategy's
Volume property; long/short entries or exits can be disabled individually through boolean parameters. Protective stop-loss and
take-profit levels are defined in points and are converted into absolute prices through Security.PriceStep.
Signal modes
- Breakdown – reacts to the oscillator crossing the zero line. Longs open on negative-to-positive flips and shorts on positive-to-negative flips. Positions are closed when the oscillator remains on the opposite side of zero.
- Twist – searches for slope reversals. A long setup appears when the oscillator was falling on the prior bar but turns up on the most recent bar; a short setup requires the inverse pattern.
- CloudTwist – monitors the oscillator crossing its signal line. Longs trigger when the oscillator rises through the signal cloud, and shorts when it falls back below it.
All modes read indicator values from the bar specified by SignalBar (default 1, meaning the last completed bar) and rely on
older values for confirmation. Set SignalBar to at least 1 because the conversion processes finished candles only.
Entry and exit rules
- Long entries: enabled when
AllowBuyEntryis true, no existing long position is open (Position <= 0), and the active mode generates a buy condition. The strategy reverses any short exposure by buyingVolume + |Position|. - Short entries: enabled when
AllowSellEntryis true, no existing short position is open (Position >= 0), and the active mode issues a sell condition. It covers any long exposure before establishing the short. - Long exits: triggered by the mode-specific condition, or whenever
StopLossPoints/TakeProfitPointsare reached. Forced exits bypass theAllowBuyExitflag so protective stops are always honored. - Short exits: analogous to the long exit logic with
AllowSellExitand stop levels for short trades.
Parameters
CandleType– timeframe for candle subscriptions (default 4-hour candles).Mode– one ofBreakdown,Twist, orCloudTwist.MomentumLength– lookback for the raw momentum difference.First/Second/ThirdSmoothingLength– EMA depths for the cascading momentum filters.SignalSmoothingLength– EMA depth for the signal line.SignalBar– index of the completed bar used to read signals (minimum1).AppliedPrices– price source feeding the oscillator (close, open, median, typical, weighted, etc.).AllowBuyEntry,AllowSellEntry,AllowBuyExit,AllowSellExit– enable or disable specific operations.StopLossPoints,TakeProfitPoints– protective distances in points (converted viaSecurity.PriceStep).
The conversion maintains the behaviour of the MQL5 expert, while leveraging the StockSharp high-level API (SubscribeCandles,
Bind) and adhering to StockSharp strategy conventions with tab indentation and English comments.
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()