Blau TS Stochastic Strategy
This strategy is a StockSharp port of the MetaTrader expert advisor "Exp_BlauTSStochastic". The system trades with William Blau's triple-smoothed stochastic oscillator that was bundled with the original MQL package. The indicator computes the highest and lowest prices over a configurable lookback, smooths the stochastic numerator and denominator three times with the selected moving average family, rescales the result to the range [-100, 100], and finally produces a smoothed signal line. All calculations are performed on finished candles that are delivered through the high-level candle subscription API.
The indicator can be built from any of the supported applied prices (close, open, high, low, median, typical, weighted, simple, quarter, two trend-following variants, or DeMark) and four different smoothing algorithms (SMA, EMA, SMMA/RMA, WMA). The SignalBar setting allows reproducing the bar shift used by the original expert advisor: the strategy evaluates signals on data that is SignalBar bars old, so with the default value of 1 it reacts to the bar that has just closed on the previous step.
Entry and exit rules
Three trading modes are available. In every mode the boolean toggles EnableLongEntry, EnableShortEntry, EnableLongExit, and EnableShortExit control whether the respective actions are allowed.
Breakdown mode
Long entry: the previous histogram value (shift SignalBar+1) is above zero and the more recent value (shift SignalBar) is at or below zero. This mirrors the original "histogram breaks through zero" condition and opens or flips a long position while also covering any shorts.
Short entry: the previous histogram value is below zero and the more recent value is at or above zero, signalling a zero-line break in the opposite direction. The strategy opens or flips to a short position and optionally closes long exposure.
The same conditions also trigger exits on the opposite side: when the histogram spends the previous bar above zero the strategy closes shorts, and when it spends the previous bar below zero it closes longs.
Twist mode
Long entry: the histogram forms a local bottom. Concretely, the value at shift SignalBar+1 is below the value at shift SignalBar+2, but the value at shift SignalBar turns upward and exceeds the intermediate bar. That reproduces the "direction change" mode from the expert advisor.
Short entry: the histogram forms a local top. The value at shift SignalBar+1 is greater than the value at shift SignalBar+2, and the most recent value drops below the intermediate bar. Positions in the opposite direction are closed when a twist occurs against them.
CloudTwist mode
This mode follows the colour changes of the indicator cloud that is defined by the histogram and its signal line.
Long entry: the histogram was above the signal line on the previous bar but the most recent value crossed below or touched the signal line. The strategy treats the change of cloud colour as a bullish signal and optionally covers shorts.
Short entry: the histogram was below the signal line on the previous bar but the most recent value crossed above or touched the signal line. This flips to a short position and optionally exits longs.
Risk management
StopLossPoints and TakeProfitPoints are measured in instrument price steps. If either value is greater than zero the strategy enables StockSharp's built-in protection block with market orders, so the stops trail the active position automatically.
- The order size is taken from the strategy
Volume property. When a reversal signal appears the strategy submits Volume + |Position| contracts, ensuring that the existing position is closed before opening a new one.
Parameters
CandleType – time-frame (data type) used for the oscillator (default: 4-hour candles).
Mode – signal detection algorithm: Breakdown, Twist, or CloudTwist.
AppliedPrice – price source for the stochastic calculation (close, open, high, low, median, typical, weighted, simple, quarter, trend-following 0/1, or DeMark).
Smoothing – moving average family used for all smoothing stages (Simple, Exponential, Smoothed, Weighted).
BaseLength – number of bars used to compute the high/low range.
SmoothLength1, SmoothLength2, SmoothLength3 – smoothing lengths for the numerator and denominator (applied sequentially).
SignalLength – smoothing length for the histogram signal line.
SignalBar – bar shift that defines which historical values are used for decisions.
StopLossPoints, TakeProfitPoints – protective stop and target size in price steps (0 disables the corresponding order).
EnableLongEntry, EnableShortEntry, EnableLongExit, EnableShortExit – permission switches for the four basic actions.
Set the desired Volume, attach the strategy to an instrument, and start it. All calculations rely on finished candles, so the system waits until indicators are formed before allowing trades.
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()