MESA Stochastic Multi Length Strategy
This strategy uses four MESA Stochastic oscillators with different lookback lengths. A long position is opened when all four oscillators are above their moving average trigger. A short position is opened when all four oscillators fall below their triggers.
Parameters
Length1– lookback for the first oscillator.Length2– lookback for the second oscillator.Length3– lookback for the third oscillator.Length4– lookback for the fourth oscillator.TriggerLength– smoothing period for the trigger moving averages.CandleType– candle time frame.
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class MesaStochasticMultiLengthStrategy : Strategy
{
private readonly StrategyParam<int> _length1;
private readonly StrategyParam<int> _length2;
private readonly StrategyParam<decimal> _upperLevel;
private readonly StrategyParam<decimal> _lowerLevel;
private readonly StrategyParam<int> _signalCooldownBars;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _prices = new();
private decimal _prevStoch1;
private decimal _prevStoch2;
private int _barsFromSignal;
public int Length1 { get => _length1.Value; set => _length1.Value = value; }
public int Length2 { get => _length2.Value; set => _length2.Value = value; }
public decimal UpperLevel { get => _upperLevel.Value; set => _upperLevel.Value = value; }
public decimal LowerLevel { get => _lowerLevel.Value; set => _lowerLevel.Value = value; }
public int SignalCooldownBars { get => _signalCooldownBars.Value; set => _signalCooldownBars.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public MesaStochasticMultiLengthStrategy()
{
_length1 = Param(nameof(Length1), 50)
.SetGreaterThanZero()
.SetDisplay("Length 1", "Primary stochastic length", "General");
_length2 = Param(nameof(Length2), 14)
.SetGreaterThanZero()
.SetDisplay("Length 2", "Secondary stochastic length", "General");
_upperLevel = Param(nameof(UpperLevel), 0.60m)
.SetDisplay("Upper Level", "Upper signal level", "General");
_lowerLevel = Param(nameof(LowerLevel), 0.40m)
.SetDisplay("Lower Level", "Lower signal level", "General");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 10)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown Bars", "Minimum bars between entries", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Candles timeframe", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prices.Clear();
_prevStoch1 = 0.5m;
_prevStoch2 = 0.5m;
_barsFromSignal = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
StartProtection(null, null);
_prices.Clear();
_prevStoch1 = 0.5m;
_prevStoch2 = 0.5m;
_barsFromSignal = SignalCooldownBars;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var price = (candle.HighPrice + candle.LowPrice) / 2m;
_prices.Add(price);
var maxLen = Math.Max(Length1, Length2);
if (_prices.Count > maxLen + 10)
_prices.RemoveAt(0);
if (_prices.Count < maxLen)
return;
var stoch1 = CalcStochastic(_prices, Length1);
var stoch2 = CalcStochastic(_prices, Length2);
var up = stoch1 > UpperLevel && stoch2 > UpperLevel && _prevStoch1 <= UpperLevel;
var down = stoch1 < LowerLevel && stoch2 < LowerLevel && _prevStoch1 >= LowerLevel;
_barsFromSignal++;
if (_barsFromSignal >= SignalCooldownBars && up && Position <= 0)
{
BuyMarket();
_barsFromSignal = 0;
}
else if (_barsFromSignal >= SignalCooldownBars && down && Position >= 0)
{
SellMarket();
_barsFromSignal = 0;
}
_prevStoch1 = stoch1;
_prevStoch2 = stoch2;
}
private static decimal CalcStochastic(List<decimal> prices, int length)
{
var count = prices.Count;
if (count < length) return 0.5m;
var high = decimal.MinValue;
var low = decimal.MaxValue;
for (int i = count - length; i < count; i++)
{
if (prices[i] > high) high = prices[i];
if (prices[i] < low) low = prices[i];
}
if (high == low) return 0.5m;
return (prices[count - 1] - low) / (high - low);
}
}
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.Strategies import Strategy
class mesa_stochastic_multi_length_strategy(Strategy):
def __init__(self):
super(mesa_stochastic_multi_length_strategy, self).__init__()
self._length1 = self.Param("Length1", 50) \
.SetGreaterThanZero() \
.SetDisplay("Length 1", "Primary stochastic length", "General")
self._length2 = self.Param("Length2", 14) \
.SetGreaterThanZero() \
.SetDisplay("Length 2", "Secondary stochastic length", "General")
self._upper_level = self.Param("UpperLevel", 0.60) \
.SetDisplay("Upper Level", "Upper signal level", "General")
self._lower_level = self.Param("LowerLevel", 0.40) \
.SetDisplay("Lower Level", "Lower signal level", "General")
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 10) \
.SetGreaterThanZero() \
.SetDisplay("Signal Cooldown Bars", "Minimum bars between entries", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Candles timeframe", "General")
self._prices = []
self._prev_stoch1 = 0.5
self._prev_stoch2 = 0.5
self._bars_from_signal = 0
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(mesa_stochastic_multi_length_strategy, self).OnReseted()
self._prices = []
self._prev_stoch1 = 0.5
self._prev_stoch2 = 0.5
self._bars_from_signal = 0
def OnStarted2(self, time):
super(mesa_stochastic_multi_length_strategy, self).OnStarted2(time)
self._prices = []
self._prev_stoch1 = 0.5
self._prev_stoch2 = 0.5
self._bars_from_signal = self._signal_cooldown_bars.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.OnProcess).Start()
def _calc_stochastic(self, prices, length):
count = len(prices)
if count < length:
return 0.5
high = -999999999.0
low = 999999999.0
start = count - length
for i in range(start, count):
if prices[i] > high:
high = prices[i]
if prices[i] < low:
low = prices[i]
if high == low:
return 0.5
return (prices[count - 1] - low) / (high - low)
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
price = (float(candle.HighPrice) + float(candle.LowPrice)) / 2.0
self._prices.append(price)
max_len = max(self._length1.Value, self._length2.Value)
if len(self._prices) > max_len + 10:
self._prices.pop(0)
if len(self._prices) < max_len:
return
stoch1 = self._calc_stochastic(self._prices, self._length1.Value)
stoch2 = self._calc_stochastic(self._prices, self._length2.Value)
ul = float(self._upper_level.Value)
ll = float(self._lower_level.Value)
up = stoch1 > ul and stoch2 > ul and self._prev_stoch1 <= ul
down = stoch1 < ll and stoch2 < ll and self._prev_stoch1 >= ll
self._bars_from_signal += 1
cd = self._signal_cooldown_bars.Value
if self._bars_from_signal >= cd and up and self.Position <= 0:
self.BuyMarket()
self._bars_from_signal = 0
elif self._bars_from_signal >= cd and down and self.Position >= 0:
self.SellMarket()
self._bars_from_signal = 0
self._prev_stoch1 = stoch1
self._prev_stoch2 = stoch2
def CreateClone(self):
return mesa_stochastic_multi_length_strategy()