View on GitHub

Close Agent Strategy

Overview

Close Agent Strategy is a risk-management assistant that mirrors the MQL CloseAgent expert advisor. The strategy does not open new trades. Instead, it monitors existing positions and closes them when price stretches beyond the Bollinger Bands while the Relative Strength Index (RSI) reaches extreme zones. The tool can watch for positions created manually or by other automated strategies and optionally liquidate everything once a global profit target is achieved.

Indicators and Data

  • Candles: configurable timeframe (default: 5-minute) used to calculate indicators.
  • Bollinger Bands (length 21, width 2): detects price excursions above the upper band or below the lower band.
  • Relative Strength Index (length 13): confirms whether the market is overbought (>70) or oversold (<30).
  • Level1 data: captures the latest bid and ask quotes to evaluate exit conditions as accurately as possible.

Parameters

  • Close Mode (CloseMode): selects which positions are eligible for closing.
    • Manual – only positions without this strategy identifier (manual trades or other bots).
    • Auto – only positions opened by this strategy instance.
    • Both – monitor every position on the strategy symbol.
  • Candle Type (CandleType): timeframe used to calculate Bollinger Bands and RSI.
  • Operation Mode (OperationMode):
    • LiveBar – use the latest forming candle; reacts faster but may use unfinished data.
    • NewBar – waits for a candle to close before generating a signal (safer but slower).
  • Close All Target (CloseAllTarget): if the floating profit (PnL) reaches this absolute value, every monitored position is closed immediately.
  • Enable Alerts (EnableAlerts): when true, logs a message every time an exit is triggered, including the realized profit estimate.

Trading Logic

  1. Subscribes to Level1 quotes and the configured candle series. Bollinger Bands and RSI are updated for each incoming candle.
  2. Maintains a compact history buffer so the strategy can reference the most recent closed candle when OperationMode is set to NewBar.
  3. Checks if the global profit target is reached. When CloseAllTarget > 0 and PnL exceeds the threshold, all eligible positions are flattened at market prices.
  4. For each monitored position on the strategy symbol:
    • Long positions: closed when the best bid is above the upper Bollinger Band, RSI is above 70, and price remains above the entry average price.
    • Short positions: closed when the best ask is below the lower Bollinger Band, RSI is below 30, and price remains below the entry average price.
  5. Uses bid/ask quotes when available; otherwise falls back to the last processed candle close to avoid missed exits.

Usage Notes

  • The strategy is designed as a protective layer and assumes positions might be opened externally.
  • Because the logic acts on market exits only, the strategy should run alongside other trading systems to manage risk exposure.
  • Alerts appear in the Designer log when EnableAlerts is active, matching the original MQL alerts.
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;
	}
}