MasterMind 2 Strategy
Overview
MasterMind 2 is a conversion of the "TheMasterMind2" MQL4 expert advisor. The strategy waits for extreme values on the Stochastic Oscillator and Williams %R indicators to detect exhaustion points. When both indicators show extreme oversold conditions it opens a long position, and when they both show extreme overbought conditions it opens a short position. The logic operates on fully closed candles only, mimicking the original Expert Advisor behaviour.
Indicators
- Stochastic Oscillator – configured with a long lookback to gauge overbought and oversold levels. The %D signal line is compared against thresholds.
- Williams %R – confirms the strength of the extreme by requiring readings close to -100 for longs and near 0 for shorts.
Entry Rules
- Wait for a candle to close.
- Calculate the Stochastic Oscillator and take its %D signal value.
- Calculate Williams %R over the configured lookback.
- Long entry: if
%D < 3andWilliams %R < -99.9, close any existing short exposure and buy. - Short entry: if
%D > 97andWilliams %R > -0.1, close any existing long exposure and sell.
Exit Rules
- Stop loss and take profit levels are applied relative to the entry price using configurable point distances.
- Trailing stop can tighten the protective stop once the price moves favourably by the specified step.
- A break-even option moves the stop loss to the entry level after the trade accumulates the required profit distance.
- Opposite signals immediately close the current position before opening a new one.
Parameters
Trade Volume– contract volume submitted with each market order.Stochastic Period,Stochastic %K,Stochastic %D– parameters of the Stochastic Oscillator.Williams %R Period– lookback period for the Williams %R calculation.Stop Loss,Take Profit– protective distances in price points.Trailing Stop,Trailing Step– control dynamic stop management.Break Even– distance in points required to lock in the entry price.Candle Type– timeframe or custom candle type used in calculations.
Notes
- The strategy relies exclusively on finished candles, matching the original MQL4 implementation.
- All orders are issued at market with volume defined by
Trade Volume. - Enable or disable the protective features by setting the distance parameters to zero.
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>
/// MasterMind 2 strategy converted from MQL4 implementation.
/// Uses Stochastic Oscillator and Williams %R to detect extreme conditions
/// and complements signals with stop management rules.
/// </summary>
public class MasterMind2Strategy : Strategy
{
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<int> _stochasticPeriod;
private readonly StrategyParam<int> _stochasticK;
private readonly StrategyParam<int> _stochasticD;
private readonly StrategyParam<int> _williamsPeriod;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<decimal> _trailingStopPoints;
private readonly StrategyParam<decimal> _trailingStepPoints;
private readonly StrategyParam<decimal> _breakEvenPoints;
private readonly StrategyParam<DataType> _candleType;
private decimal _entryPrice;
private decimal _stopPrice;
private decimal _takeProfitPrice;
/// <summary>
/// Trade volume in contracts.
/// </summary>
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
/// <summary>
/// Period for the Stochastic Oscillator.
/// </summary>
public int StochasticPeriod
{
get => _stochasticPeriod.Value;
set => _stochasticPeriod.Value = value;
}
/// <summary>
/// Smoothing length for the %K line.
/// </summary>
public int StochasticK
{
get => _stochasticK.Value;
set => _stochasticK.Value = value;
}
/// <summary>
/// Smoothing length for the %D signal line.
/// </summary>
public int StochasticD
{
get => _stochasticD.Value;
set => _stochasticD.Value = value;
}
/// <summary>
/// Period for Williams %R.
/// </summary>
public int WilliamsPeriod
{
get => _williamsPeriod.Value;
set => _williamsPeriod.Value = value;
}
/// <summary>
/// Stop loss distance expressed in price points.
/// </summary>
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
/// <summary>
/// Take profit distance expressed in price points.
/// </summary>
public decimal TakeProfitPoints
{
get => _takeProfitPoints.Value;
set => _takeProfitPoints.Value = value;
}
/// <summary>
/// Trailing stop distance expressed in price points.
/// </summary>
public decimal TrailingStopPoints
{
get => _trailingStopPoints.Value;
set => _trailingStopPoints.Value = value;
}
/// <summary>
/// Minimum price improvement required to move the trailing stop.
/// </summary>
public decimal TrailingStepPoints
{
get => _trailingStepPoints.Value;
set => _trailingStepPoints.Value = value;
}
/// <summary>
/// Distance required to move the stop loss to break-even.
/// </summary>
public decimal BreakEvenPoints
{
get => _breakEvenPoints.Value;
set => _breakEvenPoints.Value = value;
}
/// <summary>
/// Candle type processed by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes default parameters.
/// </summary>
public MasterMind2Strategy()
{
_tradeVolume = Param(nameof(TradeVolume), 0.1m)
.SetDisplay("Trade Volume", "Trade volume in contracts", "General");
_stochasticPeriod = Param(nameof(StochasticPeriod), 100)
.SetDisplay("Stochastic Period", "Period for the Stochastic Oscillator", "Indicators")
;
_stochasticK = Param(nameof(StochasticK), 3)
.SetDisplay("Stochastic %K", "Smoothing length for %K", "Indicators")
;
_stochasticD = Param(nameof(StochasticD), 3)
.SetDisplay("Stochastic %D", "Smoothing length for %D", "Indicators")
;
_williamsPeriod = Param(nameof(WilliamsPeriod), 100)
.SetDisplay("Williams %R Period", "Lookback for Williams %R", "Indicators")
;
_stopLossPoints = Param(nameof(StopLossPoints), 2000m)
.SetDisplay("Stop Loss", "Stop loss distance in price points", "Risk")
;
_takeProfitPoints = Param(nameof(TakeProfitPoints), 0m)
.SetDisplay("Take Profit", "Take profit distance in price points", "Risk")
;
_trailingStopPoints = Param(nameof(TrailingStopPoints), 0m)
.SetDisplay("Trailing Stop", "Trailing stop distance in price points", "Risk")
;
_trailingStepPoints = Param(nameof(TrailingStepPoints), 1m)
.SetDisplay("Trailing Step", "Minimum improvement to trail stop", "Risk")
;
_breakEvenPoints = Param(nameof(BreakEvenPoints), 0m)
.SetDisplay("Break Even", "Distance to move stop to break-even", "Risk")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Candle type used for calculations", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security, DataType)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetStops();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var stochastic = new StochasticOscillator();
stochastic.K.Length = StochasticPeriod;
stochastic.D.Length = StochasticD;
var williams = new WilliamsR
{
Length = WilliamsPeriod
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(stochastic, williams, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, stochastic);
DrawIndicator(area, williams);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue stochasticValue, IIndicatorValue williamsValue)
{
// Only react to fully formed candles to mirror MQL logic.
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (!stochasticValue.IsFinal || !williamsValue.IsFinal)
return;
var stoch = (StochasticOscillatorValue)stochasticValue;
if (stoch.D is not decimal signal)
return;
var wpr = williamsValue.ToDecimal();
var step = Security?.PriceStep ?? 1m;
if (step <= 0m)
step = 1m;
ManageLongPosition(candle, step);
ManageShortPosition(candle, step);
// Generate entries only when no opposite position exists.
if (signal < 5m && wpr < -95m)
{
HandleBuySignal(candle, step);
}
else if (signal > 95m && wpr > -5m)
{
HandleSellSignal(candle, step);
}
}
private void ManageLongPosition(ICandleMessage candle, decimal step)
{
if (Position <= 0)
return;
// Move stop to entry once break-even condition is reached.
if (BreakEvenPoints > 0m && candle.ClosePrice - _entryPrice >= BreakEvenPoints * step &&
(_stopPrice == 0m || _stopPrice < _entryPrice))
{
_stopPrice = _entryPrice;
}
// Tighten trailing stop when price moves favorably.
if (TrailingStopPoints > 0m)
{
var candidateStop = candle.ClosePrice - TrailingStopPoints * step;
if (_stopPrice == 0m || candidateStop - _stopPrice >= TrailingStepPoints * step)
{
_stopPrice = candidateStop;
}
}
// Exit position when stop or target is triggered.
var stopHit = _stopPrice > 0m && candle.LowPrice <= _stopPrice;
var targetHit = _takeProfitPrice > 0m && candle.HighPrice >= _takeProfitPrice;
if (stopHit || targetHit)
{
SellMarket(Position);
ResetStops();
}
}
private void ManageShortPosition(ICandleMessage candle, decimal step)
{
if (Position >= 0)
return;
if (BreakEvenPoints > 0m && _entryPrice - candle.ClosePrice >= BreakEvenPoints * step &&
(_stopPrice == 0m || _stopPrice > _entryPrice))
{
_stopPrice = _entryPrice;
}
if (TrailingStopPoints > 0m)
{
var candidateStop = candle.ClosePrice + TrailingStopPoints * step;
if (_stopPrice == 0m || _stopPrice - candidateStop >= TrailingStepPoints * step)
{
_stopPrice = candidateStop;
}
}
var stopHit = _stopPrice > 0m && candle.HighPrice >= _stopPrice;
var targetHit = _takeProfitPrice > 0m && candle.LowPrice <= _takeProfitPrice;
if (stopHit || targetHit)
{
BuyMarket(Math.Abs(Position));
ResetStops();
}
}
private void HandleBuySignal(ICandleMessage candle, decimal step)
{
if (Position < 0)
{
// Close short before opening a long position.
BuyMarket(Math.Abs(Position));
ResetStops();
}
if (Position > 0 || TradeVolume <= 0m)
return;
BuyMarket(TradeVolume);
_entryPrice = candle.ClosePrice;
_stopPrice = StopLossPoints > 0m ? _entryPrice - StopLossPoints * step : 0m;
_takeProfitPrice = TakeProfitPoints > 0m ? _entryPrice + TakeProfitPoints * step : 0m;
}
private void HandleSellSignal(ICandleMessage candle, decimal step)
{
if (Position > 0)
{
// Close long before opening a short position.
SellMarket(Position);
ResetStops();
}
if (Position < 0 || TradeVolume <= 0m)
return;
SellMarket(TradeVolume);
_entryPrice = candle.ClosePrice;
_stopPrice = StopLossPoints > 0m ? _entryPrice + StopLossPoints * step : 0m;
_takeProfitPrice = TakeProfitPoints > 0m ? _entryPrice - TakeProfitPoints * step : 0m;
}
private void ResetStops()
{
_entryPrice = 0m;
_stopPrice = 0m;
_takeProfitPrice = 0m;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import StochasticOscillator, WilliamsR
class master_mind2_strategy(Strategy):
def __init__(self):
super(master_mind2_strategy, self).__init__()
self._trade_volume = self.Param("TradeVolume", 0.1) \
.SetDisplay("Trade Volume", "Trade volume in contracts", "General")
self._stochastic_period = self.Param("StochasticPeriod", 100) \
.SetDisplay("Stochastic Period", "Period for the Stochastic Oscillator", "Indicators")
self._stochastic_k = self.Param("StochasticK", 3) \
.SetDisplay("Stochastic %K", "Smoothing length for %K", "Indicators")
self._stochastic_d = self.Param("StochasticD", 3) \
.SetDisplay("Stochastic %D", "Smoothing length for %D", "Indicators")
self._williams_period = self.Param("WilliamsPeriod", 100) \
.SetDisplay("Williams %R Period", "Lookback for Williams %R", "Indicators")
self._stop_loss_points = self.Param("StopLossPoints", 2000.0) \
.SetDisplay("Stop Loss", "Stop loss distance in price points", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 0.0) \
.SetDisplay("Take Profit", "Take profit distance in price points", "Risk")
self._trailing_stop_points = self.Param("TrailingStopPoints", 0.0) \
.SetDisplay("Trailing Stop", "Trailing stop distance in price points", "Risk")
self._trailing_step_points = self.Param("TrailingStepPoints", 1.0) \
.SetDisplay("Trailing Step", "Minimum improvement to trail stop", "Risk")
self._break_even_points = self.Param("BreakEvenPoints", 0.0) \
.SetDisplay("Break Even", "Distance to move stop to break-even", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Candle type used for calculations", "General")
self._entry_price = 0.0
self._stop_price = 0.0
self._take_profit_price = 0.0
@property
def TradeVolume(self):
return self._trade_volume.Value
@property
def StochasticPeriod(self):
return self._stochastic_period.Value
@property
def StochasticK(self):
return self._stochastic_k.Value
@property
def StochasticD(self):
return self._stochastic_d.Value
@property
def WilliamsPeriod(self):
return self._williams_period.Value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@property
def TakeProfitPoints(self):
return self._take_profit_points.Value
@property
def TrailingStopPoints(self):
return self._trailing_stop_points.Value
@property
def TrailingStepPoints(self):
return self._trailing_step_points.Value
@property
def BreakEvenPoints(self):
return self._break_even_points.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(master_mind2_strategy, self).OnStarted2(time)
stochastic = StochasticOscillator()
stochastic.K.Length = self.StochasticPeriod
stochastic.D.Length = self.StochasticD
williams = WilliamsR()
williams.Length = self.WilliamsPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(stochastic, williams, self.ProcessCandle).Start()
def ProcessCandle(self, candle, stochastic_value, williams_value):
if candle.State != CandleStates.Finished:
return
if not stochastic_value.IsFinal or not williams_value.IsFinal:
return
stoch = stochastic_value
signal = stoch.D
if signal is None:
return
signal = float(signal)
wpr = float(williams_value)
ps = self.Security.PriceStep if self.Security is not None else None
step = float(ps) if ps is not None else 1.0
if step <= 0:
step = 1.0
self._manage_long_position(candle, step)
self._manage_short_position(candle, step)
if signal < 5.0 and wpr < -95.0:
self._handle_buy_signal(candle, step)
elif signal > 95.0 and wpr > -5.0:
self._handle_sell_signal(candle, step)
def _manage_long_position(self, candle, step):
if self.Position <= 0:
return
be = float(self.BreakEvenPoints)
if be > 0 and float(candle.ClosePrice) - self._entry_price >= be * step:
if self._stop_price == 0 or self._stop_price < self._entry_price:
self._stop_price = self._entry_price
ts = float(self.TrailingStopPoints)
if ts > 0:
candidate_stop = float(candle.ClosePrice) - ts * step
tst = float(self.TrailingStepPoints)
if self._stop_price == 0 or candidate_stop - self._stop_price >= tst * step:
self._stop_price = candidate_stop
stop_hit = self._stop_price > 0 and float(candle.LowPrice) <= self._stop_price
target_hit = self._take_profit_price > 0 and float(candle.HighPrice) >= self._take_profit_price
if stop_hit or target_hit:
self.SellMarket(self.Position)
self._reset_stops()
def _manage_short_position(self, candle, step):
if self.Position >= 0:
return
be = float(self.BreakEvenPoints)
if be > 0 and self._entry_price - float(candle.ClosePrice) >= be * step:
if self._stop_price == 0 or self._stop_price > self._entry_price:
self._stop_price = self._entry_price
ts = float(self.TrailingStopPoints)
if ts > 0:
candidate_stop = float(candle.ClosePrice) + ts * step
tst = float(self.TrailingStepPoints)
if self._stop_price == 0 or self._stop_price - candidate_stop >= tst * step:
self._stop_price = candidate_stop
stop_hit = self._stop_price > 0 and float(candle.HighPrice) >= self._stop_price
target_hit = self._take_profit_price > 0 and float(candle.LowPrice) <= self._take_profit_price
if stop_hit or target_hit:
self.BuyMarket(abs(self.Position))
self._reset_stops()
def _handle_buy_signal(self, candle, step):
if self.Position < 0:
self.BuyMarket(abs(self.Position))
self._reset_stops()
tv = float(self.TradeVolume)
if self.Position > 0 or tv <= 0:
return
self.BuyMarket(tv)
self._entry_price = float(candle.ClosePrice)
sl = float(self.StopLossPoints)
tp = float(self.TakeProfitPoints)
self._stop_price = self._entry_price - sl * step if sl > 0 else 0.0
self._take_profit_price = self._entry_price + tp * step if tp > 0 else 0.0
def _handle_sell_signal(self, candle, step):
if self.Position > 0:
self.SellMarket(self.Position)
self._reset_stops()
tv = float(self.TradeVolume)
if self.Position < 0 or tv <= 0:
return
self.SellMarket(tv)
self._entry_price = float(candle.ClosePrice)
sl = float(self.StopLossPoints)
tp = float(self.TakeProfitPoints)
self._stop_price = self._entry_price + sl * step if sl > 0 else 0.0
self._take_profit_price = self._entry_price - tp * step if tp > 0 else 0.0
def _reset_stops(self):
self._entry_price = 0.0
self._stop_price = 0.0
self._take_profit_price = 0.0
def OnReseted(self):
super(master_mind2_strategy, self).OnReseted()
self._reset_stops()
def CreateClone(self):
return master_mind2_strategy()