Estrategia Blau Ergodic
Esta estrategia traduce el asesor experto Exp_BlauErgodic de MQL5 a StockSharp. Reconstruye el oscilador Blau Ergodic mediante el triple suavizado del momentum y su valor absoluto con filtros EMA, genera un oscilador normalizado y una línea de señal, y ofrece tres modos de señal distintos que reflejan el EA original.
La configuración predeterminada evalúa velas completas de 4 horas. Puede cambiar el precio aplicado (cierre, apertura, promedios basados en alto/bajo),
cada profundidad de suavizado, y el índice de barra (SignalBar) usado para leer señales. Las operaciones se dimensionan mediante la propiedad
Volume de la estrategia; las entradas/salidas largas/cortas pueden deshabilitarse individualmente mediante parámetros booleanos. Los niveles de stop loss y
take profit protectores se definen en puntos y se convierten en precios absolutos a través de Security.PriceStep.
Modos de señal
- Breakdown – reacciona al cruce del oscilador por la línea cero. Los largos se abren en transiciones de negativo a positivo y los cortos en transiciones de positivo a negativo. Las posiciones se cierran cuando el oscilador permanece en el lado opuesto de cero.
- Twist – busca reversiones de pendiente. Aparece una configuración larga cuando el oscilador estaba bajando en la barra anterior pero sube en la barra más reciente; una configuración corta requiere el patrón inverso.
- CloudTwist – monitorea el cruce del oscilador por su línea de señal. Los largos se activan cuando el oscilador sube a través de la nube de señal, y los cortos cuando cae de nuevo por debajo de ella.
Todos los modos leen los valores del indicador de la barra especificada por SignalBar (por defecto 1, es decir, la última barra completada) y se apoyan en
valores más antiguos para la confirmación. Configure SignalBar en al menos 1 porque la conversión procesa solo velas terminadas.
Reglas de entrada y salida
- Entradas largas: habilitadas cuando
AllowBuyEntryes verdadero, no hay posición larga existente (Position <= 0), y el modo activo genera una condición de compra. La estrategia revierte cualquier exposición corta comprandoVolume + |Position|. - Entradas cortas: habilitadas cuando
AllowSellEntryes verdadero, no hay posición corta existente (Position >= 0), y el modo activo emite una condición de venta. Cubre cualquier exposición larga antes de establecer el corto. - Salidas largas: activadas por la condición específica del modo, o cuando se alcanzan
StopLossPoints/TakeProfitPoints. Las salidas forzadas evitan el indicadorAllowBuyExitpara que los stops protectores siempre se respeten. - Salidas cortas: análogas a la lógica de salida larga con
AllowSellExity niveles de stop para operaciones cortas.
Parámetros
CandleType– marco temporal para las suscripciones de velas (por defecto velas de 4 horas).Mode– uno deBreakdown,Twist, oCloudTwist.MomentumLength– lookback para la diferencia de momentum bruto.First/Second/ThirdSmoothingLength– profundidades de EMA para los filtros de momentum en cascada.SignalSmoothingLength– profundidad de EMA para la línea de señal.SignalBar– índice de la barra completada usado para leer señales (mínimo1).AppliedPrices– fuente de precio que alimenta el oscilador (cierre, apertura, mediana, típica, ponderada, etc.).AllowBuyEntry,AllowSellEntry,AllowBuyExit,AllowSellExit– habilitar o deshabilitar operaciones específicas.StopLossPoints,TakeProfitPoints– distancias protectoras en puntos (convertidas medianteSecurity.PriceStep).
La conversión mantiene el comportamiento del experto MQL5, aprovechando la API de alto nivel de StockSharp (SubscribeCandles,
Bind) y adhiriéndose a las convenciones de estrategia de StockSharp con indentación de tabulaciones y comentarios en inglés.
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()