Triple Stochastic MTF Strategy
This strategy runs three Stochastic Oscillators on different timeframes and trades when the smallest timeframe crosses its signal line in the direction confirmed by the higher timeframes. It is designed to capture short-term reversals within a larger trend context.
The primary timeframe (default 30-minute) and secondary timeframe (default 15-minute) determine the market bias. The entry timeframe (default 5-minute) waits for a %K and %D crossover opposite to the previous bar, signaling a pullback. Positions are closed whenever any of the monitored timeframes signals a trend change against the active trade.
Details
- Entry Criteria:
- Long: Previous %K > %D on the 5-minute chart, current %K ≤ %D, and both higher timeframes show %K > %D.
- Short: Previous %K < %D on the 5-minute chart, current %K ≥ %D, and both higher timeframes show %K < %D.
- Long/Short: Both sides.
- Exit Criteria:
- Long: Any timeframe switches to downtrend (%K < %D).
- Short: Any timeframe switches to uptrend (%K > %D).
- Stops: Not implemented by default.
- Default Values:
Timeframe 1= 30-minute.Timeframe 2= 15-minute.Timeframe 3= 5-minute.%K Period= 5.%D Period= 3.Slowing= 3.
- Filters:
- Category: Trend following / Pullback
- Direction: Both
- Indicators: Stochastic Oscillator
- Stops: No
- Complexity: Medium
- Timeframe: Short-term
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Moderate
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>
/// Triple Stochastic strategy operating on three timeframes.
/// Opens positions when the fast stochastic crosses the signal line on the smallest timeframe while higher timeframes
/// confirm the trend.
/// </summary>
public class Exp3StoStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType1;
private readonly StrategyParam<DataType> _candleType2;
private readonly StrategyParam<DataType> _candleType3;
private readonly StrategyParam<int> _kPeriod;
private readonly StrategyParam<int> _dPeriod;
private readonly StrategyParam<int> _slowing;
private readonly StrategyParam<bool> _buyPosOpen;
private readonly StrategyParam<bool> _sellPosOpen;
private readonly StrategyParam<bool> _buyPosClose1;
private readonly StrategyParam<bool> _sellPosClose1;
private readonly StrategyParam<bool> _buyPosClose2;
private readonly StrategyParam<bool> _sellPosClose2;
private readonly StrategyParam<bool> _buyPosClose3;
private readonly StrategyParam<bool> _sellPosClose3;
private StochasticOscillator _stoch1 = null!;
private StochasticOscillator _stoch2 = null!;
private StochasticOscillator _stoch3 = null!;
private int _trend1;
private int _trend2;
private int _trend3;
private decimal? _prevK3;
private decimal? _prevD3;
private bool _buyOpenSignal;
private bool _sellOpenSignal;
private bool _buyCloseSignal;
private bool _sellCloseSignal;
/// <summary>
/// First timeframe for stochastic indicator.
/// </summary>
public DataType CandleType1
{
get => _candleType1.Value;
set => _candleType1.Value = value;
}
/// <summary>
/// Second timeframe for stochastic indicator.
/// </summary>
public DataType CandleType2
{
get => _candleType2.Value;
set => _candleType2.Value = value;
}
/// <summary>
/// Third timeframe for stochastic indicator.
/// </summary>
public DataType CandleType3
{
get => _candleType3.Value;
set => _candleType3.Value = value;
}
/// <summary>
/// %K period.
/// </summary>
public int KPeriod
{
get => _kPeriod.Value;
set => _kPeriod.Value = value;
}
/// <summary>
/// %D period.
/// </summary>
public int DPeriod
{
get => _dPeriod.Value;
set => _dPeriod.Value = value;
}
/// <summary>
/// Smoothing factor for %K line.
/// </summary>
public int Slowing
{
get => _slowing.Value;
set => _slowing.Value = value;
}
/// <summary>
/// Allow opening long positions.
/// </summary>
public bool BuyPosOpen
{
get => _buyPosOpen.Value;
set => _buyPosOpen.Value = value;
}
/// <summary>
/// Allow opening short positions.
/// </summary>
public bool SellPosOpen
{
get => _sellPosOpen.Value;
set => _sellPosOpen.Value = value;
}
/// <summary>
/// Close shorts when first timeframe indicates uptrend.
/// </summary>
public bool SellPosClose1
{
get => _sellPosClose1.Value;
set => _sellPosClose1.Value = value;
}
/// <summary>
/// Close longs when first timeframe indicates downtrend.
/// </summary>
public bool BuyPosClose1
{
get => _buyPosClose1.Value;
set => _buyPosClose1.Value = value;
}
/// <summary>
/// Close shorts when second timeframe indicates uptrend.
/// </summary>
public bool SellPosClose2
{
get => _sellPosClose2.Value;
set => _sellPosClose2.Value = value;
}
/// <summary>
/// Close longs when second timeframe indicates downtrend.
/// </summary>
public bool BuyPosClose2
{
get => _buyPosClose2.Value;
set => _buyPosClose2.Value = value;
}
/// <summary>
/// Close shorts when third timeframe indicates uptrend.
/// </summary>
public bool SellPosClose3
{
get => _sellPosClose3.Value;
set => _sellPosClose3.Value = value;
}
/// <summary>
/// Close longs when third timeframe indicates downtrend.
/// </summary>
public bool BuyPosClose3
{
get => _buyPosClose3.Value;
set => _buyPosClose3.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public Exp3StoStrategy()
{
_candleType1 = Param(nameof(CandleType1), TimeSpan.FromDays(1).TimeFrame())
.SetDisplay("Timeframe 1", "Higher timeframe", "General");
_candleType2 = Param(nameof(CandleType2), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Timeframe 2", "Middle timeframe", "General");
_candleType3 = Param(nameof(CandleType3), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Timeframe 3", "Lower timeframe", "General");
_kPeriod = Param(nameof(KPeriod), 5).SetGreaterThanZero().SetDisplay("%K Period", "Length of %K", "Stochastic");
_dPeriod = Param(nameof(DPeriod), 3).SetGreaterThanZero().SetDisplay("%D Period", "Length of %D", "Stochastic");
_slowing = Param(nameof(Slowing), 3).SetGreaterThanZero().SetDisplay("Slowing", "%K smoothing", "Stochastic");
_buyPosOpen =
Param(nameof(BuyPosOpen), true).SetDisplay("Enable Long", "Allow opening long positions", "Signals");
_sellPosOpen =
Param(nameof(SellPosOpen), true).SetDisplay("Enable Short", "Allow opening short positions", "Signals");
_buyPosClose1 =
Param(nameof(BuyPosClose1), false).SetDisplay("Close Long TF1", "Close longs if TF1 down", "Signals");
_sellPosClose1 =
Param(nameof(SellPosClose1), false).SetDisplay("Close Short TF1", "Close shorts if TF1 up", "Signals");
_buyPosClose2 =
Param(nameof(BuyPosClose2), false).SetDisplay("Close Long TF2", "Close longs if TF2 down", "Signals");
_sellPosClose2 =
Param(nameof(SellPosClose2), false).SetDisplay("Close Short TF2", "Close shorts if TF2 up", "Signals");
_buyPosClose3 =
Param(nameof(BuyPosClose3), false).SetDisplay("Close Long TF3", "Close longs if TF3 down", "Signals");
_sellPosClose3 =
Param(nameof(SellPosClose3), false).SetDisplay("Close Short TF3", "Close shorts if TF3 up", "Signals");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType1), (Security, CandleType2), (Security, CandleType3)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_stoch1 = null!;
_stoch2 = null!;
_stoch3 = null!;
_trend1 = _trend2 = _trend3 = 0;
_prevK3 = _prevD3 = null;
_buyOpenSignal = _sellOpenSignal = false;
_buyCloseSignal = _sellCloseSignal = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_stoch1 = new StochasticOscillator();
_stoch1.K.Length = KPeriod;
_stoch1.D.Length = DPeriod;
_stoch2 = new StochasticOscillator();
_stoch2.K.Length = KPeriod;
_stoch2.D.Length = DPeriod;
_stoch3 = new StochasticOscillator();
_stoch3.K.Length = KPeriod;
_stoch3.D.Length = DPeriod;
var sub1 = SubscribeCandles(CandleType1);
sub1.BindEx(_stoch1, ProcessTf1).Start();
var sub2 = SubscribeCandles(CandleType2);
sub2.BindEx(_stoch2, ProcessTf2).Start();
var sub3 = SubscribeCandles(CandleType3);
sub3.BindEx(_stoch3, ProcessTf3).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, sub3);
DrawIndicator(area, _stoch3);
DrawOwnTrades(area);
}
}
private void ProcessTf1(ICandleMessage candle, IIndicatorValue stochValue)
{
if (candle.State != CandleStates.Finished)
return;
var stoch = (StochasticOscillatorValue)stochValue;
if (stoch.K is not decimal k || stoch.D is not decimal d)
return;
_trend1 = 0;
if (k > d && BuyPosOpen)
_trend1 = 1;
else if (k < d && SellPosOpen)
_trend1 = -1;
UpdateSignals();
}
private void ProcessTf2(ICandleMessage candle, IIndicatorValue stochValue)
{
if (candle.State != CandleStates.Finished)
return;
var stoch = (StochasticOscillatorValue)stochValue;
if (stoch.K is not decimal k || stoch.D is not decimal d)
return;
_trend2 = 0;
if (k > d && BuyPosOpen)
_trend2 = 1;
else if (k < d && SellPosOpen)
_trend2 = -1;
UpdateSignals();
}
private void ProcessTf3(ICandleMessage candle, IIndicatorValue stochValue)
{
if (candle.State != CandleStates.Finished)
return;
var stoch = (StochasticOscillatorValue)stochValue;
if (stoch.K is not decimal k || stoch.D is not decimal d)
return;
if (_prevK3 is not decimal pk || _prevD3 is not decimal pd)
{
_prevK3 = k;
_prevD3 = d;
return;
}
_trend3 = 0;
if (pk > pd)
{
_trend3 = 1;
if (BuyPosOpen && k <= d && _trend1 > 0 && _trend2 > 0)
_buyOpenSignal = true;
}
else if (pk < pd)
{
_trend3 = -1;
if (SellPosOpen && k >= d && _trend1 < 0 && _trend2 < 0)
_sellOpenSignal = true;
}
_prevK3 = k;
_prevD3 = d;
UpdateSignals();
}
private void UpdateSignals()
{
_buyCloseSignal = false;
_sellCloseSignal = false;
if (_trend1 > 0 && SellPosClose1)
_sellCloseSignal = true;
if (_trend1 < 0 && BuyPosClose1)
_buyCloseSignal = true;
if (_trend2 > 0 && SellPosClose2)
_sellCloseSignal = true;
if (_trend2 < 0 && BuyPosClose2)
_buyCloseSignal = true;
if (_trend3 > 0 && SellPosClose3)
_sellCloseSignal = true;
if (_trend3 < 0 && BuyPosClose3)
_buyCloseSignal = true;
ExecuteTrades();
}
private void ExecuteTrades()
{
if (_buyOpenSignal && Position <= 0)
{
BuyMarket();
_buyOpenSignal = false;
}
else if (_sellOpenSignal && Position >= 0)
{
SellMarket();
_sellOpenSignal = false;
}
else if (_buyCloseSignal && Position > 0)
{
SellMarket();
}
else if (_sellCloseSignal && Position < 0)
{
BuyMarket();
}
_buyCloseSignal = false;
_sellCloseSignal = false;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import StochasticOscillator
from StockSharp.Algo.Strategies import Strategy
class exp3_sto_strategy(Strategy):
def __init__(self):
super(exp3_sto_strategy, self).__init__()
self._candle_type1 = self.Param("CandleType1", DataType.TimeFrame(TimeSpan.FromDays(1)))
self._candle_type2 = self.Param("CandleType2", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._candle_type3 = self.Param("CandleType3", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._k_period = self.Param("KPeriod", 5)
self._d_period = self.Param("DPeriod", 3)
self._slowing = self.Param("Slowing", 3)
self._buy_pos_open = self.Param("BuyPosOpen", True)
self._sell_pos_open = self.Param("SellPosOpen", True)
self._buy_pos_close1 = self.Param("BuyPosClose1", False)
self._sell_pos_close1 = self.Param("SellPosClose1", False)
self._buy_pos_close2 = self.Param("BuyPosClose2", False)
self._sell_pos_close2 = self.Param("SellPosClose2", False)
self._buy_pos_close3 = self.Param("BuyPosClose3", False)
self._sell_pos_close3 = self.Param("SellPosClose3", False)
self._trend1 = 0
self._trend2 = 0
self._trend3 = 0
self._prev_k3 = None
self._prev_d3 = None
self._buy_open_signal = False
self._sell_open_signal = False
self._buy_close_signal = False
self._sell_close_signal = False
@property
def CandleType1(self): return self._candle_type1.Value
@CandleType1.setter
def CandleType1(self, v): self._candle_type1.Value = v
@property
def CandleType2(self): return self._candle_type2.Value
@CandleType2.setter
def CandleType2(self, v): self._candle_type2.Value = v
@property
def CandleType3(self): return self._candle_type3.Value
@CandleType3.setter
def CandleType3(self, v): self._candle_type3.Value = v
@property
def KPeriod(self): return self._k_period.Value
@KPeriod.setter
def KPeriod(self, v): self._k_period.Value = v
@property
def DPeriod(self): return self._d_period.Value
@DPeriod.setter
def DPeriod(self, v): self._d_period.Value = v
@property
def Slowing(self): return self._slowing.Value
@Slowing.setter
def Slowing(self, v): self._slowing.Value = v
@property
def BuyPosOpen(self): return self._buy_pos_open.Value
@BuyPosOpen.setter
def BuyPosOpen(self, v): self._buy_pos_open.Value = v
@property
def SellPosOpen(self): return self._sell_pos_open.Value
@SellPosOpen.setter
def SellPosOpen(self, v): self._sell_pos_open.Value = v
@property
def BuyPosClose1(self): return self._buy_pos_close1.Value
@BuyPosClose1.setter
def BuyPosClose1(self, v): self._buy_pos_close1.Value = v
@property
def SellPosClose1(self): return self._sell_pos_close1.Value
@SellPosClose1.setter
def SellPosClose1(self, v): self._sell_pos_close1.Value = v
@property
def BuyPosClose2(self): return self._buy_pos_close2.Value
@BuyPosClose2.setter
def BuyPosClose2(self, v): self._buy_pos_close2.Value = v
@property
def SellPosClose2(self): return self._sell_pos_close2.Value
@SellPosClose2.setter
def SellPosClose2(self, v): self._sell_pos_close2.Value = v
@property
def BuyPosClose3(self): return self._buy_pos_close3.Value
@BuyPosClose3.setter
def BuyPosClose3(self, v): self._buy_pos_close3.Value = v
@property
def SellPosClose3(self): return self._sell_pos_close3.Value
@SellPosClose3.setter
def SellPosClose3(self, v): self._sell_pos_close3.Value = v
def OnStarted2(self, time):
super(exp3_sto_strategy, self).OnStarted2(time)
self._stoch1 = StochasticOscillator()
self._stoch1.K.Length = self.KPeriod
self._stoch1.D.Length = self.DPeriod
self._stoch2 = StochasticOscillator()
self._stoch2.K.Length = self.KPeriod
self._stoch2.D.Length = self.DPeriod
self._stoch3 = StochasticOscillator()
self._stoch3.K.Length = self.KPeriod
self._stoch3.D.Length = self.DPeriod
sub1 = self.SubscribeCandles(self.CandleType1)
sub1.BindEx(self._stoch1, self.ProcessTf1).Start()
sub2 = self.SubscribeCandles(self.CandleType2)
sub2.BindEx(self._stoch2, self.ProcessTf2).Start()
sub3 = self.SubscribeCandles(self.CandleType3)
sub3.BindEx(self._stoch3, self.ProcessTf3).Start()
def ProcessTf1(self, candle, sv):
if candle.State != CandleStates.Finished: return
if sv.K is None or sv.D is None: return
k, d = float(sv.K), float(sv.D)
self._trend1 = 0
if k > d and self.BuyPosOpen: self._trend1 = 1
elif k < d and self.SellPosOpen: self._trend1 = -1
self._update_signals()
def ProcessTf2(self, candle, sv):
if candle.State != CandleStates.Finished: return
if sv.K is None or sv.D is None: return
k, d = float(sv.K), float(sv.D)
self._trend2 = 0
if k > d and self.BuyPosOpen: self._trend2 = 1
elif k < d and self.SellPosOpen: self._trend2 = -1
self._update_signals()
def ProcessTf3(self, candle, sv):
if candle.State != CandleStates.Finished: return
if sv.K is None or sv.D is None: return
k, d = float(sv.K), float(sv.D)
if self._prev_k3 is None or self._prev_d3 is None:
self._prev_k3 = k
self._prev_d3 = d
return
pk, pdv = self._prev_k3, self._prev_d3
self._trend3 = 0
if pk > pdv:
self._trend3 = 1
if self.BuyPosOpen and k <= d and self._trend1 > 0 and self._trend2 > 0:
self._buy_open_signal = True
elif pk < pdv:
self._trend3 = -1
if self.SellPosOpen and k >= d and self._trend1 < 0 and self._trend2 < 0:
self._sell_open_signal = True
self._prev_k3 = k
self._prev_d3 = d
self._update_signals()
def _update_signals(self):
self._buy_close_signal = False
self._sell_close_signal = False
if self._trend1 > 0 and self.SellPosClose1: self._sell_close_signal = True
if self._trend1 < 0 and self.BuyPosClose1: self._buy_close_signal = True
if self._trend2 > 0 and self.SellPosClose2: self._sell_close_signal = True
if self._trend2 < 0 and self.BuyPosClose2: self._buy_close_signal = True
if self._trend3 > 0 and self.SellPosClose3: self._sell_close_signal = True
if self._trend3 < 0 and self.BuyPosClose3: self._buy_close_signal = True
self._execute_trades()
def _execute_trades(self):
if self._buy_open_signal and self.Position <= 0:
self.BuyMarket()
self._buy_open_signal = False
elif self._sell_open_signal and self.Position >= 0:
self.SellMarket()
self._sell_open_signal = False
elif self._buy_close_signal and self.Position > 0:
self.SellMarket()
elif self._sell_close_signal and self.Position < 0:
self.BuyMarket()
self._buy_close_signal = False
self._sell_close_signal = False
def OnReseted(self):
super(exp3_sto_strategy, self).OnReseted()
self._trend1 = 0
self._trend2 = 0
self._trend3 = 0
self._prev_k3 = None
self._prev_d3 = None
self._buy_open_signal = False
self._sell_open_signal = False
self._buy_close_signal = False
self._sell_close_signal = False
def CreateClone(self):
return exp3_sto_strategy()