AIS5 Trade Machine переносит советника MetaTrader 4 AIS5TM.mq4 на высокоуровневый API StockSharp. Исходная программа строила
профили рынка на двух таймфреймах и предоставляла полуавтоматическую панель управления. Версия для StockSharp сохраняет идею
выделения сильных и слабых ценовых зон с помощью накопления тикового объёма и превращает её в полностью автоматическую
стратегию пробоя с адаптивным управлением риском на основе ATR.
Стратегия подписывается на две ленты свечей:
Таймфрейм профиля (по умолчанию 15 минут) – аккумулирует объёмы и определяет сильные/слабые зоны.
Торговый таймфрейм (по умолчанию 1 минута) – отслеживает пробои вдали от обнаруженных зон с подтверждением объёмом.
Позиции защищаются стопами, привязанными к ATR, и масштабируемыми целями. Сжатие объёма приводит к раннему выходу из сделки,
что повторяет дисциплину мониторинга в исходном коде MT4.
Логика стратегии
Определение зон (таймфрейм профиля)
Каждая завершённая свеча старшего таймфрейма обновляет простые скользящие средние (SMA) тикового объёма.
Свеча отмечается как сильная зона, если её объём превышает средний, умноженный на параметр Strong Volume Mult. Цена
закрытия сохраняется как последняя сильная отметка.
Свеча отмечается как слабая зона, если её объём ниже среднего, делённого на Weak Volume Divider. Цена закрытия становится
последней слабой отметкой.
В расчёте участвуют только закрытые свечи. Пока SMA профиля не сформирована, сигналы игнорируются, чтобы избежать шума на
старте.
Входы на пробой (торговый таймфрейм)
Свечи младшего таймфрейма ожидают формирования своей SMA объёма и индикатора ATR.
Для входа в лонг цена закрытия должна превысить последнюю сильную отметку на величину суммы Zone Base Points и
Zone Step Points (пересчитанных через шаг цены инструмента). Одновременно текущий объём должен превышать среднее значение.
Для входа в шорт условия зеркальные: требуется пробой слабой зоны вниз на ту же комбинированную дистанцию и подтверждение
ростом объёма.
Оригинальный советник поддерживал ручные команды и сетку ордеров. Порт на StockSharp использует одиночную позицию и открывает
сделку только при отсутствии текущего экспозиции.
Управление выходом
При открытии позиции фиксируется цена входа, рассчитывается защитный стоп (ATR, умноженный на ATR Multiplier и ограниченный
базовым буфером) и целевая отметка равная стоп-дистанции, умноженной на делитель слабого объёма. Это выравнивает риск и
потенциал прибыли относительно структуры объёма.
Пока позиция открыта, каждая завершённая свеча младшего таймфрейма проверяет:
Достигнута ли цель или сработал ли стоп – в этом случае позиция закрывается сразу.
Снизился ли объём ниже слабого порога до срабатывания уровней – тогда сделка закрывается досрочно, чтобы не оставаться в
«пустой» зоне.
После возврата позиции к нулю внутреннее состояние сбрасывается и стратегия готова к следующему пробою.
Параметры
Profile Candle – тип свечей, используемых для построения профиля объёма (по умолчанию 15 минут).
Trading Candle – низкий таймфрейм для сигналов и выхода (по умолчанию 1 минута).
Volume Lookback – длина окна для SMA объёма и периода ATR.
Strong Volume Mult – множитель среднего объёма для пометки сильных зон (соответствует Parameter.1 в MQL).
Weak Volume Divider – делитель среднего объёма для слабых зон и расчёта цели (соответствует Parameter.2).
ATR Multiplier – коэффициент, применяемый к ATR при определении дистанции стопа (соответствует Parameter.3).
Zone Base Points – минимальный буфер в пунктах перед проверкой пробоя (соответствует ZoneBasePoints).
Zone Step Points – дополнительный буфер пробоя, требующий большего удаления цены от зоны (соответствует ZoneStepPoints).
Volume – параметр базового класса Strategy, задающий объём рыночных заявок.
Дополнительные замечания
Если у инструмента не задан шаг цены, стратегия использует значение 0.0001, чтобы параметры в пунктах работали для
большинства валютных пар.
Все индикаторы используют только закрытые свечи, что соответствует оригинальному советнику MT4.
В портированной версии нет ручной панели и загрузки профилей из файлов – зоны пересчитываются только по текущим данным,
благодаря чему стратегия полностью автономна.
Python-реализация отсутствует.
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// AIS5 Trade Machine: EMA breakout with RSI filter and ATR stops.
/// </summary>
public class Ais5TradeMachineStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevClose;
private decimal _entryPrice;
public Ais5TradeMachineStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_emaLength = Param(nameof(EmaLength), 20)
.SetDisplay("EMA Length", "Trend filter.", "Indicators");
_rsiLength = Param(nameof(RsiLength), 14)
.SetDisplay("RSI Length", "RSI period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevClose = 0;
_entryPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevClose = 0;
_entryPrice = 0;
var ema = new ExponentialMovingAverage { Length = EmaLength };
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ema, rsi, 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 rsiVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
if (_prevClose == 0 || atrVal <= 0)
{
_prevClose = close;
return;
}
if (Position > 0)
{
if (close >= _entryPrice + atrVal * 2.5m || close <= _entryPrice - atrVal * 1.5m || rsiVal > 75)
{
SellMarket();
_entryPrice = 0;
}
}
else if (Position < 0)
{
if (close <= _entryPrice - atrVal * 2.5m || close >= _entryPrice + atrVal * 1.5m || rsiVal < 25)
{
BuyMarket();
_entryPrice = 0;
}
}
if (Position == 0)
{
if (close > emaVal && _prevClose <= emaVal && rsiVal > 50)
{
_entryPrice = close;
BuyMarket();
}
else if (close < emaVal && _prevClose >= emaVal && rsiVal < 50)
{
_entryPrice = close;
SellMarket();
}
}
_prevClose = close;
}
}
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 ExponentialMovingAverage, RelativeStrengthIndex, AverageTrueRange
class ais5_trade_machine_strategy(Strategy):
def __init__(self):
super(ais5_trade_machine_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._ema_length = self.Param("EmaLength", 20) \
.SetDisplay("EMA Length", "Trend filter.", "Indicators")
self._rsi_length = self.Param("RsiLength", 14) \
.SetDisplay("RSI Length", "RSI period.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._prev_close = 0.0
self._entry_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def EmaLength(self):
return self._ema_length.Value
@property
def RsiLength(self):
return self._rsi_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(ais5_trade_machine_strategy, self).OnStarted2(time)
self._prev_close = 0.0
self._entry_price = 0.0
self._ema = ExponentialMovingAverage()
self._ema.Length = self.EmaLength
self._rsi = RelativeStrengthIndex()
self._rsi.Length = self.RsiLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._ema, self._rsi, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, ema_val, rsi_val, atr_val):
if candle.State != CandleStates.Finished:
return
ev = float(ema_val)
rv = float(rsi_val)
av = float(atr_val)
close = float(candle.ClosePrice)
if self._prev_close == 0 or av <= 0:
self._prev_close = close
return
if self.Position > 0:
if close >= self._entry_price + av * 2.5 or close <= self._entry_price - av * 1.5 or rv > 75:
self.SellMarket()
self._entry_price = 0.0
elif self.Position < 0:
if close <= self._entry_price - av * 2.5 or close >= self._entry_price + av * 1.5 or rv < 25:
self.BuyMarket()
self._entry_price = 0.0
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_close = close
return
if self.Position == 0:
if close > ev and self._prev_close <= ev and rv > 50:
self._entry_price = close
self.BuyMarket()
elif close < ev and self._prev_close >= ev and rv < 50:
self._entry_price = close
self.SellMarket()
self._prev_close = close
def OnReseted(self):
super(ais5_trade_machine_strategy, self).OnReseted()
self._prev_close = 0.0
self._entry_price = 0.0
def CreateClone(self):
return ais5_trade_machine_strategy()