Blau SM Stochastic Strategie
Überblick
Diese Strategie ist eine C#-Konvertierung des ursprünglichen MetaTrader 5-Experten Exp_BlauSMStochastic, der um den Blau SM Stochastic Oszillator aufgebaut ist. Der Indikator misst den Abstand zwischen Preis und dem letzten Handelsspanne, wendet mehrere Glättungsstufen an und vergleicht das Ergebnis mit einer geglätteten Referenzlinie. Die Strategie arbeitet auf abgeschlossenen Kerzen (Standard 4-Stunden-Zeitrahmen) und erlaubt den Handel in beide Richtungen.
Indikatorlogik
- Berechnung des höchsten Hochs und niedrigsten Tiefs über
LookbackLengthBalken. - Erstellen einer trendbereinigten Preisserie:
sm = price - (HH + LL) / 2wobeipriceder angewendete Preistyp ist. - Glätten der trendbereinigten Serie sequentiell durch drei gleitende Durchschnitte mit Längen
FirstSmoothingLength,SecondSmoothingLengthundThirdSmoothingLengthunter Verwendung der gewähltenSmoothMethod(SMA, EMA, SMMA oder LWMA). - Glätten des Halbbereichs
(HH - LL) / 2mit derselben dreifachen Sequenz zur Normalisierung der Volatilität. - Bildung der Hauptoszillatorlinie als
100 * smoothed(sm) / smoothed(range). - Glätten der Hauptlinie mit
SignalLengthzur Gewinnung der Signallinie.
Der Parameter Phase wird aus Kompatibilitätsgründen mit der MQL-Version beibehalten, wird jedoch nicht von der vereinfachten Glättungsmaschine verwendet.
Handelsmodi
- Breakdown: Überwacht Nulldurchgänge der Hauptlinie. Ein Übergang von positiv zu nicht-positiv eröffnet Long und schließt Shorts. Ein Übergang von negativ zu nicht-negativ eröffnet Short und schließt Longs.
- Twist: Verfolgt Momentumwenden. Wenn die Hauptlinie einen lokalen Tiefpunkt bildet (Wert steigt nach dem Fallen), wird ein Long-Einstieg ausgelöst, während ein lokaler Hochpunkt (Wert fällt nach dem Steigen) einen Short auslöst. Entgegengesetzte Positionen werden entsprechend geschlossen.
- CloudTwist: Beobachtet Kreuzungen zwischen der Hauptlinie und der Signallinie. Ein Abwärtsdurchgang der Hauptlinie durch die Signallinie öffnet Long und beendet Shorts, während ein Aufwärtsdurchgang Short öffnet und Longs beendet.
Einstiegs- und Ausstiegsschalter (EnableLongEntry, EnableShortEntry, EnableLongExit, EnableShortExit) ermöglichen das Deaktivieren bestimmter Operationen, während Indikatorberechnungen intakt bleiben.
Risikomanagement
TakeProfitPoints und StopLossPoints werden in absolute Preisabstände unter Verwendung des Instrumentenpreisschritts konvertiert und über StartProtection an den eingebauten Schutzblock übergeben. Setzen Sie sie auf null, um das entsprechende Limit zu deaktivieren.
Parameter
CandleType(DataType, Standard: 4-Stunden-Zeitrahmen) – Zeitrahmen für Kerzenabonnement und Indikatorberechnungen.Mode(BlauSmStochasticModes, Standard: Twist) – wählt den Signalgenerierungsmodus (Breakdown, Twist, CloudTwist).SignalBar(int, Standard: 1) – Anzahl der Balken zum Verschieben von Indikatorwerten bei der Signalbewertung, repliziert die ursprünglicheSignalBar-Logik.LookbackLength(int, Standard: 5) – Balken zur Berechnung der höchsten und niedrigsten Werte.FirstSmoothingLength(int, Standard: 20) – Länge der ersten Glättungsstufe.SecondSmoothingLength(int, Standard: 5) – Länge der zweiten Glättungsstufe.ThirdSmoothingLength(int, Standard: 3) – Länge der dritten Glättungsstufe.SignalLength(int, Standard: 3) – Glättungslänge der Signallinie.SmoothMethod(BlauSmSmoothMethods, Standard: EMA) – Gleitender-Durchschnitt-Familie für alle Glättungsstufen (SMA, EMA, SMMA, LWMA).PriceType(BlauSmAppliedPrices, Standard: Close) – angewendeter Preis für den Oszillator (Schluss, Eröffnung, Hoch, Tief, Median, Typisch, Gewichtet, Einfach, Quartil, Trendfolgevarianten, Demark).EnableLongEntry(bool, Standard: true) – Erlaubt das Öffnen von Long-Positionen.EnableShortEntry(bool, Standard: true) – Erlaubt das Öffnen von Short-Positionen.EnableLongExit(bool, Standard: true) – Erlaubt das Schließen von Long-Positionen.EnableShortExit(bool, Standard: true) – Erlaubt das Schließen von Short-Positionen.TakeProfitPoints(int, Standard: 2000) – Fester Take-Profit-Abstand ausgedrückt in Instrumentenpunkten.StopLossPoints(int, Standard: 1000) – Fester Stop-Loss-Abstand ausgedrückt in Instrumentenpunkten.
Hinweise
- Die Glättungsmaschine unterstützt derzeit klassische gleitende Durchschnitte (SMA, EMA, SMMA, LWMA). Exotische Modi aus der MQL-Bibliothek (JMA, JurX, etc.) sind in StockSharp nicht verfügbar und daher nicht enthalten.
- Phase wird als Parameter zur Vollständigkeit beibehalten; passen Sie es nur für Dokumentationszwecke an.
- Funktioniert mit jedem von StockSharp unterstützten Symbol. Passen Sie Kerzentyp, Glättungslängen und Stops an die Instrumentvolatilität an.
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 SM Stochastic based strategy converted from the MQL5 Expert Advisor.
/// </summary>
public class BlauSmStochasticStrategy : Strategy
{
/// <summary>
/// Signal generation modes.
/// </summary>
public enum BlauSmStochasticModes
{
/// <summary>
/// Uses histogram zero crossings.
/// </summary>
Breakdown,
/// <summary>
/// Uses momentum twists.
/// </summary>
Twist,
/// <summary>
/// Uses crossings between main and signal lines.
/// </summary>
CloudTwist
}
/// <summary>
/// Moving average options used in the oscillator smoothing stages.
/// </summary>
public enum BlauSmSmoothMethods
{
/// <summary>
/// Simple moving average.
/// </summary>
Sma,
/// <summary>
/// Exponential moving average.
/// </summary>
Ema,
/// <summary>
/// Smoothed moving average.
/// </summary>
Smma,
/// <summary>
/// Linear weighted moving average.
/// </summary>
Lwma
}
/// <summary>
/// Applied price options for oscillator input.
/// </summary>
public enum BlauSmAppliedPrices
{
/// <summary>
/// Close price.
/// </summary>
Close,
/// <summary>
/// Open price.
/// </summary>
Open,
/// <summary>
/// High price.
/// </summary>
High,
/// <summary>
/// Low price.
/// </summary>
Low,
/// <summary>
/// (High + Low) / 2.
/// </summary>
Median,
/// <summary>
/// (Close + High + Low) / 3.
/// </summary>
Typical,
/// <summary>
/// (2 * Close + High + Low) / 4.
/// </summary>
Weighted,
/// <summary>
/// (Open + Close) / 2.
/// </summary>
Simple,
/// <summary>
/// (Open + Close + High + Low) / 4.
/// </summary>
Quarter,
/// <summary>
/// Trend-follow price using highs and lows.
/// </summary>
TrendFollow0,
/// <summary>
/// Average between close and extreme price in trend direction.
/// </summary>
TrendFollow1,
/// <summary>
/// Demark price variant.
/// </summary>
Demark
}
private readonly StrategyParam<BlauSmStochasticModes> _mode;
private readonly StrategyParam<int> _signalBar;
private readonly StrategyParam<int> _lookbackLength;
private readonly StrategyParam<int> _firstSmoothingLength;
private readonly StrategyParam<int> _secondSmoothingLength;
private readonly StrategyParam<int> _thirdSmoothingLength;
private readonly StrategyParam<int> _signalLength;
private readonly StrategyParam<BlauSmSmoothMethods> _smoothMethod;
private readonly StrategyParam<int> _phase;
private readonly StrategyParam<BlauSmAppliedPrices> _priceType;
private readonly StrategyParam<bool> _enableLongEntry;
private readonly StrategyParam<bool> _enableShortEntry;
private readonly StrategyParam<bool> _enableLongExit;
private readonly StrategyParam<bool> _enableShortExit;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _mainHistory = new();
private readonly List<decimal> _signalHistory = new();
private BlauSmStochasticIndicator _indicator;
private decimal _entryPrice;
/// <summary>
/// Initializes a new instance of the <see cref="BlauSmStochasticStrategy"/> class.
/// </summary>
public BlauSmStochasticStrategy()
{
_mode = Param(nameof(Mode), BlauSmStochasticModes.Twist)
.SetDisplay("Mode", "Signal generation mode", "Parameters");
_signalBar = Param(nameof(SignalBar), 1)
.SetNotNegative()
.SetDisplay("Signal Bar Shift", "Number of bars to shift indicator values", "Parameters");
_lookbackLength = Param(nameof(LookbackLength), 5)
.SetGreaterThanZero()
.SetDisplay("Lookback Length", "Bars used to compute highest and lowest prices", "Indicator")
.SetOptimize(5, 25, 5);
_firstSmoothingLength = Param(nameof(FirstSmoothingLength), 20)
.SetGreaterThanZero()
.SetDisplay("First Smoothing", "Length of the first smoothing stage", "Indicator")
.SetOptimize(10, 40, 5);
_secondSmoothingLength = Param(nameof(SecondSmoothingLength), 5)
.SetGreaterThanZero()
.SetDisplay("Second Smoothing", "Length of the second smoothing stage", "Indicator")
.SetOptimize(3, 15, 2);
_thirdSmoothingLength = Param(nameof(ThirdSmoothingLength), 3)
.SetGreaterThanZero()
.SetDisplay("Third Smoothing", "Length of the third smoothing stage", "Indicator")
.SetOptimize(2, 10, 1);
_signalLength = Param(nameof(SignalLength), 3)
.SetGreaterThanZero()
.SetDisplay("Signal Smoothing", "Length of the signal line smoothing", "Indicator")
.SetOptimize(2, 12, 1);
_smoothMethod = Param(nameof(SmoothMethod), BlauSmSmoothMethods.Ema)
.SetDisplay("Smoothing Method", "Moving average type used for all smoothing stages", "Indicator");
_phase = Param(nameof(Phase), 15)
.SetDisplay("Phase", "Compatibility parameter kept from the original indicator", "Indicator");
_priceType = Param(nameof(PriceType), BlauSmAppliedPrices.Close)
.SetDisplay("Applied Price", "Price input used in oscillator calculations", "Indicator");
_enableLongEntry = Param(nameof(EnableLongEntry), true)
.SetDisplay("Enable Long Entry", "Allow opening long positions", "Trading");
_enableShortEntry = Param(nameof(EnableShortEntry), true)
.SetDisplay("Enable Short Entry", "Allow opening short positions", "Trading");
_enableLongExit = Param(nameof(EnableLongExit), true)
.SetDisplay("Enable Long Exit", "Allow closing existing long positions", "Trading");
_enableShortExit = Param(nameof(EnableShortExit), true)
.SetDisplay("Enable Short Exit", "Allow closing existing short positions", "Trading");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000)
.SetNotNegative()
.SetDisplay("Take Profit (points)", "Take-profit distance expressed in instrument points", "Risk")
.SetOptimize(0, 4000, 500);
_stopLossPoints = Param(nameof(StopLossPoints), 1000)
.SetNotNegative()
.SetDisplay("Stop Loss (points)", "Stop-loss distance expressed in instrument points", "Risk")
.SetOptimize(0, 4000, 500);
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for indicator calculations", "General");
}
/// <summary>
/// Strategy operating mode.
/// </summary>
public BlauSmStochasticModes Mode
{
get => _mode.Value;
set => _mode.Value = value;
}
/// <summary>
/// Number of bars to shift indicator values before evaluation.
/// </summary>
public int SignalBar
{
get => _signalBar.Value;
set => _signalBar.Value = value;
}
/// <summary>
/// Length used to search for highs and lows.
/// </summary>
public int LookbackLength
{
get => _lookbackLength.Value;
set => _lookbackLength.Value = value;
}
/// <summary>
/// First smoothing stage length.
/// </summary>
public int FirstSmoothingLength
{
get => _firstSmoothingLength.Value;
set => _firstSmoothingLength.Value = value;
}
/// <summary>
/// Second smoothing stage length.
/// </summary>
public int SecondSmoothingLength
{
get => _secondSmoothingLength.Value;
set => _secondSmoothingLength.Value = value;
}
/// <summary>
/// Third smoothing stage length.
/// </summary>
public int ThirdSmoothingLength
{
get => _thirdSmoothingLength.Value;
set => _thirdSmoothingLength.Value = value;
}
/// <summary>
/// Signal line smoothing length.
/// </summary>
public int SignalLength
{
get => _signalLength.Value;
set => _signalLength.Value = value;
}
/// <summary>
/// Moving average method.
/// </summary>
public BlauSmSmoothMethods SmoothMethod
{
get => _smoothMethod.Value;
set => _smoothMethod.Value = value;
}
/// <summary>
/// Phase parameter kept for compatibility.
/// </summary>
public int Phase
{
get => _phase.Value;
set => _phase.Value = value;
}
/// <summary>
/// Applied price type.
/// </summary>
public BlauSmAppliedPrices PriceType
{
get => _priceType.Value;
set => _priceType.Value = value;
}
/// <summary>
/// Allow opening long positions.
/// </summary>
public bool EnableLongEntry
{
get => _enableLongEntry.Value;
set => _enableLongEntry.Value = value;
}
/// <summary>
/// Allow opening short positions.
/// </summary>
public bool EnableShortEntry
{
get => _enableShortEntry.Value;
set => _enableShortEntry.Value = value;
}
/// <summary>
/// Allow closing existing long positions.
/// </summary>
public bool EnableLongExit
{
get => _enableLongExit.Value;
set => _enableLongExit.Value = value;
}
/// <summary>
/// Allow closing existing short positions.
/// </summary>
public bool EnableShortExit
{
get => _enableShortExit.Value;
set => _enableShortExit.Value = value;
}
/// <summary>
/// Take-profit distance expressed in points.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Stop-loss distance expressed in points.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Candle type used for indicator subscription.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_mainHistory.Clear();
_signalHistory.Clear();
_indicator = null;
_entryPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_indicator = new BlauSmStochasticIndicator
{
LookbackLength = LookbackLength,
FirstSmoothingLength = FirstSmoothingLength,
SecondSmoothingLength = SecondSmoothingLength,
ThirdSmoothingLength = ThirdSmoothingLength,
SignalLength = SignalLength,
SmoothMethod = SmoothMethod,
Phase = Phase,
PriceType = PriceType,
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (trade?.Trade == null) return;
if (Position != 0m && _entryPrice == 0m)
_entryPrice = trade.Trade.Price;
if (Position == 0m)
_entryPrice = 0m;
}
private void HandleProtectiveLevels(ICandleMessage candle)
{
var step = Security?.PriceStep ?? 1m;
if (Position > 0m)
{
if (StopLossPoints > 0 && candle.LowPrice <= _entryPrice - StopLossPoints * step)
{
SellMarket(Position);
return;
}
if (TakeProfitPoints > 0 && candle.HighPrice >= _entryPrice + TakeProfitPoints * step)
{
SellMarket(Position);
return;
}
}
else if (Position < 0m)
{
var abs = Math.Abs(Position);
if (StopLossPoints > 0 && candle.HighPrice >= _entryPrice + StopLossPoints * step)
{
BuyMarket(abs);
return;
}
if (TakeProfitPoints > 0 && candle.LowPrice <= _entryPrice - TakeProfitPoints * step)
{
BuyMarket(abs);
return;
}
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
HandleProtectiveLevels(candle);
var indicatorValue = _indicator.Process(candle);
if (!_indicator.IsFormed)
return;
var main = indicatorValue.ToDecimal();
// Get signal from indicator's stored value
var signal = _indicator.LastSignal;
UpdateHistory(main, signal);
var hasCurrent = TryGetMain(SignalBar, out var currentMain);
var hasPrevious = TryGetMain(SignalBar + 1, out var previousMain);
var hasCurrentSignal = TryGetSignal(SignalBar, out var currentSignal);
var hasPreviousSignal = TryGetSignal(SignalBar + 1, out var previousSignal);
if (!hasCurrent || !hasPrevious)
return;
var buyEntry = false;
var sellEntry = false;
var buyExit = false;
var sellExit = false;
switch (Mode)
{
case BlauSmStochasticModes.Breakdown:
{
// Detect histogram sign change through zero.
if (previousMain > 0m && currentMain <= 0m)
{
if (EnableLongEntry)
buyEntry = true;
if (EnableShortExit)
sellExit = true;
}
if (previousMain < 0m && currentMain >= 0m)
{
if (EnableShortEntry)
sellEntry = true;
if (EnableLongExit)
buyExit = true;
}
break;
}
case BlauSmStochasticModes.Twist:
{
if (!TryGetMain(SignalBar + 2, out var olderMain))
return;
// Identify twists in momentum slope.
if (previousMain < olderMain && currentMain > previousMain)
{
if (EnableLongEntry)
buyEntry = true;
if (EnableShortExit)
sellExit = true;
}
if (previousMain > olderMain && currentMain < previousMain)
{
if (EnableShortEntry)
sellEntry = true;
if (EnableLongExit)
buyExit = true;
}
break;
}
case BlauSmStochasticModes.CloudTwist:
{
if (!hasCurrentSignal || !hasPreviousSignal)
return;
// Watch for crossings between main and smoothed signal lines.
if (previousMain > previousSignal && currentMain <= currentSignal)
{
if (EnableLongEntry)
buyEntry = true;
if (EnableShortExit)
sellExit = true;
}
if (previousMain < previousSignal && currentMain >= currentSignal)
{
if (EnableShortEntry)
sellEntry = true;
if (EnableLongExit)
buyExit = true;
}
break;
}
}
// Close positions before opening opposite trades.
if (buyExit && Position > 0)
SellMarket(Position);
if (sellExit && Position < 0)
BuyMarket(-Position);
if (buyEntry && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
if (sellEntry && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
private void UpdateHistory(decimal main, decimal signal)
{
_mainHistory.Add(main);
_signalHistory.Add(signal);
var max = SignalBar + 5;
while (_mainHistory.Count > max)
_mainHistory.RemoveAt(0);
while (_signalHistory.Count > max)
_signalHistory.RemoveAt(0);
}
private bool TryGetMain(int shift, out decimal value)
{
var index = _mainHistory.Count - 1 - shift;
if (index < 0)
{
value = 0m;
return false;
}
value = _mainHistory[index];
return true;
}
private bool TryGetSignal(int shift, out decimal value)
{
var index = _signalHistory.Count - 1 - shift;
if (index < 0)
{
value = 0m;
return false;
}
value = _signalHistory[index];
return true;
}
/// <summary>
/// Custom indicator implementing the Blau SM Stochastic oscillator.
/// </summary>
public class BlauSmStochasticIndicator : BaseIndicator
{
private readonly List<decimal> _highs = new();
private readonly List<decimal> _lows = new();
private IIndicator _smooth1;
private IIndicator _smooth2;
private IIndicator _smooth3;
private IIndicator _halfSmooth1;
private IIndicator _halfSmooth2;
private IIndicator _halfSmooth3;
private IIndicator _signalSmooth;
/// <summary>
/// Bars used to search for highest and lowest values.
/// </summary>
public int LookbackLength { get; set; } = 5;
/// <summary>
/// First smoothing length.
/// </summary>
public int FirstSmoothingLength { get; set; } = 20;
/// <summary>
/// Second smoothing length.
/// </summary>
public int SecondSmoothingLength { get; set; } = 5;
/// <summary>
/// Third smoothing length.
/// </summary>
public int ThirdSmoothingLength { get; set; } = 3;
/// <summary>
/// Signal line smoothing length.
/// </summary>
public int SignalLength { get; set; } = 3;
/// <summary>
/// Moving average type used throughout the calculations.
/// </summary>
public BlauSmSmoothMethods SmoothMethod { get; set; } = BlauSmSmoothMethods.Ema;
/// <summary>
/// Phase parameter kept for compatibility with the original code.
/// </summary>
public int Phase { get; set; } = 15;
/// <summary>
/// Applied price type.
/// </summary>
public BlauSmAppliedPrices PriceType { get; set; } = BlauSmAppliedPrices.Close;
/// <summary>
/// Last computed signal line value.
/// </summary>
public decimal LastSignal { get; private set; }
/// <inheritdoc />
protected override IIndicatorValue OnProcess(IIndicatorValue input)
{
IsFormed = false;
if (!input.IsFinal)
return new DecimalIndicatorValue(this, default, input.Time);
var candle = input.GetValue<ICandleMessage>();
if (candle == null)
return new DecimalIndicatorValue(this, default, input.Time);
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
while (_highs.Count > LookbackLength)
_highs.RemoveAt(0);
while (_lows.Count > LookbackLength)
_lows.RemoveAt(0);
EnsureIndicators();
_lastInputTime = input.Time;
if (_highs.Count < LookbackLength || _lows.Count < LookbackLength)
return new DecimalIndicatorValue(this, default, input.Time);
var highest = GetMaximum(_highs);
var lowest = GetMinimum(_lows);
var price = GetAppliedPrice(candle, PriceType);
var sm = price - 0.5m * (lowest + highest);
var half = 0.5m * (highest - lowest);
var sm1 = ProcessStage(_smooth1, sm);
if (sm1 is null)
return new DecimalIndicatorValue(this, default, input.Time);
var sm2 = ProcessStage(_smooth2, sm1.Value);
if (sm2 is null)
return new DecimalIndicatorValue(this, default, input.Time);
var sm3 = ProcessStage(_smooth3, sm2.Value);
if (sm3 is null)
return new DecimalIndicatorValue(this, default, input.Time);
var half1 = ProcessStage(_halfSmooth1, half);
if (half1 is null)
return new DecimalIndicatorValue(this, default, input.Time);
var half2 = ProcessStage(_halfSmooth2, half1.Value);
if (half2 is null)
return new DecimalIndicatorValue(this, default, input.Time);
var half3 = ProcessStage(_halfSmooth3, half2.Value);
if (half3 is null || half3.Value == 0m)
return new DecimalIndicatorValue(this, default, input.Time);
var main = 100m * sm3.Value / half3.Value;
var signal = ProcessStage(_signalSmooth, main);
if (signal is null)
return new DecimalIndicatorValue(this, default, input.Time);
IsFormed = true;
LastSignal = signal.Value;
return new DecimalIndicatorValue(this, main, input.Time) { IsFinal = input.IsFinal };
}
private void EnsureIndicators()
{
if (_smooth1 != null)
return;
_smooth1 = CreateAverage(FirstSmoothingLength);
_smooth2 = CreateAverage(SecondSmoothingLength);
_smooth3 = CreateAverage(ThirdSmoothingLength);
_halfSmooth1 = CreateAverage(FirstSmoothingLength);
_halfSmooth2 = CreateAverage(SecondSmoothingLength);
_halfSmooth3 = CreateAverage(ThirdSmoothingLength);
_signalSmooth = CreateAverage(SignalLength);
}
private static decimal GetMaximum(List<decimal> values)
{
var max = decimal.MinValue;
foreach (var value in values)
{
if (value > max)
max = value;
}
return max;
}
private static decimal GetMinimum(List<decimal> values)
{
var min = decimal.MaxValue;
foreach (var value in values)
{
if (value < min)
min = value;
}
return min;
}
private static decimal GetAppliedPrice(ICandleMessage candle, BlauSmAppliedPrices priceType)
{
return priceType switch
{
BlauSmAppliedPrices.Close => candle.ClosePrice,
BlauSmAppliedPrices.Open => candle.OpenPrice,
BlauSmAppliedPrices.High => candle.HighPrice,
BlauSmAppliedPrices.Low => candle.LowPrice,
BlauSmAppliedPrices.Median => (candle.HighPrice + candle.LowPrice) / 2m,
BlauSmAppliedPrices.Typical => (candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 3m,
BlauSmAppliedPrices.Weighted => (2m * candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 4m,
BlauSmAppliedPrices.Simple => (candle.OpenPrice + candle.ClosePrice) / 2m,
BlauSmAppliedPrices.Quarter => (candle.OpenPrice + candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 4m,
BlauSmAppliedPrices.TrendFollow0 => candle.ClosePrice > candle.OpenPrice ? candle.HighPrice : candle.ClosePrice < candle.OpenPrice ? candle.LowPrice : candle.ClosePrice,
BlauSmAppliedPrices.TrendFollow1 => candle.ClosePrice > candle.OpenPrice ? (candle.HighPrice + candle.ClosePrice) / 2m : candle.ClosePrice < candle.OpenPrice ? (candle.LowPrice + candle.ClosePrice) / 2m : candle.ClosePrice,
BlauSmAppliedPrices.Demark => CalculateDemarkPrice(candle),
_ => candle.ClosePrice,
};
}
private static decimal CalculateDemarkPrice(ICandleMessage candle)
{
var res = candle.HighPrice + candle.LowPrice + candle.ClosePrice;
if (candle.ClosePrice < candle.OpenPrice)
res = (res + candle.LowPrice) / 2m;
else if (candle.ClosePrice > candle.OpenPrice)
res = (res + candle.HighPrice) / 2m;
else
res = (res + candle.ClosePrice) / 2m;
return ((res - candle.LowPrice) + (res - candle.HighPrice)) / 2m;
}
private IIndicator CreateAverage(int length)
{
return SmoothMethod switch
{
BlauSmSmoothMethods.Sma => new SimpleMovingAverage { Length = length },
BlauSmSmoothMethods.Ema => new ExponentialMovingAverage { Length = length },
BlauSmSmoothMethods.Smma => new SmoothedMovingAverage { Length = length },
BlauSmSmoothMethods.Lwma => new WeightedMovingAverage { Length = length },
_ => new ExponentialMovingAverage { Length = length },
};
}
private DateTime _lastInputTime;
private decimal? ProcessStage(IIndicator indicator, decimal value)
{
var result = indicator.Process(new DecimalIndicatorValue(indicator, value, _lastInputTime) { IsFinal = true });
return indicator.IsFormed ? result.ToDecimal() : null;
}
/// <inheritdoc />
public override void Reset()
{
base.Reset();
_highs.Clear();
_lows.Clear();
_smooth1 = null;
_smooth2 = null;
_smooth3 = null;
_halfSmooth1 = null;
_halfSmooth2 = null;
_halfSmooth3 = null;
_signalSmooth = null;
LastSignal = 0m;
IsFormed = false;
}
}
}
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 (
ExponentialMovingAverage,
SimpleMovingAverage, SmoothedMovingAverage, WeightedMovingAverage
)
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class blau_sm_stochastic_strategy(Strategy):
MODE_BREAKDOWN = 0
MODE_TWIST = 1
MODE_CLOUD_TWIST = 2
SMOOTH_SMA = 0
SMOOTH_EMA = 1
SMOOTH_SMMA = 2
SMOOTH_LWMA = 3
PRICE_CLOSE = 0
PRICE_OPEN = 1
PRICE_HIGH = 2
PRICE_LOW = 3
PRICE_MEDIAN = 4
PRICE_TYPICAL = 5
PRICE_WEIGHTED = 6
PRICE_SIMPLE = 7
PRICE_QUARTER = 8
PRICE_TRENDFOLLOW0 = 9
PRICE_TRENDFOLLOW1 = 10
PRICE_DEMARK = 11
def __init__(self):
super(blau_sm_stochastic_strategy, self).__init__()
self._mode = self.Param("Mode", self.MODE_TWIST)
self._signal_bar = self.Param("SignalBar", 1)
self._lookback_length = self.Param("LookbackLength", 5)
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_length = self.Param("SignalLength", 3)
self._smooth_method = self.Param("SmoothMethod", self.SMOOTH_EMA)
self._phase = self.Param("Phase", 15)
self._price_type = self.Param("PriceType", self.PRICE_CLOSE)
self._enable_long_entry = self.Param("EnableLongEntry", True)
self._enable_short_entry = self.Param("EnableShortEntry", True)
self._enable_long_exit = self.Param("EnableLongExit", True)
self._enable_short_exit = self.Param("EnableShortExit", True)
self._take_profit_points = self.Param("TakeProfitPoints", 2000)
self._stop_loss_points = self.Param("StopLossPoints", 1000)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8)))
self._main_history = []
self._signal_history = []
self._entry_price = 0.0
self._highs = []
self._lows = []
self._smooth1 = None
self._smooth2 = None
self._smooth3 = None
self._half_smooth1 = None
self._half_smooth2 = None
self._half_smooth3 = None
self._signal_smooth = None
self._ind_formed = False
self._last_signal = 0.0
@property
def Mode(self):
return self._mode.Value
@property
def SignalBar(self):
return self._signal_bar.Value
@property
def LookbackLength(self):
return self._lookback_length.Value
@property
def FirstSmoothingLength(self):
return self._first_smoothing_length.Value
@property
def SecondSmoothingLength(self):
return self._second_smoothing_length.Value
@property
def ThirdSmoothingLength(self):
return self._third_smoothing_length.Value
@property
def SignalLength(self):
return self._signal_length.Value
@property
def SmoothMethod(self):
return self._smooth_method.Value
@property
def Phase(self):
return self._phase.Value
@property
def PriceType(self):
return self._price_type.Value
@property
def EnableLongEntry(self):
return self._enable_long_entry.Value
@property
def EnableShortEntry(self):
return self._enable_short_entry.Value
@property
def EnableLongExit(self):
return self._enable_long_exit.Value
@property
def EnableShortExit(self):
return self._enable_short_exit.Value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(blau_sm_stochastic_strategy, self).OnStarted2(time)
self._smooth1 = self._create_average(self.FirstSmoothingLength)
self._smooth2 = self._create_average(self.SecondSmoothingLength)
self._smooth3 = self._create_average(self.ThirdSmoothingLength)
self._half_smooth1 = self._create_average(self.FirstSmoothingLength)
self._half_smooth2 = self._create_average(self.SecondSmoothingLength)
self._half_smooth3 = self._create_average(self.ThirdSmoothingLength)
self._signal_smooth = self._create_average(self.SignalLength)
self._ind_formed = False
self._last_signal = 0.0
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 OnOwnTradeReceived(self, trade):
super(blau_sm_stochastic_strategy, self).OnOwnTradeReceived(trade)
if trade is None or trade.Trade is None:
return
pos = float(self.Position)
if pos != 0 and self._entry_price == 0.0:
self._entry_price = float(trade.Trade.Price)
if pos == 0:
self._entry_price = 0.0
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
self._handle_protective_levels(candle)
main, signal, formed = self._compute_indicator(candle)
if not formed:
return
self._update_history(main, signal)
has_current, current_main = self._try_get_main(self.SignalBar)
has_previous, previous_main = self._try_get_main(self.SignalBar + 1)
has_current_signal, current_signal = self._try_get_signal(self.SignalBar)
has_previous_signal, previous_signal = self._try_get_signal(self.SignalBar + 1)
if not has_current or not has_previous:
return
buy_entry = False
sell_entry = False
buy_exit = False
sell_exit = False
if self.Mode == self.MODE_BREAKDOWN:
if previous_main > 0 and current_main <= 0:
if self.EnableLongEntry:
buy_entry = True
if self.EnableShortExit:
sell_exit = True
if previous_main < 0 and current_main >= 0:
if self.EnableShortEntry:
sell_entry = True
if self.EnableLongExit:
buy_exit = True
elif self.Mode == self.MODE_TWIST:
has_older, older_main = self._try_get_main(self.SignalBar + 2)
if not has_older:
return
if previous_main < older_main and current_main > previous_main:
if self.EnableLongEntry:
buy_entry = True
if self.EnableShortExit:
sell_exit = True
if previous_main > older_main and current_main < previous_main:
if self.EnableShortEntry:
sell_entry = True
if self.EnableLongExit:
buy_exit = True
elif self.Mode == self.MODE_CLOUD_TWIST:
if not has_current_signal or not has_previous_signal:
return
if previous_main > previous_signal and current_main <= current_signal:
if self.EnableLongEntry:
buy_entry = True
if self.EnableShortExit:
sell_exit = True
if previous_main < previous_signal and current_main >= current_signal:
if self.EnableShortEntry:
sell_entry = True
if self.EnableLongExit:
buy_exit = True
pos = float(self.Position)
if buy_exit and pos > 0:
self.SellMarket(pos)
if sell_exit and pos < 0:
self.BuyMarket(abs(pos))
pos = float(self.Position)
if buy_entry and pos <= 0:
self.BuyMarket(float(self.Volume) + abs(pos))
if sell_entry and pos >= 0:
self.SellMarket(float(self.Volume) + abs(pos))
def _handle_protective_levels(self, candle):
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 1.0
pos = float(self.Position)
if pos > 0:
if self.StopLossPoints > 0 and float(candle.LowPrice) <= self._entry_price - self.StopLossPoints * step:
self.SellMarket(pos)
return
if self.TakeProfitPoints > 0 and float(candle.HighPrice) >= self._entry_price + self.TakeProfitPoints * step:
self.SellMarket(pos)
return
elif pos < 0:
ap = abs(pos)
if self.StopLossPoints > 0 and float(candle.HighPrice) >= self._entry_price + self.StopLossPoints * step:
self.BuyMarket(ap)
return
if self.TakeProfitPoints > 0 and float(candle.LowPrice) <= self._entry_price - self.TakeProfitPoints * step:
self.BuyMarket(ap)
return
def _compute_indicator(self, candle):
self._candle_time = candle.ServerTime
h = float(candle.HighPrice)
l = float(candle.LowPrice)
self._highs.append(h)
self._lows.append(l)
while len(self._highs) > self.LookbackLength:
self._highs.pop(0)
while len(self._lows) > self.LookbackLength:
self._lows.pop(0)
if len(self._highs) < self.LookbackLength or len(self._lows) < self.LookbackLength:
return (0.0, 0.0, False)
highest = max(self._highs)
lowest = min(self._lows)
price = self._get_applied_price(candle)
sm = price - 0.5 * (lowest + highest)
half = 0.5 * (highest - lowest)
sm1 = self._process_stage(self._smooth1, sm)
if sm1 is None:
return (0.0, 0.0, False)
sm2 = self._process_stage(self._smooth2, sm1)
if sm2 is None:
return (0.0, 0.0, False)
sm3 = self._process_stage(self._smooth3, sm2)
if sm3 is None:
return (0.0, 0.0, False)
h1 = self._process_stage(self._half_smooth1, half)
if h1 is None:
return (0.0, 0.0, False)
h2 = self._process_stage(self._half_smooth2, h1)
if h2 is None:
return (0.0, 0.0, False)
h3 = self._process_stage(self._half_smooth3, h2)
if h3 is None or h3 == 0.0:
return (0.0, 0.0, False)
main = 100.0 * sm3 / h3
sig = self._process_stage(self._signal_smooth, main)
if sig is None:
return (0.0, 0.0, False)
self._last_signal = sig
return (main, sig, True)
def _process_stage(self, indicator, value):
result = process_float(indicator, Decimal(float(value)), self._candle_time, True)
if not indicator.IsFormed:
return None
return float(result.Value)
def _get_applied_price(self, candle):
o = float(candle.OpenPrice)
h = float(candle.HighPrice)
l = float(candle.LowPrice)
c = float(candle.ClosePrice)
pt = self.PriceType
if pt == self.PRICE_OPEN:
return o
elif pt == self.PRICE_HIGH:
return h
elif pt == self.PRICE_LOW:
return l
elif pt == self.PRICE_MEDIAN:
return (h + l) / 2.0
elif pt == self.PRICE_TYPICAL:
return (c + h + l) / 3.0
elif pt == self.PRICE_WEIGHTED:
return (2.0 * c + h + l) / 4.0
elif pt == self.PRICE_SIMPLE:
return (o + c) / 2.0
elif pt == self.PRICE_QUARTER:
return (o + c + h + l) / 4.0
elif pt == self.PRICE_TRENDFOLLOW0:
if c > o:
return h
elif c < o:
return l
return c
elif pt == self.PRICE_TRENDFOLLOW1:
if c > o:
return (h + c) / 2.0
elif c < o:
return (l + c) / 2.0
return c
elif pt == self.PRICE_DEMARK:
return self._calculate_demark(o, h, l, c)
return c
def _calculate_demark(self, o, h, l, c):
res = h + l + c
if c < o:
res = (res + l) / 2.0
elif c > o:
res = (res + h) / 2.0
else:
res = (res + c) / 2.0
return ((res - l) + (res - h)) / 2.0
def _create_average(self, length):
if self.SmoothMethod == self.SMOOTH_SMA:
ind = SimpleMovingAverage()
ind.Length = length
return ind
elif self.SmoothMethod == self.SMOOTH_SMMA:
ind = SmoothedMovingAverage()
ind.Length = length
return ind
elif self.SmoothMethod == self.SMOOTH_LWMA:
ind = WeightedMovingAverage()
ind.Length = length
return ind
else:
ind = ExponentialMovingAverage()
ind.Length = length
return ind
def _update_history(self, main, signal):
self._main_history.append(main)
self._signal_history.append(signal)
mx = self.SignalBar + 5
while len(self._main_history) > mx:
self._main_history.pop(0)
while len(self._signal_history) > mx:
self._signal_history.pop(0)
def _try_get_main(self, shift):
index = len(self._main_history) - 1 - shift
if index < 0:
return (False, 0.0)
return (True, self._main_history[index])
def _try_get_signal(self, shift):
index = len(self._signal_history) - 1 - shift
if index < 0:
return (False, 0.0)
return (True, self._signal_history[index])
def OnReseted(self):
super(blau_sm_stochastic_strategy, self).OnReseted()
self._main_history = []
self._signal_history = []
self._entry_price = 0.0
self._highs = []
self._lows = []
self._smooth1 = None
self._smooth2 = None
self._smooth3 = None
self._half_smooth1 = None
self._half_smooth2 = None
self._half_smooth3 = None
self._signal_smooth = None
self._ind_formed = False
self._last_signal = 0.0
def CreateClone(self):
return blau_sm_stochastic_strategy()