Blau SM Stochastic Strategy
Overview
This strategy is a C# conversion of the original MetaTrader 5 expert Exp_BlauSMStochastic built around the Blau SM Stochastic oscillator. The indicator measures the distance between price and the recent trading range, applies multiple smoothing stages and compares the result with a smoothed reference line. The strategy works on completed candles (default 4-hour timeframe) and allows trading in both directions.
Indicator Logic
- Compute the highest high and lowest low over
LookbackLengthbars. - Build a detrended price series:
sm = price - (HH + LL) / 2wherepriceis the applied price type. - Smooth the detrended series sequentially by three moving averages with lengths
FirstSmoothingLength,SecondSmoothingLengthandThirdSmoothingLengthusing the selectedSmoothMethod(SMA, EMA, SMMA or LWMA). - Smooth the half-range
(HH - LL) / 2with the same triple sequence to normalize volatility. - Form the main oscillator line as
100 * smoothed(sm) / smoothed(range). - Smooth the main line with
SignalLengthto obtain the signal line.
The parameter Phase is kept for compatibility with the MQL version but is not used by the simplified smoothing engine.
Trading Modes
- Breakdown: monitors zero crossings of the main line. A cross from positive to non-positive opens a long and closes shorts. A cross from negative to non-negative opens a short and closes longs.
- Twist: tracks momentum twists. If the main line forms a local trough (value rises after declining), a long entry is triggered, while a local peak (value falls after rising) triggers a short. Opposite positions are closed accordingly.
- CloudTwist: observes crossings between the main line and the signal line. A downward cross of the main line through the signal line opens a long and exits shorts, while an upward cross opens a short and exits longs.
Entry and exit switches (EnableLongEntry, EnableShortEntry, EnableLongExit, EnableShortExit) allow disabling specific operations while keeping indicator calculations intact.
Risk Management
TakeProfitPoints and StopLossPoints convert to absolute price distances using the instrument price step and are passed to the built-in protective block via StartProtection. Set them to zero to disable the corresponding limit.
Parameters
CandleType(DataType, default: 4-hour time frame) – timeframe used for candle subscription and indicator calculations.Mode(BlauSmStochasticModes, default: Twist) – selects the signal generation mode (Breakdown, Twist, CloudTwist).SignalBar(int, default: 1) – number of bars to shift indicator values when evaluating signals, reproducing the originalSignalBarlogic.LookbackLength(int, default: 5) – bars used to compute highest and lowest values.FirstSmoothingLength(int, default: 20) – length of the first smoothing stage.SecondSmoothingLength(int, default: 5) – length of the second smoothing stage.ThirdSmoothingLength(int, default: 3) – length of the third smoothing stage.SignalLength(int, default: 3) – smoothing length of the signal line.SmoothMethod(BlauSmSmoothMethods, default: EMA) – moving average family applied to all smoothing stages (SMA, EMA, SMMA, LWMA).PriceType(BlauSmAppliedPrices, default: Close) – applied price used to feed the oscillator (close, open, high, low, median, typical, weighted, simple, quarter, trend-follow variants, Demark).EnableLongEntry(bool, default: true) – allow opening long positions.EnableShortEntry(bool, default: true) – allow opening short positions.EnableLongExit(bool, default: true) – allow closing long positions.EnableShortExit(bool, default: true) – allow closing short positions.TakeProfitPoints(int, default: 2000) – fixed take-profit distance expressed in instrument points.StopLossPoints(int, default: 1000) – fixed stop-loss distance expressed in instrument points.
Notes
- The smoothing engine currently supports classic moving averages (SMA, EMA, SMMA, LWMA). Exotic modes from the MQL library (JMA, JurX, etc.) are not available in StockSharp and are therefore not included.
- Phase is preserved as a parameter for completeness; adjust it for documentation purposes only.
- Works on any symbol supported by StockSharp. Adjust candle type, smoothing lengths and stops to match instrument volatility.
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()