Doctor Strategy
Implementation of strategy #15233 "Doctor" converted from MQL to StockSharp.
Overview
The strategy combines several classic indicators to detect trend direction and momentum:
- Slope detection using a 40-period Weighted Moving Average to evaluate trend direction.
- Linear location via a 400-period Weighted Moving Average compared against the highs and lows of the last three candles.
- Momentum confirmation with Relative Strength Index periods 14 and 5.
- Trend reversal filter from Parabolic SAR.
A long position is opened when all bullish conditions align, and a short position when all bearish conditions align. Existing positions are closed on opposite signals or when protective levels are hit. An optional trailing stop pushes the stop loss forward once half of the stop distance is achieved.
Parameters
StopLossTicks– stop-loss distance in ticks.TakeProfitTicks– take-profit distance in ticks.TrailingStop– enables trailing stop logic.CandleType– timeframe used for candles (default 30 minutes).
Trading rules
- Buy when:
- WMA(40) slope is rising.
- WMA(400) is above the highs of the last three candles.
- RSI(14) is above 50 and RSI(5) is below RSI(14).
- No open long position.
- Sell when:
- WMA(40) slope is falling.
- WMA(400) is below the lows of the last three candles.
- RSI(14) is below 50 and RSI(5) is above RSI(14).
- No open short position.
- Exit when the opposite conditions occur or stop-loss/take-profit levels are reached. The trailing stop updates the stop level after price moves half of the stop distance in favor.
Indicators
- Weighted Moving Average (40, 400)
- Relative Strength Index (14, 5)
- Parabolic SAR
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Doctor strategy ported from MQL. Combines WMA slope, MA position, RSI and PSAR.
/// </summary>
public class DoctorStrategy : Strategy
{
private readonly StrategyParam<int> _stopLossTicks;
private readonly StrategyParam<int> _takeProfitTicks;
private readonly StrategyParam<bool> _trailingStop;
private readonly StrategyParam<DataType> _candleType;
private readonly decimal[] _wma40 = new decimal[2];
private readonly decimal[] _wma400 = new decimal[4];
private readonly decimal[] _high = new decimal[4];
private readonly decimal[] _low = new decimal[4];
private decimal _entryPrice;
private decimal _stopPrice;
private decimal _takePrice;
/// <summary>
/// Stop-loss distance in ticks.
/// </summary>
public int StopLossTicks
{
get => _stopLossTicks.Value;
set => _stopLossTicks.Value = value;
}
/// <summary>
/// Take-profit distance in ticks.
/// </summary>
public int TakeProfitTicks
{
get => _takeProfitTicks.Value;
set => _takeProfitTicks.Value = value;
}
/// <summary>
/// Enable trailing stop logic.
/// </summary>
public bool TrailingStop
{
get => _trailingStop.Value;
set => _trailingStop.Value = value;
}
/// <summary>
/// Candle type for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
private WeightedMovingAverage _wmaSlope = null!;
private WeightedMovingAverage _wmaTrend = null!;
private RelativeStrengthIndex _rsi14 = null!;
private RelativeStrengthIndex _rsi5 = null!;
private ParabolicSar _psar = null!;
/// <summary>
/// Initialize <see cref="DoctorStrategy"/>.
/// </summary>
public DoctorStrategy()
{
_stopLossTicks = Param(nameof(StopLossTicks), 70)
.SetGreaterThanZero()
.SetDisplay("Stop Loss", "Stop-loss distance in ticks", "Risk");
_takeProfitTicks = Param(nameof(TakeProfitTicks), 40)
.SetGreaterThanZero()
.SetDisplay("Take Profit", "Take-profit distance in ticks", "Risk");
_trailingStop = Param(nameof(TrailingStop), true)
.SetDisplay("Trailing Stop", "Use trailing stop", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
Array.Clear(_wma40);
Array.Clear(_wma400);
Array.Clear(_high);
Array.Clear(_low);
_entryPrice = 0m;
_stopPrice = 0m;
_takePrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_wmaSlope = new WeightedMovingAverage { Length = 10 };
_wmaTrend = new WeightedMovingAverage { Length = 50 };
_rsi14 = new RelativeStrengthIndex { Length = 14 };
_rsi5 = new RelativeStrengthIndex { Length = 5 };
_psar = new ParabolicSar();
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_wmaSlope, _wmaTrend, _rsi14, _rsi5, _psar, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _wmaSlope);
DrawIndicator(area, _wmaTrend);
DrawIndicator(area, _psar);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal wma40, decimal wma400, decimal rsi14, decimal rsi5, decimal psar)
{
if (candle.State != CandleStates.Finished)
return;
// Shift history arrays (always, even during warmup)
_wma40[1] = _wma40[0];
_wma40[0] = wma40;
for (var i = 3; i > 0; i--)
{
_wma400[i] = _wma400[i - 1];
_high[i] = _high[i - 1];
_low[i] = _low[i - 1];
}
_wma400[0] = wma400;
_high[0] = candle.HighPrice;
_low[0] = candle.LowPrice;
if (_wma40[1] == 0m || _wma400[3] == 0m)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
// Determine slope direction
var slope = _wma40[0] > _wma40[1] ? 2 : 1;
// Check long-term MA relative to recent bars
var maBelow = _wma400[1] < _low[1] && _wma400[2] < _low[2] && _wma400[3] < _low[3];
var maAbove = _wma400[1] > _high[1] && _wma400[2] > _high[2] && _wma400[3] > _high[3];
var maLinear = maAbove ? 2 : maBelow ? 1 : 0;
// RSI relations
var rsiState = rsi14 < 50m && rsi5 > rsi14 ? 1 : rsi14 > 50m && rsi5 < rsi14 ? 2 : 0;
// Parabolic SAR position
var psarState = psar <= candle.LowPrice ? 1 : psar >= candle.HighPrice ? 2 : 0;
var step = Security?.PriceStep ?? 1m;
var stopDistance = StopLossTicks * step;
var takeDistance = TakeProfitTicks * step;
// Close positions on opposite signals
if (Position > 0 && slope == 1 && (maLinear == 1 || rsiState == 1 || psarState == 2))
{
SellMarket();
}
else if (Position < 0 && slope == 2 && (maLinear == 2 || rsiState == 2 || psarState == 1))
{
BuyMarket();
}
// Trailing and protective exits
if (Position > 0)
{
if (TrailingStop && candle.ClosePrice - _entryPrice > stopDistance / 2m)
_stopPrice = Math.Max(_stopPrice, candle.ClosePrice - stopDistance);
if (candle.LowPrice <= _stopPrice || candle.HighPrice >= _takePrice)
SellMarket();
}
else if (Position < 0)
{
if (TrailingStop && _entryPrice - candle.ClosePrice > stopDistance / 2m)
_stopPrice = Math.Min(_stopPrice, candle.ClosePrice + stopDistance);
if (candle.HighPrice >= _stopPrice || candle.LowPrice <= _takePrice)
BuyMarket();
}
// Entry conditions
if (slope == 2 && (maLinear == 2 || rsiState == 2) && Position <= 0)
{
_entryPrice = candle.ClosePrice;
_stopPrice = _entryPrice - stopDistance;
_takePrice = _entryPrice + takeDistance;
BuyMarket();
}
else if (slope == 1 && (maLinear == 1 || rsiState == 1) && Position >= 0)
{
_entryPrice = candle.ClosePrice;
_stopPrice = _entryPrice + stopDistance;
_takePrice = _entryPrice - takeDistance;
SellMarket();
}
}
}
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.Indicators import WeightedMovingAverage, RelativeStrengthIndex, ParabolicSar
from StockSharp.Algo.Strategies import Strategy
class doctor_strategy(Strategy):
"""
Doctor strategy ported from MQL. Combines WMA slope, MA position, RSI and PSAR.
"""
def __init__(self):
super(doctor_strategy, self).__init__()
self._stop_loss_ticks = self.Param("StopLossTicks", 70) \
.SetDisplay("Stop Loss", "Stop-loss distance in ticks", "Risk")
self._take_profit_ticks = self.Param("TakeProfitTicks", 40) \
.SetDisplay("Take Profit", "Take-profit distance in ticks", "Risk")
self._trailing_stop = self.Param("TrailingStop", True) \
.SetDisplay("Trailing Stop", "Use trailing stop", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe for candles", "General")
self._wma40 = [0.0, 0.0]
self._wma400 = [0.0, 0.0, 0.0, 0.0]
self._high = [0.0, 0.0, 0.0, 0.0]
self._low = [0.0, 0.0, 0.0, 0.0]
self._entry_price = 0.0
self._stop_price = 0.0
self._take_price = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(doctor_strategy, self).OnReseted()
self._wma40 = [0.0, 0.0]
self._wma400 = [0.0, 0.0, 0.0, 0.0]
self._high = [0.0, 0.0, 0.0, 0.0]
self._low = [0.0, 0.0, 0.0, 0.0]
self._entry_price = 0.0
self._stop_price = 0.0
self._take_price = 0.0
def OnStarted2(self, time):
super(doctor_strategy, self).OnStarted2(time)
wma_slope = WeightedMovingAverage()
wma_slope.Length = 10
wma_trend = WeightedMovingAverage()
wma_trend.Length = 50
rsi14 = RelativeStrengthIndex()
rsi14.Length = 14
rsi5 = RelativeStrengthIndex()
rsi5.Length = 5
psar = ParabolicSar()
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(wma_slope, wma_trend, rsi14, rsi5, psar, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, wma_slope)
self.DrawIndicator(area, wma_trend)
self.DrawIndicator(area, psar)
self.DrawOwnTrades(area)
def _process_candle(self, candle, wma40, wma400, rsi14, rsi5, psar):
if candle.State != CandleStates.Finished:
return
wma40 = float(wma40)
wma400 = float(wma400)
rsi14 = float(rsi14)
rsi5 = float(rsi5)
psar = float(psar)
self._wma40[1] = self._wma40[0]
self._wma40[0] = wma40
for i in range(3, 0, -1):
self._wma400[i] = self._wma400[i - 1]
self._high[i] = self._high[i - 1]
self._low[i] = self._low[i - 1]
self._wma400[0] = wma400
self._high[0] = float(candle.HighPrice)
self._low[0] = float(candle.LowPrice)
if self._wma40[1] == 0.0 or self._wma400[3] == 0.0:
return
slope = 2 if self._wma40[0] > self._wma40[1] else 1
ma_below = (self._wma400[1] < self._low[1] and
self._wma400[2] < self._low[2] and
self._wma400[3] < self._low[3])
ma_above = (self._wma400[1] > self._high[1] and
self._wma400[2] > self._high[2] and
self._wma400[3] > self._high[3])
ma_linear = 2 if ma_above else (1 if ma_below else 0)
rsi_state = 0
if rsi14 < 50 and rsi5 > rsi14:
rsi_state = 1
elif rsi14 > 50 and rsi5 < rsi14:
rsi_state = 2
psar_state = 0
if psar <= float(candle.LowPrice):
psar_state = 1
elif psar >= float(candle.HighPrice):
psar_state = 2
step = 1.0
if self.Security is not None and self.Security.PriceStep is not None:
step = float(self.Security.PriceStep)
if step <= 0:
step = 1.0
stop_distance = self._stop_loss_ticks.Value * step
take_distance = self._take_profit_ticks.Value * step
if self.Position > 0 and slope == 1 and (ma_linear == 1 or rsi_state == 1 or psar_state == 2):
self.SellMarket()
elif self.Position < 0 and slope == 2 and (ma_linear == 2 or rsi_state == 2 or psar_state == 1):
self.BuyMarket()
close = float(candle.ClosePrice)
if self.Position > 0:
if self._trailing_stop.Value and close - self._entry_price > stop_distance / 2.0:
self._stop_price = max(self._stop_price, close - stop_distance)
if float(candle.LowPrice) <= self._stop_price or float(candle.HighPrice) >= self._take_price:
self.SellMarket()
elif self.Position < 0:
if self._trailing_stop.Value and self._entry_price - close > stop_distance / 2.0:
self._stop_price = min(self._stop_price, close + stop_distance)
if float(candle.HighPrice) >= self._stop_price or float(candle.LowPrice) <= self._take_price:
self.BuyMarket()
if slope == 2 and (ma_linear == 2 or rsi_state == 2) and self.Position <= 0:
self._entry_price = close
self._stop_price = self._entry_price - stop_distance
self._take_price = self._entry_price + take_distance
self.BuyMarket()
elif slope == 1 and (ma_linear == 1 or rsi_state == 1) and self.Position >= 0:
self._entry_price = close
self._stop_price = self._entry_price + stop_distance
self._take_price = self._entry_price - take_distance
self.SellMarket()
def CreateClone(self):
return doctor_strategy()