Открыть на GitHub

Стратегия Close Agent

Обзор

Close Agent — вспомогательный модуль управления риском, являющийся портом MQL-советника CloseAgent. Стратегия не открывает новых сделок, а наблюдает за уже существующими позициями и закрывает их, когда цена выходит за пределы полос Боллинджера и RSI показывает экстремальные значения. Модуль способен отслеживать вручную открытые сделки или позиции других алгоритмов и при необходимости закрывать всё при достижении целевого профита.

Данные и индикаторы

  • Свечи: настраиваемый таймфрейм (по умолчанию 5 минут), служащий источником данных для индикаторов.
  • Полосы Боллинджера (период 21, ширина 2): фиксируют выход цены за верхнюю/нижнюю границу канала.
  • RSI (период 13): определяет состояние перекупленности (>70) или перепроданности (<30).
  • Котировки Level1: обеспечивают актуальные bid/ask для точного выполнения рыночных закрытий.

Параметры

  • Close Mode (CloseMode): выбирает, какие позиции контролировать.
    • Manual – только сделки без идентификатора текущей стратегии (ручные или внешние).
    • Auto – только позиции, открытые текущим экземпляром стратегии.
    • Both – все позиции по инструменту стратегии.
  • Candle Type (CandleType): таймфрейм, используемый при вычислении индикаторов.
  • Operation Mode (OperationMode):
    • LiveBar – использовать формирующуюся свечу; быстрее реагирует, но данные могут быть незавершёнными.
    • NewBar – ждать закрытия свечи для формирования сигнала; безопаснее, но медленнее.
  • Close All Target (CloseAllTarget): абсолютное значение PnL, при достижении которого все отслеживаемые позиции закрываются.
  • Enable Alerts (EnableAlerts): включает вывод в лог сообщений о каждом закрытии и величине зафиксированного профита.

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

  1. Стратегия подписывается на поток Level1 и выбранный свечной таймфрейм, рассчитывая полосы Боллинджера и RSI.
  2. Поддерживается небольшой буфер истории, чтобы при режиме NewBar использовать данные последней завершённой свечи.
  3. При достижении порога CloseAllTarget стратегия немедленно закрывает все подходящие позиции рыночными заявками.
  4. Для каждой позиции по текущему инструменту выполняются условия выхода:
    • Длинные позиции: закрываются, если лучшая цена Bid выше верхней полосы, RSI превышает 70, а текущая цена выше средней цены входа.
    • Короткие позиции: закрываются, если лучшая цена Ask ниже нижней полосы, RSI опускается ниже 30, а текущая цена ниже средней цены входа.
  5. Если bid/ask недоступны, используется последняя обработанная цена закрытия свечи, чтобы не пропустить сигнал.

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

  • Close Agent предназначен для совместной работы с другими торговыми системами и служит защитным слоем.
  • Поскольку стратегия занимается только закрытием, её следует запускать параллельно с основными алгоритмами открытия позиций.
  • При активированном параметре EnableAlerts в логе Designer появятся сообщения, аналогичные оригинальным уведомлениям MQL.
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>
/// Closes open positions when price stretches beyond Bollinger Bands and RSI reaches extreme values.
/// Adds SMA crossover entries for backtesting.
/// </summary>
public class CloseAgentStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _rsiLength;
	private readonly StrategyParam<int> _bollingerLength;
	private readonly StrategyParam<decimal> _rsiOverbought;
	private readonly StrategyParam<decimal> _rsiOversold;

	/// <summary>
	/// Candle type used to calculate indicators.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Length of the RSI indicator.
	/// </summary>
	public int RsiLength
	{
		get => _rsiLength.Value;
		set => _rsiLength.Value = value;
	}

	/// <summary>
	/// Length of the Bollinger Bands indicator.
	/// </summary>
	public int BollingerLength
	{
		get => _bollingerLength.Value;
		set => _bollingerLength.Value = value;
	}

	/// <summary>
	/// RSI threshold for overbought.
	/// </summary>
	public decimal RsiOverbought
	{
		get => _rsiOverbought.Value;
		set => _rsiOverbought.Value = value;
	}

	/// <summary>
	/// RSI threshold for oversold.
	/// </summary>
	public decimal RsiOversold
	{
		get => _rsiOversold.Value;
		set => _rsiOversold.Value = value;
	}

	/// <summary>
	/// Initializes parameters.
	/// </summary>
	public CloseAgentStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe used for indicators", "General");

		_rsiLength = Param(nameof(RsiLength), 13)
			.SetGreaterThanZero()
			.SetDisplay("RSI Length", "RSI period", "Indicators");

		_bollingerLength = Param(nameof(BollingerLength), 21)
			.SetGreaterThanZero()
			.SetDisplay("Bollinger Length", "Bollinger Bands period", "Indicators");

		_rsiOverbought = Param(nameof(RsiOverbought), 70m)
			.SetRange(0m, 100m)
			.SetDisplay("RSI Overbought", "Overbought threshold", "Signals");

		_rsiOversold = Param(nameof(RsiOversold), 30m)
			.SetRange(0m, 100m)
			.SetDisplay("RSI Oversold", "Oversold threshold", "Signals");
	}

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

	private decimal _prevFast;
	private decimal _prevSlow;
	private bool _hasPrev;

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevFast = 0m;
		_prevSlow = 0m;
		_hasPrev = false;
	}

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

		_hasPrev = false;

		var smaFast = new SimpleMovingAverage { Length = 10 };
		var smaSlow = new SimpleMovingAverage { Length = 30 };

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

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

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

		if (!_hasPrev)
		{
			_prevFast = fast;
			_prevSlow = slow;
			_hasPrev = true;
			return;
		}

		var crossUp = _prevFast <= _prevSlow && fast > slow;
		var crossDown = _prevFast >= _prevSlow && fast < slow;

		if (crossUp && Position <= 0)
		{
			if (Position < 0)
				BuyMarket();
			BuyMarket();
		}
		else if (crossDown && Position >= 0)
		{
			if (Position > 0)
				SellMarket();
			SellMarket();
		}

		_prevFast = fast;
		_prevSlow = slow;
	}
}