Estrategia Blau SM Stochastic
Descripción general
Esta estrategia es una conversión en C# del expert original de MetaTrader 5 Exp_BlauSMStochastic construido alrededor del oscilador Blau SM Stochastic. El indicador mide la distancia entre el precio y el rango de trading reciente, aplica múltiples etapas de suavizado y compara el resultado con una línea de referencia suavizada. La estrategia trabaja en velas completadas (marco temporal de 4 horas por defecto) y permite el trading en ambas direcciones.
Lógica del indicador
- Calcular el máximo más alto y el mínimo más bajo durante
LookbackLengthbarras. - Construir una serie de precio sin tendencia:
sm = price - (HH + LL) / 2dondepricees el tipo de precio aplicado. - Suavizar la serie sin tendencia secuencialmente mediante tres medias móviles con longitudes
FirstSmoothingLength,SecondSmoothingLengthyThirdSmoothingLengthusando elSmoothMethodseleccionado (SMA, EMA, SMMA o LWMA). - Suavizar el semirango
(HH - LL) / 2con la misma secuencia triple para normalizar la volatilidad. - Formar la línea principal del oscilador como
100 * smoothed(sm) / smoothed(range). - Suavizar la línea principal con
SignalLengthpara obtener la línea de señal.
El parámetro Phase se mantiene por compatibilidad con la versión MQL pero no es utilizado por el motor de suavizado simplificado.
Modos de trading
- Breakdown: monitorea los cruces de cero de la línea principal. Un cruce de positivo a no positivo abre un largo y cierra cortos. Un cruce de negativo a no negativo abre un corto y cierra largos.
- Twist: rastrea giros de momentum. Si la línea principal forma un mínimo local (el valor sube después de bajar), se desencadena una entrada larga, mientras que un máximo local (el valor baja después de subir) desencadena un corto. Las posiciones opuestas se cierran en consecuencia.
- CloudTwist: observa los cruces entre la línea principal y la línea de señal. Un cruce descendente de la línea principal a través de la línea de señal abre un largo y cierra cortos, mientras que un cruce ascendente abre un corto y cierra largos.
Los interruptores de entrada y salida (EnableLongEntry, EnableShortEntry, EnableLongExit, EnableShortExit) permiten deshabilitar operaciones específicas manteniendo los cálculos del indicador intactos.
Gestión de riesgo
TakeProfitPoints y StopLossPoints se convierten a distancias de precio absolutas usando el paso de precio del instrumento y se pasan al bloque de protección incorporado a través de StartProtection. Establézcalos en cero para deshabilitar el límite correspondiente.
Parámetros
CandleType(DataType, predeterminado: marco temporal de 4 horas) – marco temporal usado para la suscripción de velas y cálculos de indicadores.Mode(BlauSmStochasticModes, predeterminado: Twist) – selecciona el modo de generación de señales (Breakdown, Twist, CloudTwist).SignalBar(int, predeterminado: 1) – número de barras para desplazar los valores del indicador al evaluar señales, reproduciendo la lógicaSignalBaroriginal.LookbackLength(int, predeterminado: 5) – barras usadas para calcular los valores más altos y más bajos.FirstSmoothingLength(int, predeterminado: 20) – longitud de la primera etapa de suavizado.SecondSmoothingLength(int, predeterminado: 5) – longitud de la segunda etapa de suavizado.ThirdSmoothingLength(int, predeterminado: 3) – longitud de la tercera etapa de suavizado.SignalLength(int, predeterminado: 3) – longitud de suavizado de la línea de señal.SmoothMethod(BlauSmSmoothMethods, predeterminado: EMA) – familia de medias móviles aplicada a todas las etapas de suavizado (SMA, EMA, SMMA, LWMA).PriceType(BlauSmAppliedPrices, predeterminado: Close) – precio aplicado usado para alimentar el oscilador (cierre, apertura, máximo, mínimo, mediana, típico, ponderado, simple, cuartil, variantes de seguimiento de tendencia, Demark).EnableLongEntry(bool, predeterminado: true) – permitir la apertura de posiciones largas.EnableShortEntry(bool, predeterminado: true) – permitir la apertura de posiciones cortas.EnableLongExit(bool, predeterminado: true) – permitir el cierre de posiciones largas.EnableShortExit(bool, predeterminado: true) – permitir el cierre de posiciones cortas.TakeProfitPoints(int, predeterminado: 2000) – distancia de take-profit fija expresada en puntos del instrumento.StopLossPoints(int, predeterminado: 1000) – distancia de stop-loss fija expresada en puntos del instrumento.
Notas
- El motor de suavizado actualmente soporta medias móviles clásicas (SMA, EMA, SMMA, LWMA). Los modos exóticos de la biblioteca MQL (JMA, JurX, etc.) no están disponibles en StockSharp y por lo tanto no están incluidos.
- Phase se preserva como parámetro por completitud; ajústelo solo con fines de documentación.
- Funciona con cualquier símbolo soportado por StockSharp. Ajuste el tipo de vela, las longitudes de suavizado y los stops para que coincidan con la volatilidad del instrumento.
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()