在 GitHub 上查看
TradeXpert 手动交易面板策略
概述
原始的 TradeXpert MQL5 专家顾问是一款手动操作的交易面板,通过一组按钮完成开仓、挂单、设置防护止损/止盈以及一键反手等功能。本移植版本在 StockSharp 中通过策略参数重现这些工具。策略本身不会产生交易信号,而是监听用户输入的指令,按需发送订单,并在新的 K 线到达时持续监控防护退出。
复现的功能
- 市价操作。 使用当前设置的交易手数执行单次
Buy 或 Sell 市价单。
- 挂单操作。 可一次性下达买入限价/止损、卖出限价/止损单,价格可以是绝对值或相对于最新收盘价的偏移量。
- 防护管理。 止损和止盈既可以直接指定价格,也可以指定相对入场价的偏移。当 K 线的最高价或最低价突破设定阈值时,策略会立刻用市价单平仓。
- 手动退出控制。 参数中提供了“平仓”和“反手”开关,对应原面板中的按钮,可按需关闭或反转当前仓位。
策略逻辑
- 策略订阅由
CandleType 指定的蜡烛序列,用于计算偏移量及检测止损止盈触发情况。
- 每根收盘的 K 线到来时,策略会:
- 将最新的
TradeVolume 同步到基类的 Volume 属性;
- 即使指标尚未形成,也优先处理手动的平仓或反手请求;
- 在确认行情数据就绪后,执行新的市价指令、注册挂单,并检查止损/止盈是否被触发。
- 只要仓位数量发生变化(开仓、加仓或减仓),策略就会刷新记录的入场价,使偏移量类的防护设置与最新交易保持一致。
- 防护逻辑基于 K 线的极值判断是否突破。一旦触发,就按仓位的绝对数量发送反向市价单,确保仓位彻底关闭。
参数说明
CandleType – 用于监控价格并计算偏移的蜡烛类型。
TradeVolume – 每次市价单与挂单使用的数量(必须大于 0)。
EntryAction – 瞬时的市价指令选择器,可取 None、BuyMarket、SellMarket。设置为非 None 时只执行一次对应的市价单,随后自动恢复为 None。
PendingAction – 挂单类型选择器(None、BuyLimit、BuyStop、SellLimit、SellStop),成功下单后自动清除。
PendingPrice – 挂单的绝对价格。保持为 0 时使用 PendingOffset 计算价格。
PendingOffset – 当 PendingPrice 为 0 时,相对于最新收盘价的偏移量,根据挂单方向自动决定加减。
UseStopLoss / StopLossPrice / StopLossOffset – 是否启用止损及其价格/偏移设置;当绝对价格为 0 时,将根据偏移量和记录的入场价计算止损价。
UseTakeProfit / TakeProfitPrice / TakeProfitOffset – 与止盈相关的设置,与止损逻辑一致。
ClosePositionRequest – 置为 true 时立即按市价平掉全部仓位,执行完毕后自动恢复为 false。
ReversePositionRequest – 置为 true 时对当前仓位反手操作:先平掉现有仓位,再按照 ReverseVolume 建立反向持仓,然后恢复为 false。
ReverseVolume – 反手后需要建立的新仓位数量。若希望反手后的仓位大小与当前仓位一致,可将该值设置为当前仓位的绝对值。
使用指南
- 选择合适的
CandleType 作为偏移计算与风险监控的基础。默认的 1 分钟 K 线与原面板处理实时报价的行为一致。
- 设置
TradeVolume 以及可选的止损/止盈参数(StopLoss*、TakeProfit*)。绝对价格与偏移量可以自由切换,当绝对值为 0 时自动使用偏移量。
- 对于挂单,决定是直接指定价格 (
PendingPrice) 还是使用偏移量 (PendingOffset)。策略会在发送挂单前根据当时的收盘价计算最终价格。
- 通过修改
EntryAction、PendingAction、ClosePositionRequest 或 ReversePositionRequest 发出操作指令。这些参数都像按钮一样工作:执行完后自动复位,避免下一根 K 线重复触发。
- 在持仓期间,策略会持续检查价格行为。一旦止损或止盈条件满足,就以市价平仓,并在下一次开仓前禁止重复触发。
与原 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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import CommodityChannelIndex
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class trade_xpert_manual_trading_panel_strategy(Strategy):
"""CCI zero-line crossover."""
def __init__(self):
super(trade_xpert_manual_trading_panel_strategy, self).__init__()
self._cci_period = self.Param("CciPeriod", 20).SetGreaterThanZero().SetDisplay("CCI Period", "CCI lookback", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))).SetDisplay("Candle Type", "Timeframe", "General")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(trade_xpert_manual_trading_panel_strategy, self).OnReseted()
self._prev_cci = None
def OnStarted2(self, time):
super(trade_xpert_manual_trading_panel_strategy, self).OnStarted2(time)
self._prev_cci = None
cci = CommodityChannelIndex()
cci.Length = self._cci_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(cci, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, sub)
self.DrawOwnTrades(area)
def OnProcess(self, candle, cci_val):
if candle.State != CandleStates.Finished:
return
if self._prev_cci is None:
self._prev_cci = float(cci_val)
return
cci_f = float(cci_val)
if self._prev_cci < 0 and cci_f >= 0 and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif self._prev_cci > 0 and cci_f <= 0 and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_cci = cci_f
def CreateClone(self):
return trade_xpert_manual_trading_panel_strategy()