Открыть на GitHub

Стратегия A System Championship

Обзор

  • Конвертация экспертного советника MetaTrader 4 «A System: Championship Strategy Final Edit» (файл ACB6.MQ4).
  • Ищет пробои на выбранном старшем таймфрейме и подтверждает импульс актуальными ценами bid/ask.
  • Второй таймфрейм воспроизводит «потоки» исходного робота и используется для расчёта дистанции трейлинг-стопа.
  • Реализованы глобальный эквити-стоп, пауза между сделками и адаптивное управление риском, присутствующие в оригинальном коде.

Источники данных

  • Подписка на две серии свечей (PrimaryTimeFrame, SecondaryTimeFrame) для восстановления диапазонов, из которых строятся цели и трейлинг.
  • Подписка на Level 1-данные необходима для отслеживания лучшего бид/аск, запускающих входы, стопы, тейки и выход по откату.

Условия входа

  1. Дождаться закрытия свечи старшего таймфрейма и умножить её диапазон на TakeFactor.
  2. Открыть покупку, если:
    • Свеча закрылась выше своего середины.
    • Текущая цена ask пробивает максимум свечи.
    • Разница между бидом и минимумом свечи превышает MinStopDistance.
  3. Продажа открывается при зеркальных условиях для нисходящего пробоя.
  4. Сигнал игнорируется, если рассчитанный тейк меньше минимально допустимой дистанции до стопа.

Управление позицией

  • Начальные уровни: стоп устанавливается на минимум/максимум предыдущей свечи, тейк равен цене входа ± диапазон * TakeFactor.
  • Выход по откату (FallLimit/FallFactor):
    • Хранится максимальное благоприятное движение цены.
    • Если текущее движение упало ниже FallLimit * maxMove, но при этом достигло FallFactor * target, позиция закрывается по рынку.
  • Трейлинг-стоп (TrailFactor):
    • Дистанция равна диапазону вторичного таймфрейма, умноженному на TrailFactor.
    • Стоп смещается только в сторону прибыли и не пересекает уровень тейк-профита или минимальную дистанцию до стопа.
  • Жёсткие стоп/тейк: при касании соответствующего уровня позиция немедленно закрывается рыночной заявкой.

Управление рисками

  • Динамический объём: RiskPerTrade умножается на стоимость пункта (берётся из StepSize/StepPrice), результат приводится к биржевым ограничениям и не опускается ниже BaseVolume.
  • Статистическая поправка: отношение LossesExpected/TradesExpected сравнивается с фактической долей убыточных сделок и корректирует риск.
  • Эквити-стоп (SystemStop): фиксирует максимум equity и запрещает новые сделки, если значение опускается ниже SystemStop * максимум. Логи информируют о срабатывании и восстановлении.
  • Пауза между сделками (TradePause): после каждой рыночной заявки запускается таймер, как и в версии для MT4.

Параметры

Название Значение по умолчанию Описание
PrimaryTimeFrame 1 день Таймфрейм, на котором ищется пробой.
SecondaryTimeFrame 4 часа Таймфрейм для расчёта трейлинг-стопа.
TakeFactor 0.8 Множитель диапазона основной свечи для тейк-профита.
TrailFactor 10 Множитель диапазона вторичной свечи для трейлинга.
FallLimit 0.5 Доля от максимальной прибыли, разрешающая выход по откату.
FallFactor 0.4 Минимальная доля целевого хода, необходимая для откатного выхода.
RiskPerTrade 0.02 Базовая доля капитала, выделяемая на сделку.
BaseVolume 1 Минимальный объём, используемый при недостатке рассчитанного риска.
MinStopDistance 0 Минимальная дистанция до стопа в ценовых единицах.
TradePause 5 минут Время ожидания после исполнения ордера.
SystemStop 0.8 Коэффициент эквити-стопа (0.8 = допустимо до 20% просадки).
LossesExpected 20 Ожидаемое количество убыточных сделок.
TradesExpected 85 Ожидаемое общее количество сделок.

Особенности реализации

  • Блоки блокировки/хеджирования из MQL не перенесены: в StockSharp стратегия оперирует чистой позицией, а трейлинг и защита капитала обеспечивают аналогичный эффект.
  • Уровни стоп/тейк отслеживаются внутри стратегии, чтобы корректно работать в тестере без дополнительных заявок.
  • Для корректной работы нужен инструмент с заданными StepSize, StepPrice, MinVolume, VolumeStep, иначе объём будет равен BaseVolume.
  • Желательно получать поток Level 1 в реальном времени, иначе реакции на стоп/тейк будут происходить только по факту закрытия свечей.
