Открыть на GitHub

Стратегия SimpleTrade Flip

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

  • Перенос экспертного советника MetaTrader 4 SimpleTrade.mq4 (в исходнике также встречается название «neroTrade») на платформу StockSharp.
  • Работает с одним инструментом и временным интервалом, задаваемым параметром CandleType.
  • В любой момент времени держит не более одной позиции и на открытии каждой новой свечи переходит в нужное направление.

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

  1. При появлении новой свечи в состоянии Active стратегия считывает её цену открытия и сравнивает с ценой открытия свечи, находящейся на LookbackBars периодов левее.
  2. Если текущая цена открытия выше эталонной, все позиции закрываются и отправляется рыночная покупка объёмом TradeVolume лотов.
  3. Если текущая цена открытия ниже или равна эталонной, открытые позиции также закрываются и выполняется рыночная продажа тем же объёмом.
  4. Параметр StopLossPoints полностью повторяет поле stop из MQL-версии. При наличии у инструмента PriceStep значение переводится в абсолютные цены и передаётся в StartProtection, что позволяет StockSharp автоматически сопровождать стоп-лосс.
  5. История цен открытия накапливается через высокоуровневую подписку на свечи: завершённые свечи пополняют буфер, а активная свеча инициирует расчёт ровно один раз за бар.

Параметры

Параметр Назначение Значение по умолчанию
TradeVolume Объём заявки в лотах. Должен быть больше нуля. 1
StopLossPoints Дистанция защитного стоп-лосса в пунктах инструмента. Значение 0 отключает автоматический стоп. 120
LookbackBars Количество свечей для сравнения цен открытия. При 3 реализуется проверка Open[0] против Open[3]. 3
CandleType Тип данных (временной интервал) для запрашиваемых свечей. Определяет частоту появления сигналов. таймфрейм 1 час

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

  • Задействован высокоуровневый пайплайн SubscribeCandles(...).Bind(...), что обеспечивает одинаковое поведение в бэктесте и в онлайне.
  • В OnStarted вызывается StartProtection. Убедитесь, что у инструмента задан PriceStep; без него рассчитать абсолютный стоп-лосс невозможно.
  • Все сделки совершаются рыночными ордерами на открытии свечей, поэтому отдельный параметр проскальзывания не нужен.
  • Буфер цен открытия ограничен LookbackBars + 5 элементами и не разрастается бесконтрольно.
  • В папке присутствует только реализация на C#; версия на Python не создавалась.

Структура каталога

4002_SimpleTrade/
├── CS/
│   └── SimpleTradeFlipStrategy.cs
├── README.md
├── README_zh.md
└── README_ru.md
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;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// StockSharp port of the MetaTrader "SimpleTrade" expert advisor.
/// Compares the opening price of the current bar with the bar from several periods ago and flips the position accordingly.
/// </summary>
public class SimpleTradeFlipStrategy : Strategy
{
	private readonly StrategyParam<decimal> _tradeVolume;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<int> _lookbackBars;
	private readonly StrategyParam<DataType> _candleType;

	private readonly List<decimal> _openHistory = new();
	private int _cooldown;

	/// <summary>
	/// Initializes a new instance of the <see cref="SimpleTradeFlipStrategy"/> class.
	/// </summary>
	public SimpleTradeFlipStrategy()
	{
		_tradeVolume = Param(nameof(TradeVolume), 1m)
			.SetGreaterThanZero()
			.SetDisplay("Trade Volume", "Order size in lots", "Trading");

		_stopLossPoints = Param(nameof(StopLossPoints), 120m)
			.SetNotNegative()
			.SetDisplay("Stop-Loss Points", "Protective stop distance expressed in instrument points", "Risk");

		_lookbackBars = Param(nameof(LookbackBars), 10)
			.SetGreaterThanZero()
			.SetDisplay("Lookback Bars", "Number of bars used for the open price comparison", "Signals");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
			.SetDisplay("Candle Type", "Primary timeframe used for signal calculations", "General");
	}

	/// <summary>
	/// Order size submitted with each entry.
	/// </summary>
	public decimal TradeVolume
	{
		get => _tradeVolume.Value;
		set => _tradeVolume.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in instrument points.
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = Math.Max(0m, value);
	}

	/// <summary>
	/// Number of historical bars used for the open price comparison.
	/// </summary>
	public int LookbackBars
	{
		get => _lookbackBars.Value;
		set => _lookbackBars.Value = Math.Max(1, value);
	}

	/// <summary>
	/// Candle type that defines the working timeframe.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

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

		_openHistory.Clear();
		_cooldown = 0;
	}

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

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

		var step = Security?.PriceStep ?? 0m;
		Unit stopLossUnit = null;

		if (StopLossPoints > 0m && step > 0m)
			stopLossUnit = new Unit(StopLossPoints * step, UnitTypes.Absolute);

		StartProtection(null, stopLossUnit);
	}

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

		// Store the open price for future comparisons.
		_openHistory.Add(candle.OpenPrice);

		var maxHistory = Math.Max(LookbackBars + 5, 5);
		if (_openHistory.Count > maxHistory)
			_openHistory.RemoveRange(0, _openHistory.Count - maxHistory);

		var lookback = LookbackBars;
		if (_openHistory.Count <= lookback)
			return;

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		if (_cooldown > 0)
		{
			_cooldown--;
			return;
		}

		var volume = TradeVolume;
		if (volume <= 0m)
			return;

		var currentOpen = candle.OpenPrice;
		var referenceOpen = _openHistory[^(lookback + 1)];

		// Only trade on clear directional difference
		var diff = currentOpen - referenceOpen;
		if (Math.Abs(diff) < currentOpen * 0.001m)
			return;

		if (diff > 0 && Position <= 0)
		{
			if (Position < 0m)
				BuyMarket(Math.Abs(Position));
			BuyMarket(volume);
			_cooldown = 5;
		}
		else if (diff < 0 && Position >= 0)
		{
			if (Position > 0m)
				SellMarket(Position);
			SellMarket(volume);
			_cooldown = 5;
		}
	}
}