Blau Ergodic MDI-Strategie
Übersicht
Die Blau Ergodic Market Directional Indicator (MDI)-Strategie reproduziert das Verhalten des MetaTrader-Expertenberaters Exp_BlauErgodicMDI. Der Algorithmus arbeitet auf einem höheren Zeitrahmen-Kerzen-Stream (Standard 4H) und wendet eine dreifache Glättungspipeline auf den ausgewählten Preiseingang an, um ein Momentum-Histogramm und eine Signallinie zu erstellen. Handelsentscheidungen werden aus diesem Histogramm mithilfe eines von drei konfigurierbaren Einstiegsmodi abgeleitet:
- Breakdown – handelt, wenn das Histogramm die Nulllinie kreuzt.
- Twist – reagiert auf Umkehrungen in der Histogrammsteigung (Momentum ändert die Richtung).
- CloudTwist – reagiert auf Histogramm/Signallinie-Kreuzungen.
Jedes Signal kann optional entgegengesetzte Positionen schließen und/oder neue Trades eröffnen, abhängig von den vom Benutzer bereitgestellten Berechtigungsflags.
Indikatorlogik
- Den ausgewählten angewandten Preis mit dem konfigurierten Moving-Average-Typ und
PrimaryLengthglätten, um den Basispreis zu erhalten. - Die Momentum-Differenz
(price - baseline) / point_valueberechnen. - Dieses Momentum mit
FirstSmoothingLengthundSecondSmoothingLengthglätten, um das Histogramm zu erstellen. - Das Histogramm noch einmal mit
SignalLengthglätten, um die Signallinie zu erhalten. - Historische Werte gemäß
SignalBarShiftpuffern, damit Signale auf geschlossenen Kerzen bestätigt werden können.
Unterstützte Glättungsfamilien sind EMA, SMA, SMMA/RMA und WMA. Die Auswahl des angewandten Preises spiegelt die MetaTrader-Implementierung wider (Schluss, Eröffnung, Hoch, Tief, Median, typisch, gewichtet, einfach, Viertel, trendfolgenden Varianten).
Parameter
| Name | Beschreibung |
|---|---|
Volume |
Auftragsgröße beim Eröffnen von Positionen. |
StopLossPoints |
Stop-Loss-Abstand in Instrument-Punkten (0 deaktiviert). |
TakeProfitPoints |
Take-Profit-Abstand in Instrument-Punkten (0 deaktiviert). |
SlippagePoints |
Maximaler Preis-Slippage in Punkten bei Marktaufträgen. |
AllowLongEntries / AllowShortEntries |
Erlaubt das Eröffnen von Positionen in der jeweiligen Richtung. |
AllowLongExits / AllowShortExits |
Erlaubt das Schließen bestehender Positionen bei entgegengesetzten Signalen. |
Mode |
Einstiegsmodus (Breakdown / Twist / CloudTwist). |
CandleType |
Zeitrahmen der für Berechnungen verwendeten Kerzen (Standard 4H). |
SmoothingMethods |
Moving-Average-Familie, die in allen Glättungsschritten verwendet wird. |
PrimaryLength |
Basis-Glättungslänge für den angewandten Preis. |
FirstSmoothingLength |
Erste Glättungslänge für das Momentum. |
SecondSmoothingLength |
Zweite Glättungslänge zur Bildung des Histogramms. |
SignalLength |
Glättungslänge des Histogramms zur Erstellung der Signallinie. |
AppliedPrices |
Preisquelle für Indikatorberechnungen. |
SignalBarShift |
Anzahl der geschlossenen Bars, die bei der Signalauswertung zurückgeblickt werden. |
Phase |
Reservierter Parameter für Kompatibilität (in der aktuellen Implementierung nicht verwendet). |
Signalbedingungen
- Breakdown
- Long: Histogramm bei
SignalBarShiftist positiv, während die vorherige Bar es nicht ist. - Short: Histogramm bei
SignalBarShiftist negativ, während die vorherige Bar es nicht ist.
- Long: Histogramm bei
- Twist
- Long: Histogramm bei
SignalBarShiftsteigt nach einer fallenden Periode (vorherige < neueste und zwei Bars zurück > vorherige). - Short: Histogramm bei
SignalBarShiftfällt nach einer steigenden Periode (vorherige > neueste und zwei Bars zurück < vorherige).
- Long: Histogramm bei
- CloudTwist
- Long: Histogramm kreuzt oberhalb der Signallinie (neuestes Histogramm > neueste Signale, vorheriges Histogramm <= vorherige Signale).
- Short: Histogramm kreuzt unterhalb der Signallinie.
Jedes Signal kann sowohl das entgegengesetzte Exposure abflachen (wenn Ausstiege erlaubt sind) als auch einen neuen Trade mit dem konfigurierten Volumen eröffnen.
Risikomanagement
StartProtection wird mit den angegebenen Stop-Loss- und Take-Profit-Abständen initialisiert (von Punkten in Preiseinheiten umgerechnet unter Verwendung der Tick-Größe des Instruments). Wenn einer der Abstände null ist, wird der jeweilige Schutz weggelassen. Slippage wird ebenfalls mithilfe derselben Tick-Größe in Preiseinheiten umgerechnet.
Hinweise
- Signale werden nur auf abgeschlossenen Kerzen verarbeitet, um das ursprüngliche MetaTrader-Verhalten zu spiegeln.
SignalBarShiftermöglicht das Verzögern der Handelsbestätigung, um nicht auf der aktuellsten Bar zu handeln.- Der
Phase-Parameter wird für die Vollständigkeit beibehalten, hat aber keinen Effekt bei den unterstützten Glättungsmethoden. - Alle Code-Kommentare sind auf Englisch bereitgestellt, um die zukünftige Wartung zu vereinfachen.
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 Market Directional Indicator strategy converted from MetaTrader.
/// Uses a triple-smoothed momentum histogram with configurable entry confirmation modes.
/// </summary>
public class BlauErgodicMdiStrategy : Strategy
{
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly StrategyParam<int> _slippagePoints;
private readonly StrategyParam<bool> _allowLongEntries;
private readonly StrategyParam<bool> _allowShortEntries;
private readonly StrategyParam<bool> _allowLongExits;
private readonly StrategyParam<bool> _allowShortExits;
private readonly StrategyParam<EntryModes> _entryMode;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<SmoothingMethods> _smoothingMethod;
private readonly StrategyParam<int> _primaryLength;
private readonly StrategyParam<int> _firstSmoothingLength;
private readonly StrategyParam<int> _secondSmoothingLength;
private readonly StrategyParam<int> _signalLength;
private readonly StrategyParam<AppliedPrices> _appliedPrice;
private readonly StrategyParam<int> _signalBarShift;
private readonly StrategyParam<int> _phase;
private IIndicator _priceAverage = null!;
private IIndicator _firstSmoothing = null!;
private IIndicator _secondSmoothing = null!;
private IIndicator _signalSmoothing = null!;
private decimal[] _histogramBuffer = Array.Empty<decimal>();
private decimal[] _signalBuffer = Array.Empty<decimal>();
private int _bufferIndex;
private int _bufferFilled;
private decimal _pointValue = 1m;
private decimal _entryPrice;
private decimal? _stopPrice;
private decimal? _takePrice;
/// <summary>
/// Initializes a new instance of <see cref="BlauErgodicMdiStrategy"/>.
/// </summary>
public BlauErgodicMdiStrategy()
{
_stopLossPoints = Param(nameof(StopLossPoints), 1000)
.SetNotNegative()
.SetDisplay("Stop Loss", "Stop loss in points", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000)
.SetNotNegative()
.SetDisplay("Take Profit", "Take profit in points", "Risk");
_slippagePoints = Param(nameof(SlippagePoints), 10)
.SetNotNegative()
.SetDisplay("Slippage", "Maximum slippage in points", "Risk");
_allowLongEntries = Param(nameof(AllowLongEntries), true)
.SetDisplay("Allow Long Entries", "Enable opening long positions", "Permissions");
_allowShortEntries = Param(nameof(AllowShortEntries), true)
.SetDisplay("Allow Short Entries", "Enable opening short positions", "Permissions");
_allowLongExits = Param(nameof(AllowLongExits), true)
.SetDisplay("Allow Long Exits", "Enable closing long positions", "Permissions");
_allowShortExits = Param(nameof(AllowShortExits), true)
.SetDisplay("Allow Short Exits", "Enable closing short positions", "Permissions");
_entryMode = Param(nameof(Mode), EntryModes.Twist)
.SetDisplay("Entry Mode", "Signal interpretation mode", "Strategy");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Indicator Timeframe", "Timeframe used for calculations", "Data");
_smoothingMethod = Param(nameof(SmoothingMethod), SmoothingMethods.Exponential)
.SetDisplay("Smoothing Method", "Type of moving average", "Indicator");
_primaryLength = Param(nameof(PrimaryLength), 20)
.SetGreaterThanZero()
.SetDisplay("Primary Length", "Base smoothing length", "Indicator")
.SetOptimize(5, 60, 1);
_firstSmoothingLength = Param(nameof(FirstSmoothingLength), 5)
.SetGreaterThanZero()
.SetDisplay("Momentum Smoothing", "First smoothing length", "Indicator")
.SetOptimize(2, 20, 1);
_secondSmoothingLength = Param(nameof(SecondSmoothingLength), 3)
.SetGreaterThanZero()
.SetDisplay("Histogram Smoothing", "Second smoothing length", "Indicator")
.SetOptimize(2, 20, 1);
_signalLength = Param(nameof(SignalLength), 8)
.SetGreaterThanZero()
.SetDisplay("Signal Length", "Signal line smoothing", "Indicator")
.SetOptimize(2, 30, 1);
_appliedPrice = Param(nameof(AppliedPrice), AppliedPrices.Close)
.SetDisplay("Applied Price", "Price source for calculations", "Indicator");
_signalBarShift = Param(nameof(SignalBarShift), 1)
.SetNotNegative()
.SetDisplay("Signal Bar", "Shift of the bar used for signals", "Strategy");
_phase = Param(nameof(Phase), 15)
.SetDisplay("Phase", "Reserved smoothing phase parameter", "Indicator");
}
/// <summary>
/// Stop loss distance expressed in instrument points.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit distance expressed in instrument points.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Allowed price slippage in points.
/// </summary>
public int SlippagePoints
{
get => _slippagePoints.Value;
set => _slippagePoints.Value = value;
}
/// <summary>
/// Enables opening long positions.
/// </summary>
public bool AllowLongEntries
{
get => _allowLongEntries.Value;
set => _allowLongEntries.Value = value;
}
/// <summary>
/// Enables opening short positions.
/// </summary>
public bool AllowShortEntries
{
get => _allowShortEntries.Value;
set => _allowShortEntries.Value = value;
}
/// <summary>
/// Enables closing existing long positions on opposite signals.
/// </summary>
public bool AllowLongExits
{
get => _allowLongExits.Value;
set => _allowLongExits.Value = value;
}
/// <summary>
/// Enables closing existing short positions on opposite signals.
/// </summary>
public bool AllowShortExits
{
get => _allowShortExits.Value;
set => _allowShortExits.Value = value;
}
/// <summary>
/// Selected entry confirmation mode.
/// </summary>
public EntryModes Mode
{
get => _entryMode.Value;
set => _entryMode.Value = value;
}
/// <summary>
/// Candle type used for indicator calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Moving average family used for smoothing steps.
/// </summary>
public SmoothingMethods SmoothingMethod
{
get => _smoothingMethod.Value;
set => _smoothingMethod.Value = value;
}
/// <summary>
/// Length for the initial smoothing of price.
/// </summary>
public int PrimaryLength
{
get => _primaryLength.Value;
set => _primaryLength.Value = value;
}
/// <summary>
/// Length of the first smoothing applied to momentum.
/// </summary>
public int FirstSmoothingLength
{
get => _firstSmoothingLength.Value;
set => _firstSmoothingLength.Value = value;
}
/// <summary>
/// Length of the second smoothing forming the histogram.
/// </summary>
public int SecondSmoothingLength
{
get => _secondSmoothingLength.Value;
set => _secondSmoothingLength.Value = value;
}
/// <summary>
/// Length of the signal line smoothing.
/// </summary>
public int SignalLength
{
get => _signalLength.Value;
set => _signalLength.Value = value;
}
/// <summary>
/// Applied price selection for calculations.
/// </summary>
public AppliedPrices AppliedPrice
{
get => _appliedPrice.Value;
set => _appliedPrice.Value = value;
}
/// <summary>
/// Offset of the bar used for signal confirmation.
/// </summary>
public int SignalBarShift
{
get => _signalBarShift.Value;
set => _signalBarShift.Value = value;
}
/// <summary>
/// Reserved phase parameter kept for compatibility with the original script.
/// </summary>
public int Phase
{
get => _phase.Value;
set => _phase.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_histogramBuffer = Array.Empty<decimal>();
_signalBuffer = Array.Empty<decimal>();
_bufferIndex = 0;
_bufferFilled = 0;
_entryPrice = 0m;
_stopPrice = null;
_takePrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pointValue = Security?.PriceStep ?? 1m;
if (_pointValue <= 0m)
_pointValue = 1m;
_priceAverage = CreateMovingAverage(SmoothingMethod, PrimaryLength);
_firstSmoothing = CreateMovingAverage(SmoothingMethod, FirstSmoothingLength);
_secondSmoothing = CreateMovingAverage(SmoothingMethod, SecondSmoothingLength);
_signalSmoothing = CreateMovingAverage(SmoothingMethod, SignalLength);
InitializeBuffers();
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
ApplyRiskManagement(candle);
var price = SelectPrice(candle);
var time = candle.CloseTime;
// Smooth the selected price to match the indicator baseline.
var baseValue = _priceAverage.Process(new DecimalIndicatorValue(_priceAverage, price, time) { IsFinal = true });
if (!baseValue.IsFormed)
return;
var basePrice = baseValue.ToDecimal();
var momentum = _pointValue != 0m ? (price - basePrice) / _pointValue : 0m;
// Apply the first momentum smoothing stage.
var firstValue = _firstSmoothing.Process(new DecimalIndicatorValue(_firstSmoothing, momentum, time) { IsFinal = true });
if (!firstValue.IsFormed)
return;
var first = firstValue.ToDecimal();
// Build the histogram with the second smoothing stage.
var secondValue = _secondSmoothing.Process(new DecimalIndicatorValue(_secondSmoothing, first, time) { IsFinal = true });
if (!secondValue.IsFormed)
return;
var histogram = secondValue.ToDecimal();
// Smooth the histogram to generate the signal line.
var signalValue = _signalSmoothing.Process(new DecimalIndicatorValue(_signalSmoothing, histogram, time) { IsFinal = true });
if (!signalValue.IsFormed)
return;
var signal = signalValue.ToDecimal();
// Store values so that shifted comparisons work like in the MQL version.
AddToBuffer(histogram, signal);
if (!TryGetHist(SignalBarShift, out var latestHist) || !TryGetHist(SignalBarShift + 1, out var previousHist))
return;
var currentPosition = Position;
var buySignal = false;
var sellSignal = false;
switch (Mode)
{
case EntryModes.Breakdown:
{
buySignal = latestHist > 0m && previousHist <= 0m;
sellSignal = latestHist < 0m && previousHist >= 0m;
break;
}
case EntryModes.Twist:
{
if (!TryGetHist(SignalBarShift + 2, out var olderHist))
return;
buySignal = previousHist < latestHist && olderHist > previousHist;
sellSignal = previousHist > latestHist && olderHist < previousHist;
break;
}
case EntryModes.CloudTwist:
{
if (!TryGetSignal(SignalBarShift, out var latestSignal) || !TryGetSignal(SignalBarShift + 1, out var previousSignal))
return;
buySignal = latestHist > latestSignal && previousHist <= previousSignal;
sellSignal = latestHist < latestSignal && previousHist >= previousSignal;
break;
}
}
if (buySignal)
{
ExecuteBuy(currentPosition, candle.ClosePrice);
}
else if (sellSignal)
{
ExecuteSell(currentPosition, candle.ClosePrice);
}
}
private void ExecuteBuy(decimal currentPosition, decimal price)
{
var volume = 0m;
if (AllowShortExits && currentPosition < 0m)
volume += Math.Abs(currentPosition);
if (AllowLongEntries && (currentPosition <= 0m || (AllowShortExits && currentPosition < 0m)))
volume += Volume;
if (volume > 0m)
{
BuyMarket(volume);
_entryPrice = price;
var slDist = StopLossPoints > 0 ? StopLossPoints * _pointValue : 0m;
var tpDist = TakeProfitPoints > 0 ? TakeProfitPoints * _pointValue : 0m;
_stopPrice = slDist > 0m ? price - slDist : null;
_takePrice = tpDist > 0m ? price + tpDist : null;
}
}
private void ExecuteSell(decimal currentPosition, decimal price)
{
var volume = 0m;
if (AllowLongExits && currentPosition > 0m)
volume += Math.Abs(currentPosition);
if (AllowShortEntries && (currentPosition >= 0m || (AllowLongExits && currentPosition > 0m)))
volume += Volume;
if (volume > 0m)
{
SellMarket(volume);
_entryPrice = price;
var slDist = StopLossPoints > 0 ? StopLossPoints * _pointValue : 0m;
var tpDist = TakeProfitPoints > 0 ? TakeProfitPoints * _pointValue : 0m;
_stopPrice = slDist > 0m ? price + slDist : null;
_takePrice = tpDist > 0m ? price - tpDist : null;
}
}
private void ApplyRiskManagement(ICandleMessage candle)
{
if (Position > 0)
{
if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
{
SellMarket(Position);
ResetTargets();
return;
}
if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
{
SellMarket(Position);
ResetTargets();
}
}
else if (Position < 0)
{
if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
{
BuyMarket(Math.Abs(Position));
ResetTargets();
return;
}
if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
{
BuyMarket(Math.Abs(Position));
ResetTargets();
}
}
}
private void ResetTargets()
{
_entryPrice = 0m;
_stopPrice = null;
_takePrice = null;
}
private void InitializeBuffers()
{
var size = Math.Max(3, SignalBarShift + 3);
_histogramBuffer = new decimal[size];
_signalBuffer = new decimal[size];
_bufferIndex = 0;
_bufferFilled = 0;
}
private void AddToBuffer(decimal histogram, decimal signal)
{
if (_histogramBuffer.Length == 0)
return;
_histogramBuffer[_bufferIndex] = histogram;
_signalBuffer[_bufferIndex] = signal;
_bufferIndex = (_bufferIndex + 1) % _histogramBuffer.Length;
if (_bufferFilled < _histogramBuffer.Length)
_bufferFilled++;
}
private bool TryGetHist(int shift, out decimal value)
{
return TryGetBufferedValue(_histogramBuffer, shift, out value);
}
private bool TryGetSignal(int shift, out decimal value)
{
return TryGetBufferedValue(_signalBuffer, shift, out value);
}
private bool TryGetBufferedValue(decimal[] buffer, int shift, out decimal value)
{
value = default;
if (shift < 0 || shift >= _bufferFilled)
return false;
var index = _bufferIndex - 1 - shift;
if (index < 0)
index += buffer.Length;
value = buffer[index];
return true;
}
private decimal SelectPrice(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.ClosePrice + candle.HighPrice + candle.LowPrice) / 3m,
AppliedPrices.Weighted => (2m * candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 4m,
AppliedPrices.Simple => (candle.OpenPrice + candle.ClosePrice) / 2m,
AppliedPrices.Quarter => (candle.OpenPrice + candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 4m,
AppliedPrices.TrendFollow0 => candle.ClosePrice > candle.OpenPrice ? candle.HighPrice : candle.ClosePrice < candle.OpenPrice ? candle.LowPrice : candle.ClosePrice,
AppliedPrices.TrendFollow1 => candle.ClosePrice > candle.OpenPrice ? (candle.HighPrice + candle.ClosePrice) / 2m : candle.ClosePrice < candle.OpenPrice ? (candle.LowPrice + candle.ClosePrice) / 2m : candle.ClosePrice,
_ => candle.ClosePrice,
};
}
private static IIndicator CreateMovingAverage(SmoothingMethods method, int length)
{
return method switch
{
SmoothingMethods.Simple => new SimpleMovingAverage { Length = length },
SmoothingMethods.Smoothed => new SmoothedMovingAverage { Length = length },
SmoothingMethods.Weighted => new WeightedMovingAverage { Length = length },
_ => new ExponentialMovingAverage { Length = length },
};
}
/// <summary>
/// Entry confirmation modes replicated from the original expert advisor.
/// </summary>
public enum EntryModes
{
/// <summary>
/// Histogram breaks above or below the zero line.
/// </summary>
Breakdown,
/// <summary>
/// Histogram changes slope direction.
/// </summary>
Twist,
/// <summary>
/// Histogram crosses the signal line.
/// </summary>
CloudTwist
}
/// <summary>
/// Supported smoothing families.
/// </summary>
public enum SmoothingMethods
{
/// <summary>
/// Exponential moving average.
/// </summary>
Exponential,
/// <summary>
/// Simple moving average.
/// </summary>
Simple,
/// <summary>
/// Smoothed (RMA) moving average.
/// </summary>
Smoothed,
/// <summary>
/// Weighted moving average.
/// </summary>
Weighted
}
/// <summary>
/// Applied price sources identical to the MetaTrader version.
/// </summary>
public enum AppliedPrices
{
/// <summary>
/// Close price.
/// </summary>
Close,
/// <summary>
/// Open price.
/// </summary>
Open,
/// <summary>
/// High price.
/// </summary>
High,
/// <summary>
/// Low price.
/// </summary>
Low,
/// <summary>
/// Median price (high + low) / 2.
/// </summary>
Median,
/// <summary>
/// Typical price (close + high + low) / 3.
/// </summary>
Typical,
/// <summary>
/// Weighted price (2 * close + high + low) / 4.
/// </summary>
Weighted,
/// <summary>
/// Simple price (open + close) / 2.
/// </summary>
Simple,
/// <summary>
/// Quarted price (open + high + low + close) / 4.
/// </summary>
Quarter,
/// <summary>
/// Trend-following price using candle extremes.
/// </summary>
TrendFollow0,
/// <summary>
/// Half-trend-following price.
/// </summary>
TrendFollow1
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import (
SimpleMovingAverage,
ExponentialMovingAverage,
SmoothedMovingAverage,
WeightedMovingAverage,
)
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class blau_ergodic_mdi_strategy(Strategy):
MODE_BREAKDOWN = 0
MODE_TWIST = 1
MODE_CLOUD_TWIST = 2
SMOOTH_EMA = 0
SMOOTH_SMA = 1
SMOOTH_SMMA = 2
SMOOTH_WMA = 3
AP_CLOSE = 0
AP_OPEN = 1
AP_HIGH = 2
AP_LOW = 3
AP_MEDIAN = 4
AP_TYPICAL = 5
AP_WEIGHTED = 6
AP_SIMPLE = 7
AP_QUARTER = 8
AP_TREND0 = 9
AP_TREND1 = 10
def __init__(self):
super(blau_ergodic_mdi_strategy, self).__init__()
self._stop_loss_points = self.Param("StopLossPoints", 1000)
self._take_profit_points = self.Param("TakeProfitPoints", 2000)
self._slippage_points = self.Param("SlippagePoints", 10)
self._allow_long_entries = self.Param("AllowLongEntries", True)
self._allow_short_entries = self.Param("AllowShortEntries", True)
self._allow_long_exits = self.Param("AllowLongExits", True)
self._allow_short_exits = self.Param("AllowShortExits", True)
self._entry_mode = self.Param("Mode", self.MODE_TWIST)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._smoothing_method = self.Param("SmoothingMethod", self.SMOOTH_EMA)
self._primary_length = self.Param("PrimaryLength", 20)
self._first_smoothing_length = self.Param("FirstSmoothingLength", 5)
self._second_smoothing_length = self.Param("SecondSmoothingLength", 3)
self._signal_length = self.Param("SignalLength", 8)
self._applied_price = self.Param("AppliedPrice", self.AP_CLOSE)
self._signal_bar_shift = self.Param("SignalBarShift", 1)
self._phase = self.Param("Phase", 15)
self._price_average = None
self._first_smoothing = None
self._second_smoothing = None
self._signal_smoothing = None
self._histogram_buffer = []
self._signal_buffer = []
self._buffer_index = 0
self._buffer_filled = 0
self._point_value = 1.0
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@property
def AllowLongEntries(self):
return self._allow_long_entries.Value
@property
def AllowShortEntries(self):
return self._allow_short_entries.Value
@property
def AllowLongExits(self):
return self._allow_long_exits.Value
@property
def AllowShortExits(self):
return self._allow_short_exits.Value
@property
def Mode(self):
return self._entry_mode.Value
@property
def CandleType(self):
return self._candle_type.Value
@property
def SmoothingMethod(self):
return self._smoothing_method.Value
@property
def PrimaryLength(self):
return self._primary_length.Value
@property
def FirstSmoothingLength(self):
return self._first_smoothing_length.Value
@property
def SecondSmoothingLength(self):
return self._second_smoothing_length.Value
@property
def SignalLength(self):
return self._signal_length.Value
@property
def AppliedPrice(self):
return self._applied_price.Value
@property
def SignalBarShift(self):
return self._signal_bar_shift.Value
@property
def Phase(self):
return self._phase.Value
def OnStarted2(self, time):
super(blau_ergodic_mdi_strategy, self).OnStarted2(time)
sec = self.Security
self._point_value = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None and float(sec.PriceStep) > 0 else 1.0
self._price_average = self._create_ma(self.SmoothingMethod, self.PrimaryLength)
self._first_smoothing = self._create_ma(self.SmoothingMethod, self.FirstSmoothingLength)
self._second_smoothing = self._create_ma(self.SmoothingMethod, self.SecondSmoothingLength)
self._signal_smoothing = self._create_ma(self.SmoothingMethod, self.SignalLength)
self._initialize_buffers()
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
self._apply_risk_management(candle)
price = self._select_price(candle)
t = candle.ServerTime
base_val = process_float(self._price_average, Decimal(float(price)), t, True)
if not self._price_average.IsFormed:
return
base_price = float(base_val.Value)
momentum = (float(price) - base_price) / self._point_value if self._point_value != 0 else 0.0
first_val = process_float(self._first_smoothing, Decimal(momentum), t, True)
if not self._first_smoothing.IsFormed:
return
second_val = process_float(self._second_smoothing, Decimal(float(first_val.Value)), t, True)
if not self._second_smoothing.IsFormed:
return
histogram = float(second_val.Value)
signal_val = process_float(self._signal_smoothing, Decimal(histogram), t, True)
if not self._signal_smoothing.IsFormed:
return
signal = float(signal_val.Value)
self._add_to_buffer(histogram, signal)
shift = self.SignalBarShift
latest_hist = self._try_get_hist(shift)
prev_hist = self._try_get_hist(shift + 1)
if latest_hist is None or prev_hist is None:
return
pos = float(self.Position)
buy_signal = False
sell_signal = False
mode = self.Mode
if mode == self.MODE_BREAKDOWN:
buy_signal = latest_hist > 0 and prev_hist <= 0
sell_signal = latest_hist < 0 and prev_hist >= 0
elif mode == self.MODE_TWIST:
older_hist = self._try_get_hist(shift + 2)
if older_hist is None:
return
buy_signal = prev_hist < latest_hist and older_hist > prev_hist
sell_signal = prev_hist > latest_hist and older_hist < prev_hist
elif mode == self.MODE_CLOUD_TWIST:
latest_sig = self._try_get_signal(shift)
prev_sig = self._try_get_signal(shift + 1)
if latest_sig is None or prev_sig is None:
return
buy_signal = latest_hist > latest_sig and prev_hist <= prev_sig
sell_signal = latest_hist < latest_sig and prev_hist >= prev_sig
if buy_signal:
self._execute_buy(pos, float(candle.ClosePrice))
elif sell_signal:
self._execute_sell(pos, float(candle.ClosePrice))
def _execute_buy(self, current_pos, price):
volume = 0.0
if self.AllowShortExits and current_pos < 0:
volume += abs(current_pos)
if self.AllowLongEntries and (current_pos <= 0 or (self.AllowShortExits and current_pos < 0)):
volume += float(self.Volume)
if volume > 0:
self.BuyMarket(volume)
self._entry_price = price
sl_dist = self.StopLossPoints * self._point_value if self.StopLossPoints > 0 else 0.0
tp_dist = self.TakeProfitPoints * self._point_value if self.TakeProfitPoints > 0 else 0.0
self._stop_price = price - sl_dist if sl_dist > 0 else None
self._take_price = price + tp_dist if tp_dist > 0 else None
def _execute_sell(self, current_pos, price):
volume = 0.0
if self.AllowLongExits and current_pos > 0:
volume += abs(current_pos)
if self.AllowShortEntries and (current_pos >= 0 or (self.AllowLongExits and current_pos > 0)):
volume += float(self.Volume)
if volume > 0:
self.SellMarket(volume)
self._entry_price = price
sl_dist = self.StopLossPoints * self._point_value if self.StopLossPoints > 0 else 0.0
tp_dist = self.TakeProfitPoints * self._point_value if self.TakeProfitPoints > 0 else 0.0
self._stop_price = price + sl_dist if sl_dist > 0 else None
self._take_price = price - tp_dist if tp_dist > 0 else None
def _apply_risk_management(self, candle):
pos = float(self.Position)
if pos > 0:
if self._stop_price is not None and float(candle.LowPrice) <= self._stop_price:
self.SellMarket(pos)
self._reset_targets()
return
if self._take_price is not None and float(candle.HighPrice) >= self._take_price:
self.SellMarket(pos)
self._reset_targets()
elif pos < 0:
if self._stop_price is not None and float(candle.HighPrice) >= self._stop_price:
self.BuyMarket(abs(pos))
self._reset_targets()
return
if self._take_price is not None and float(candle.LowPrice) <= self._take_price:
self.BuyMarket(abs(pos))
self._reset_targets()
def _reset_targets(self):
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
def _initialize_buffers(self):
size = max(3, self.SignalBarShift + 3)
self._histogram_buffer = [0.0] * size
self._signal_buffer = [0.0] * size
self._buffer_index = 0
self._buffer_filled = 0
def _add_to_buffer(self, histogram, signal):
if len(self._histogram_buffer) == 0:
return
self._histogram_buffer[self._buffer_index] = histogram
self._signal_buffer[self._buffer_index] = signal
self._buffer_index = (self._buffer_index + 1) % len(self._histogram_buffer)
if self._buffer_filled < len(self._histogram_buffer):
self._buffer_filled += 1
def _try_get_hist(self, shift):
return self._try_get_buffered(self._histogram_buffer, shift)
def _try_get_signal(self, shift):
return self._try_get_buffered(self._signal_buffer, shift)
def _try_get_buffered(self, buf, shift):
if shift < 0 or shift >= self._buffer_filled:
return None
idx = self._buffer_index - 1 - shift
if idx < 0:
idx += len(buf)
return buf[idx]
def _select_price(self, candle):
ap = self.AppliedPrice
if ap == self.AP_OPEN:
return candle.OpenPrice
elif ap == self.AP_HIGH:
return candle.HighPrice
elif ap == self.AP_LOW:
return candle.LowPrice
elif ap == self.AP_MEDIAN:
return (float(candle.HighPrice) + float(candle.LowPrice)) / 2.0
elif ap == self.AP_TYPICAL:
return (float(candle.ClosePrice) + float(candle.HighPrice) + float(candle.LowPrice)) / 3.0
elif ap == self.AP_WEIGHTED:
return (2.0 * float(candle.ClosePrice) + float(candle.HighPrice) + float(candle.LowPrice)) / 4.0
elif ap == self.AP_SIMPLE:
return (float(candle.OpenPrice) + float(candle.ClosePrice)) / 2.0
elif ap == self.AP_QUARTER:
return (float(candle.OpenPrice) + float(candle.HighPrice) + float(candle.LowPrice) + float(candle.ClosePrice)) / 4.0
elif ap == self.AP_TREND0:
if float(candle.ClosePrice) > float(candle.OpenPrice):
return candle.HighPrice
elif float(candle.ClosePrice) < float(candle.OpenPrice):
return candle.LowPrice
else:
return candle.ClosePrice
elif ap == self.AP_TREND1:
if float(candle.ClosePrice) > float(candle.OpenPrice):
return (float(candle.HighPrice) + float(candle.ClosePrice)) / 2.0
elif float(candle.ClosePrice) < float(candle.OpenPrice):
return (float(candle.LowPrice) + float(candle.ClosePrice)) / 2.0
else:
return candle.ClosePrice
else:
return candle.ClosePrice
def _create_ma(self, method, length):
if method == self.SMOOTH_SMA:
ma = SimpleMovingAverage()
ma.Length = length
return ma
elif method == self.SMOOTH_SMMA:
ma = SmoothedMovingAverage()
ma.Length = length
return ma
elif method == self.SMOOTH_WMA:
ma = WeightedMovingAverage()
ma.Length = length
return ma
else:
ma = ExponentialMovingAverage()
ma.Length = length
return ma
def OnReseted(self):
super(blau_ergodic_mdi_strategy, self).OnReseted()
self._histogram_buffer = []
self._signal_buffer = []
self._buffer_index = 0
self._buffer_filled = 0
self._entry_price = 0.0
self._stop_price = None
self._take_price = None
def CreateClone(self):
return blau_ergodic_mdi_strategy()