Открыть на GitHub

Стратегия Open Close (ID 3996)

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

Стратегия повторяет советник MetaTrader 4 open_close.mq4. Она работает с одним инструментом и сравнивает цены открытия и закрытия двух последовательных свечей. При отсутствии позиции робот торгует против резких одноcвечных движений (паттерн разрыва и разворота). Когда сделка уже открыта, выход происходит при смене паттерна или при достижении порога защитного убытка по плавающему результату.

Торговые правила

Вход

  • Торговля возможна только после формирования предыдущей свечи (аналог исходного условия Volume[0] == 1).
  • Покупка: текущая свеча открылась выше, чем предыдущая, и закрылась ниже предыдущего закрытия. Объём покупается по рынку.
  • Продажа: текущая свеча открылась ниже предыдущей и закрылась выше предыдущего закрытия. Объём продаётся по рынку.

В каждый момент времени допускается только одна позиция. Новые сигналы игнорируются до её закрытия.

Выход

  1. Защита капитала. Плавающий результат рассчитывается от средней цены входа. Если убыток превышает MaximumRisk × Portfolio.CurrentValue, позиция немедленно закрывается. В оригинале использовалась функция AccountMargin; здесь она заменена на доступную оценку стоимости портфеля.
  2. Смена паттерна.
    • Длинная позиция закрывается, когда следующая свеча продолжает падение (open < предыдущий open и close < предыдущий close).
    • Короткая позиция закрывается, когда следующая свеча продолжает рост (open > предыдущий open и close > предыдущий close).

Управление объёмом

  • Базовый объём вычисляется по параметру MaximumRisk: стоимость портфеля умножается на коэффициент риска и делится на 1000, что повторяет формулу MetaTrader AccountFreeMargin * MaximumRisk / 1000.
  • Если данные портфеля недоступны, используется резервный параметр InitialVolume.
  • После двух и более убыточных сделок подряд объём уменьшается на volume × losses / DecreaseFactor, что воспроизводит цикл подсчёта убытков в исходном коде.
  • Минимальный размер сделки — 0.1 лота. Затем объём приводится к шагу инструмента и ограничениям биржи.

Параметры

Имя Тип Значение по умолчанию Описание
InitialVolume decimal 0.1 Резервный объём, если нет данных о капитале.
MaximumRisk decimal 0.3 Доля капитала, определяющая размер сделки и максимально допустимый плавающий убыток.
DecreaseFactor decimal 100 Коэффициент снижения объёма после серии убыточных сделок.
CandleType DataType таймфрейм 15 минут Свечи, на которых оценивается паттерн.

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

  • Подписка выполняется на выбранный тип свечей; обрабатываются только закрытые свечи, что соответствует проверке Volume[0] > 1.
  • Плавающий результат вычисляется по текущей позиции и последней цене закрытия, поскольку в StockSharp нет прямых аналогов AccountProfit и AccountMargin из MetaTrader.
  • Серия убыточных сделок отслеживается по факту исполнения заявок, поэтому DecreaseFactor работает так же, как исторический анализ в MQL.
  • Объём корректируется по Security.VolumeStep, MinVolume и MaxVolume, чтобы соблюсти требования площадки.
  • При наличии области графика отображаются свечи и собственные сделки для визуальной отладки.

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

  • Подбирайте таймфрейм, соответствующий настройкам оригинального советника MetaTrader.
  • Регулируйте MaximumRisk и DecreaseFactor, чтобы настроить агрессивность расчёта объёма.
  • Стратегия контртрендовая, поэтому лучше всего работает на инструментах с частыми одноcвечными перекупленностями/перепроданностями и быстрыми откатами.
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>
/// Contrarian pattern strategy converted from the MetaTrader expert "open_close".
/// Evaluates relationships between consecutive candle opens and closes.
/// Buys when a bearish candle opens above the previous open (fading the move),
/// and sells when a bullish candle opens below the previous open.
/// </summary>
public class OpenCloseStrategy : Strategy
{
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<DataType> _candleType;

	private ExponentialMovingAverage _ema;

	private bool _hasPreviousCandle;
	private decimal _previousOpen;
	private decimal _previousClose;

	public OpenCloseStrategy()
	{
		_stopLossPoints = Param(nameof(StopLossPoints), 500m)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Stop loss in absolute points", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 500m)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take profit in absolute points", "Risk");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Time-frame used to evaluate the open/close pattern.", "Data");

		Volume = 1;
	}

	/// <summary>
	/// Stop loss distance in absolute points.
	/// </summary>
	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	/// <summary>
	/// Take profit distance in absolute points.
	/// </summary>
	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

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

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

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_hasPreviousCandle = false;
		_previousOpen = 0m;
		_previousClose = 0m;
		_ema = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		_ema = new ExponentialMovingAverage { Length = 20 };

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

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

		var tp = TakeProfitPoints > 0 ? new Unit(TakeProfitPoints, UnitTypes.Absolute) : null;
		var sl = StopLossPoints > 0 ? new Unit(StopLossPoints, UnitTypes.Absolute) : null;
		if (tp != null || sl != null)
			StartProtection(tp, sl);

		base.OnStarted2(time);
	}

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

		var open = candle.OpenPrice;
		var close = candle.ClosePrice;

		if (!_hasPreviousCandle)
		{
			_previousOpen = open;
			_previousClose = close;
			_hasPreviousCandle = true;
			return;
		}

		// Exit logic
		if (Position > 0)
		{
			// Close long on bearish continuation
			if (open < _previousOpen && close < _previousClose)
				SellMarket(Position);
		}
		else if (Position < 0)
		{
			// Close short on bullish continuation
			if (open > _previousOpen && close > _previousClose)
				BuyMarket(Math.Abs(Position));
		}

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_previousOpen = open;
			_previousClose = close;
			return;
		}

		// Entry logic
		if (Position == 0)
		{
			// Buy: fade a bearish candle that opened above the previous open
			if (open > _previousOpen && close < _previousClose)
			{
				BuyMarket(Volume);
			}
			// Sell: fade a bullish candle that opened below the previous open
			else if (open < _previousOpen && close > _previousClose)
			{
				SellMarket(Volume);
			}
		}

		_previousOpen = open;
		_previousClose = close;
	}
}