La estrategia Cycle Lines es la adaptación a StockSharp del asesor experto de MetaTrader "Cycle Lines". El script original combinaba dibujo en el gráfico con botones manuales de trading. Esta versión se centra en la lógica de negociación automatizada que acompañaba a esos controles. La estrategia opera cruces de la línea MACD, mantiene el riesgo bajo control estricto mediante límites absolutos de stop-loss y take-profit, y admite gestión de break-even y trailing stop.
Cuando la línea MACD cruza por encima de su línea de señal, la estrategia abre una posición larga. Cuando la línea cruza por debajo de la línea de señal, abre una posición corta. Las operaciones abiertas se cierran si el indicador cambia a la dirección opuesta o si se activa cualquiera de las reglas de protección (stop-loss, take-profit, break-even o trailing stop).
Reglas de trading
Condiciones de entrada
Largo: MACD cruza por encima de la línea de señal en la serie de velas seleccionada.
Corto: MACD cruza por debajo de la línea de señal en la serie de velas seleccionada.
Las entradas solo se evalúan después de que el indicador esté completamente formado y la estrategia esté conectada y autorizada para operar.
Condiciones de salida
Cruce MACD opuesto.
Stop-loss alcanzado.
Take-profit alcanzado.
Nivel de protección break-even tocado.
Nivel de trailing stop tocado.
Parámetros
Nombre
Descripción
Predeterminado
Notas
Volume
Volumen de orden por operación.
1
Debe ser positivo.
MacdFastPeriod
Período de la EMA rápida dentro del cálculo MACD.
12
Optimizable.
MacdSlowPeriod
Período de la EMA lenta dentro de MACD.
26
Optimizable.
MacdSignalPeriod
Período de la línea de señal MACD.
9
Optimizable.
StopLoss
Distancia absoluta de precio para el stop de protección.
0
Se desactiva cuando se establece en 0.
TakeProfit
Distancia absoluta de precio para el objetivo de take-profit.
0
Se desactiva cuando se establece en 0.
TrailingOffset
Distancia mantenida entre el mejor precio favorable y el trailing stop.
0
Se desactiva cuando se establece en 0.
BreakEvenTrigger
Distancia de ganancia necesaria antes de mover el stop a break-even.
0
Se desactiva cuando se establece en 0.
BreakEvenOffset
Desplazamiento adicional aplicado al nivel de break-even.
0
Permite asegurar algo de ganancia extra por encima/debajo de la entrada.
CandleType
Serie de velas usada para los cálculos de indicadores.
Velas de marco temporal de 15 minutos
Acepta cualquier DataType compatible con StockSharp.
Gestión de posiciones
Stop-loss / take-profit: Ambas comprobaciones evalúan los extremos intrabar (máximo/mínimo) de velas cerradas, asegurando que la salida respete la distancia absoluta configurada desde el precio de entrada.
Break-even: Cuando el precio avanza a favor al menos BreakEvenTrigger, la estrategia arma un stop en entry ± BreakEvenOffset. Cualquier retroceso que toque ese nivel cierra la posición.
Trailing stop: En operaciones largas se vigila el precio máximo alcanzado. El nivel de stop sigue al máximo menos TrailingOffset. En operaciones cortas, la lógica refleja el comportamiento alrededor del precio mínimo.
Notas de uso
La estrategia mantiene una sola posición a la vez. Las señales consecutivas no piramidarán una posición existente.
Los parámetros se exponen como objetos StrategyParam<T> y, por lo tanto, admiten optimización dentro de StockSharp.
Para reproducir el comportamiento predeterminado del EA original, configure los períodos MACD en 12 / 26 / 9 y ajuste los parámetros de riesgo según los valores en pips deseados.
Diferencias con la versión MQL
Las funciones de dibujo en el gráfico y los botones manuales BUY/SELL se omitieron porque StockSharp gestiona la visualización de otra forma.
Todas las reglas de protección se reescribieron para operar sobre datos de velas en lugar de eventos tick de MetaTrader, lo que las mantiene compatibles con la API de alto nivel de StockSharp.
La lógica de trailing y break-even es simétrica para operaciones largas y cortas, por claridad y robustez.
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 CycleLinesStrategy : 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 CycleLinesStrategy()
{
_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 cycle_lines_strategy(Strategy):
def __init__(self):
super(cycle_lines_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(cycle_lines_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(cycle_lines_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 cycle_lines_strategy()