The ACB1 Strategy is the StockSharp port of the MetaTrader expert advisor distributed as MQL/8586/ACB1.MQ4. The original system trades the EURUSD pair and waits for strong daily breakouts before entering the market. This conversion reproduces the same decision process with StockSharp high level primitives:
Daily candles (SignalCandleType) define the breakout direction and provide the stop and take-profit anchors.
H4 candles (TrailCandleType) determine the trailing distance that is multiplied by TrailFactor.
Orders are executed at market once the breakout conditions are satisfied and the strategy keeps only one net position, mirroring the OrdersTotal() checks in the MQL code.
Stop-loss and take-profit are managed internally: the strategy watches best bid/ask prices and closes the position with market orders when the virtual protective levels are breached.
Trading rules
Long setup
Use the previous finished daily candle.
If Close > (High + Low) / 2and the current ask price is above the previous high, open a long market position.
Stop-loss is placed at the previous low (rounded to the instrument price step).
Take-profit equals the entry price plus (High − Low) × TakeFactor.
Short setup
If Close < (High + Low) / 2and the current bid price is below the previous low, open a short market position.
Stop-loss is set to the previous high; take-profit subtracts (High − Low) × TakeFactor from the entry price.
Trailing stop
The most recent finished TrailCandleType candle supplies (High − Low) × TrailFactor.
For long positions the stop follows Bid − TrailDistance while price remains below the take-profit minus the broker stop level.
For short positions the stop follows Ask + TrailDistance while price stays above the take-profit plus the broker stop level.
Risk guard
The strategy tracks the maximum observed portfolio equity. Trading halts whenever the current equity drops below 50 % of that peak, exactly as in the original advisor.
A five second cooldown (CooldownSeconds) prevents new orders or stop updates too frequently, reproducing the TimeLocal() throttle from MQL.
Position sizing & risk control
The volume per trade is derived from Portfolio.CurrentValue × RiskFraction.
Monetary risk per contract is calculated from the stop distance and the security metadata (PriceStep and StepPrice).
The resulting size is aligned to Security.VolumeStep and clamped to [Security.MinVolume, Security.MaxVolume], then limited by the MaxVolume parameter (default 5 lots).
Orders are skipped when the normalised volume is zero or when the stop distance violates MinStopDistancePoints, which emulates the MetaTrader MODE_STOPLEVEL check.
Parameters
Parameter
Default
Description
SignalCandleType
Daily
Candle type used for breakout detection.
TrailCandleType
4 hours
Candle type that supplies the trailing stop distance.
TakeFactor
0.8
Multiplier applied to the daily range to compute take-profit.
TrailFactor
10
Multiplier applied to the trailing range when updating the stop.
RiskFraction
0.05
Fraction of portfolio equity risked on each trade (5 %).
MaxVolume
5
Hard cap for the final order volume.
MinStopDistancePoints
0
Minimal stop/take distance expressed in price points; set it to the broker MODE_STOPLEVEL.
CooldownSeconds
5
Minimum delay between consecutive trade actions.
Implementation notes
The strategy requires proper instrument metadata: Security.PriceStep, Security.StepPrice, Security.VolumeStep, Security.MinVolume, and (if available) Security.MaxVolume.
Protective levels are virtual. StockSharp closes positions via market orders when bid/ask touches the computed stop-loss or take-profit.
Equity tracking uses Portfolio.CurrentValue. If the connector does not provide this field the risk guard will keep trading disabled until it is available.
Only a single net position is maintained. Opposite signals while a trade is active are ignored until the position is fully closed.
No Python port is included; this directory only contains the C# implementation and documentation.
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()