在 GitHub 上查看

Close Agent 策略

概述

Close Agent 策略是一款风控辅助工具,对应原始的 MQL CloseAgent 智能交易系统。策略本身不会开仓,而是持续监控已有仓位:当价格突破布林带边界并且 RSI 指标进入极值区时,自动平仓。它可以筛选人工开仓或其他策略生成的仓位,并在浮动盈利达到设定阈值时一键清仓。

数据与指标

  • K 线: 可配置周期(默认 5 分钟),用于驱动所有指标。
  • 布林带(周期 21,带宽 2): 用于识别价格是否冲破上轨或下轨。
  • RSI 相对强弱指数(周期 13): 判断市场是否处于超买(>70)或超卖(<30)状态。
  • Level1 报价: 订阅最新买价/卖价,以便尽量按实际盘口条件执行平仓。

参数

  • Close Mode (CloseMode): 选择哪些仓位需要监控。
    • Manual – 仅处理没有当前策略标识的仓位(人工或其他策略开仓)。
    • Auto – 仅处理由当前策略实例开出的仓位。
    • Both – 监控该标的上的所有仓位。
  • Candle Type (CandleType): 计算布林带与 RSI 时使用的时间框架。
  • Operation Mode (OperationMode):
    • LiveBar – 使用最新生成中的 K 线,响应更快但数据可能尚未收盘。
    • NewBar – 仅在 K 线收盘后评估信号,更稳健但触发稍慢。
  • Close All Target (CloseAllTarget): 当策略的浮动盈亏 PnL 达到此绝对值时,立即关闭所有受监控仓位。
  • Enable Alerts (EnableAlerts): 开启后,每次平仓都会在日志中输出提示,并显示估算的收益。

交易逻辑

  1. 订阅 Level1 报价和配置的 K 线序列,同步更新布林带与 RSI。
  2. 维护一个小型历史缓冲区,在 OperationMode = NewBar 时可以获得最近一次完整收盘的数据。
  3. 若设置了全局盈利目标且 PnL 达到阈值,则立即按市价关闭所有符合过滤条件的仓位。
  4. 针对监控标的的每个仓位逐一检查:
    • 多头仓位: 当最优买价突破布林带上轨、RSI 超过 70 且价格仍高于平均开仓价时平仓。
    • 空头仓位: 当最优卖价跌破布林带下轨、RSI 低于 30 且价格仍低于平均开仓价时平仓。
  5. 平仓时优先使用实时买卖价,如无可用报价则回退至最新处理过的 K 线收盘价,避免遗漏信号。

使用建议

  • 该策略定位为风险控制层,假设仓位可能由外部策略或人工开立。
  • 由于仅负责平仓,建议与其他交易策略同时运行,以便在出现极端行情时及时退出。
  • 开启 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;
	}
}