Открыть на GitHub

Стратегия AMA Trader 2

Описание

AMA Trader 2 повторяет логику одноимённого советника MetaTrader Владимира Карпутова. Стратегия сочетает адаптивную скользящую среднюю Кауфмана (AMA) и осциллятор RSI: покупки открываются при закрытии свечи выше AMA и одновременном падении RSI в зону перепроданности, продажи — при обратном сочетании. Добавочные усредняющие заявки отправляются фиксированным объёмом и ограничиваются параметрами управления риском (максимальное число позиций, минимальный шаг, трейлинг и т.д.).

Предпосылки

  • Инструмент: валютные пары/CFD с низкими спредами; допустимо использование на других ликвидных рынках с приемлемыми просадками при усреднении.
  • Данные: стратегия работает по закрытию свечей выбранного таймфрейма (CandleType, по умолчанию 1 минута).
  • Сессия: при необходимости торговля ограничивается временным окном StartTimeEndTime (UTC) с помощью флага UseTimeWindow.

Индикаторы

  1. Kaufman Adaptive Moving Average (AMA) — определяет направление тренда через длину усреднения и параметры быстрого/медленного сглаживания.
  2. Relative Strength Index (RSI) — подтверждает экстремумы импульса. Количество последних значений для проверки задаёт StepLength (0 трактуется как 1, как и в оригинале).

Торговый алгоритм

  1. Обрабатываются только завершённые свечи, стратегия должна быть онлайн и иметь разрешение на торговлю.
  2. При активированном временном фильтре сигналы вне окна игнорируются.
  3. Очередь значений RSI обновляется, одновременно рассчитываются возможные сдвиги трейлинг-стопа.
  4. Лонг: цена закрытия выше AMA и одно из проверяемых значений RSI ниже RsiLevelDown. Если текущий лонг убыточен, сначала отправляется усредняющая заявка, затем стандартный вход. Шорт: зеркальные условия (RsiLevelUp).
  5. Параметры MaxPositions, MinStep и OnlyOnePosition ограничивают количество усреднений и повторные входы. При CloseOpposite = true сначала закрываются противоположные позиции, и только после их подтверждённого закрытия стратегия рассматривает новый вход.
  6. Для каждого входа можно включать фиксированные 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();
	}
}