Estrategia Blau TS Stochastic
Esta estrategia es un port de StockSharp del expert advisor de MetaTrader "Exp_BlauTSStochastic". El sistema opera con el oscilador estocástico de triple suavizado de William Blau que venía incluido con el paquete MQL original. El indicador calcula los precios máximos y mínimos durante una ventana de retroceso configurable, suaviza el numerador y denominador estocástico tres veces con la familia de media móvil seleccionada, reescala el resultado al rango [-100, 100], y finalmente produce una línea de señal suavizada. Todos los cálculos se realizan en velas terminadas que se entregan a través de la API de suscripción de velas de alto nivel.
El indicador puede construirse a partir de cualquiera de los precios aplicados soportados (cierre, apertura, máximo, mínimo, mediana, típico, ponderado, simple, cuartil, dos variantes de seguimiento de tendencia, o DeMark) y cuatro algoritmos de suavizado diferentes (SMA, EMA, SMMA/RMA, WMA). El ajuste SignalBar permite reproducir el desplazamiento de barra usado por el expert advisor original: la estrategia evalúa señales sobre datos que tienen SignalBar barras de antigüedad, por lo que con el valor predeterminado de 1 reacciona a la barra que acaba de cerrarse en el paso anterior.
Reglas de entrada y salida
Están disponibles tres modos de trading. En cada modo, los interruptores booleanos EnableLongEntry, EnableShortEntry, EnableLongExit y EnableShortExit controlan si las acciones respectivas están permitidas.
Modo Breakdown
Entrada larga: el valor anterior del histograma (desplazamiento SignalBar+1) está por encima de cero y el valor más reciente (desplazamiento SignalBar) está en o por debajo de cero. Esto refleja la condición original de "el histograma rompe a través de cero" y abre o voltea una posición larga mientras también cubre cualquier corto.
Entrada corta: el valor anterior del histograma está por debajo de cero y el valor más reciente está en o por encima de cero, señalando una ruptura de la línea cero en la dirección opuesta. La estrategia abre o voltea a una posición corta y opcionalmente cierra la exposición larga.
Las mismas condiciones también desencadenan salidas en el lado opuesto: cuando el histograma pasa la barra anterior por encima de cero, la estrategia cierra cortos, y cuando pasa la barra anterior por debajo de cero, cierra largos.
Modo Twist
Entrada larga: el histograma forma un fondo local. Concretamente, el valor en el desplazamiento SignalBar+1 está por debajo del valor en el desplazamiento SignalBar+2, pero el valor en el desplazamiento SignalBar gira hacia arriba y supera la barra intermedia. Eso reproduce el modo de "cambio de dirección" del expert advisor.
Entrada corta: el histograma forma una cima local. El valor en el desplazamiento SignalBar+1 es mayor que el valor en el desplazamiento SignalBar+2, y el valor más reciente cae por debajo de la barra intermedia. Las posiciones en la dirección opuesta se cierran cuando ocurre un giro en su contra.
Modo CloudTwist
Este modo sigue los cambios de color de la nube del indicador que está definida por el histograma y su línea de señal.
Entrada larga: el histograma estaba por encima de la línea de señal en la barra anterior pero el valor más reciente cruzó por debajo o tocó la línea de señal. La estrategia trata el cambio de color de la nube como una señal alcista y opcionalmente cubre cortos.
Entrada corta: el histograma estaba por debajo de la línea de señal en la barra anterior pero el valor más reciente cruzó por encima o tocó la línea de señal. Esto voltea a una posición corta y opcionalmente sale de largos.
Gestión de riesgos
StopLossPoints y TakeProfitPoints se miden en pasos de precio del instrumento. Si cualquier valor es mayor que cero, la estrategia habilita el bloque de protección incorporado de StockSharp con órdenes de mercado, por lo que los stops siguen la posición activa automáticamente.
- El tamaño de la orden se toma de la propiedad
Volume de la estrategia. Cuando aparece una señal de reversión, la estrategia envía Volume + |Position| contratos, asegurando que la posición existente se cierre antes de abrir una nueva.
Parámetros
CandleType – marco temporal (tipo de datos) usado para el oscilador (por defecto: velas de 4 horas).
Mode – algoritmo de detección de señales: Breakdown, Twist o CloudTwist.
AppliedPrice – fuente de precio para el cálculo estocástico (cierre, apertura, máximo, mínimo, mediana, típico, ponderado, simple, cuartil, seguimiento de tendencia 0/1, o DeMark).
Smoothing – familia de media móvil usada para todas las etapas de suavizado (Simple, Exponential, Smoothed, Weighted).
BaseLength – número de barras usadas para calcular el rango máximo/mínimo.
SmoothLength1, SmoothLength2, SmoothLength3 – longitudes de suavizado para el numerador y denominador (aplicadas secuencialmente).
SignalLength – longitud de suavizado para la línea de señal del histograma.
SignalBar – desplazamiento de barra que define qué valores históricos se usan para las decisiones.
StopLossPoints, TakeProfitPoints – tamaño de stop protector y objetivo en pasos de precio (0 deshabilita la orden correspondiente).
EnableLongEntry, EnableShortEntry, EnableLongExit, EnableShortExit – interruptores de permiso para las cuatro acciones básicas.
Establezca el Volume deseado, adjunte la estrategia a un instrumento y ejecútela. Todos los cálculos dependen de velas terminadas, por lo que el sistema espera hasta que los indicadores estén formados antes de permitir operaciones.
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()