La Estrategia ACB1 es el puerto StockSharp del asesor experto MetaTrader distribuido como MQL/8586/ACB1.MQ4. El sistema original opera con el par EURUSD y espera fuertes rupturas diarias antes de ingresar al mercado. Esta conversión reproduce el mismo proceso de decisión con StockSharp primitivas de alto nivel:
Las velas diarias (SignalCandleType) definen la dirección de ruptura y proporcionan los anclajes de parada y toma de ganancias.
Las velas H4 (TrailCandleType) determinan la distancia de seguimiento que se multiplica por TrailFactor.
Las órdenes se ejecutan en el mercado una vez que se cumplen las condiciones de ruptura y la estrategia mantiene solo una posición neta, reflejando las comprobaciones OrdersTotal() en el código MQL.
El stop-loss y el take-profit se gestionan internamente: la estrategia vigila los mejores precios de oferta y demanda y cierra la posición con órdenes de mercado cuando se superan los niveles de protección virtual.
Reglas comerciales
Configuración larga
Utilice la vela diaria terminada anteriormente.
Si Close > (High + Low) / 2y el precio de venta actual está por encima del máximo anterior, abra una posición larga en el mercado.
El stop-loss se coloca en el mínimo anterior (redondeado al nivel del precio del instrumento).
La toma de ganancias es igual al precio de entrada más (High − Low) × TakeFactor.
Configuración corta
Si Close < (High + Low) / 2y el precio de oferta actual está por debajo del mínimo anterior, abra una posición corta en el mercado.
El stop-loss se fija en el máximo anterior; la toma de ganancias resta (High − Low) × TakeFactor del precio de entrada.
Parada de seguimiento
Las TrailCandleType velas terminadas más recientes suministran (High − Low) × TrailFactor.
Para posiciones largas, el stop sigue a Bid − TrailDistance mientras el precio permanece por debajo del nivel de obtención de beneficios menos el stop del corredor.
Para posiciones cortas, el stop sigue Ask + TrailDistance mientras que el precio se mantiene por encima del nivel de toma de ganancias más el stop del corredor.
Guardia de riesgos
La estrategia rastrea el capital máximo observado de la cartera. La negociación se detiene cada vez que el capital actual cae por debajo del 50% de ese pico, exactamente como en el asesor original.
Un tiempo de reutilización de cinco segundos (CooldownSeconds) evita nuevos pedidos o detiene las actualizaciones con demasiada frecuencia, reproduciendo el acelerador TimeLocal() de MQL.
Dimensionamiento de posiciones y control de riesgos
El volumen por operación se deriva de Portfolio.CurrentValue × RiskFraction.
El riesgo monetario por contrato se calcula a partir de la distancia de parada y los metadatos de seguridad (PriceStep y StepPrice).
El tamaño resultante se alinea con Security.VolumeStep y se fija en [Security.MinVolume, Security.MaxVolume], luego se limita por el parámetro MaxVolume (5 lotes predeterminados).
Las órdenes se omiten cuando el volumen normalizado es cero o cuando la distancia de parada viola MinStopDistancePoints, lo que emula la verificación MetaTrader MODE_STOPLEVEL.
Parámetros
Parámetro
Predeterminado
Descripción
SignalCandleType
Diariamente
Tipo de vela utilizado para la detección de fugas.
TrailCandleType
4 horas
Tipo de vela que suministra la distancia del trailing stop.
TakeFactor
0,8
Multiplicador aplicado al rango diario para calcular la obtención de beneficios.
TrailFactor
10
Multiplicador aplicado al rango final al actualizar la parada.
RiskFraction
0,05
Fracción del capital de la cartera arriesgada en cada operación (5%).
MaxVolume
5
Límite estricto para el volumen final del pedido.
MinStopDistancePoints
0
Distancia mínima de parada/toma expresada en puntos de precio; configúrelo en el corredor MODE_STOPLEVEL.
CooldownSeconds
5
Retraso mínimo entre acciones comerciales consecutivas.
Notas de implementación
La estrategia requiere metadatos de instrumentos adecuados: Security.PriceStep, Security.StepPrice, Security.VolumeStep, Security.MinVolume y (si está disponible) Security.MaxVolume.
Los niveles de protección son virtuales. StockSharp cierra posiciones mediante órdenes de mercado cuando la oferta/demanda toca el límite de pérdidas o la toma de ganancias calculados.
El seguimiento de acciones utiliza Portfolio.CurrentValue. Si el conector no proporciona este campo, la protección contra riesgos mantendrá las operaciones desactivadas hasta que esté disponible.
Sólo se mantiene una única posición neta. Las señales opuestas mientras una operación está activa se ignoran hasta que la posición se cierra por completo.
No se incluye ningún puerto Python; este directorio solo contiene la implementación y documentación de C#.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Breakout strategy converted from the "ACB1" MetaTrader expert advisor.
/// Enters on breakouts above previous candle high / below previous candle low,
/// with trailing stop based on ATR.
/// </summary>
public class Acb1Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _takeFactor;
private readonly StrategyParam<decimal> _trailFactor;
private decimal _prevHigh;
private decimal _prevLow;
private decimal _prevClose;
private decimal _prevMid;
private decimal _entryPrice;
private decimal _stopPrice;
private bool _hasPrev;
public Acb1Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for breakout detection.", "General");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetDisplay("ATR Period", "Period for ATR indicator used in trailing.", "Indicators");
_takeFactor = Param(nameof(TakeFactor), 2m)
.SetDisplay("Take Factor", "ATR multiplier for take profit distance.", "Execution");
_trailFactor = Param(nameof(TrailFactor), 1.5m)
.SetDisplay("Trail Factor", "ATR multiplier for trailing stop distance.", "Execution");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
public decimal TakeFactor
{
get => _takeFactor.Value;
set => _takeFactor.Value = value;
}
public decimal TrailFactor
{
get => _trailFactor.Value;
set => _trailFactor.Value = value;
}
/// <inheritdoc />
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevHigh = 0;
_prevLow = 0;
_prevClose = 0;
_prevMid = 0;
_entryPrice = 0;
_stopPrice = 0;
_hasPrev = false;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevHigh = 0;
_prevLow = 0;
_prevClose = 0;
_prevMid = 0;
_entryPrice = 0;
_stopPrice = 0;
_hasPrev = false;
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, atr);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (atrValue <= 0)
return;
// Manage open position
if (Position != 0)
{
if (Position > 0)
{
// Trailing stop for long
var newStop = candle.ClosePrice - atrValue * TrailFactor;
if (newStop > _stopPrice)
_stopPrice = newStop;
// Check stop hit
if (candle.LowPrice <= _stopPrice)
{
SellMarket();
_entryPrice = 0;
_stopPrice = 0;
}
// Check take profit
else if (_entryPrice > 0 && candle.HighPrice >= _entryPrice + atrValue * TakeFactor)
{
SellMarket();
_entryPrice = 0;
_stopPrice = 0;
}
}
else
{
// Trailing stop for short
var newStop = candle.ClosePrice + atrValue * TrailFactor;
if (_stopPrice == 0 || newStop < _stopPrice)
_stopPrice = newStop;
// Check stop hit
if (candle.HighPrice >= _stopPrice)
{
BuyMarket();
_entryPrice = 0;
_stopPrice = 0;
}
// Check take profit
else if (_entryPrice > 0 && candle.LowPrice <= _entryPrice - atrValue * TakeFactor)
{
BuyMarket();
_entryPrice = 0;
_stopPrice = 0;
}
}
}
// Entry logic after managing position
if (_hasPrev && Position == 0)
{
if (_prevClose > _prevMid && candle.ClosePrice > _prevHigh)
{
// Bullish breakout
BuyMarket();
_entryPrice = candle.ClosePrice;
_stopPrice = _prevLow;
}
else if (_prevClose < _prevMid && candle.ClosePrice < _prevLow)
{
// Bearish breakout
SellMarket();
_entryPrice = candle.ClosePrice;
_stopPrice = _prevHigh;
}
}
// Store for next candle
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
_prevClose = candle.ClosePrice;
_prevMid = (candle.HighPrice + candle.LowPrice) / 2m;
_hasPrev = true;
}
}
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 CandleStates
from StockSharp.Algo.Indicators import AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
class acb1_strategy(Strategy):
"""
Breakout strategy converted from the "ACB1" MetaTrader expert advisor.
Enters on breakouts above previous candle high / below previous candle low,
with trailing stop based on ATR.
"""
def __init__(self):
super(acb1_strategy, self).__init__()
self._candle_type = self.Param("CandleType", tf(5)) \
.SetDisplay("Candle Type", "Timeframe for breakout detection.", "General")
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "Period for ATR indicator used in trailing.", "Indicators")
self._take_factor = self.Param("TakeFactor", 2.0) \
.SetDisplay("Take Factor", "ATR multiplier for take profit distance.", "Execution")
self._trail_factor = self.Param("TrailFactor", 1.5) \
.SetDisplay("Trail Factor", "ATR multiplier for trailing stop distance.", "Execution")
self._prev_high = 0.0
self._prev_low = 0.0
self._prev_close = 0.0
self._prev_mid = 0.0
self._entry_price = 0.0
self._stop_price = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def AtrPeriod(self):
return self._atr_period.Value
@AtrPeriod.setter
def AtrPeriod(self, value):
self._atr_period.Value = value
@property
def TakeFactor(self):
return self._take_factor.Value
@TakeFactor.setter
def TakeFactor(self, value):
self._take_factor.Value = value
@property
def TrailFactor(self):
return self._trail_factor.Value
@TrailFactor.setter
def TrailFactor(self, value):
self._trail_factor.Value = value
def OnReseted(self):
super(acb1_strategy, self).OnReseted()
self._prev_high = 0.0
self._prev_low = 0.0
self._prev_close = 0.0
self._prev_mid = 0.0
self._entry_price = 0.0
self._stop_price = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(acb1_strategy, self).OnStarted2(time)
self._prev_high = 0.0
self._prev_low = 0.0
self._prev_close = 0.0
self._prev_mid = 0.0
self._entry_price = 0.0
self._stop_price = 0.0
self._has_prev = False
atr = AverageTrueRange()
atr.Length = self.AtrPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(atr, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, atr)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
if atr_value <= 0:
return
# Manage open position
if self.Position != 0:
if self.Position > 0:
# Trailing stop for long
new_stop = float(candle.ClosePrice) - atr_value * self.TrailFactor
if new_stop > self._stop_price:
self._stop_price = new_stop
# Check stop hit
if float(candle.LowPrice) <= self._stop_price:
self.SellMarket()
self._entry_price = 0.0
self._stop_price = 0.0
# Check take profit
elif self._entry_price > 0 and float(candle.HighPrice) >= self._entry_price + atr_value * self.TakeFactor:
self.SellMarket()
self._entry_price = 0.0
self._stop_price = 0.0
else:
# Trailing stop for short
new_stop = float(candle.ClosePrice) + atr_value * self.TrailFactor
if self._stop_price == 0 or new_stop < self._stop_price:
self._stop_price = new_stop
# Check stop hit
if float(candle.HighPrice) >= self._stop_price:
self.BuyMarket()
self._entry_price = 0.0
self._stop_price = 0.0
# Check take profit
elif self._entry_price > 0 and float(candle.LowPrice) <= self._entry_price - atr_value * self.TakeFactor:
self.BuyMarket()
self._entry_price = 0.0
self._stop_price = 0.0
# Entry logic after managing position
if self._has_prev and self.Position == 0:
if self._prev_close > self._prev_mid and float(candle.ClosePrice) > self._prev_high:
# Bullish breakout
self.BuyMarket()
self._entry_price = float(candle.ClosePrice)
self._stop_price = self._prev_low
elif self._prev_close < self._prev_mid and float(candle.ClosePrice) < self._prev_low:
# Bearish breakout
self.SellMarket()
self._entry_price = float(candle.ClosePrice)
self._stop_price = self._prev_high
# Store for next candle
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
self._prev_close = float(candle.ClosePrice)
self._prev_mid = (float(candle.HighPrice) + float(candle.LowPrice)) / 2.0
self._has_prev = True
def CreateClone(self):
"""!! REQUIRED!! Creates a new instance of the strategy."""
return acb1_strategy()