Стратегия MasterMind 2
Обзор
MasterMind 2 — это конвертация советника "TheMasterMind2" с платформы MQL4. Стратегия ждёт экстремальных значений индикаторов Stochastic Oscillator и Williams %R, чтобы определить истощение движения. При одновременном подтверждении перепроданности открывается длинная позиция, а при подтверждённой перекупленности открывается короткая позиция. Обработка ведётся только по закрытым свечам, что повторяет логику оригинального советника.
Индикаторы
- Stochastic Oscillator — используется с большим окном расчёта, чтобы отслеживать зоны перекупленности и перепроданности. В качестве сигнала используется линия %D.
- Williams %R — служит дополнительным фильтром, требуя значений, близких к -100 для покупок и к 0 для продаж.
Правила входа
- Дождаться закрытия очередной свечи.
- Рассчитать Stochastic и получить значение сигнальной линии %D.
- Рассчитать Williams %R на выбранном интервале.
- Покупка: если
%D < 3иWilliams %R < -99.9, закрываются все короткие позиции и открывается покупка. - Продажа: если
%D > 97иWilliams %R > -0.1, закрываются все длинные позиции и открывается продажа.
Правила выхода
- Стоп-лосс и тейк-профит задаются в пунктах относительно цены входа.
- Трейлинг-стоп позволяет подтягивать защитный уровень при благоприятном движении цены.
- Опция безубыточности переносит стоп-лосс на цену входа после накопления заданной прибыли.
- Появление обратного сигнала приводит к немедленному закрытию текущей позиции перед открытием новой.
Параметры
Trade Volume— объём заявки в контрактах.Stochastic Period,Stochastic %K,Stochastic %D— параметры Stochastic Oscillator.Williams %R Period— период расчёта Williams %R.Stop Loss,Take Profit— защитные уровни в пунктах.Trailing Stop,Trailing Step— параметры управления трейлинг-стопом.Break Even— расстояние до перевода сделки в безубыток.Candle Type— тип свечей или таймфрейм, используемый в расчётах.
Примечания
- Стратегия обрабатывает только свечи в состоянии
Finished, исключая сигналы на незавершённых барах. - Все заявки регистрируются по рынку с объёмом
Trade Volume. - Установка нуля в параметрах расстояний отключает соответствующие защитные механизмы.
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()