La estrategia replica el experto de MetaTrader "iMA iStochastic Custom" dentro del framework de StockSharp. Combina un envolvente de media móvil con un filtro de oscilador estocástico. El trading tiene lugar en las velas completadas del marco temporal seleccionado (CandleType). Todos los comentarios a continuación utilizan la misma nomenclatura que el asesor original.
Componentes clave:
Envolvente de media móvil – la media móvil base se desplaza por LevelUpPips y LevelDownPips (expresados en pips) para construir bandas de resistencia y soporte. El método de promediación coincide con las opciones de MetaTrader: Simple, Exponencial, Suavizada (SMMA) y Ponderada Lineal (LWMA).
Oscilador estocástico – las longitudes de %K, %D y suavizado siguen los parámetros originales. Dos umbrales (StochasticLevel1 y StochasticLevel2) validan condiciones de sobrecompra/sobreventa.
Gestión monetaria – el selector original de lot/risk se preserva a través del parámetro ManagementMode. En modo FixedLot, el tamaño de la orden equivale a VolumeValue. En modo RiskPercent, la estrategia arriesga el porcentaje configurado del patrimonio de la cartera contra la distancia de stop-loss, reproduciendo el comportamiento de CMoneyFixedMargin.
Protecciones – las distancias de stop-loss, take-profit y trailing se ingresan en pips. El trailing se actualiza en velas completadas, replicando la lógica MQL mientras permanece compatible con el modelo de eventos de StockSharp.
Lógica de trading
Las señales largas y cortas son simétricas:
Compra cuando el cierre de la vela está por encima del envolvente superior (ma + LevelUpPips) y cualquiera de las líneas del estocástico está por encima de StochasticLevel1.
Venta cuando el cierre de la vela está por debajo del envolvente inferior (ma + LevelDownPips) y cualquiera de las líneas del estocástico está por debajo de StochasticLevel2.
Establecer ReverseSignals = true intercambia la dirección de entrada.
Solo una posición neta está activa a la vez. Cuando la señal cambia, la estrategia envía una orden suficientemente grande para aplanar la exposición actual y abrir una nueva posición en la dirección opuesta.
Control de riesgo y salidas
Stop-loss / take-profit – distancias en pips convertidas mediante el PriceStep del instrumento. Se verifican en cada vela finalizada usando el máximo/mínimo de la vela.
Trailing stop – comienza después de que el precio se ha movido TrailingStopPips a favor de la posición. Requiere una mejora adicional de TrailingStepPips antes de cada ajuste, igual que la rutina de trailing MQL.
Gestión monetaria – en modo de riesgo el tamaño de posición es equity * VolumeValue / 100 / perUnitRisk, donde perUnitRisk es la pérdida monetaria por un lote hasta el stop-loss.
Parámetros
Parámetro
Descripción
CandleType
Marco temporal usado para los cálculos.
StopLossPips, TakeProfitPips
Distancias protectoras en pips.
TrailingStopPips, TrailingStepPips
Activación del trailing y paso (pips). Establecer cero para deshabilitar.
ManagementMode, VolumeValue
Dimensionamiento de lote fijo o porcentaje de riesgo.
MaPeriod, MaShift, MaMethod
Longitud de la media móvil, desplazamiento de barras y método (SMA/EMA/SMMA/LWMA).
LevelUpPips, LevelDownPips
Desplazamientos del envolvente superior/inferior en pips. Se permiten valores negativos para la banda inferior.
Niveles de confirmación para verificaciones de compra/venta.
ReverseSignals
Invertir la dirección de todas las operaciones.
Notas de implementación
Las velas, indicadores y órdenes están conectados a través de la API de alto nivel (SubscribeCandles().BindEx(...)).
El tamaño del pip se ajusta automáticamente a símbolos forex de 3/5 dígitos multiplicando el PriceStep cuando es necesario.
La lógica de trailing se ejecuta en velas completadas. Si se requiere trailing intrabarra, conectar la lógica a datos de nivel tick.
No se proporciona port en Python; la carpeta PY está intencionalmente ausente según lo solicitado.
Diferencias respecto a la versión de MetaTrader
El dimensionamiento de riesgo es explícito y se basa en métricas del portafolio de StockSharp en lugar de la clase auxiliar CMoneyFixedMargin. Los lotes resultantes coinciden con el comportamiento original cuando el stop-loss está habilitado; con stop-loss cero el tamaño de posición permanece cero, reflejando la salvaguarda MQL.
Las verificaciones de protección (stop-loss, take-profit, trailing) se evalúan en velas completadas porque las estrategias de StockSharp son orientadas a eventos. Esto mantiene la lógica determinista y coincide con las restricciones de backtesting.
El logging se simplifica a la salida estándar de StockSharp; el flag verboso InpPrintLog no se transfiere.
Utilice esta estrategia como reemplazo directo al migrar de MetaTrader a StockSharp Designer o Runner. Ajuste los parámetros para adaptarse al instrumento y marco temporal objetivo.
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;
public class IMAIStochasticCustomStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _stopLossPoints;
private readonly StrategyParam<int> _takeProfitPoints;
private ExponentialMovingAverage _fast;
private ExponentialMovingAverage _slow;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private int _cooldown;
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public IMAIStochasticCustomStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
}
protected override void OnReseted()
{
base.OnReseted();
_fast = null; _slow = null;
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fast = new ExponentialMovingAverage { Length = FastPeriod };
_slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(_fast, _slow, ProcessCandle);
subscription.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished) return;
if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }
var close = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
if (Position > 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
else if (Position < 0 && _entryPrice > 0)
{
if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
}
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }
_prevFast = fastValue; _prevSlow = slowValue;
}
}
import clr
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class imai_stochastic_custom_strategy(Strategy):
def __init__(self):
super(imai_stochastic_custom_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 14) \
.SetDisplay("Fast Period", "Fast MA period", "Indicator")
self._slow_period = self.Param("SlowPeriod", 50) \
.SetDisplay("Slow Period", "Slow MA period", "Indicator")
self._stop_loss_points = self.Param("StopLossPoints", 200) \
.SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk")
self._take_profit_points = self.Param("TakeProfitPoints", 400) \
.SetDisplay("Take Profit", "Take-profit in price steps", "Risk")
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
@property
def fast_period(self):
return self._fast_period.Value
@property
def slow_period(self):
return self._slow_period.Value
@property
def stop_loss_points(self):
return self._stop_loss_points.Value
@property
def take_profit_points(self):
return self._take_profit_points.Value
def OnReseted(self):
super(imai_stochastic_custom_strategy, self).OnReseted()
self._fast = None
self._slow = None
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._cooldown = 0
def OnStarted2(self, time):
super(imai_stochastic_custom_strategy, self).OnStarted2(time)
self._fast = ExponentialMovingAverage()
self._fast.Length = self.fast_period
self._slow = ExponentialMovingAverage()
self._slow.Length = self.slow_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(5)))
subscription.Bind(self._fast, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if not self._fast.IsFormed or not self._slow.IsFormed:
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._cooldown > 0:
self._cooldown -= 1
self._prev_fast = fast_val
self._prev_slow = slow_val
return
close = float(candle.ClosePrice)
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
if self.Position > 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close <= self._entry_price - self.stop_loss_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close >= self._entry_price + self.take_profit_points * step:
self.SellMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
elif self.Position < 0 and self._entry_price > 0:
if self.stop_loss_points > 0 and close >= self._entry_price + self.stop_loss_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self.take_profit_points > 0 and close <= self._entry_price - self.take_profit_points * step:
self.BuyMarket()
self._entry_price = 0.0
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
return
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._entry_price = close
self._cooldown = 100
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._entry_price = close
self._cooldown = 100
self._prev_fast = fast_val
self._prev_slow = slow_val
def CreateClone(self):
return imai_stochastic_custom_strategy()