在 GitHub 上查看

手动持仓跟踪面板策略

概述

原始的 MQL5 专家顾问通过图形界面让交易者一次管理多达五个多头和五个空头仓位。按钮可以删除已有的止盈、按照开仓价重新计算止盈,或将止盈移动到保本价。移植到 StockSharp 之后,这些保护操作在没有可视面板的情况下自动执行。策略持续监控所选品种的总体仓位,并按照面板的行为维护一个保护性的止盈委托。

自动化流程包括:

  • 当出现仓位时,以开仓价加/减配置的 MetaTrader 点数距离挂出止盈。
  • 当市场向有利方向移动达到设定点数时,可选地把止盈拉回平均开仓价,从而锁定保本退出。
  • 如果交易所通过 Level1 数据公布冻结/止损距离,则严格遵守;否则使用当前点差乘以用户设定的系数近似这些距离。
  • 当仓位被平掉或管理条件不满足时撤销止盈订单,对应原面板中的“删除 TP”按钮。

策略完全基于 StockSharp 的高级 API(例如 SubscribeLevel1SellLimitBuyLimitReRegisterOrder 等),并自动对价格和数量做步长归一化,因而可以附加到任何受支持的交易品种上。

参数

参数 说明
Take profit distance (pips) 创建保护性止盈时,在开仓价基础上增加的 MetaTrader 点数距离。
Enable entry-based take profit 是否按开仓价自动挂出止盈。关闭时仅在触发保本时修改。
Enable break-even 是否在满足触发条件后将止盈移动到平均开仓价。
Break-even trigger (pips) 触发保本所需的最小有利移动距离(MetaTrader 点)。为 0 表示立即保本。
Manage long positions 是否管理多头仓位。
Manage short positions 是否管理空头仓位。
Remove take profit when disabled 当管理条件不满足时是否自动撤销现有止盈。
Log management actions 是否为每次创建、修改或撤销止盈写入日志。
Freeze distance multiplier 当没有冻结/止损信息时,用当前点差估算安全缓冲的乘数。

信号与执行规则

  1. 启动后订阅 Level1 数据,以获取最优买卖价及可选的冻结、止损信息。
  2. 一旦出现新成交、仓位发生变化或收到新的 Level1 数据,就重新计算保护逻辑。
  3. 没有仓位时会撤销所有保护性止盈。
  4. 当仓位存在且对应方向被启用时:
    • 如果启用开仓价止盈,则以配置点数偏移计算基础目标价。
    • 若启用保本且市场已达到触发距离,则将目标价限制在平均开仓价。
    • 通过比较当前行情,确保目标价满足冻结/止损距离。
    • 根据价格步长和数量步长进行归一化,然后在对向挂出或修改限价单。
  5. 如果该方向的管理被禁用且 Remove take profit when disabled 为真,则撤销已存在的止盈订单。

风险提示

  • 策略仅管理止盈,不会创建止损、追踪止损或部分平仓。
  • 为兼容外汇品种,自动根据 PriceStep 和精度计算 MetaTrader 点值。
  • 若交易所未提供冻结/止损距离,可通过乘数参数在当前点差基础上留出缓冲,避免订单被拒绝。
  • 策略不会主动开仓,适合与人工或其他系统的开仓逻辑组合使用。

使用建议

  1. 将策略附加到需要管理的品种,并确保连接器提供 Level1 行情。
  2. 设置与原先 MetaTrader 面板一致的点数距离。
  3. 想要锁定盈利时启用保本模块;若希望立即保本,将触发点数设为 0
  4. 如果某个方向仍需人工控制,可关闭对应的管理开关。
  5. 打开 Log management actions 后查看日志,确认订单按预期创建或调整。
namespace StockSharp.Samples.Strategies;

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

/// <summary>
/// Manual Position Tracking Panel strategy: MFI crossover.
/// Buys when MFI crosses above 20 (oversold exit), sells when crosses below 80 (overbought exit).
/// </summary>
public class ManualPositionTrackingPanelStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _period;
	private readonly StrategyParam<int> _signalCooldownCandles;

	private decimal _prevMfi;
	private int _candlesSinceTrade;
	private bool _hasPrev;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int Period { get => _period.Value; set => _period.Value = value; }
	public int SignalCooldownCandles { get => _signalCooldownCandles.Value; set => _signalCooldownCandles.Value = value; }

	public ManualPositionTrackingPanelStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_period = Param(nameof(Period), 14)
			.SetGreaterThanZero()
			.SetDisplay("Period", "MFI period", "Indicators");
		_signalCooldownCandles = Param(nameof(SignalCooldownCandles), 4)
			.SetGreaterThanZero()
			.SetDisplay("Signal Cooldown", "Bars to wait between trades", "Trading");
	}

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

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

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

		if (_candlesSinceTrade < SignalCooldownCandles)
			_candlesSinceTrade++;

		if (_hasPrev)
		{
			if (_prevMfi < 15 && mfiValue >= 15 && Position <= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				BuyMarket();
				_candlesSinceTrade = 0;
			}
			else if (_prevMfi > 85 && mfiValue <= 85 && Position >= 0 && _candlesSinceTrade >= SignalCooldownCandles)
			{
				SellMarket();
				_candlesSinceTrade = 0;
			}
		}

		_prevMfi = mfiValue;
		_hasPrev = true;
	}
}