Estrategia Doctor
Implementación de la estrategia #15233 "Doctor" convertida de MQL a StockSharp.
Descripción general
La estrategia combina varios indicadores clásicos para detectar la dirección de la tendencia y el momentum:
- Detección de pendiente usando una Media Móvil Ponderada de 40 períodos para evaluar la dirección de la tendencia.
- Ubicación lineal mediante una Media Móvil Ponderada de 400 períodos comparada con los máximos y mínimos de las últimas tres velas.
- Confirmación de momentum con el Índice de Fuerza Relativa de períodos 14 y 5.
- Filtro de reversión de tendencia del Parabolic SAR.
Se abre una posición larga cuando todas las condiciones alcistas se alinean, y una posición corta cuando todas las bajistas se alinean. Las posiciones existentes se cierran ante señales opuestas o cuando se alcanzan los niveles de protección. Un trailing stop opcional avanza el stop loss una vez que se alcanza la mitad de la distancia del stop.
Parámetros
StopLossTicks– distancia del stop loss en ticks.TakeProfitTicks– distancia del take profit en ticks.TrailingStop– activa la lógica del trailing stop.CandleType– marco temporal usado para las velas (predeterminado 30 minutos).
Reglas de trading
- Comprar cuando:
- La pendiente de WMA(40) es ascendente.
- WMA(400) está por encima de los máximos de las últimas tres velas.
- RSI(14) está por encima de 50 y RSI(5) está por debajo de RSI(14).
- No hay posición larga abierta.
- Vender cuando:
- La pendiente de WMA(40) es descendente.
- WMA(400) está por debajo de los mínimos de las últimas tres velas.
- RSI(14) está por debajo de 50 y RSI(5) está por encima de RSI(14).
- No hay posición corta abierta.
- Salir cuando ocurren las condiciones opuestas o se alcanzan los niveles de stop loss/take profit. El trailing stop actualiza el nivel de stop después de que el precio se mueve la mitad de la distancia del stop a favor.
Indicadores
- Media Móvil Ponderada (40, 400)
- Índice de Fuerza Relativa (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()