Открыть на GitHub

Стратегия Simple Trade

Обзор

Эта стратегия представляет собой портирование эксперта MetaTrader 5 «SimpleTrade (barabashkakvn's edition)» на платформу StockSharp. Алгоритм сравнивает цену открытия текущей свечи с ценой открытия свечи три бара назад: если текущая цена открытия выше, открывается длинная позиция, иначе — короткая. Каждая сделка удерживается ровно одну завершённую свечу и защищается фиксированным стоп-лоссом, заданным в пунктах.

Реализация использует высокоуровневый API StockSharp: стратегия подписывается на выбранный поток свечей и обрабатывает только свечи в состоянии Finished, чтобы принимать решения на основе полностью сформированных данных. Позиция закрывается при смене свечи либо раньше, если цена касается стоп-уровня внутри бара.

Логика торговли

  • Вход
    • После закрытия каждой свечи стратегия сохраняет её цену открытия и поддерживает скользящую историю из четырёх значений.
    • Когда позиций нет и доступно минимум четыре цены открытия, текущая цена сравнивается со значением трёх свечей назад.
    • Если текущая цена открытия выше, совершается покупка; в противном случае выполняется продажа.
  • Выход
    • Для каждой сделки рассчитывается стоп-уровень как произведение StopLossPips × размер пункта и смещение от цены открытия входной свечи.
    • На следующем баре позиция закрывается независимо от результата, что соответствует оригинальному советнику, который не держит сделку дольше одной свечи.
    • Если максимум (для шорта) или минимум (для лонга) текущей свечи пересекает стоп-уровень, стратегия пытается немедленно закрыть позицию по рынку.

Параметры

Параметр Значение по умолчанию Описание
StopLossPips 120 Расстояние до стоп-уровня в пунктах. Код воспроизводит поведение MetaTrader: для инструментов с 3 или 5 знаками после запятой шаг цены умножается на 10, чтобы получить стандартный размер пункта.
TradeVolume 1 Объём рыночной заявки. Настройте параметр в соответствии с размером контракта выбранного инструмента.
CandleType Таймфрейм 1 час Тип свечей, на которые подписывается стратегия. Выберите таймфрейм, соответствующий используемому в MetaTrader.

Все параметры представлены через StrategyParam<T>, поэтому их можно настраивать в интерфейсе и использовать в оптимизации.

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

  • История цен открытия хранится в наборе полей без дополнительных коллекций, что соответствует правилам репозитория.
  • Стоп-ордера не отправляются как отдельные заявки: стратегия анализирует диапазон свечи и при пробое уровня выставляет рыночную заявку на закрытие.
  • Поскольку обновление позиции в StockSharp происходит асинхронно, стратегия сначала закрывает текущую сделку, а затем переходит к поиску новой точки входа, что повторяет последовательность «сначала закрыть, потом открыть» из оригинального эксперта и исключает пересечение заявок.
  • Размер пункта вычисляется на основе Security.PriceStep. Для инструментов с 3 или 5 десятичными знаками шаг дополнительно умножается на 10, чтобы соответствовать определению pip в MetaTrader.

Рекомендации по применению

  • Используйте стратегию на инструментах с стабильным шагом цены, например на основных валютных парах, где понятие пункта имеет практический смысл.
  • Подбирайте значение StopLossPips для каждого инструмента: большие значения обеспечивают больший буфер, малые — повышают чувствительность к внутридневным колебаниям.
  • Убедитесь, что подключение к брокеру передаёт свечи в состоянии Finished, чтобы стратегия получала корректные цены открытия.

Риски и ограничения

  • Поскольку сделки удерживаются всего одну свечу, результат сильно зависит от выбранного таймфрейма. Рекомендуется тестировать разные периоды.
  • Эмуляция исполнения стопа по экстремумам свечи может отличаться от фактического исполнения стоп-заявок в условиях высокой волатильности.
  • После формирования стартовой истории стратегия почти постоянно находится в рынке (лонг или шорт), что может приводить к частой смене позиций во флэте.
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>
/// Simple trade strategy converted from MetaTrader that compares the current open with the open three bars ago.
/// The logic holds positions for a single bar and protects the trade with a fixed stop distance.
/// </summary>
public class SimpleTradeStrategy : Strategy
{
	private readonly StrategyParam<int> _stopLossPips;
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<DataType> _candleType;

	private decimal _openCurrent;
	private decimal _openMinus1;
	private decimal _openMinus2;
	private decimal _openMinus3;
	private int _historyCount;
	private decimal? _stopPrice;

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

	/// <summary>
	/// Trade volume used for market orders.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	/// <summary>
	/// Candle type processed by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initialize <see cref="SimpleTradeStrategy"/> parameters.
	/// </summary>
	public SimpleTradeStrategy()
	{
		_stopLossPips = Param(nameof(StopLossPips), 120)
			.SetGreaterThanZero()
			.SetDisplay("Stop Loss (pips)", "Fixed protective distance in pips", "Risk Management")
			;

		_tradeVolume = Param(nameof(TradeVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Volume", "Order volume in lots", "General");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Primary candle source for decisions", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, CandleType)];

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

		_openCurrent = 0m;
		_openMinus1 = 0m;
		_openMinus2 = 0m;
		_openMinus3 = 0m;
		_historyCount = 0;
		_stopPrice = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

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

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

		// Exit existing trades before evaluating new entries to mimic the original MQL behaviour.
		if (TryCloseExistingPosition(candle))
			return;

		UpdateHistory(candle.OpenPrice);

		// Need at least four opens to compare with the value three bars ago.
		if (_historyCount < 4)
			return;

		ExecuteEntry(candle);
	}

	private bool TryCloseExistingPosition(ICandleMessage candle)
	{
		if (Position > 0)
		{
			var volume = Position;

			// Close long trades at the protective stop or at the bar change.
			if (_stopPrice is decimal stop && candle.LowPrice <= stop)
			{
				SellMarket();
			}
			else
			{
				if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
			}

			_stopPrice = null;
			return true;
		}

		if (Position < 0)
		{
			var volume = Math.Abs(Position);

			// Close short trades at the protective stop or at the bar change.
			if (_stopPrice is decimal stop && candle.HighPrice >= stop)
			{
				BuyMarket();
			}
			else
			{
				if (Position > 0) SellMarket(); else if (Position < 0) BuyMarket();
			}

			_stopPrice = null;
			return true;
		}

		return false;
	}

	private void UpdateHistory(decimal currentOpen)
	{
		// Shift the stored opens so that _openMinus3 keeps the value three candles back.
		_openMinus3 = _openMinus2;
		_openMinus2 = _openMinus1;
		_openMinus1 = _openCurrent;
		_openCurrent = currentOpen;

		if (_historyCount < 4)
			_historyCount++;
	}

	private void ExecuteEntry(ICandleMessage candle)
	{
		var pipSize = CalculatePipSize();
		var stopOffset = pipSize * StopLossPips;

		// Enter long when the current open is above the open three bars ago, otherwise enter short.
		if (_openCurrent > _openMinus3)
		{
			BuyMarket();
			_stopPrice = candle.OpenPrice - stopOffset;
		}
		else
		{
			SellMarket();
			_stopPrice = candle.OpenPrice + stopOffset;
		}
	}

	private decimal CalculatePipSize()
	{
		// Reproduce the MetaTrader adjustment: multiply by ten for symbols with 3 or 5 decimals.
		var step = Security?.PriceStep ?? 1m;
		var decimals = Security?.Decimals;

		if (decimals == 3 || decimals == 5)
			step *= 10m;

		return step;
	}
}