AMA Trader 2 повторяет логику одноимённого советника MetaTrader Владимира Карпутова. Стратегия сочетает адаптивную скользящую среднюю Кауфмана (AMA) и осциллятор RSI: покупки открываются при закрытии свечи выше AMA и одновременном падении RSI в зону перепроданности, продажи — при обратном сочетании. Добавочные усредняющие заявки отправляются фиксированным объёмом и ограничиваются параметрами управления риском (максимальное число позиций, минимальный шаг, трейлинг и т.д.).
Предпосылки
Инструмент: валютные пары/CFD с низкими спредами; допустимо использование на других ликвидных рынках с приемлемыми просадками при усреднении.
Данные: стратегия работает по закрытию свечей выбранного таймфрейма (CandleType, по умолчанию 1 минута).
Сессия: при необходимости торговля ограничивается временным окном StartTime–EndTime (UTC) с помощью флага UseTimeWindow.
Индикаторы
Kaufman Adaptive Moving Average (AMA) — определяет направление тренда через длину усреднения и параметры быстрого/медленного сглаживания.
Relative Strength Index (RSI) — подтверждает экстремумы импульса. Количество последних значений для проверки задаёт StepLength (0 трактуется как 1, как и в оригинале).
Торговый алгоритм
Обрабатываются только завершённые свечи, стратегия должна быть онлайн и иметь разрешение на торговлю.
При активированном временном фильтре сигналы вне окна игнорируются.
Очередь значений RSI обновляется, одновременно рассчитываются возможные сдвиги трейлинг-стопа.
Лонг: цена закрытия выше AMA и одно из проверяемых значений RSI ниже RsiLevelDown. Если текущий лонг убыточен, сначала отправляется усредняющая заявка, затем стандартный вход. Шорт: зеркальные условия (RsiLevelUp).
Параметры MaxPositions, MinStep и OnlyOnePosition ограничивают количество усреднений и повторные входы. При CloseOpposite = true сначала закрываются противоположные позиции, и только после их подтверждённого закрытия стратегия рассматривает новый вход.
Для каждого входа можно включать фиксированные StopLoss/TakeProfit, а также трейлинг с тремя параметрами: активация, дистанция и шаг подтяжки.
Управление риском
Фиксированный объём: все ордера отправляются объёмом LotSize.
Глубина усреднения: MaxPositions ограничивает количество добавок в одном направлении.
Минимальный шаг: параметр MinStep предотвращает открытие новых позиций слишком близко к предыдущим.
Защитные стопы: опциональные stop-loss, take-profit и трейлинг-стоп повторяют инструменты защиты оригинального советника.
Противоположные позиции: CloseOpposite принудительно закрывает встречные позиции, OnlyOnePosition запрещает хранить обе стороны одновременно.
Параметры
Параметр
Назначение
CandleType
Тип свечей/таймфрейм для расчёта индикаторов.
LotSize
Объём каждой рыночной заявки.
RsiLength
Период усреднения RSI.
StepLength
Количество последних значений RSI для проверки (0 → 1).
RsiLevelUp
Уровень перекупленности для шорт-сигналов.
RsiLevelDown
Уровень перепроданности для лонг-сигналов.
AmaLength
Длина AMA.
AmaFastPeriod
Быстрый коэффициент сглаживания AMA.
AmaSlowPeriod
Медленный коэффициент сглаживания AMA.
StopLoss
Дистанция защитного стопа (0 — выключено).
TakeProfit
Дистанция тейк-профита (0 — выключено).
TrailingActivation
Прибыль, необходимая для включения трейлинга.
TrailingDistance
Отступ трейлинг-стопа от цены.
TrailingStep
Минимальное улучшение для подтягивания трейлинга.
MaxPositions
Максимум добавок в одну сторону (0 — без ограничения).
MinStep
Минимальная дистанция между входами (0 — без проверки).
CloseOpposite
Закрывать противоположную позицию перед новым входом.
OnlyOnePosition
Запрет на новые входы при открытых позициях.
UseTimeWindow
Включить фильтр торгового окна.
StartTime
Начало торгового окна (UTC).
EndTime
Конец торгового окна (UTC).
Особенности реализации
Используется только высокоуровневый API StockSharp: подписка на свечи через SubscribeCandles, передача значений индикаторов в делегат через .Bind без прямого обращения к буферам.
Учёт позиций ведётся раздельно для лонгов и шортов, что позволяет оценивать плавающую прибыль/убыток при усреднении без вызова запрещённых агрегаторов.
Трейлинг-стоп реализован через обновление стоп-лосса стратегии, без ручного управления заявками.
Во избежание повторных входов в одном баре сохраняется время последней сделки для каждой стороны.
Отличия от MetaTrader-версии
Исключены параметры, связанные с magic number, отклонением, проверкой freeze/stops level и фиктивными выводами средств — эти функции обрабатываются инфраструктурой StockSharp либо не требуются.
Стопы и цели рассчитываются от цены закрытия свечи, поскольку поток тиков в рамках данной реализации не используется.
Автоматический расчёт объёма по проценту риска отсутствует: объём задаётся напрямую (LotSize).
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Adaptive moving average strategy with RSI confirmation.
/// Simplified from the "AMA Trader 2" MetaTrader expert using EMA + RSI.
/// </summary>
public class AmaTrader2Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _emaLength;
private readonly StrategyParam<decimal> _rsiLevelUp;
private readonly StrategyParam<decimal> _rsiLevelDown;
private RelativeStrengthIndex _rsi;
private ExponentialMovingAverage _ema;
private decimal? _prevPrice;
private decimal? _prevEma;
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// RSI period.
/// </summary>
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <summary>
/// EMA period.
/// </summary>
public int EmaLength
{
get => _emaLength.Value;
set => _emaLength.Value = value;
}
/// <summary>
/// RSI overbought level.
/// </summary>
public decimal RsiLevelUp
{
get => _rsiLevelUp.Value;
set => _rsiLevelUp.Value = value;
}
/// <summary>
/// RSI oversold level.
/// </summary>
public decimal RsiLevelDown
{
get => _rsiLevelDown.Value;
set => _rsiLevelDown.Value = value;
}
public AmaTrader2Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for calculations", "General");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "Indicators");
_emaLength = Param(nameof(EmaLength), 50)
.SetGreaterThanZero()
.SetDisplay("EMA Length", "Adaptive MA period", "Indicators");
_rsiLevelUp = Param(nameof(RsiLevelUp), 60m)
.SetDisplay("RSI Up", "RSI bullish confirmation threshold", "Signals");
_rsiLevelDown = Param(nameof(RsiLevelDown), 40m)
.SetDisplay("RSI Down", "RSI bearish confirmation threshold", "Signals");
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevPrice = null;
_prevEma = null;
_rsi = new RelativeStrengthIndex { Length = RsiLength };
_ema = new ExponentialMovingAverage { Length = EmaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_rsi, _ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal rsiValue, decimal emaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_rsi.IsFormed || !_ema.IsFormed)
{
_prevPrice = candle.ClosePrice;
_prevEma = emaValue;
return;
}
if (_prevPrice is null || _prevEma is null)
{
_prevPrice = candle.ClosePrice;
_prevEma = emaValue;
return;
}
var volume = Volume;
if (volume <= 0)
volume = 1;
// Price crosses above EMA + RSI confirms bullish
var priceAboveEma = candle.ClosePrice > emaValue;
var prevBelowEma = _prevPrice < _prevEma;
if (priceAboveEma && prevBelowEma && rsiValue > RsiLevelUp)
{
if (Position <= 0)
BuyMarket(Position < 0 ? Math.Abs(Position) + volume : volume);
}
// Price crosses below EMA + RSI confirms bearish
else if (!priceAboveEma && !prevBelowEma && rsiValue < RsiLevelDown)
{
if (Position >= 0)
SellMarket(Position > 0 ? Math.Abs(Position) + volume : volume);
}
_prevPrice = candle.ClosePrice;
_prevEma = emaValue;
}
/// <inheritdoc />
protected override void OnReseted()
{
_rsi = null;
_ema = null;
_prevPrice = null;
_prevEma = null;
base.OnReseted();
}
}
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 RelativeStrengthIndex, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ama_trader2_strategy(Strategy):
def __init__(self):
super(ama_trader2_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._rsi_length = self.Param("RsiLength", 14)
self._ema_length = self.Param("EmaLength", 50)
self._rsi_level_up = self.Param("RsiLevelUp", 60.0)
self._rsi_level_down = self.Param("RsiLevelDown", 40.0)
self._prev_close = 0.0
self._prev_ema = 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 RsiLength(self):
return self._rsi_length.Value
@RsiLength.setter
def RsiLength(self, value):
self._rsi_length.Value = value
@property
def EmaLength(self):
return self._ema_length.Value
@EmaLength.setter
def EmaLength(self, value):
self._ema_length.Value = value
@property
def RsiLevelUp(self):
return self._rsi_level_up.Value
@RsiLevelUp.setter
def RsiLevelUp(self, value):
self._rsi_level_up.Value = value
@property
def RsiLevelDown(self):
return self._rsi_level_down.Value
@RsiLevelDown.setter
def RsiLevelDown(self, value):
self._rsi_level_down.Value = value
def OnReseted(self):
super(ama_trader2_strategy, self).OnReseted()
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(ama_trader2_strategy, self).OnStarted2(time)
self._prev_close = 0.0
self._prev_ema = 0.0
self._has_prev = False
rsi = RelativeStrengthIndex()
rsi.Length = self.RsiLength
ema = ExponentialMovingAverage()
ema.Length = self.EmaLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(rsi, ema, self._process_candle).Start()
def _process_candle(self, candle, rsi_value, ema_value):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
rsi_val = float(rsi_value)
ema_val = float(ema_value)
if self._has_prev:
price_above_ema = close > ema_val
prev_below_ema = self._prev_close < self._prev_ema
if price_above_ema and prev_below_ema and rsi_val > float(self.RsiLevelUp) and self.Position <= 0:
self.BuyMarket()
elif not price_above_ema and not prev_below_ema and rsi_val < float(self.RsiLevelDown) and self.Position >= 0:
self.SellMarket()
self._prev_close = close
self._prev_ema = ema_val
self._has_prev = True
def CreateClone(self):
return ama_trader2_strategy()