AIS2 Trading Robot 20005 — это внутридневная стратегия пробоя диапазона, изначально созданная для MetaTrader 4. Порт переносит её многофреймовую логику на высокоуровневый API StockSharp и воспроизводит ключевые элементы оригинала:
анализ завершённых свечей старшего таймфрейма (по умолчанию 15 минут) и расчёт целевых дистанций по амплитуде свечи;
входы по рынку при пробое середины и экстремума предыдущей свечи с учётом спрэда и брокерских ограничений;
сопровождение позиции по виртуальному трейлинг-стопу, шаг которого задаётся диапазоном более быстрого таймфрейма (по умолчанию 1 минута).
Риск-менеджмент повторяет концепцию резервов из MQL-версии: часть капитала блокируется (AccountReserve), оставшаяся доля (OrderReserve) используется для расчёта объёма сделки. Дополнительно реализована пауза между операциями, чтобы исключить чрезмерную торговую активность.
Логика работы
Свеча старшего таймфрейма — вычисляются середина диапазона, расстояния до стоп-лосса/тейк-профита (умножение диапазона на StopFactor и TakeFactor), минимальный шаг трейлинга и буферы стоп/фриз уровней.
Условия входа —
Лонг открывается, если закрытие выше середины, а ask пробивает максимум предыдущей свечи + спрэд.
Шорт открывается, если закрытие ниже середины, а bid пробивает минимум предыдущей свечи.
Сделка отклоняется, если рассчитанные стоп/тейк ближе допустимых брокером уровней.
Размер позиции — рассчитывается по текущей стоимости портфеля. При недостатке капитала или несоблюдении ограничений объём обнуляется и сигнал игнорируется.
Сопровождение — вторичный таймфрейм обновляет расстояние трейлинга. Стоп подтягивается только при устойчивом движении цены и соблюдении минимального шага.
Защита — TradingPauseSeconds задаёт паузу между операциями. Для оценки спрэда стратегия подписывается на стакан, но при его отсутствии использует цены закрытия свечей.
Параметры
Параметр
Описание
Значение по умолчанию
PrimaryCandleType
Таймфрейм для поиска сигналов.
15-минутные свечи
SecondaryCandleType
Таймфрейм для расчёта трейлинга.
1-минутные свечи
TakeFactor
Множитель диапазона для тейк-профита.
1.7
StopFactor
Множитель диапазона для стоп-лосса.
1.7
TrailFactor
Множитель диапазона для трейлинг-стопа.
0.5
AccountReserve
Доля капитала, зарезервированная от торговли.
0.20
OrderReserve
Доля капитала на одну сделку.
0.04
BaseVolume
Резервный объём при невозможности рассчитать риск.
1 лот
StopBufferTicks
Дополнительные тики для проверки стоп-уровней.
0
FreezeBufferTicks
Буфер для предотвращения частых переносов стопа.
0
TrailStepMultiplier
Множитель спрэда при проверке шага трейлинга.
1
TradingPauseSeconds
Пауза между сделками.
5 секунд
Большинство числовых параметров подготовлены для оптимизации через SetCanOptimize().
Рекомендации по использованию
Перед запуском убедитесь, что по инструменту доступны данные Level1/стакана, иначе расчёт спрэда и стоп-уровней будет консервативным.
Настройте PrimaryCandleType и SecondaryCandleType в соответствии с доступными таймфреймами источника данных. Подписка выполняется через SubscribeCandles.
Трейлинг-стоп реализован внутри стратегии — фактические защитные ордера не выставляются. При необходимости доработайте код и регистрируйте стоп-заявки после входа.
Метод StartProtection() активирован при старте стратегии и помогает закрыть чужие позиции, которые могли остаться в портфеле.
Отличия от MQL-реализации
Вместо глобальных переменных MetaTrader параметры инкапсулированы в StrategyParam, что упрощает изменение настроек и оптимизацию.
Вместо OrderModify применяются рыночные операции закрытия — это логичнее в рамках StockSharp и обеспечивает мгновенную реакцию на условия выхода.
Расчёт капитала и риск-параметров основан на данных портфеля StockSharp, а не на балансе счёта в MT4.
Содержимое
CS/Ais2TradingRobot20005Strategy.cs — исходный код стратегии.
README.md — описание на английском языке.
README_zh.md — описание на китайском языке.
README_ru.md — описание на русском языке (этот файл).
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()