AIS2 Trading Robot 20005 is an intraday breakout expert advisor originally written for MetaTrader 4. The port re-creates its multi-timeframe logic on top of StockSharp's high level strategy API. The strategy waits for momentum breakouts above/below the midpoint of the previous higher timeframe candle, applies dynamic take-profit and stop-loss distances derived from that candle's range, and manages positions with a secondary, faster timeframe that drives a trailing stop.
The conversion focuses on transparency and manual control: positions are opened with market orders, protective levels are enforced inside the strategy itself, and a configurable trading pause prevents rapid-fire re-entries. Equity-based position sizing mirrors the original "reserve" logic, allowing users to allocate a fraction of portfolio value to each trade while keeping a capital buffer untouched.
Core Logic
Primary timeframe analysis – On each finished candle of the main timeframe (default 15 minutes) the strategy calculates:
Candle midpoint (High + Low) / 2.
Range-based take-profit and stop-loss distances (range * TakeFactor and range * StopFactor).
Current spread approximation, stop/freeze buffers, and a minimal trailing step.
Breakout conditions – Long entries require both a close above the midpoint and the current ask price breaking the previous high plus spread. Shorts mirror the condition for lows. Orders are blocked if the computed stop/target distances fail broker-level constraints.
Risk management – Position size is derived from portfolio equity: OrderReserve defines the tradable fraction, while AccountReserve keeps a portion untouched. If available capital or broker limits disallow the trade, the setup is skipped.
Trade management – The faster timeframe (default 1 minute) continuously updates the trailing distance. As price advances, the stop migrates in the trade's favor once the secondary range justifies it. Reaching either target or stop results in an immediate market exit.
Operational guardrails – A cooldown timer (TradingPauseSeconds) replicates the original MQL trading pause. The strategy also subscribes to the order book to capture live bid/ask values; when unavailable, it falls back to candle closes.
Parameters
Parameter
Description
Default
PrimaryCandleType
Higher timeframe used to generate entry signals.
15-minute candles
SecondaryCandleType
Lower timeframe for trailing stop calculations.
1-minute candles
TakeFactor
Multiplier applied to the primary candle range to build take-profit distance.
1.7
StopFactor
Multiplier applied to the primary candle range to build stop-loss distance.
1.7
TrailFactor
Multiplier applied to the secondary candle range for trailing updates.
0.5
AccountReserve
Fraction of equity held in reserve (not used for trading).
0.20
OrderReserve
Fraction of total equity allocated per trade before buffers.
0.04
BaseVolume
Fallback trade volume when risk sizing cannot be computed.
1 lot
StopBufferTicks
Extra ticks added to broker stop-level compliance checks.
0
FreezeBufferTicks
Extra ticks preventing frequent stop updates near freeze levels.
0
TrailStepMultiplier
Multiplier applied to spread when validating trailing steps.
1
TradingPauseSeconds
Cooldown between consecutive trades.
5 seconds
All numeric parameters expose SetCanOptimize() (where meaningful) so they can participate in StockSharp optimization scenarios.
Usage Notes
Attach the strategy to a security and ensure Level1/order book data are available for accurate spread detection. Without live quotes, the logic still executes using candle closes, but stop validations become conservative.
Set PrimaryCandleType/SecondaryCandleType to timeframes that exist in your data feed. The port uses SubscribeCandles and binds handlers through StockSharp's high level API.
The trailing stop is virtual (managed internally); no stop orders are sent to the broker. If you require server-side stops, extend the code to register protective orders after entries.
StartProtection() is called on start so the engine will liquidate unexpected positions if necessary.
Differences from the Original EA
The MetaTrader version manipulated terminal-wide global variables; this port keeps parameters inside the strategy and exposes them via StrategyParam wrappers.
Order modifications were replaced with direct market exits because StockSharp handles stop/target logic within the algorithm itself.
Risk calculations operate on portfolio equity supplied by StockSharp rather than account balance queries from MT4.
Files
CS/Ais2TradingRobot20005Strategy.cs – Strategy implementation using StockSharp high-level API.
README.md – English description (this file).
README_zh.md – Chinese translation.
README_ru.md – Russian translation.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// AIS2 Trading Robot: range breakout strategy.
/// Enters on close above/below previous candle range midpoint,
/// uses ATR for trailing stop management.
/// </summary>
public class Ais2TradingRobot20005Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _takeFactor;
private readonly StrategyParam<decimal> _stopFactor;
private decimal _prevHigh;
private decimal _prevLow;
private decimal _prevMid;
private decimal _entryPrice;
private decimal _stopPrice;
public Ais2TradingRobot20005Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
_takeFactor = Param(nameof(TakeFactor), 1.7m)
.SetDisplay("Take Factor", "ATR multiplier for take profit.", "Risk");
_stopFactor = Param(nameof(StopFactor), 1.0m)
.SetDisplay("Stop Factor", "ATR multiplier for stop loss.", "Risk");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
public decimal TakeFactor
{
get => _takeFactor.Value;
set => _takeFactor.Value = value;
}
public decimal StopFactor
{
get => _stopFactor.Value;
set => _stopFactor.Value = value;
}
/// <inheritdoc />
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevHigh = 0;
_prevLow = 0;
_prevMid = 0;
_entryPrice = 0;
_stopPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevHigh = 0;
_prevLow = 0;
_prevMid = 0;
_entryPrice = 0;
_stopPrice = 0;
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
if (_prevHigh == 0 || atrVal <= 0)
{
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
_prevMid = (candle.HighPrice + candle.LowPrice) / 2m;
return;
}
var close = candle.ClosePrice;
var takeDistance = atrVal * TakeFactor;
var stopDistance = atrVal * StopFactor;
// Manage open position
if (Position > 0)
{
// Take profit
if (close - _entryPrice >= takeDistance)
{
SellMarket();
_entryPrice = 0;
_stopPrice = 0;
}
// Stop loss
else if (_stopPrice > 0 && close <= _stopPrice)
{
SellMarket();
_entryPrice = 0;
_stopPrice = 0;
}
// Trail stop
else
{
var newStop = close - stopDistance;
if (newStop > _stopPrice)
_stopPrice = newStop;
}
}
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 newStop = close + stopDistance;
if (newStop < _stopPrice || _stopPrice == 0)
_stopPrice = newStop;
}
}
// New entry: breakout above previous high with close above midpoint
if (Position == 0)
{
if (close > _prevHigh && close > _prevMid)
{
_entryPrice = close;
_stopPrice = close - stopDistance;
BuyMarket();
}
else if (close < _prevLow && close < _prevMid)
{
_entryPrice = close;
_stopPrice = close + stopDistance;
SellMarket();
}
}
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
_prevMid = (candle.HighPrice + candle.LowPrice) / 2m;
}
}
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.Strategies import Strategy
from StockSharp.Algo.Indicators import AverageTrueRange
class ais2_trading_robot20005_strategy(Strategy):
def __init__(self):
super(ais2_trading_robot20005_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period", "Indicators")
self._take_factor = self.Param("TakeFactor", 1.7) \
.SetDisplay("Take Factor", "ATR multiplier for take profit", "Risk")
self._stop_factor = self.Param("StopFactor", 1.0) \
.SetDisplay("Stop Factor", "ATR multiplier for stop loss", "Risk")
self._prev_high = 0.0
self._prev_low = 0.0
self._prev_mid = 0.0
self._entry_price = 0.0
self._stop_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def AtrLength(self):
return self._atr_length.Value
@property
def TakeFactor(self):
return self._take_factor.Value
@property
def StopFactor(self):
return self._stop_factor.Value
def OnStarted2(self, time):
super(ais2_trading_robot20005_strategy, self).OnStarted2(time)
self._prev_high = 0.0
self._prev_low = 0.0
self._prev_mid = 0.0
self._entry_price = 0.0
self._stop_price = 0.0
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, atr_val):
if candle.State != CandleStates.Finished:
return
av = float(atr_val)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
if self._prev_high == 0 or av <= 0:
self._prev_high = high
self._prev_low = low
self._prev_mid = (high + low) / 2.0
return
take_distance = av * float(self.TakeFactor)
stop_distance = av * float(self.StopFactor)
# Manage open 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:
new_stop = close - stop_distance
if new_stop > self._stop_price:
self._stop_price = new_stop
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:
new_stop = close + stop_distance
if new_stop < self._stop_price or self._stop_price == 0:
self._stop_price = new_stop
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_high = high
self._prev_low = low
self._prev_mid = (high + low) / 2.0
return
# New entry
if self.Position == 0:
if close > self._prev_high and close > self._prev_mid:
self._entry_price = close
self._stop_price = close - stop_distance
self.BuyMarket()
elif close < self._prev_low and close < self._prev_mid:
self._entry_price = close
self._stop_price = close + stop_distance
self.SellMarket()
self._prev_high = high
self._prev_low = low
self._prev_mid = (high + low) / 2.0
def OnReseted(self):
super(ais2_trading_robot20005_strategy, self).OnReseted()
self._prev_high = 0.0
self._prev_low = 0.0
self._prev_mid = 0.0
self._entry_price = 0.0
self._stop_price = 0.0
def CreateClone(self):
return ais2_trading_robot20005_strategy()