Открыть на GitHub

Стратегия ACB1

Общее описание

ACB1 — порт советника MetaTrader MQL/8586/ACB1.MQ4 на платформу StockSharp. Исходная система торгует EURUSD и ждёт мощных пробоев дневного диапазона. Перенос повторяет логику оригинала, используя высокоуровневые компоненты StockSharp:

  • Дневные свечи (SignalCandleType) задают направление пробоя, а также уровни стоп-лосса и тейк-профита.
  • Свечи H4 (TrailCandleType) определяют величину трейлинг-стопа, умножаемую на TrailFactor.
  • Сделки открываются рыночными ордерами при выполнении условий пробоя; стратегия поддерживает только одну совокупную позицию, что соответствует проверке OrdersTotal() в MQL.
  • Стопы и тейки виртуальные: стратегия отслеживает лучшие котировки Bid/Ask и закрывает позицию рыночным приказом при достижении расчётных уровней.

Правила торговли

  1. Сигнал на покупку

    • Используется последняя завершённая дневная свеча.
    • Если Close > (High + Low) / 2 и текущая цена Ask выше вчерашнего максимума, открывается длинная позиция по рынку.
    • Стоп-лосс размещается на минимуме предыдущей свечи (с округлением к шагу цены).
    • Тейк-профит равен цене входа плюс (High − Low) × TakeFactor.
  2. Сигнал на продажу

    • Если Close < (High + Low) / 2 и текущий Bid ниже вчерашнего минимума, открывается короткая позиция.
    • Стоп-лосс ставится на максимуме предыдущей свечи; тейк-профит уменьшается на (High − Low) × TakeFactor.
  3. Трейлинг-стоп

    • Последняя завершённая свеча TrailCandleType даёт диапазон (High − Low) × TrailFactor.
    • Для длинной позиции стоп следует за Bid − TrailDistance, пока цена не приблизится к тейку ближе, чем на величину брокерского стоп-уровня.
    • Для короткой позиции стоп обновляется на Ask + TrailDistance, пока цена выше тейка плюс стоп-уровень.
  4. Защита капитала

    • Стратегия фиксирует максимум портфельной стоимости. Торговля блокируется, если текущая стоимость падает ниже 50 % от максимума, как и в оригинале.
    • Пятесекундная задержка (CooldownSeconds) запрещает повторные сделки и обновления стопа слишком часто, имитируя фильтр TimeLocal().

Размер позиции и риск

  • Объём рассчитывается как Portfolio.CurrentValue × RiskFraction.
  • Денежный риск на контракт получаем из расстояния до стопа и свойств инструмента (PriceStep, StepPrice).
  • Итоговый объём приводится к Security.VolumeStep, ограничивается Security.MinVolume / Security.MaxVolume, а затем ограничивается параметром MaxVolume (по умолчанию 5 лотов).
  • Сделка пропускается, если нормализованный объём равен нулю или если расстояние до стопа нарушает MinStopDistancePoints, что имитирует проверку MODE_STOPLEVEL.

Параметры

Параметр Значение по умолчанию Описание
SignalCandleType Дневные Тип свечей для определения пробоя.
TrailCandleType 4 часа Тип свечей, задающих дистанцию трейлинг-стопа.
TakeFactor 0.8 Множитель дневного диапазона для расчёта тейк-профита.
TrailFactor 10 Множитель диапазона свечей H4 при обновлении стопа.
RiskFraction 0.05 Доля капитала, рискуемая в сделке (5 %).
MaxVolume 5 Максимальный допустимый объём заявки.
MinStopDistancePoints 0 Минимальная дистанция стопа/тейка в пунктах; используйте значение брокерского MODE_STOPLEVEL.
CooldownSeconds 5 Минимальная пауза между торговыми действиями.

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

  • Необходимо корректно настроить свойства инструмента: Security.PriceStep, Security.StepPrice, Security.VolumeStep, Security.MinVolume и при наличии Security.MaxVolume.
  • Защитные уровни реализованы виртуально. Закрытие происходит рыночным ордером при достижении ценой вычисленного стопа или тейка.
  • Контроль просадки использует Portfolio.CurrentValue. Если коннектор не заполняет это поле, торговля будет заблокирована до появления значения.
  • Одновременно поддерживается только одна совокупная позиция; противоположные сигналы игнорируются до полного выхода из рынка.
  • Python-версии нет — директория содержит только реализацию на C# и документацию.
using System;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Breakout strategy converted from the "ACB1" MetaTrader expert advisor.
/// Enters on breakouts above previous candle high / below previous candle low,
/// with trailing stop based on ATR.
/// </summary>
public class Acb1Strategy : 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 decimal _prevMid;
	private decimal _entryPrice;
	private decimal _stopPrice;
	private bool _hasPrev;

	public Acb1Strategy()
	{
		_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 indicator used in trailing.", "Indicators");

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

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

	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;
		_prevMid = 0;
		_entryPrice = 0;
		_stopPrice = 0;
		_hasPrev = false;
	}

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

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

		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)
			return;

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

				// Check stop hit
				if (candle.LowPrice <= _stopPrice)
				{
					SellMarket();
					_entryPrice = 0;
					_stopPrice = 0;
				}
				// Check take profit
				else if (_entryPrice > 0 && candle.HighPrice >= _entryPrice + atrValue * TakeFactor)
				{
					SellMarket();
					_entryPrice = 0;
					_stopPrice = 0;
				}
			}
			else
			{
				// Trailing stop for short
				var newStop = candle.ClosePrice + atrValue * TrailFactor;
				if (_stopPrice == 0 || newStop < _stopPrice)
					_stopPrice = newStop;

				// Check stop hit
				if (candle.HighPrice >= _stopPrice)
				{
					BuyMarket();
					_entryPrice = 0;
					_stopPrice = 0;
				}
				// Check take profit
				else if (_entryPrice > 0 && candle.LowPrice <= _entryPrice - atrValue * TakeFactor)
				{
					BuyMarket();
					_entryPrice = 0;
					_stopPrice = 0;
				}
			}
		}

		// Entry logic after managing position
		if (_hasPrev && Position == 0)
		{
			if (_prevClose > _prevMid && candle.ClosePrice > _prevHigh)
			{
				// Bullish breakout
				BuyMarket();
				_entryPrice = candle.ClosePrice;
				_stopPrice = _prevLow;
			}
			else if (_prevClose < _prevMid && candle.ClosePrice < _prevLow)
			{
				// Bearish breakout
				SellMarket();
				_entryPrice = candle.ClosePrice;
				_stopPrice = _prevHigh;
			}
		}

		// Store for next candle
		_prevHigh = candle.HighPrice;
		_prevLow = candle.LowPrice;
		_prevClose = candle.ClosePrice;
		_prevMid = (candle.HighPrice + candle.LowPrice) / 2m;
		_hasPrev = true;
	}
}