Открыть на GitHub

Стратегия HPCS Inter5

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

HPCS Inter5 Strategy — это одноразовая импульсная стратегия, перенесённая из MetaTrader 4 (скрипт _HPCS_Inter5_MT4_EA_V01_WE). После запуска стратегия анализирует последние завершённые свечи и, если закрытие пятой свечи назад выше ближайшего закрытия, отправляет один рыночный ордер на покупку. Опциональные уровни стоп-лосса и тейк-профита рассчитываются в «пунктах» MetaTrader, повторяя оригинальную реализацию.

Логика работы

  1. Подписаться на выбранный тип свечей и хранить шесть последних закрытий.
  2. После заполнения буфера сравнить закрытие пятой свечи назад с последним закрытием (Close[5] > Close[1]).
  3. Если условие выполняется и сделка ещё не совершена, отправить рыночный ордер на покупку с заданным объёмом.
  4. В момент старта вызвать StartProtection, чтобы активировать защитные приказы. Размер пункта рассчитывается так же, как в MetaTrader: для инструментов с 3 или 5 знаками после запятой используется PriceStep * 10, иначе берётся PriceStep без изменения.

После открытия позиции стратегия игнорирует все новые сигналы и не совершает дополнительных сделок.

Параметры

Имя Значение по умолчанию Описание
Candle Type Таймфрейм 1 минута Тип свечей, по которым собираются цены закрытия. Настройте в соответствии с нужным горизонтом анализа.
Stop Loss (pips) 10 Расстояние защитного стоп-лосса в пунктах MetaTrader. Значение 0 отключает стоп.
Take Profit (pips) 10 Расстояние защитного тейк-профита в пунктах MetaTrader. Значение 0 отключает тейк-профит.
Trade Volume 1 Объём рыночного ордера при срабатывании сигнала.

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

  • Для расчёта защитных уровней необходимо корректно заполнить Security.PriceStep (или Security.Step). При его отсутствии защитные отступы не активируются, но вход по сигналу остаётся рабочим.
  • Обрабатываются только завершённые свечи (CandleStates.Finished), что соответствует логике MetaTrader с использованием Close[1] и более старых значений.
  • Внутренний буфер содержит ровно шесть закрытий без использования индикаторов, что подчёркивает минималистичность исходного скрипта.
  • Перед отправкой ордера вызывается IsFormedAndOnlineAndAllowTrading(), чтобы убедиться в готовности соединения и разрешениях на торговлю.

Рекомендации по использованию

  1. Выберите валютную пару с корректно настроенными шагом цены и объёмами.
  2. Настройте Candle Type под нужный таймфрейм.
  3. Установите 0 в параметрах стопа или тейк-профита, если планируете управлять выходом вручную.
  4. Перезапускайте стратегию каждый раз, когда хотите повторно оценить условие входа: сигнал исполняется только один раз за запуск.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

using StockSharp.Algo;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Single-shot momentum strategy converted from the "_HPCS_Inter5" MetaTrader script.
/// Places one market buy when the close price from five bars ago exceeds the most recent close.
/// </summary>
public class HpcsInter5Strategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<int> _takeProfitPips;
	private readonly StrategyParam<decimal> _tradeVolume;

	private readonly decimal?[] _recentCloses = new decimal?[6];
	private decimal _pipSize;
	private bool _wasLongSignal;
	private bool _hasSignal;

	/// <summary>
	/// Candle type used to evaluate the closing prices.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Stop-loss distance expressed in MetaTrader-style pips.
	/// </summary>
	public int StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Take-profit distance expressed in MetaTrader-style pips.
	/// </summary>
	public int TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Trade volume submitted with the market entry.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="HpcsInter5Strategy"/> class.
	/// </summary>
	public HpcsInter5Strategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(120).TimeFrame())
			.SetDisplay("Candle Type", "Candle type used for the close comparison", "General");

		_stopLossPips = Param(nameof(StopLossPips), 20)
			.SetDisplay("Stop Loss (pips)", "Stop-loss distance expressed in pips", "Risk Management");

		_takeProfitPips = Param(nameof(TakeProfitPips), 20)
			.SetDisplay("Take Profit (pips)", "Take-profit distance expressed in pips", "Risk Management");

		_tradeVolume = Param(nameof(TradeVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Trade Volume", "Volume submitted with the market entry", "Trading");
	}

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

		Array.Clear(_recentCloses, 0, _recentCloses.Length);
		_pipSize = 0m;
		_wasLongSignal = false;
		_hasSignal = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		if (Security is null)
			throw new InvalidOperationException("Security must be assigned before starting the strategy.");

		base.OnStarted2(time);

		InitializePipSize();
		Volume = TradeVolume;
		_wasLongSignal = false;
		_hasSignal = false;

		var stopLoss = StopLossPips > 0 && _pipSize > 0m
			? new Unit(StopLossPips * _pipSize, UnitTypes.Absolute)
			: null;

		var takeProfit = TakeProfitPips > 0 && _pipSize > 0m
			? new Unit(TakeProfitPips * _pipSize, UnitTypes.Absolute)
			: null;

		if (stopLoss != null || takeProfit != null)
			StartProtection(stopLoss: stopLoss, takeProfit: takeProfit);

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

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

		ShiftCloses(candle.ClosePrice);

		if (_recentCloses[1] is not decimal lastClose || _recentCloses[5] is not decimal olderClose)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		var threshold = lastClose * 0.005m;
		var longSignal = olderClose - lastClose > threshold;
		var shortSignal = lastClose - olderClose > threshold;
		var crossedLong = longSignal && (!_hasSignal || !_wasLongSignal);
		var crossedShort = shortSignal && (!_hasSignal || _wasLongSignal);

		if (crossedLong && Position <= 0)
		{
			BuyMarket();
		}
		else if (crossedShort && Position >= 0)
		{
			SellMarket();
		}

		if (longSignal || shortSignal)
		{
			_wasLongSignal = longSignal;
			_hasSignal = true;
		}
	}

	private void ShiftCloses(decimal close)
	{
		for (var i = _recentCloses.Length - 1; i > 0; i--)
			_recentCloses[i] = _recentCloses[i - 1];

		_recentCloses[0] = close;
	}

	private void InitializePipSize()
	{
		var step = Security.PriceStep ?? 0.01m;
		if (step <= 0m)
			step = 0.01m;

		var pipFactor = Security.Decimals is 3 or 5 ? 10m : 1m;
		_pipSize = step * pipFactor;
	}
}