Blau TS Stochastic 策略
本策略是 MetaTrader 专家顾问“Exp_BlauTSStochastic”的 StockSharp 版本。系统使用 William Blau 提出的三重平滑随机指标,该指标在原始 MQL 套件中提供。它在设定的回溯区间内计算最高价和最低价,分别对随机指标的分子和分母使用所选移动平均类型进行三次平滑,将结果缩放到 [-100, 100] 区间,并额外生成平滑的信号线。所有计算仅基于通过高级蜡烛订阅获得的已完成 K 线。
指标可以使用任意支持的价格源(收盘价、开盘价、最高价、最低价、中价、典型价、加权价、简单价、四分位价、两种趋势跟随价或 DeMark 价)以及四种平滑算法(SMA、EMA、SMMA/RMA、WMA)。SignalBar 参数复现了原始顾问的位移设置:策略根据 SignalBar 个 bar 之前的数据做决策,默认值为 1 时会对上一个已经收盘的 bar 做出反应。
入场与出场规则
策略包含三种交易模式。无论在哪种模式下,EnableLongEntry、EnableShortEntry、EnableLongExit、EnableShortExit 这四个布尔开关决定是否允许对应的操作。
Breakdown 模式
多头入场:SignalBar+1 位置的柱状图值大于零,而 SignalBar 位置的值小于或等于零。该条件对应原策略中“柱状图突破零轴”的触发方式,会开多(或反手做多),并同时平掉空头。
空头入场:SignalBar+1 位置的柱状图值小于零,而 SignalBar 位置的值大于或等于零,表示柱状图向上突破零轴。策略会开空(或反手做空),并在需要时平掉多头。
同样的条件也用于离场:如果柱状图在上一根 bar 高于零轴,则平掉空头;如果上一根 bar 低于零轴,则平掉多头。
Twist 模式
多头入场:柱状图出现局部低点。具体而言,SignalBar+1 处的值低于 SignalBar+2,而 SignalBar 处的值向上反转并超过中间的值。这与原顾问中的“方向改变”模式一致。
空头入场:柱状图出现局部高点。SignalBar+1 处的值高于 SignalBar+2,而最新的值跌破中间的值。当出现与当前持仓方向相反的转折时,相应的持仓会被平仓。
CloudTwist 模式
该模式跟踪由柱状图和信号线组成的“云图”颜色变化。
多头入场:上一根 bar 的柱状图高于信号线,但当前值向下穿越(或触碰)信号线。颜色变化被视为多头信号,同时可以平掉空头。
空头入场:上一根 bar 的柱状图低于信号线,但当前值向上穿越(或触碰)信号线。策略因此开空,并在需要时平掉多头。
风险管理
StopLossPoints与TakeProfitPoints以合约最小价格波动为单位。如果其中任意一个大于零,策略会启用 StockSharp 内置的保护模块(使用市价单),从而自动跟踪止损与止盈。- 下单数量取自策略的
Volume属性。当出现反手信号时,策略提交Volume + |Position|的交易量,保证先平掉原仓位再建立新的方向。
参数
CandleType—— 用于计算振荡器的时间框架(默认 4 小时蜡烛)。Mode—— 信号检测算法:Breakdown、Twist或CloudTwist。AppliedPrice—— 随机指标的价格来源(收盘、开盘、最高、最低、中价、典型价、加权价、简单价、四分位价、TrendFollow0/1 或 DeMark)。Smoothing—— 应用于所有平滑阶段的移动平均类型(Simple、Exponential、Smoothed、Weighted)。BaseLength—— 计算最高价/最低价区间时使用的 bar 数量。SmoothLength1、SmoothLength2、SmoothLength3—— 对分子和分母依次进行三次平滑时的长度。SignalLength—— 柱状图信号线的平滑长度。SignalBar—— 决策时使用的历史 bar 位移。StopLossPoints、TakeProfitPoints—— 以价格步长表示的止损和止盈距离(为 0 时禁用)。EnableLongEntry、EnableShortEntry、EnableLongExit、EnableShortExit—— 控制是否允许四种基本操作。
设定合适的 Volume,将策略附加到目标品种并启动它。所有计算基于收盘后的 K 线,因此在指标形成之前策略会保持等待,不会立即发单。
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()