Murrey Math Bollinger & Stochastic Strategy
This strategy trades reversals at extreme Murrey Math lines using Bollinger Bands and a Stochastic Oscillator as confirmation.
The method computes Murrey levels from the highest and lowest prices over a configurable frame. When price approaches the 0/8 line during oversold conditions the strategy buys. When price nears the 8/8 line during overbought conditions it sells. A minimum Bollinger band width filter prevents trading in flat markets.
Details
- Entry Criteria
- Long: Close is within Entry Margin above the 0/8 line, Stochastic <= 21 and Bollinger band width >= threshold.
- Short: Close is within Entry Margin below the 8/8 line, Stochastic >= 79 and Bollinger band width >= threshold.
- Long/Short: Both.
- Exit Criteria
- Long positions close at the 1/8 line or if price falls below the -2/8 line.
- Short positions close at the 7/8 line or if price rises above the +2/8 line.
- Stops: Murrey lines (-2/8 or +2/8) act as protective stops.
- Filters
- Bollinger band width filter.
- Stochastic oscillator filter.
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>
/// Murrey Math reversal strategy filtered by Bollinger Bands and Stochastic oscillator.
/// Buys near Murrey support when stochastic oversold; sells near Murrey resistance when stochastic overbought.
/// </summary>
public class MurreyBBandStochasticStrategy : Strategy
{
private readonly StrategyParam<int> _frame;
private readonly StrategyParam<decimal> _entryMarginPct;
private readonly StrategyParam<int> _bbPeriod;
private readonly StrategyParam<decimal> _bbDeviation;
private readonly StrategyParam<int> _stochK;
private readonly StrategyParam<int> _stochD;
private readonly StrategyParam<decimal> _stochOversold;
private readonly StrategyParam<decimal> _stochOverbought;
private readonly StrategyParam<DataType> _candleType;
private Highest _highest;
private Lowest _lowest;
public int Frame { get => _frame.Value; set => _frame.Value = value; }
public decimal EntryMarginPct { get => _entryMarginPct.Value; set => _entryMarginPct.Value = value; }
public int BbPeriod { get => _bbPeriod.Value; set => _bbPeriod.Value = value; }
public decimal BbDeviation { get => _bbDeviation.Value; set => _bbDeviation.Value = value; }
public int StochK { get => _stochK.Value; set => _stochK.Value = value; }
public int StochD { get => _stochD.Value; set => _stochD.Value = value; }
public decimal StochOversold { get => _stochOversold.Value; set => _stochOversold.Value = value; }
public decimal StochOverbought { get => _stochOverbought.Value; set => _stochOverbought.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public MurreyBBandStochasticStrategy()
{
_frame = Param(nameof(Frame), 64)
.SetGreaterThanZero()
.SetDisplay("Frame", "Murrey frame size", "General");
_entryMarginPct = Param(nameof(EntryMarginPct), 2m)
.SetDisplay("Entry Margin %", "Percentage distance from Murrey line for entry", "General");
_bbPeriod = Param(nameof(BbPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("Bollinger Period", "Bollinger Bands period", "Indicators");
_bbDeviation = Param(nameof(BbDeviation), 2m)
.SetGreaterThanZero()
.SetDisplay("Bollinger Deviation", "Bollinger Bands deviation", "Indicators");
_stochK = Param(nameof(StochK), 14)
.SetGreaterThanZero()
.SetDisplay("Stochastic %K", "%K length", "Indicators");
_stochD = Param(nameof(StochD), 3)
.SetGreaterThanZero()
.SetDisplay("Stochastic %D", "%D length", "Indicators");
_stochOversold = Param(nameof(StochOversold), 30m)
.SetDisplay("Stochastic Oversold", "Level for long setups", "Indicators");
_stochOverbought = Param(nameof(StochOverbought), 70m)
.SetDisplay("Stochastic Overbought", "Level for short setups", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_highest = new Highest { Length = Frame };
_lowest = new Lowest { Length = Frame };
var bollinger = new BollingerBands { Length = BbPeriod, Width = BbDeviation };
var stochastic = new StochasticOscillator
{
K = { Length = StochK },
D = { Length = StochD },
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_highest, _lowest, bollinger, stochastic, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, bollinger);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue highValue, IIndicatorValue lowValue, IIndicatorValue bbValue, IIndicatorValue stochValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!highValue.IsFormed || !lowValue.IsFormed || !bbValue.IsFormed || !stochValue.IsFormed)
return;
var nHigh = highValue.ToDecimal();
var nLow = lowValue.ToDecimal();
var range = nHigh - nLow;
if (range <= 0m)
return;
// Murrey Math level calculation
decimal fractal;
if (nHigh <= 250000m && nHigh > 25000m)
fractal = 100000m;
else if (nHigh <= 25000m && nHigh > 2500m)
fractal = 10000m;
else if (nHigh <= 2500m && nHigh > 250m)
fractal = 1000m;
else if (nHigh <= 250m && nHigh > 25m)
fractal = 100m;
else if (nHigh <= 25m && nHigh > 6.25m)
fractal = 12.5m;
else if (nHigh <= 6.25m && nHigh > 3.125m)
fractal = 6.25m;
else if (nHigh <= 3.125m && nHigh > 1.5625m)
fractal = 3.125m;
else if (nHigh <= 1.5625m && nHigh > 0.390625m)
fractal = 1.5625m;
else if (nHigh > 250000m)
fractal = 1000000m;
else
fractal = 0.1953125m;
var logVal = Math.Log((double)(fractal / range), 2);
if (double.IsNaN(logVal) || double.IsInfinity(logVal))
return;
var sum = (decimal)Math.Floor(logVal);
var octave = fractal * (decimal)Math.Pow(0.5, (double)sum);
if (octave <= 0)
return;
var minimum = Math.Floor(nLow / octave) * octave;
var maximum = minimum + 2m * octave;
if (maximum > nHigh)
maximum = minimum + octave;
var diff = maximum - minimum;
if (diff <= 0)
return;
var level0 = minimum;
var level1 = minimum + diff / 8m;
var level4 = minimum + diff / 2m;
var level7 = minimum + diff * 7m / 8m;
var level8 = maximum;
var close = candle.ClosePrice;
var entryMargin = close * EntryMarginPct / 100m;
// Bollinger filter
var bb = (BollingerBandsValue)bbValue;
if (bb.LowBand is not decimal lower || bb.UpBand is not decimal upper)
return;
// Stochastic filter
var stoch = (StochasticOscillatorValue)stochValue;
if (stoch.K is not decimal kValue)
return;
// Buy: price near Murrey support (level0-level1), stochastic oversold, price below upper band
if (Position <= 0 && kValue < StochOversold && close <= level1 + entryMargin && close < upper)
{
if (Position < 0) BuyMarket();
BuyMarket();
}
// Sell: price near Murrey resistance (level7-level8), stochastic overbought, price above lower band
else if (Position >= 0 && kValue > StochOverbought && close >= level7 - entryMargin && close > lower)
{
if (Position > 0) SellMarket();
SellMarket();
}
// Exit long at level4 (midpoint) or above level8
else if (Position > 0 && (close >= level8 || close >= level4))
{
SellMarket();
}
// Exit short at level4 (midpoint) or below level0
else if (Position < 0 && (close <= level0 || close <= level4))
{
BuyMarket();
}
}
}
import clr
import math
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 Highest, Lowest, BollingerBands, StochasticOscillator
from StockSharp.Algo.Strategies import Strategy
class murrey_bband_stochastic_strategy(Strategy):
"""
Murrey Math reversal filtered by Bollinger Bands and Stochastic.
"""
def __init__(self):
super(murrey_bband_stochastic_strategy, self).__init__()
self._frame = self.Param("Frame", 64).SetDisplay("Frame", "Murrey frame", "General")
self._entry_margin_pct = self.Param("EntryMarginPct", 2.0).SetDisplay("Entry Margin %", "Entry margin", "General")
self._bb_period = self.Param("BbPeriod", 50).SetDisplay("BB Period", "Bollinger period", "Indicators")
self._bb_dev = self.Param("BbDeviation", 2.0).SetDisplay("BB Dev", "Bollinger deviation", "Indicators")
self._stoch_k = self.Param("StochK", 14).SetDisplay("Stoch K", "Stochastic K", "Indicators")
self._stoch_d = self.Param("StochD", 3).SetDisplay("Stoch D", "Stochastic D", "Indicators")
self._stoch_os = self.Param("StochOversold", 30.0).SetDisplay("Stoch OS", "Oversold level", "Signals")
self._stoch_ob = self.Param("StochOverbought", 70.0).SetDisplay("Stoch OB", "Overbought level", "Signals")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Candles", "General")
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(murrey_bband_stochastic_strategy, self).OnReseted()
def OnStarted2(self, time):
super(murrey_bband_stochastic_strategy, self).OnStarted2(time)
highest = Highest()
highest.Length = self._frame.Value
lowest = Lowest()
lowest.Length = self._frame.Value
bb = BollingerBands()
bb.Length = self._bb_period.Value
bb.Width = self._bb_dev.Value
stoch = StochasticOscillator()
stoch.K.Length = self._stoch_k.Value
stoch.D.Length = self._stoch_d.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(highest, lowest, bb, stoch, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, bb)
self.DrawOwnTrades(area)
def _process_candle(self, candle, high_value, low_value, bb_value, stoch_value):
if candle.State != CandleStates.Finished:
return
if not high_value.IsFormed or not low_value.IsFormed or not bb_value.IsFormed or not stoch_value.IsFormed:
return
n_high = float(high_value)
n_low = float(low_value)
rng = n_high - n_low
if rng <= 0:
return
if n_high <= 250000 and n_high > 25000:
fractal = 100000
elif n_high <= 25000 and n_high > 2500:
fractal = 10000
elif n_high <= 2500 and n_high > 250:
fractal = 1000
elif n_high <= 250 and n_high > 25:
fractal = 100
elif n_high <= 25 and n_high > 6.25:
fractal = 12.5
elif n_high <= 6.25 and n_high > 3.125:
fractal = 6.25
elif n_high <= 3.125 and n_high > 1.5625:
fractal = 3.125
elif n_high <= 1.5625 and n_high > 0.390625:
fractal = 1.5625
elif n_high > 250000:
fractal = 1000000
else:
fractal = 0.1953125
log_val = math.log(fractal / rng, 2)
s = math.floor(log_val)
octave = fractal * (0.5 ** s)
if octave <= 0:
return
minimum = math.floor(n_low / octave) * octave
maximum = minimum + 2.0 * octave
if maximum > n_high:
maximum = minimum + octave
diff = maximum - minimum
if diff <= 0:
return
level0 = minimum
level1 = minimum + diff / 8.0
level4 = minimum + diff / 2.0
level7 = minimum + diff * 7.0 / 8.0
level8 = maximum
close = float(candle.ClosePrice)
margin = close * float(self._entry_margin_pct.Value) / 100.0
bb_low = bb_value.LowBand
bb_up = bb_value.UpBand
if bb_low is None or bb_up is None:
return
lower = float(bb_low)
upper = float(bb_up)
stoch_k = stoch_value.K
if stoch_k is None:
return
k_val = float(stoch_k)
os_level = float(self._stoch_os.Value)
ob_level = float(self._stoch_ob.Value)
if self.Position <= 0 and k_val < os_level and close <= level1 + margin and close < upper:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif self.Position >= 0 and k_val > ob_level and close >= level7 - margin and close > lower:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
elif self.Position > 0 and (close >= level8 or close >= level4):
self.SellMarket()
elif self.Position < 0 and (close <= level0 or close <= level4):
self.BuyMarket()
def CreateClone(self):
return murrey_bband_stochastic_strategy()