AIS3 Trading Robot Template
Overview
The AIS3 Trading Robot Template is a MetaTrader breakout system that relies on two coordinated timeframes. The primary timeframe
captures the structure of the previous candle, while a secondary timeframe gauges recent volatility to control trailing updates.
This StockSharp port faithfully reproduces the original order sizing, entry checks, and trailing logic, but it is implemented on
top of the high-level strategy API so it can run inside Designer, Shell, or any custom StockSharp host.
Trading Workflow
- Market data subscriptions: the strategy subscribes to two candle series. The primary series (default 15 minutes) provides
the previous candle high, low, close, midpoint, and range. The secondary series (default 1 minute) measures the fast range used
for trailing stops. A live order book feed keeps the current best bid/ask prices in sync with the original MQL
MarketInfo
requests.
- Breakout validation:
- A long setup triggers when the previous close is above the midpoint and the current ask price breaks out above the previous
high plus the measured spread. The entry price is the current ask.
- A short setup requires the previous close to stay below the midpoint and the bid to pierce the previous low. The entry price
is the current bid.
- Both directions inherit the broker safety checks from the template: the distance between entry and the projected stop/target
must exceed the configured stop buffer, and the stop must remain on the correct side of the entry price even after adding the
spread.
- Protective orders:
- Stop-loss distance equals
primaryRange × StopMultiplier and is anchored above (for longs) or below (for shorts) the
breakout candle as described in the integration manual.
- Take-profit distance equals
primaryRange × TakeMultiplier and is placed from the entry price in the trade direction.
- Trade management:
- When a position is open, the secondary timeframe range multiplied by
TrailMultiplier defines the trailing distance.
- The trailing stop is only updated if the trade is in profit, the new level is farther than the configured freeze and stop
buffers, and the distance between the current and proposed stop exceeds
TrailStepMultiplier × spread. This mirrors the
template requirement that the price must advance by at least one trail step before modifying the stop.
- Positions are closed with market orders whenever the bid/ask touches the stored stop-loss or take-profit levels.
Risk Management
- Account reserve:
AccountReserve keeps a fraction of portfolio equity locked. The strategy refuses to open new positions
if the reserved capital would fall below the requested order budget. This matches the template behaviour where the risk
reserve shields the account from cascading losses.
- Order reserve:
OrderReserve controls the portion of the remaining capital that may be risked per trade. The position size
is calculated as riskBudget / |entry - stop| and then aligned to the security volume step. If no portfolio metrics are
available, the fallback BaseVolume parameter is used instead.
- Stop & freeze buffers:
StopBufferTicks and FreezeBufferTicks translate broker stop limitations (e.g. MODE_STOPLEVEL
and MODE_FREEZELEVEL from MetaTrader) into price units using the security price step. They prevent the strategy from issuing
orders that would violate exchange constraints or from moving the trailing stop too aggressively.
- Trailing step multiplier:
TrailStepMultiplier mirrors the acd.TrailStepping constant from the MQ4 template. It ensures
that trailing updates only happen when the new stop is at least one spread-multiple away from the previous value.
Parameters
| Parameter |
Description |
AccountReserve |
Fraction of equity kept as safety reserve (0–0.95). |
OrderReserve |
Fraction of tradable equity allocated to the risk budget per trade (0–0.5 by default). |
PrimaryCandleType |
Working timeframe for breakout detection (default 15-minute candles). |
SecondaryCandleType |
Faster timeframe that controls trailing distance (default 1-minute candles). |
TakeMultiplier |
Multiplier of the primary range used to place the take-profit order. |
StopMultiplier |
Multiplier of the primary range used to compute the protective stop. |
TrailMultiplier |
Multiplier of the secondary range defining the trailing distance. |
BaseVolume |
Fallback position size when portfolio metrics are unavailable. |
StopBufferTicks |
Extra distance, in price ticks, that must remain between entry and stop/target levels. |
FreezeBufferTicks |
Additional buffer that avoids stop updates too close to the broker freeze level. |
TrailStepMultiplier |
Spread multiplier that defines the minimal increment between trailing adjustments. |
Usage Notes
- Feed the strategy with both candle series and a level-1 or order book stream so the best bid/ask prices are available. Running
it on last-trade data only will alter the breakout checks because they rely on the spread.
- The default parameter values replicate the MQ4 template example (
TakeMultiplier = 1, StopMultiplier = 2,
TrailMultiplier = 3). Adjust them to match the assets you trade or to experiment with the breakout intensity.
- The trailing stop is virtual—orders are not modified on the exchange. When the trailing condition is met the strategy simply
issues a market exit, mirroring how the original expert advisor managed stops internally.
- Combine the strategy with StockSharp's built-in protection module (already enabled in the constructor) to maintain emergency
stop-loss handling even if the strategy is temporarily disconnected.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// AIS3 Trading Robot: breakout strategy with ATR-based stops and trailing.
/// Enters on breakout above/below previous candle range with EMA filter.
/// </summary>
public class Ais3TradingRobotTemplateStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _takeMultiplier;
private readonly StrategyParam<decimal> _stopMultiplier;
private decimal _prevHigh;
private decimal _prevLow;
private decimal _entryPrice;
private decimal _stopPrice;
public Ais3TradingRobotTemplateStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_emaLength = Param(nameof(EmaLength), 20)
.SetDisplay("EMA Length", "EMA period for trend filter.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
_takeMultiplier = Param(nameof(TakeMultiplier), 2.0m)
.SetDisplay("Take Multiplier", "ATR multiplier for TP.", "Risk");
_stopMultiplier = Param(nameof(StopMultiplier), 1.5m)
.SetDisplay("Stop Multiplier", "ATR multiplier for SL.", "Risk");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
public decimal TakeMultiplier
{
get => _takeMultiplier.Value;
set => _takeMultiplier.Value = value;
}
public decimal StopMultiplier
{
get => _stopMultiplier.Value;
set => _stopMultiplier.Value = value;
}
/// <inheritdoc />
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevHigh = 0;
_prevLow = 0;
_entryPrice = 0;
_stopPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevHigh = 0;
_prevLow = 0;
_entryPrice = 0;
_stopPrice = 0;
var ema = new ExponentialMovingAverage { Length = EmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ema, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
if (_prevHigh == 0 || atrVal <= 0)
{
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
return;
}
var close = candle.ClosePrice;
var takeDistance = atrVal * TakeMultiplier;
var stopDistance = atrVal * StopMultiplier;
// Manage position
if (Position > 0)
{
if (close - _entryPrice >= takeDistance)
{
SellMarket();
_entryPrice = 0;
_stopPrice = 0;
}
else if (_stopPrice > 0 && close <= _stopPrice)
{
SellMarket();
_entryPrice = 0;
_stopPrice = 0;
}
else
{
var trail = close - stopDistance;
if (trail > _stopPrice) _stopPrice = trail;
}
}
else if (Position < 0)
{
if (_entryPrice - close >= takeDistance)
{
BuyMarket();
_entryPrice = 0;
_stopPrice = 0;
}
else if (_stopPrice > 0 && close >= _stopPrice)
{
BuyMarket();
_entryPrice = 0;
_stopPrice = 0;
}
else
{
var trail = close + stopDistance;
if (trail < _stopPrice || _stopPrice == 0) _stopPrice = trail;
}
}
// Entry on breakout + EMA filter
if (Position == 0)
{
if (close > _prevHigh && close > emaVal)
{
_entryPrice = close;
_stopPrice = close - stopDistance;
BuyMarket();
}
else if (close < _prevLow && close < emaVal)
{
_entryPrice = close;
_stopPrice = close + stopDistance;
SellMarket();
}
}
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
}
}
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, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class ais3_trading_robot_template_strategy(Strategy):
"""
AIS3 Trading Robot: breakout strategy with ATR-based stops and trailing.
Enters on breakout above/below previous candle range with EMA filter.
"""
def __init__(self):
super(ais3_trading_robot_template_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._ema_length = self.Param("EmaLength", 20) \
.SetDisplay("EMA Length", "EMA period for trend filter.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._take_multiplier = self.Param("TakeMultiplier", 2.0) \
.SetDisplay("Take Multiplier", "ATR multiplier for TP.", "Risk")
self._stop_multiplier = self.Param("StopMultiplier", 1.5) \
.SetDisplay("Stop Multiplier", "ATR multiplier for SL.", "Risk")
self._prev_high = 0.0
self._prev_low = 0.0
self._entry_price = 0.0
self._stop_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def EmaLength(self):
return self._ema_length.Value
@EmaLength.setter
def EmaLength(self, value):
self._ema_length.Value = value
@property
def AtrLength(self):
return self._atr_length.Value
@AtrLength.setter
def AtrLength(self, value):
self._atr_length.Value = value
@property
def TakeMultiplier(self):
return self._take_multiplier.Value
@TakeMultiplier.setter
def TakeMultiplier(self, value):
self._take_multiplier.Value = value
@property
def StopMultiplier(self):
return self._stop_multiplier.Value
@StopMultiplier.setter
def StopMultiplier(self, value):
self._stop_multiplier.Value = value
def OnReseted(self):
super(ais3_trading_robot_template_strategy, self).OnReseted()
self._prev_high = 0.0
self._prev_low = 0.0
self._entry_price = 0.0
self._stop_price = 0.0
def OnStarted2(self, time):
super(ais3_trading_robot_template_strategy, self).OnStarted2(time)
self._prev_high = 0.0
self._prev_low = 0.0
self._entry_price = 0.0
self._stop_price = 0.0
ema = ExponentialMovingAverage()
ema.Length = self.EmaLength
atr = AverageTrueRange()
atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ema, atr, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, ema_val, atr_val):
if candle.State != CandleStates.Finished:
return
if self._prev_high == 0 or atr_val <= 0:
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
return
close = float(candle.ClosePrice)
take_distance = atr_val * self.TakeMultiplier
stop_distance = atr_val * self.StopMultiplier
# Manage position
if self.Position > 0:
if close - self._entry_price >= take_distance:
self.SellMarket()
self._entry_price = 0.0
self._stop_price = 0.0
elif self._stop_price > 0 and close <= self._stop_price:
self.SellMarket()
self._entry_price = 0.0
self._stop_price = 0.0
else:
trail = close - stop_distance
if trail > self._stop_price:
self._stop_price = trail
elif self.Position < 0:
if self._entry_price - close >= take_distance:
self.BuyMarket()
self._entry_price = 0.0
self._stop_price = 0.0
elif self._stop_price > 0 and close >= self._stop_price:
self.BuyMarket()
self._entry_price = 0.0
self._stop_price = 0.0
else:
trail = close + stop_distance
if trail < self._stop_price or self._stop_price == 0:
self._stop_price = trail
# Entry on breakout + EMA filter
if self.Position == 0:
if close > self._prev_high and close > ema_val:
self._entry_price = close
self._stop_price = close - stop_distance
self.BuyMarket()
elif close < self._prev_low and close < ema_val:
self._entry_price = close
self._stop_price = close + stop_distance
self.SellMarket()
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
def CreateClone(self):
"""!! REQUIRED!! Creates a new instance of the strategy."""
return ais3_trading_robot_template_strategy()