MA Stochastic Strategy
MA Stochastic uses a moving average trend filter with stochastic oscillator pullbacks. When price trends above the average and the stochastic dips into oversold, the system prepares to buy the next upturn.
Testing indicates an average annual return of about 151%. It performs best in the stocks market.
Short trades mirror this logic for downtrends, selling rallies when stochastic reaches overbought.
Fixed percent stops help avoid large losses if the trend suddenly reverses.
Details
- Entry Criteria: indicator signal
- Long/Short: Both
- Exit Criteria: stop-loss or opposite signal
- Stops: Yes, percent based
- Default Values:
CandleType= 15 minuteStopLoss= 2%
- Filters:
- Category: Trend following
- Direction: Both
- Indicators: Moving Average, Stochastic
- Stops: Yes
- Complexity: Intermediate
- Timeframe: Intraday
- Seasonality: No
- Neural networks: No
- Divergence: No
- Risk level: Medium
using System;
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;
/// <summary>
/// Strategy that combines Moving Average and manual Stochastic %K calculation.
/// Enters when price is above MA and Stochastic oversold (longs)
/// or below MA and Stochastic overbought (shorts).
/// </summary>
public class MaStochasticStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _stochPeriod;
private readonly StrategyParam<decimal> _stochOversold;
private readonly StrategyParam<decimal> _stochOverbought;
private readonly StrategyParam<int> _cooldownBars;
private int _cooldown;
private readonly List<decimal> _highs = new();
private readonly List<decimal> _lows = new();
private readonly List<decimal> _closes = new();
/// <summary>
/// Candle type for strategy calculation.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Moving Average period.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Stochastic period for %K calculation.
/// </summary>
public int StochPeriod
{
get => _stochPeriod.Value;
set => _stochPeriod.Value = value;
}
/// <summary>
/// Stochastic oversold level.
/// </summary>
public decimal StochOversold
{
get => _stochOversold.Value;
set => _stochOversold.Value = value;
}
/// <summary>
/// Stochastic overbought level.
/// </summary>
public decimal StochOverbought
{
get => _stochOverbought.Value;
set => _stochOverbought.Value = value;
}
/// <summary>
/// Cooldown bars between trades.
/// </summary>
public int CooldownBars
{
get => _cooldownBars.Value;
set => _cooldownBars.Value = value;
}
/// <summary>
/// Strategy constructor.
/// </summary>
public MaStochasticStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
_maPeriod = Param(nameof(MaPeriod), 20)
.SetRange(10, 50)
.SetDisplay("MA Period", "Period of the Moving Average", "Indicators");
_stochPeriod = Param(nameof(StochPeriod), 14)
.SetRange(5, 30)
.SetDisplay("Stochastic Period", "Period for %K calculation", "Indicators");
_stochOversold = Param(nameof(StochOversold), 20m)
.SetDisplay("Stochastic Oversold", "Level considered oversold", "Indicators");
_stochOverbought = Param(nameof(StochOverbought), 80m)
.SetDisplay("Stochastic Overbought", "Level considered overbought", "Indicators");
_cooldownBars = Param(nameof(CooldownBars), 100)
.SetDisplay("Cooldown Bars", "Bars between trades", "General")
.SetRange(5, 500);
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_cooldown = 0;
_highs.Clear();
_lows.Clear();
_closes.Clear();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var ma = new SimpleMovingAverage { Length = MaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ma);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal maValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Track highs, lows, closes for manual stochastic
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
_closes.Add(candle.ClosePrice);
// Keep buffers manageable
var maxBuf = StochPeriod * 2;
if (_highs.Count > maxBuf)
{
_highs.RemoveRange(0, _highs.Count - maxBuf);
_lows.RemoveRange(0, _lows.Count - maxBuf);
_closes.RemoveRange(0, _closes.Count - maxBuf);
}
if (_highs.Count < StochPeriod)
return;
// Calculate %K manually
var start = _highs.Count - StochPeriod;
var highestHigh = decimal.MinValue;
var lowestLow = decimal.MaxValue;
for (var i = start; i < _highs.Count; i++)
{
if (_highs[i] > highestHigh) highestHigh = _highs[i];
if (_lows[i] < lowestLow) lowestLow = _lows[i];
}
var diff = highestHigh - lowestLow;
if (diff == 0)
return;
var stochK = 100m * (candle.ClosePrice - lowestLow) / diff;
var close = candle.ClosePrice;
if (_cooldown > 0)
{
_cooldown--;
return;
}
// Long: price above MA + Stochastic oversold
if (close > maValue && stochK < StochOversold && Position == 0)
{
BuyMarket();
_cooldown = CooldownBars;
}
// Short: price below MA + Stochastic overbought
else if (close < maValue && stochK > StochOverbought && Position == 0)
{
SellMarket();
_cooldown = CooldownBars;
}
// Exit long: price below MA
if (Position > 0 && close < maValue)
{
SellMarket();
_cooldown = CooldownBars;
}
// Exit short: price above MA
else if (Position < 0 && close > maValue)
{
BuyMarket();
_cooldown = CooldownBars;
}
}
}
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 SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ma_stochastic_strategy(Strategy):
"""
MA + manual Stochastic strategy.
Enters when price is above MA and Stochastic oversold (longs)
or below MA and Stochastic overbought (shorts).
"""
def __init__(self):
super(ma_stochastic_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._ma_period = self.Param("MaPeriod", 20).SetDisplay("MA Period", "Period of the Moving Average", "Indicators")
self._stoch_period = self.Param("StochPeriod", 14).SetDisplay("Stochastic Period", "Period for %K calculation", "Indicators")
self._stoch_oversold = self.Param("StochOversold", 20.0).SetDisplay("Stochastic Oversold", "Level considered oversold", "Indicators")
self._stoch_overbought = self.Param("StochOverbought", 80.0).SetDisplay("Stochastic Overbought", "Level considered overbought", "Indicators")
self._cooldown_bars = self.Param("CooldownBars", 100).SetDisplay("Cooldown Bars", "Bars between trades", "General")
self._cooldown = 0
self._highs = []
self._lows = []
self._closes = []
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ma_stochastic_strategy, self).OnReseted()
self._cooldown = 0
self._highs = []
self._lows = []
self._closes = []
def OnStarted2(self, time):
super(ma_stochastic_strategy, self).OnStarted2(time)
self._cooldown = 0
self._highs = []
self._lows = []
self._closes = []
ma = SimpleMovingAverage()
ma.Length = self._ma_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ma, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ma)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ma_val):
if candle.State != CandleStates.Finished:
return
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
ma = float(ma_val)
cd = self._cooldown_bars.Value
sp = self._stoch_period.Value
oversold = float(self._stoch_oversold.Value)
overbought = float(self._stoch_overbought.Value)
# Track highs, lows, closes for manual stochastic
self._highs.append(high)
self._lows.append(low)
self._closes.append(close)
# Keep buffers manageable
max_buf = sp * 2
if len(self._highs) > max_buf:
self._highs = self._highs[-max_buf:]
self._lows = self._lows[-max_buf:]
self._closes = self._closes[-max_buf:]
if len(self._highs) < sp:
return
# Calculate %K manually
recent_h = self._highs[-sp:]
recent_l = self._lows[-sp:]
hh = max(recent_h)
ll = min(recent_l)
diff = hh - ll
if diff == 0:
return
stoch_k = 100.0 * (close - ll) / diff
if self._cooldown > 0:
self._cooldown -= 1
return
# Long: price above MA + Stochastic oversold
if close > ma and stoch_k < oversold and self.Position == 0:
self.BuyMarket()
self._cooldown = cd
# Short: price below MA + Stochastic overbought
elif close < ma and stoch_k > overbought and self.Position == 0:
self.SellMarket()
self._cooldown = cd
# Exit long: price below MA
if self.Position > 0 and close < ma:
self.SellMarket()
self._cooldown = cd
# Exit short: price above MA
elif self.Position < 0 and close > ma:
self.BuyMarket()
self._cooldown = cd
def CreateClone(self):
return ma_stochastic_strategy()