在 GitHub 上查看

SpaceX 删除止损止盈按钮策略

概述

该策略复刻了 MetaTrader 面板 SpaceX_Delete_StopLoss_TakeProfit_button.mq5 中的 “DELETE SL_TP” 按钮。它作为一个实用工具,扫描投资组合中的持仓,并取消与这些持仓相关的所有止损和止盈保护订单。转换版本基于 StockSharp 的高级 API,帮助交易者在不逐一打开订单的情况下快速清除保护性挂单。

策略本身不会开仓或平仓,只会在收到指令后清理已有持仓的保护订单。因此非常适合人工管理仓位、或通过其他自动化系统建仓的交易者,在需要时一键撤除所有止损、止盈。

原始 EA 行为

MetaTrader 版本会在图表上创建一个带有 DELETE SL_TP 按钮的对话框。用户点击按钮时,EA 会遍历全部持仓并调用 PositionModify 将止损和止盈设置为零,从而移除所有保护级别,但保持持仓量不变。

核心特性:

  • 不会产生新的交易。
  • 处理终端中所有品种,不做过滤。
  • 仅删除止损和止盈,订单备注及魔术号保持不变。
  • 行为仅由图形界面的按钮触发。

StockSharp 实现方式

StockSharp 版本同样专注于移除保护订单,但通过策略参数而不是 GUI 按钮来触发。用户可以在 StockSharp Terminal/Hydra 的参数面板中切换这些参数,或在代码里直接修改。策略适用于任何能够提供止损或止盈订单信息的连接器。

实现提供两种执行方式:

  1. 启动时自动执行(可选):启用后策略启动时立即移除保护订单。
  2. 手动触发命令:布尔参数模拟原始按钮。将其设置为 true 后,定时器在下一次轮询时执行清理,并自动将参数恢复为 false

转换逻辑会对所有识别出的保护性订单调用 CancelOrder,包括止损、止盈以及任何条件单。持仓数量不会被修改。

参数

名称 说明 默认值
Run On Start (ApplyOnStart) 当为 true 时,策略启动后立即移除保护订单。 true
All Securities (AffectAllSecurities) 是否处理投资组合中所有品种。为 false 时只处理策略绑定的品种。 true
Delete Request (DeleteRequest) 模拟 MetaTrader 按钮的手动触发开关。设为 true 执行一次清理,完成后自动复位。 false
Polling Interval (s) (PollingIntervalSeconds) 定时器轮询手动触发参数的时间间隔(秒)。 1

工作流程

  1. 启动时验证轮询间隔并启动定时器。
  2. 如果启用了 Run On Start,立即执行一次清理。
  3. 每次定时器触发时检查 Delete Request。当参数为 true 时,收集所选范围内仍有持仓的品种,并取消其所有保护订单。
  4. 操作完成后自动将 Delete Request 复位为 false,确保每次触发只执行一次。

识别保护订单

若订单满足以下任意条件,则视为保护订单:

  • 订单类型为 StopTakeProfitConditional
  • 订单包含止损价、止盈价或非空的订单条件。

该判断方式覆盖了大多数常见适配器。如果你的连接器使用自定义订单类型,需要相应扩展识别逻辑。

使用建议

  • 将策略附加到负责持仓管理的连接器,确保相关持仓对当前投资组合可见。
  • 在 Hydra 或 Terminal 的参数面板中勾选 Delete Request 来模拟按钮点击。
  • 与其他策略配合使用,例如在重新设置保护订单之前先统一清空旧的止损、止盈。
  • 为了获得类似按钮的即时响应,保持较小的轮询间隔(默认 1 秒)。若想减少定时器触发次数,可适当增大该值。

与原 EA 的差异

  • 原版通过图表对话框即时响应,StockSharp 版本通过参数与定时器组合实现。
  • StockSharp 中止损和止盈通常以独立订单存在,因此转换版本通过取消订单来实现,而不是修改持仓属性。
  • 新增了作用范围参数,可仅针对绑定品种操作,这是对原 EA 的扩展。

限制

  • 需要连接器能够将止损、止盈作为可取消的订单暴露出来。若经纪商只在服务器端保存保护价格而不生成订单,则无法通过该策略取消。
  • 策略不提供 GUI 对话框,所有控制均通过参数完成。
  • 仅负责移除保护订单,不会重新设置止损或止盈。

测试

策略不包含独立的自动化测试,因为其逻辑较为简单。建议手动测试:建立示例持仓,附加策略,并在触发后确认所有止损、止盈订单已被取消。

namespace StockSharp.Samples.Strategies;

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

/// <summary>
/// SpaceX Delete SL/TP strategy: Standard Deviation breakout.
/// Buys when price breaks above upper StdDev band, sells on break below lower.
/// </summary>
public class SpaceXDeleteStopLossTakeProfitButtonStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _smaPeriod;
	private readonly StrategyParam<int> _stdDevPeriod;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int SmaPeriod { get => _smaPeriod.Value; set => _smaPeriod.Value = value; }
	public int StdDevPeriod { get => _stdDevPeriod.Value; set => _stdDevPeriod.Value = value; }

	public SpaceXDeleteStopLossTakeProfitButtonStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_smaPeriod = Param(nameof(SmaPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("SMA Period", "SMA period for baseline", "Indicators");
		_stdDevPeriod = Param(nameof(StdDevPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("StdDev Period", "Standard Deviation period", "Indicators");
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		var sma = new SimpleMovingAverage { Length = SmaPeriod };
		var stdDev = new StandardDeviation { Length = StdDevPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(sma, stdDev, ProcessCandle).Start();
	}

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

		var upper = smaValue + 2 * stdDevValue;
		var lower = smaValue - 2 * stdDevValue;

		if (candle.ClosePrice > upper && Position <= 0)
			BuyMarket();
		else if (candle.ClosePrice < lower && Position >= 0)
			SellMarket();
	}
}