在 GitHub 上查看

Close Orders Risk Control 策略

概览

平仓指令策略 是一个风险管理工具,复刻了原始 MQL 专家顾问 CloseOrders.mq4 的逻辑。策略持续监控持仓的浮动盈亏,只要达到设定的盈利目标或跌破止损阈值,就会自动关闭符合条件的订单,从而保护账户并同步多策略的退出时机。

工作流程

  1. 策略订阅可配置的K线数据(默认 1 分钟),每当K线收盘时评估最新的浮动盈亏。
  2. 浮动盈亏基于投资组合中的持仓计算;如果设置了魔术号,则只统计 StrategyId 与该值相同的持仓。
  3. 当浮动盈亏大于或等于盈利目标时,所有匹配的订单与持仓都会被立即关闭。
  4. 当浮动盈亏小于等于设定的止损金额(负值)时,同样会触发强制平仓,以减少进一步的亏损。
  5. 在发送市价单之前,策略会先取消满足魔术号条件的挂单,避免在清仓过程中重新建立敞口。

清算流程会持续执行,直到所有匹配的持仓都被平掉,从而优雅地处理部分成交的情况。

参数说明

参数 说明
Target Profit Money 触发平仓的浮动盈利金额(账户货币),必须大于零。
Cut Loss Money 触发强制平仓的浮动亏损金额(账户货币,负值)。设置为 0 表示不启用亏损阈值。
Magic Number 可选的策略标识。留空表示管理所有持仓;若填写,则只管理 StrategyId 等于该值的持仓。
Candle Type 用于触发定期检查的K线类型。可调整时间框架以满足更高频的监控需求。

实现要点

  • MQL 的魔术号概念映射到 StockSharp 中的 UserOrderId / StrategyId 字段,请确保被管理的策略使用相同的标识。
  • 源代码使用制表符缩进,并遵循转换策略的统一结构要求。
  • 在发送平仓市价单之前会取消相关挂单,避免在退出过程中重新开仓。
  • 如果策略与实时交易组件配合使用,可根据需要进一步扩展保护逻辑。

使用建议

  • 将该策略与设置了自定义 StrategyId 的交易策略配合部署,可集中处理风险退出逻辑。
  • 根据实际需要调整 Candle Type 参数,较短的时间框架可以更快响应浮动盈亏变化。
  • 可结合通知或报警机制,在自动平仓发生时及时获知账户状态。
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Close Orders Risk Control strategy: CCI crossover with risk management.
/// Buys when CCI crosses above zero, sells when crosses below zero.
/// </summary>
public class CloseOrdersRiskControlStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cciPeriod;
	private readonly StrategyParam<decimal> _cciLevel;
	private readonly StrategyParam<int> _signalCooldownCandles;

	private decimal _prevCci;
	private int _candlesSinceTrade;
	private bool _hasPrev;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int CciPeriod { get => _cciPeriod.Value; set => _cciPeriod.Value = value; }
	public decimal CciLevel { get => _cciLevel.Value; set => _cciLevel.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public CloseOrdersRiskControlStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_cciPeriod = Param(nameof(CciPeriod), 30)
			.SetGreaterThanZero()
			.SetDisplay("CCI Period", "CCI period", "Indicators");
		_cciLevel = Param(nameof(CciLevel), 100m)
			.SetDisplay("CCI Level", "CCI threshold for crossover", "Signals");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 4)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevCci = 0;
		_candlesSinceTrade = SignalCooldownCandles;
		_hasPrev = false;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevCci = 0;
		_candlesSinceTrade = SignalCooldownCandles;
		_hasPrev = false;
		var cci = new CommodityChannelIndex { Length = CciPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(cci, ProcessCandle).Start();
	}

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

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		if (_hasPrev)
		{
			if (_prevCci < -CciLevel && cciValue >= -CciLevel && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
			else if (_prevCci > CciLevel && cciValue <= CciLevel && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
		}

		_prevCci = cciValue;
		_hasPrev = true;
	}
}