在 GitHub 上查看

TradeXpert 手动交易面板策略

概述

原始的 TradeXpert MQL5 专家顾问是一款手动操作的交易面板,通过一组按钮完成开仓、挂单、设置防护止损/止盈以及一键反手等功能。本移植版本在 StockSharp 中通过策略参数重现这些工具。策略本身不会产生交易信号,而是监听用户输入的指令,按需发送订单,并在新的 K 线到达时持续监控防护退出。

复现的功能

  • 市价操作。 使用当前设置的交易手数执行单次 BuySell 市价单。
  • 挂单操作。 可一次性下达买入限价/止损、卖出限价/止损单,价格可以是绝对值或相对于最新收盘价的偏移量。
  • 防护管理。 止损和止盈既可以直接指定价格,也可以指定相对入场价的偏移。当 K 线的最高价或最低价突破设定阈值时,策略会立刻用市价单平仓。
  • 手动退出控制。 参数中提供了“平仓”和“反手”开关,对应原面板中的按钮,可按需关闭或反转当前仓位。

策略逻辑

  1. 策略订阅由 CandleType 指定的蜡烛序列,用于计算偏移量及检测止损止盈触发情况。
  2. 每根收盘的 K 线到来时,策略会:
    • 将最新的 TradeVolume 同步到基类的 Volume 属性;
    • 即使指标尚未形成,也优先处理手动的平仓或反手请求;
    • 在确认行情数据就绪后,执行新的市价指令、注册挂单,并检查止损/止盈是否被触发。
  3. 只要仓位数量发生变化(开仓、加仓或减仓),策略就会刷新记录的入场价,使偏移量类的防护设置与最新交易保持一致。
  4. 防护逻辑基于 K 线的极值判断是否突破。一旦触发,就按仓位的绝对数量发送反向市价单,确保仓位彻底关闭。

参数说明

  • CandleType – 用于监控价格并计算偏移的蜡烛类型。
  • TradeVolume – 每次市价单与挂单使用的数量(必须大于 0)。
  • EntryAction – 瞬时的市价指令选择器,可取 NoneBuyMarketSellMarket。设置为非 None 时只执行一次对应的市价单,随后自动恢复为 None
  • PendingAction – 挂单类型选择器(NoneBuyLimitBuyStopSellLimitSellStop),成功下单后自动清除。
  • PendingPrice – 挂单的绝对价格。保持为 0 时使用 PendingOffset 计算价格。
  • PendingOffset – 当 PendingPrice0 时,相对于最新收盘价的偏移量,根据挂单方向自动决定加减。
  • UseStopLoss / StopLossPrice / StopLossOffset – 是否启用止损及其价格/偏移设置;当绝对价格为 0 时,将根据偏移量和记录的入场价计算止损价。
  • UseTakeProfit / TakeProfitPrice / TakeProfitOffset – 与止盈相关的设置,与止损逻辑一致。
  • ClosePositionRequest – 置为 true 时立即按市价平掉全部仓位,执行完毕后自动恢复为 false
  • ReversePositionRequest – 置为 true 时对当前仓位反手操作:先平掉现有仓位,再按照 ReverseVolume 建立反向持仓,然后恢复为 false
  • ReverseVolume – 反手后需要建立的新仓位数量。若希望反手后的仓位大小与当前仓位一致,可将该值设置为当前仓位的绝对值。

使用指南

  1. 选择合适的 CandleType 作为偏移计算与风险监控的基础。默认的 1 分钟 K 线与原面板处理实时报价的行为一致。
  2. 设置 TradeVolume 以及可选的止损/止盈参数(StopLoss*TakeProfit*)。绝对价格与偏移量可以自由切换,当绝对值为 0 时自动使用偏移量。
  3. 对于挂单,决定是直接指定价格 (PendingPrice) 还是使用偏移量 (PendingOffset)。策略会在发送挂单前根据当时的收盘价计算最终价格。
  4. 通过修改 EntryActionPendingActionClosePositionRequestReversePositionRequest 发出操作指令。这些参数都像按钮一样工作:执行完后自动复位,避免下一根 K 线重复触发。
  5. 在持仓期间,策略会持续检查价格行为。一旦止损或止盈条件满足,就以市价平仓,并在下一次开仓前禁止重复触发。

与原 MQL 版本的区别

  • 图形化面板被策略参数所取代。原来 UI 上的每个按钮现在都对应一个可以在 StockSharp 参数面板或自动化脚本中修改的设置。
  • 防护退出通过市价平仓实现,而不是提交单独的止损/止盈订单。这种做法可以完全利用高层 API,避免管理额外的挂单。
  • 偏移计算基于收盘完成的 K 线,而不是逐笔行情,使得回测与实盘行为更加一致且可复现。

注意事项

  • 可以在同一根 K 线内发出多个指令(例如,先买入,再设置偏移止盈),策略会在下一根收盘 K 线按顺序执行。
  • 如需重复同一操作,只需再次选择对应的参数值;内部逻辑会检测到变化并重新执行。
  • 当进行加仓或减仓时,记录的入场价会更新为反映仓位变化的那根 K 线的收盘价。如需保持精确的偏移距离,请及时调整偏移量设置。
using System;
using System.Collections.Generic;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// TradeXpert Manual Trading Panel strategy. Uses CCI zero-line crossover (period 20).
/// </summary>
public class TradeXpertManualTradingPanelStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _cciPeriod;
	private decimal? _prevCci;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int CciPeriod { get => _cciPeriod.Value; set => _cciPeriod.Value = value; }

	public TradeXpertManualTradingPanelStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
		_cciPeriod = Param(nameof(CciPeriod), 20).SetGreaterThanZero().SetDisplay("CCI Period", "CCI lookback", "Indicators");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevCci = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevCci = null;
		var cci = new CommodityChannelIndex { Length = CciPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(cci, ProcessCandle).Start();
		var area = CreateChartArea();
		if (area != null) { DrawCandles(area, subscription); DrawOwnTrades(area); }
	}

	private void ProcessCandle(ICandleMessage candle, decimal cciVal)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!IsFormedAndOnlineAndAllowTrading()) { _prevCci = cciVal; return; }
		if (_prevCci == null) { _prevCci = cciVal; return; }
		if (_prevCci.Value < 0m && cciVal >= 0m && Position <= 0) { if (Position < 0) BuyMarket(); BuyMarket(); }
		else if (_prevCci.Value > 0m && cciVal <= 0m && Position >= 0) { if (Position > 0) SellMarket(); SellMarket(); }
		_prevCci = cciVal;
	}
}