Стратегия Blau TS Stochastic
Эта стратегия представляет собой порт на StockSharp экспертного советника MetaTrader «Exp_BlauTSStochastic». Система торгует с использованием тройного сглаженного стохастического осциллятора Уильяма Блау, который поставлялся вместе с исходным пакетом MQL. Индикатор вычисляет максимум и минимум цены за настраиваемое количество баров, трижды сглаживает числитель и знаменатель стохастика выбранным типом скользящей средней, преобразует результат в диапазон [-100; 100] и дополнительно строит сглаженную сигнальную линию. Все расчёты выполняются только на закрытых свечах, которые поступают через высокоуровневую подписку на свечи.
Индикатор может строиться по любому из поддерживаемых источников цен (close, open, high, low, median, typical, weighted, simple, quarter, две трендовые модификации или DeMark) и использовать одно из четырёх сглаживаний (SMA, EMA, SMMA/RMA, WMA). Параметр SignalBar воспроизводит сдвиг, применявшийся в оригинальном советнике: стратегия анализирует данные давностью SignalBar баров, поэтому при значении 1 реагирует на бар, который закрылся на предыдущем шаге.
Правила входа и выхода
Доступно три торговых режима. В каждом режиме флаги EnableLongEntry, EnableShortEntry, EnableLongExit и EnableShortExit определяют, разрешены ли соответствующие действия.
Режим Breakdown
Вход в лонг: значение гистограммы на баре SignalBar+1 выше нуля, а более свежее значение на баре SignalBar меньше либо равно нулю. Это соответствует условию «гистограмма пробивает ноль» из оригинальной версии и открывает (или переворачивает в) длинную позицию, одновременно закрывая шорты.
Вход в шорт: значение гистограммы на баре SignalBar+1 ниже нуля, а более свежее значение на баре SignalBar выше либо равно нулю. Стратегия открывает (или переворачивает в) короткую позицию и при необходимости закрывает лонги.
Те же условия отвечают и за выходы: если гистограмма на предыдущем баре была выше нуля, шорты закрываются, а если ниже нуля — закрываются лонги.
Режим Twist
Вход в лонг: гистограмма формирует локальное дно. Значение на баре SignalBar+1 меньше значения на баре SignalBar+2, но последнее значение (SignalBar) разворачивается вверх и превышает промежуточный бар. Это воспроизводит «смену направления», реализованную в советнике.
Вход в шорт: гистограмма формирует локальную вершину. Значение на баре SignalBar+1 больше значения на баре SignalBar+2, а последнее значение падает ниже промежуточного. При появлении разворота, направленного против позиции, стратегия закрывает позиции противоположного типа.
Режим CloudTwist
Режим отслеживает изменение цвета облака индикатора, которое задаётся гистограммой и сигнальной линией.
Вход в лонг: гистограмма была выше сигнальной линии на предыдущем баре, но последнее значение пересекло сигнальную линию сверху вниз (или коснулось её). Такое изменение цвета трактуется как бычий сигнал, и шорты при необходимости закрываются.
Вход в шорт: гистограмма была ниже сигнальной линии, но последнее значение пересекло её снизу вверх (или коснулось). Стратегия открывает шорт и опционально закрывает длинные позиции.
Управление рисками
StopLossPoints и TakeProfitPoints задаются в шагах цены инструмента. Если хотя бы одно значение больше нуля, стратегия включает встроенный блок защиты StockSharp с рыночными ордерами — стопы автоматически сопровождают позицию.
- Объём заявок берётся из свойства
Volume стратегии. При разворотном сигнале выставляется объём Volume + |Position|, чтобы сначала закрыть текущую позицию и только затем открыть новую.
Параметры
CandleType — таймфрейм (тип данных) для расчёта осциллятора (по умолчанию четырёхчасовые свечи).
Mode — алгоритм формирования сигналов: Breakdown, Twist или CloudTwist.
AppliedPrice — источник цен для расчёта стохастика (close, open, high, low, median, typical, weighted, simple, quarter, trend-following 0/1 или DeMark).
Smoothing — тип сглаживания для всех этапов (Simple, Exponential, Smoothed, Weighted).
BaseLength — число баров для расчёта диапазона максимум/минимум.
SmoothLength1, SmoothLength2, SmoothLength3 — длины последовательных сглаживаний числителя и знаменателя.
SignalLength — длина сглаживания сигнальной линии гистограммы.
SignalBar — сдвиг по барам, определяющий, какие значения используются при принятии решений.
StopLossPoints, TakeProfitPoints — размеры защитного стопа и цели в шагах цены (0 отключает соответствующий ордер).
EnableLongEntry, EnableShortEntry, EnableLongExit, EnableShortExit — переключатели, разрешающие четыре базовых действия.
Задайте желаемый Volume, прикрепите стратегию к инструменту и запустите её. Все расчёты выполняются по закрытым свечам, поэтому перед началом торговли стратегия ждёт формирования индикаторов.
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>
/// Strategy based on William Blau's triple smoothed stochastic oscillator.
/// </summary>
public class BlauTsStochasticStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<BlauSignalModes> _mode;
private readonly StrategyParam<AppliedPriceTypes> _appliedPrice;
private readonly StrategyParam<BlauSmoothingTypes> _smoothing;
private readonly StrategyParam<int> _baseLength;
private readonly StrategyParam<int> _smoothLength1;
private readonly StrategyParam<int> _smoothLength2;
private readonly StrategyParam<int> _smoothLength3;
private readonly StrategyParam<int> _signalLength;
private readonly StrategyParam<int> _signalBar;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private readonly StrategyParam<bool> _enableLongEntry;
private readonly StrategyParam<bool> _enableShortEntry;
private readonly StrategyParam<bool> _enableLongExit;
private readonly StrategyParam<bool> _enableShortExit;
private Highest _highest = null!;
private Lowest _lowest = null!;
private IIndicator _stochSmooth1 = null!;
private IIndicator _stochSmooth2 = null!;
private IIndicator _stochSmooth3 = null!;
private IIndicator _rangeSmooth1 = null!;
private IIndicator _rangeSmooth2 = null!;
private IIndicator _rangeSmooth3 = null!;
private IIndicator _signalSmooth = null!;
private readonly List<decimal> _histHistory = new();
private readonly List<decimal> _signalHistory = new();
/// <summary>
/// Candle type used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Entry and exit signal mode.
/// </summary>
public BlauSignalModes Mode
{
get => _mode.Value;
set => _mode.Value = value;
}
/// <summary>
/// Applied price used for the stochastic calculation.
/// </summary>
public AppliedPriceTypes AppliedPrice
{
get => _appliedPrice.Value;
set => _appliedPrice.Value = value;
}
/// <summary>
/// Smoothing algorithm used for stochastic and signal averaging.
/// </summary>
public BlauSmoothingTypes Smoothing
{
get => _smoothing.Value;
set => _smoothing.Value = value;
}
/// <summary>
/// Lookback for the highest and lowest price range.
/// </summary>
public int BaseLength
{
get => _baseLength.Value;
set => _baseLength.Value = value;
}
/// <summary>
/// First smoothing length for the stochastic numerator and denominator.
/// </summary>
public int SmoothLength1
{
get => _smoothLength1.Value;
set => _smoothLength1.Value = value;
}
/// <summary>
/// Second smoothing length for the stochastic numerator and denominator.
/// </summary>
public int SmoothLength2
{
get => _smoothLength2.Value;
set => _smoothLength2.Value = value;
}
/// <summary>
/// Third smoothing length for the stochastic numerator and denominator.
/// </summary>
public int SmoothLength3
{
get => _smoothLength3.Value;
set => _smoothLength3.Value = value;
}
/// <summary>
/// Smoothing length for the signal line.
/// </summary>
public int SignalLength
{
get => _signalLength.Value;
set => _signalLength.Value = value;
}
/// <summary>
/// Bar shift used to evaluate trading signals.
/// </summary>
public int SignalBar
{
get => _signalBar.Value;
set => _signalBar.Value = value;
}
/// <summary>
/// Stop-loss size expressed in price steps.
/// </summary>
public int StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take-profit size expressed in price steps.
/// </summary>
public int TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Enable opening long positions.
/// </summary>
public bool EnableLongEntry
{
get => _enableLongEntry.Value;
set => _enableLongEntry.Value = value;
}
/// <summary>
/// Enable opening short positions.
/// </summary>
public bool EnableShortEntry
{
get => _enableShortEntry.Value;
set => _enableShortEntry.Value = value;
}
/// <summary>
/// Enable closing long positions on indicator signals.
/// </summary>
public bool EnableLongExit
{
get => _enableLongExit.Value;
set => _enableLongExit.Value = value;
}
/// <summary>
/// Enable closing short positions on indicator signals.
/// </summary>
public bool EnableShortExit
{
get => _enableShortExit.Value;
set => _enableShortExit.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public BlauTsStochasticStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
.SetDisplay("Candle Type", "Time frame for signal calculations", "General");
_mode = Param(nameof(Mode), BlauSignalModes.Twist)
.SetDisplay("Signal Mode", "Signal detection algorithm", "Signals");
_appliedPrice = Param(nameof(AppliedPrice), AppliedPriceTypes.Close)
.SetDisplay("Applied Price", "Price source for the oscillator", "Indicator");
_smoothing = Param(nameof(Smoothing), BlauSmoothingTypes.Exponential)
.SetDisplay("Smoothing Type", "Moving average used for smoothing", "Indicator");
_baseLength = Param(nameof(BaseLength), 5)
.SetGreaterThanZero()
.SetDisplay("Range Length", "Number of bars for high/low range", "Indicator")
.SetOptimize(3, 20, 1);
_smoothLength1 = Param(nameof(SmoothLength1), 10)
.SetGreaterThanZero()
.SetDisplay("Smoothing #1", "First smoothing length", "Indicator")
.SetOptimize(5, 40, 5);
_smoothLength2 = Param(nameof(SmoothLength2), 5)
.SetGreaterThanZero()
.SetDisplay("Smoothing #2", "Second smoothing length", "Indicator")
.SetOptimize(2, 20, 1);
_smoothLength3 = Param(nameof(SmoothLength3), 3)
.SetGreaterThanZero()
.SetDisplay("Smoothing #3", "Third smoothing length", "Indicator")
.SetOptimize(2, 15, 1);
_signalLength = Param(nameof(SignalLength), 3)
.SetGreaterThanZero()
.SetDisplay("Signal Length", "Length of the signal line", "Indicator")
.SetOptimize(2, 15, 1);
_signalBar = Param(nameof(SignalBar), 1)
.SetNotNegative()
.SetDisplay("Signal Bar", "Shift used for signal evaluation", "Signals")
;
_stopLossPoints = Param(nameof(StopLossPoints), 1000)
.SetNotNegative()
.SetDisplay("Stop Loss (points)", "Stop size in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 2000)
.SetNotNegative()
.SetDisplay("Take Profit (points)", "Target size in price steps", "Risk");
_enableLongEntry = Param(nameof(EnableLongEntry), true)
.SetDisplay("Enable Long Entries", "Allow opening long trades", "Trading");
_enableShortEntry = Param(nameof(EnableShortEntry), true)
.SetDisplay("Enable Short Entries", "Allow opening short trades", "Trading");
_enableLongExit = Param(nameof(EnableLongExit), true)
.SetDisplay("Close Long Positions", "Allow indicator-based long exits", "Trading");
_enableShortExit = Param(nameof(EnableShortExit), true)
.SetDisplay("Close Short Positions", "Allow indicator-based short exits", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_histHistory.Clear();
_signalHistory.Clear();
_entryPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_highest = new Highest { Length = BaseLength };
_lowest = new Lowest { Length = BaseLength };
_stochSmooth1 = CreateMovingAverage(Smoothing, SmoothLength1);
_stochSmooth2 = CreateMovingAverage(Smoothing, SmoothLength2);
_stochSmooth3 = CreateMovingAverage(Smoothing, SmoothLength3);
_rangeSmooth1 = CreateMovingAverage(Smoothing, SmoothLength1);
_rangeSmooth2 = CreateMovingAverage(Smoothing, SmoothLength2);
_rangeSmooth3 = CreateMovingAverage(Smoothing, SmoothLength3);
_signalSmooth = CreateMovingAverage(Smoothing, SignalLength);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private decimal _entryPrice;
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var highResult = _highest.Process(candle);
var lowResult = _lowest.Process(candle);
if (highResult.IsEmpty || lowResult.IsEmpty || !_highest.IsFormed || !_lowest.IsFormed)
return;
// Manage SL/TP
if (Position != 0)
{
var step = Security?.PriceStep ?? 1m;
if (Position > 0)
{
if (StopLossPoints > 0 && candle.LowPrice <= _entryPrice - StopLossPoints * step)
{ SellMarket(Position); return; }
if (TakeProfitPoints > 0 && candle.HighPrice >= _entryPrice + TakeProfitPoints * step)
{ SellMarket(Position); return; }
}
else
{
var vol = Math.Abs(Position);
if (StopLossPoints > 0 && candle.HighPrice >= _entryPrice + StopLossPoints * step)
{ BuyMarket(vol); return; }
if (TakeProfitPoints > 0 && candle.LowPrice <= _entryPrice - TakeProfitPoints * step)
{ BuyMarket(vol); return; }
}
}
var t = candle.OpenTime;
var high = highResult.ToDecimal();
var low = lowResult.ToDecimal();
var price = GetAppliedPrice(candle, AppliedPrice);
var stochRaw = price - low;
var rangeRaw = high - low;
var stoch1 = _stochSmooth1.Process(new DecimalIndicatorValue(_stochSmooth1, stochRaw, t) { IsFinal = true });
if (stoch1.IsEmpty)
return;
var stoch2 = _stochSmooth2.Process(new DecimalIndicatorValue(_stochSmooth2, stoch1.ToDecimal(), t) { IsFinal = true });
if (stoch2.IsEmpty)
return;
var stoch3 = _stochSmooth3.Process(new DecimalIndicatorValue(_stochSmooth3, stoch2.ToDecimal(), t) { IsFinal = true });
if (stoch3.IsEmpty)
return;
var range1 = _rangeSmooth1.Process(new DecimalIndicatorValue(_rangeSmooth1, rangeRaw, t) { IsFinal = true });
if (range1.IsEmpty)
return;
var range2 = _rangeSmooth2.Process(new DecimalIndicatorValue(_rangeSmooth2, range1.ToDecimal(), t) { IsFinal = true });
if (range2.IsEmpty)
return;
var range3 = _rangeSmooth3.Process(new DecimalIndicatorValue(_rangeSmooth3, range2.ToDecimal(), t) { IsFinal = true });
if (range3.IsEmpty)
return;
var denom = range3.ToDecimal();
if (denom == 0m)
return;
var hist = 200m * stoch3.ToDecimal() / denom - 100m;
var signalValue = _signalSmooth.Process(new DecimalIndicatorValue(_signalSmooth, hist, t) { IsFinal = true });
if (signalValue.IsEmpty)
return;
var signal = signalValue.ToDecimal();
UpdateHistory(_histHistory, hist);
UpdateHistory(_signalHistory, signal);
var required = Mode == BlauSignalModes.Twist ? SignalBar + 3 : SignalBar + 2;
if (_histHistory.Count < required)
return;
if (Mode == BlauSignalModes.CloudTwist && _signalHistory.Count < SignalBar + 2)
return;
var histCurrent = _histHistory[SignalBar];
var histPrev = _histHistory[SignalBar + 1];
var histPrev2 = Mode == BlauSignalModes.Twist ? _histHistory[SignalBar + 2] : 0m;
var openLong = false;
var openShort = false;
var closeLong = false;
var closeShort = false;
switch (Mode)
{
case BlauSignalModes.Breakdown:
{
if (histPrev > 0m)
{
if (EnableLongEntry && histCurrent <= 0m)
openLong = true;
if (EnableShortExit)
closeShort = true;
}
if (histPrev < 0m)
{
if (EnableShortEntry && histCurrent >= 0m)
openShort = true;
if (EnableLongExit)
closeLong = true;
}
break;
}
case BlauSignalModes.Twist:
{
if (_histHistory.Count < SignalBar + 3)
return;
if (histPrev < histPrev2)
{
if (EnableLongEntry && histCurrent > histPrev)
openLong = true;
if (EnableShortExit)
closeShort = true;
}
if (histPrev > histPrev2)
{
if (EnableShortEntry && histCurrent < histPrev)
openShort = true;
if (EnableLongExit)
closeLong = true;
}
break;
}
case BlauSignalModes.CloudTwist:
{
if (_signalHistory.Count < SignalBar + 2)
return;
var upPrev = histPrev;
var upCurrent = histCurrent;
var sigPrev = _signalHistory[SignalBar + 1];
var sigCurrent = _signalHistory[SignalBar];
if (upPrev > sigPrev)
{
if (EnableLongEntry && upCurrent <= sigCurrent)
openLong = true;
if (EnableShortExit)
closeShort = true;
}
if (upPrev < sigPrev)
{
if (EnableShortEntry && upCurrent >= sigCurrent)
openShort = true;
if (EnableLongExit)
closeLong = true;
}
break;
}
}
if (closeLong && Position > 0)
SellMarket(Position);
if (closeShort && Position < 0)
BuyMarket(-Position);
var volume = Volume + Math.Abs(Position);
if (openLong && Position <= 0)
{
BuyMarket(volume);
_entryPrice = candle.ClosePrice;
}
else if (openShort && Position >= 0)
{
SellMarket(volume);
_entryPrice = candle.ClosePrice;
}
}
private void UpdateHistory(List<decimal> buffer, decimal value)
{
buffer.Insert(0, value);
var capacity = Math.Max(SignalBar + 3, 4);
while (buffer.Count > capacity)
{
buffer.RemoveAt(buffer.Count - 1);
}
}
private static decimal GetAppliedPrice(ICandleMessage candle, AppliedPriceTypes type)
{
return type switch
{
AppliedPriceTypes.Close => candle.ClosePrice,
AppliedPriceTypes.Open => candle.OpenPrice,
AppliedPriceTypes.High => candle.HighPrice,
AppliedPriceTypes.Low => candle.LowPrice,
AppliedPriceTypes.Median => (candle.HighPrice + candle.LowPrice) / 2m,
AppliedPriceTypes.Typical => (candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 3m,
AppliedPriceTypes.Weighted => (2m * candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 4m,
AppliedPriceTypes.Simple => (candle.OpenPrice + candle.ClosePrice) / 2m,
AppliedPriceTypes.Quarter => (candle.OpenPrice + candle.ClosePrice + candle.HighPrice + candle.LowPrice) / 4m,
AppliedPriceTypes.TrendFollow0 => candle.ClosePrice > candle.OpenPrice ? candle.HighPrice : candle.ClosePrice < candle.OpenPrice ? candle.LowPrice : candle.ClosePrice,
AppliedPriceTypes.TrendFollow1 => candle.ClosePrice > candle.OpenPrice ? (candle.HighPrice + candle.ClosePrice) / 2m : candle.ClosePrice < candle.OpenPrice ? (candle.LowPrice + candle.ClosePrice) / 2m : candle.ClosePrice,
AppliedPriceTypes.Demark =>
GetDemarkPrice(candle),
_ => candle.ClosePrice,
};
}
private static decimal GetDemarkPrice(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 static IIndicator CreateMovingAverage(BlauSmoothingTypes type, int length)
{
return type switch
{
BlauSmoothingTypes.Simple => new SimpleMovingAverage { Length = length },
BlauSmoothingTypes.Exponential => new ExponentialMovingAverage { Length = length },
BlauSmoothingTypes.Smoothed => new SmoothedMovingAverage { Length = length },
BlauSmoothingTypes.Weighted => new WeightedMovingAverage { Length = length },
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null),
};
}
/// <summary>
/// Signal modes replicated from the original MQL expert advisor.
/// </summary>
public enum BlauSignalModes
{
/// <summary>Histogram crosses the zero line.</summary>
Breakdown,
/// <summary>Histogram direction change.</summary>
Twist,
/// <summary>Signal cloud color change (histogram vs. signal line crossover).</summary>
CloudTwist,
}
/// <summary>
/// Price sources supported by the strategy.
/// </summary>
public enum AppliedPriceTypes
{
/// <summary>Close price.</summary>
Close = 1,
/// <summary>Open price.</summary>
Open,
/// <summary>High price.</summary>
High,
/// <summary>Low price.</summary>
Low,
/// <summary>Median price (high+low)/2.</summary>
Median,
/// <summary>Typical price (high+low+close)/3.</summary>
Typical,
/// <summary>Weighted close price (2*close+high+low)/4.</summary>
Weighted,
/// <summary>Simple price (open+close)/2.</summary>
Simple,
/// <summary>Quarter price (open+close+high+low)/4.</summary>
Quarter,
/// <summary>Trend-following price variant #1.</summary>
TrendFollow0,
/// <summary>Trend-following price variant #2.</summary>
TrendFollow1,
/// <summary>Tom DeMark price calculation.</summary>
Demark,
}
/// <summary>
/// Moving average families supported by the smoothed stochastic.
/// </summary>
public enum BlauSmoothingTypes
{
/// <summary>Simple moving average.</summary>
Simple,
/// <summary>Exponential moving average.</summary>
Exponential,
/// <summary>Smoothed moving average (RMA/SMMA).</summary>
Smoothed,
/// <summary>Weighted moving average.</summary>
Weighted,
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from StockSharp.Algo.Indicators import (SimpleMovingAverage, ExponentialMovingAverage,
SmoothedMovingAverage, WeightedMovingAverage, Highest, Lowest, CandleIndicatorValue)
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Messages import DataType, CandleStates
from System import TimeSpan, Math, Decimal
from indicator_extensions import *
class blau_ts_stochastic_strategy(Strategy):
def __init__(self):
super(blau_ts_stochastic_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8)))
self._base_length = self.Param("BaseLength", 5)
self._smooth1 = self.Param("SmoothLength1", 10)
self._smooth2 = self.Param("SmoothLength2", 5)
self._smooth3 = self.Param("SmoothLength3", 3)
self._signal_length = self.Param("SignalLength", 3)
self._signal_bar = self.Param("SignalBar", 1)
self._stop_loss_points = self.Param("StopLossPoints", 1000)
self._take_profit_points = self.Param("TakeProfitPoints", 2000)
self._highest = None
self._lowest = None
self._stoch_s1 = None
self._stoch_s2 = None
self._stoch_s3 = None
self._range_s1 = None
self._range_s2 = None
self._range_s3 = None
self._signal_smooth = None
self._hist_history = []
self._signal_history = []
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
def _create_ma(self, length):
ma = ExponentialMovingAverage()
ma.Length = length
return ma
def OnStarted2(self, time):
super(blau_ts_stochastic_strategy, self).OnStarted2(time)
self._highest = Highest()
self._highest.Length = self._base_length.Value
self._lowest = Lowest()
self._lowest.Length = self._base_length.Value
self._stoch_s1 = self._create_ma(self._smooth1.Value)
self._stoch_s2 = self._create_ma(self._smooth2.Value)
self._stoch_s3 = self._create_ma(self._smooth3.Value)
self._range_s1 = self._create_ma(self._smooth1.Value)
self._range_s2 = self._create_ma(self._smooth2.Value)
self._range_s3 = self._create_ma(self._smooth3.Value)
self._signal_smooth = self._create_ma(self._signal_length.Value)
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
civ_h = CandleIndicatorValue(self._highest, candle)
civ_h.IsFinal = True
high_result = self._highest.Process(civ_h)
civ_l = CandleIndicatorValue(self._lowest, candle)
civ_l.IsFinal = True
low_result = self._lowest.Process(civ_l)
if high_result.IsEmpty or low_result.IsEmpty or not self._highest.IsFormed or not self._lowest.IsFormed:
return
# Manage SL/TP
if self.Position != 0:
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0:
if self._stop_loss_points.Value > 0 and float(candle.LowPrice) <= self._entry_price - self._stop_loss_points.Value * step:
self.SellMarket(self.Position)
return
if self._take_profit_points.Value > 0 and float(candle.HighPrice) >= self._entry_price + self._take_profit_points.Value * step:
self.SellMarket(self.Position)
return
else:
vol = abs(self.Position)
if self._stop_loss_points.Value > 0 and float(candle.HighPrice) >= self._entry_price + self._stop_loss_points.Value * step:
self.BuyMarket(vol)
return
if self._take_profit_points.Value > 0 and float(candle.LowPrice) <= self._entry_price - self._take_profit_points.Value * step:
self.BuyMarket(vol)
return
t = candle.OpenTime
high = float(high_result.Value)
low = float(low_result.Value)
price = float(candle.ClosePrice)
stoch_raw = price - low
range_raw = high - low
s1 = process_float(self._stoch_s1, Decimal(float(stoch_raw)), t, True)
if s1.IsEmpty:
return
s2 = process_float(self._stoch_s2, Decimal(float(s1.Value)), t, True)
if s2.IsEmpty:
return
s3 = process_float(self._stoch_s3, Decimal(float(s2.Value)), t, True)
if s3.IsEmpty:
return
r1 = process_float(self._range_s1, Decimal(float(range_raw)), t, True)
if r1.IsEmpty:
return
r2 = process_float(self._range_s2, Decimal(float(r1.Value)), t, True)
if r2.IsEmpty:
return
r3 = process_float(self._range_s3, Decimal(float(r2.Value)), t, True)
if r3.IsEmpty:
return
denom = float(r3.Value)
if denom == 0:
return
hist = 200.0 * float(s3.Value) / denom - 100.0
sig_result = process_float(self._signal_smooth, Decimal(float(hist)), t, True)
if sig_result.IsEmpty:
return
signal = float(sig_result.Value)
self._hist_history.insert(0, hist)
self._signal_history.insert(0, signal)
sb = self._signal_bar.Value
cap = max(sb + 3, 4)
while len(self._hist_history) > cap:
self._hist_history.pop()
while len(self._signal_history) > cap:
self._signal_history.pop()
required = sb + 3
if len(self._hist_history) < required:
return
hist_current = self._hist_history[sb]
hist_prev = self._hist_history[sb + 1]
hist_prev2 = self._hist_history[sb + 2]
open_long = False
open_short = False
close_long = False
close_short = False
# Twist mode
if hist_prev < hist_prev2:
if hist_current > hist_prev:
open_long = True
close_short = True
if hist_prev > hist_prev2:
if hist_current < hist_prev:
open_short = True
close_long = True
if close_long and self.Position > 0:
self.SellMarket(self.Position)
if close_short and self.Position < 0:
self.BuyMarket(abs(self.Position))
volume = float(self.Volume) + abs(self.Position)
if open_long and self.Position <= 0:
self.BuyMarket(volume)
self._entry_price = float(candle.ClosePrice)
elif open_short and self.Position >= 0:
self.SellMarket(volume)
self._entry_price = float(candle.ClosePrice)
def OnReseted(self):
super(blau_ts_stochastic_strategy, self).OnReseted()
self._hist_history = []
self._signal_history = []
self._entry_price = 0.0
def CreateClone(self):
return blau_ts_stochastic_strategy()