Стратегия Blau SM Stochastic
Обзор
Стратегия представляет собой перенос советника MetaTrader 5 Exp_BlauSMStochastic на C#. В основе лежит осциллятор Blau SM Stochastic, измеряющий расстояние цены от недавнего торгового диапазона. Значение проходит несколько ступеней сглаживания и сравнивается с дополнительно сглаженной опорной линией. Расчёты выполняются по закрытым свечам (по умолчанию таймфрейм 4 часа) и позволяют торговать в обе стороны.
Логика индикатора
- Рассчитываются максимум и минимум за последние
LookbackLengthбаров. - Формируется детрендированный ряд:
sm = price - (HH + LL) / 2, гдеprice— выбранный тип цены. - Ряд
smпоследовательно сглаживается тремя средними с периодамиFirstSmoothingLength,SecondSmoothingLength,ThirdSmoothingLengthи типомSmoothMethod(SMA, EMA, SMMA или LWMA). - Половина диапазона
(HH - LL) / 2проходит через те же три этапа сглаживания для нормализации волатильности. - Основная линия осциллятора вычисляется как
100 * smoothed(sm) / smoothed(range). - Основная линия дополнительно сглаживается периодом
SignalLength, образуя сигнальную линию.
Параметр Phase сохранён для совместимости с MQL-версией, но в упрощённой реализации сглаживания не используется.
Торговые режимы
- Breakdown – отслеживает пересечения основной линии через ноль. Переход из положительной области в неположительную открывает покупку и закрывает продажи. Обратное пересечение открывает продажу и закрывает покупки.
- Twist – ловит развороты наклона. Локальный минимум основной линии (значение растёт после падения) формирует сигнал на покупку; локальный максимум (значение падает после роста) даёт сигнал на продажу. Позиции противоположного направления закрываются.
- CloudTwist – анализирует пересечения основной линии и сигнальной. Пересечение основной линии сверху вниз через сигнальную открывает покупку и закрывает продажи. Пересечение снизу вверх открывает продажу и закрывает покупки.
Флаги EnableLongEntry, EnableShortEntry, EnableLongExit, EnableShortExit позволяют отключать отдельные действия без остановки расчётов индикатора.
Управление рисками
TakeProfitPoints и StopLossPoints переводятся в абсолютное расстояние по цене с учётом шага цены инструмента и передаются в StartProtection. Значение 0 отключает соответствующий лимит.
Параметры
CandleType(DataType, по умолчанию 4 часа) – таймфрейм свечей и расчёта индикатора.Mode(BlauSmStochasticModes, по умолчанию Twist) – режим генерации сигналов (Breakdown, Twist, CloudTwist).SignalBar(int, по умолчанию 1) – сдвиг по барам при анализе значений индикатора, соответствует параметруSignalBarв оригинале.LookbackLength(int, по умолчанию 5) – количество баров для поиска максимумов и минимумов.FirstSmoothingLength(int, по умолчанию 20) – период первого сглаживания.SecondSmoothingLength(int, по умолчанию 5) – период второго сглаживания.ThirdSmoothingLength(int, по умолчанию 3) – период третьего сглаживания.SignalLength(int, по умолчанию 3) – период сглаживания сигнальной линии.SmoothMethod(BlauSmSmoothMethods, по умолчанию EMA) – тип скользящих средних для всех этапов (SMA, EMA, SMMA, LWMA).PriceType(BlauSmAppliedPrices, по умолчанию Close) – тип цены для расчётов (close, open, high, low, median, typical, weighted, simple, quarter, два варианта trend-follow и Demark).EnableLongEntry(bool, по умолчанию true) – разрешить открытие длинных позиций.EnableShortEntry(bool, по умолчанию true) – разрешить открытие коротких позиций.EnableLongExit(bool, по умолчанию true) – разрешить закрытие длинных позиций.EnableShortExit(bool, по умолчанию true) – разрешить закрытие коротких позиций.TakeProfitPoints(int, по умолчанию 2000) – дистанция тейк-профита в пунктах инструмента.StopLossPoints(int, по умолчанию 1000) – дистанция стоп-лосса в пунктах инструмента.
Примечания
- Доступны только классические типы скользящих средних (SMA, EMA, SMMA, LWMA). Экзотические методы из MQL-библиотеки (JMA, JurX и др.) отсутствуют в StockSharp.
- Параметр
Phaseоставлен для полноты, изменение носит справочный характер. - Стратегия применима к любым инструментам, поддерживаемым StockSharp. Рекомендуется подбирать таймфрейм, длины сглаживания и защитные уровни под волатильность актива.
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()