Blau Ergodic策略
该策略是 MQL5 平台 Exp_BlauErgodic 顾问在 StockSharp 上的移植版本。通过对动量及其绝对值进行三级 EMA 平滑, 构建 Blau Ergodic 振荡器的归一化主线与信号线,并提供三个与原版一致的信号模式。
默认订阅已完成的 4 小时K线。可以选择不同的价格来源(收盘价、开盘价、各种平均价),调整每一级平滑长度,
以及指定读取信号的柱索引 SignalBar。仓位规模由策略的 Volume 属性控制,可分别禁用多空开仓或平仓标志。
止损和止盈以点数设置,并通过 Security.PriceStep 转换成绝对价格。
信号模式
- Breakdown:关注振荡器穿越零轴。指标由负转正时开多,由正转负时开空;当振荡器保持在相反的区域时 平掉现有仓位。
- Twist:寻找斜率反转。如果上一根柱子仍在下行而最新柱子转为上行则出现多头信号;空头信号为相反情况。
- CloudTwist:监控振荡器与信号线的交叉。穿越信号云向上时开多,跌回信号线下方时开空。
所有模式都以 SignalBar 指定的已完成柱(默认 1,即上一根完成的K线)为当前值,并结合更早的数值进行确认。
由于策略只处理收盘后的数据,请将 SignalBar 设为不小于 1。
进出场规则
- 做多:
AllowBuyEntry = true且当前净头寸不为多头(Position <= 0)时,只要所选模式给出买入条件就会建仓。 如果存在空头敞口,系统会一次性买入Volume + |Position|以反向并建立多单。 - 做空:
AllowSellEntry = true且当前净头寸不为空头(Position >= 0)时,模式触发卖出条件就会建立空单, 同时平掉可能存在的多头仓位。 - 平多:当模式给出反向信号或触发
StopLossPoints/TakeProfitPoints时执行。被动退出会检查AllowBuyExit,而由止损/止盈触发的强制退出会忽略该标志以确保保护单有效。 - 平空:与平多逻辑相同,使用
AllowSellExit及相应的止损/止盈距离。
参数
CandleType:订阅的K线类型(默认 4 小时)。Mode:Breakdown、Twist或CloudTwist三种模式之一。MomentumLength:原始动量差分的长度。First/Second/ThirdSmoothingLength:动量级联 EMA 的长度。SignalSmoothingLength:信号线 EMA 的长度。SignalBar:用于读取信号的已完成柱索引(至少为1)。AppliedPrices:振荡器使用的价格来源(收盘价、开盘价、均价等)。AllowBuyEntry、AllowSellEntry、AllowBuyExit、AllowSellExit:分别控制多空的开平仓权限。StopLossPoints、TakeProfitPoints:以点数表示的止损/止盈距离(通过Security.PriceStep转换)。
该移植使用 StockSharp 的高级 API(SubscribeCandles、Bind),保持 MQL5 原策略行为,并遵循项目对制表符缩进
与英文注释的要求。
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()