using System;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Multi-timeframe breakout strategy converted from the original MetaTrader "A System" expert advisor.
/// Enters on momentum breakouts when close is above/below the midpoint of the previous candle range.
/// Uses ATR-based trailing stop for position management.
/// </summary>
public class ASystemChampionshipStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _atrPeriod;
	private readonly StrategyParam<decimal> _takeFactor;
	private readonly StrategyParam<decimal> _trailFactor;

	private decimal _prevHigh;
	private decimal _prevLow;
	private decimal _prevClose;
	private bool _hasPrev;
	private decimal _entryPrice;
	private decimal _stopPrice;

	public ASystemChampionshipStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe for breakout detection.", "General");

		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetDisplay("ATR Period", "Period for ATR used in trailing stop.", "Indicators");

		_takeFactor = Param(nameof(TakeFactor), 2.5m)
			.SetDisplay("Take Factor", "ATR multiplier for take profit.", "Risk");

		_trailFactor = Param(nameof(TrailFactor), 1.5m)
			.SetDisplay("Trail Factor", "ATR multiplier for trailing stop.", "Risk");
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public int AtrPeriod
	{
		get => _atrPeriod.Value;
		set => _atrPeriod.Value = value;
	}

	public decimal TakeFactor
	{
		get => _takeFactor.Value;
		set => _takeFactor.Value = value;
	}

	public decimal TrailFactor
	{
		get => _trailFactor.Value;
		set => _trailFactor.Value = value;
	}

	/// <inheritdoc />
	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_prevHigh = 0;
		_prevLow = 0;
		_prevClose = 0;
		_hasPrev = false;
		_entryPrice = 0;
		_stopPrice = 0;
	}

		protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_prevHigh = 0;
		_prevLow = 0;
		_prevClose = 0;
		_hasPrev = false;
		_entryPrice = 0;
		_stopPrice = 0;

		var atr = new AverageTrueRange { Length = AtrPeriod };

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(atr, ProcessCandle)
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, atr);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle, decimal atrValue)
	{
		if (candle.State != CandleStates.Finished)
			return;

		if (atrValue <= 0)
		{
			SaveCandle(candle);
			return;
		}

		// Manage open position
		if (Position > 0)
		{
			// Trail stop up
			var newStop = candle.ClosePrice - atrValue * TrailFactor;
			if (newStop > _stopPrice)
				_stopPrice = newStop;

			if (candle.LowPrice <= _stopPrice)
			{
				SellMarket();
				ResetPosition();
			}
			else if (_entryPrice > 0 && candle.HighPrice >= _entryPrice + atrValue * TakeFactor)
			{
				SellMarket();
				ResetPosition();
			}
		}
		else if (Position < 0)
		{
			// Trail stop down
			var newStop = candle.ClosePrice + atrValue * TrailFactor;
			if (_stopPrice == 0 || newStop < _stopPrice)
				_stopPrice = newStop;

			if (candle.HighPrice >= _stopPrice)
			{
				BuyMarket();
				ResetPosition();
			}
			else if (_entryPrice > 0 && candle.LowPrice <= _entryPrice - atrValue * TakeFactor)
			{
				BuyMarket();
				ResetPosition();
			}
		}

		// Entry logic
		if (_hasPrev && Position == 0)
		{
			var mid = (_prevHigh + _prevLow) / 2m;

			if (_prevClose > mid && candle.ClosePrice > _prevHigh)
			{
				// Bullish breakout
				BuyMarket();
				_entryPrice = candle.ClosePrice;
				_stopPrice = candle.ClosePrice - atrValue * TrailFactor;
			}
			else if (_prevClose < mid && candle.ClosePrice < _prevLow)
			{
				// Bearish breakout
				SellMarket();
				_entryPrice = candle.ClosePrice;
				_stopPrice = candle.ClosePrice + atrValue * TrailFactor;
			}
		}

		SaveCandle(candle);
	}

	private void SaveCandle(ICandleMessage candle)
	{
		_prevHigh = candle.HighPrice;
		_prevLow = candle.LowPrice;
		_prevClose = candle.ClosePrice;
		_hasPrev = true;
	}

	private void ResetPosition()
	{
		_entryPrice = 0;
		_stopPrice = 0;
	}
}