Blau TS Stochastic 戦略
この戦略は、MetaTrader エキスパートアドバイザー「Exp_BlauTSStochastic」の StockSharp ポートです。システムは元の MQL パッケージに同梱されていた William Blau の三重平滑化ストキャスティクス・オシレーターを使用して取引します。指標は設定可能なルックバック期間にわたって最高値と最安値を計算し、選択した移動平均ファミリーでストキャスティクスの分子と分母を 3 回平滑化し、結果を [-100, 100] の範囲に再スケールし、最終的に平滑化されたシグナルラインを生成します。すべての計算は、高レベルのローソク足サブスクリプション API を通じて配信される完成したローソク足で実行されます。
指標は、サポートされている任意の適用価格(終値、始値、高値、安値、中間値、典型値、加重値、単純値、四分位値、2 つのトレンドフォロー変種、または DeMark)と 4 つの異なる平滑化アルゴリズム(SMA、EMA、SMMA/RMA、WMA)から構築できます。SignalBar 設定は、元のエキスパートアドバイザーが使用したバーシフトを再現することを可能にします:戦略は SignalBar バー古いデータに対してシグナルを評価するため、デフォルト値の 1 では前のステップでちょうど閉じたバーに反応します。
エントリーとエグジットルール
3 つのトレードモードが利用可能です。すべてのモードで、ブール型トグル EnableLongEntry、EnableShortEntry、EnableLongExit、EnableShortExit がそれぞれのアクションが許可されているかどうかを制御します。
Breakdown モード
ロングエントリー: 前のヒストグラム値(シフト SignalBar+1)がゼロより上にあり、より最近の値(シフト SignalBar)がゼロ以下にあります。これは元の「ヒストグラムがゼロを突き破る」条件を反映し、ロングポジションを開くか反転させながら、ショートもカバーします。
ショートエントリー: 前のヒストグラム値がゼロより下にあり、より最近の値がゼロ以上にあり、反対方向のゼライン突破を示しています。戦略はショートポジションを開くか反転させ、オプションでロングエクスポージャーを閉じます。
同じ条件が反対側のエグジットも引き起こします:ヒストグラムが前のバーをゼロより上で過ごすと戦略はショートを閉じ、前のバーをゼロより下で過ごすとロングを閉じます。
Twist モード
ロングエントリー: ヒストグラムがローカルボトムを形成します。具体的には、シフト SignalBar+1 の値がシフト SignalBar+2 の値より下にありますが、シフト SignalBar の値が上向きに転換し、中間バーを超えます。それはエキスパートアドバイザーの「方向変化」モードを再現します。
ショートエントリー: ヒストグラムがローカルトップを形成します。シフト SignalBar+1 の値はシフト SignalBar+2 の値より大きく、最新の値は中間バーを下回ります。反対方向のポジションは、それらに対してツイストが発生すると閉じられます。
CloudTwist モード
このモードは、ヒストグラムとそのシグナルラインによって定義される指標クラウドの色の変化に従います。
ロングエントリー: ヒストグラムは前のバーでシグナルラインより上にありましたが、最新の値がシグナルラインを下回るかまたは触れるようにクロスしました。戦略はクラウドの色の変化を強気シグナルとして扱い、オプションでショートをカバーします。
ショートエントリー: ヒストグラムは前のバーでシグナルラインより下にありましたが、最新の値がシグナルラインを上回るかまたは触れるようにクロスしました。これはショートポジションに反転し、オプションでロングを終了します。
リスク管理
StopLossPointsとTakeProfitPointsは銘柄の価格ステップで測定されます。いずれかの値がゼロより大きい場合、戦略は成行注文で StockSharp の組み込み保護ブロックを有効にするため、ストップはアクティブポジションを自動的に追跡します。- 注文サイズは戦略の
Volumeプロパティから取られます。反転シグナルが現れると、戦略はVolume + |Position|契約を送信し、新しいものを開く前に既存のポジションが閉じられることを確保します。
パラメーター
CandleType– オシレーターに使用されるタイムフレーム(データタイプ)(デフォルト:4 時間ローソク足)。Mode– シグナル検出アルゴリズム:Breakdown、Twist、またはCloudTwist。AppliedPrice– ストキャスティクス計算の価格ソース(終値、始値、高値、安値、中間値、典型値、加重値、単純値、四分位値、トレンドフォロー 0/1、または DeMark)。Smoothing– すべての平滑化ステージに使用される移動平均ファミリー(Simple、Exponential、Smoothed、Weighted)。BaseLength– 高値/安値レンジを計算するために使用されるバーの数。SmoothLength1、SmoothLength2、SmoothLength3– 分子と分母の平滑化長(順次適用)。SignalLength– ヒストグラムシグナルラインの平滑化長。SignalBar– 決定に使用される履歴値を定義するバーシフト。StopLossPoints、TakeProfitPoints– 価格ステップでの保護ストップとターゲットサイズ(0 は対応する注文を無効にします)。EnableLongEntry、EnableShortEntry、EnableLongExit、EnableShortExit– 4 つの基本アクションの許可スイッチ。
希望の 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()