在 GitHub 上查看
交易面板策略 (ID 3468)
概述
TradingPanelStrategy 源自 MQL5 专家顾问 EA_TradingPanel,是一款用于手动下单的辅助策略。它提供 PlaceBuyOrders() 与 PlaceSellOrders() 两个方法,对应 MT5 面板上的 BUY/SELL 按钮:一次调用即可批量发送多笔市价单,并按设定的 pips 距离自动附加止损和止盈,还可以选择自定义交易标的。默认参数与原始 EA 完全一致(1 笔订单、2 个点止损、10 个点止盈、0.01 手)。
在 StockSharp 中该策略不再自带 UI,而是方便开发者在自定义界面或脚本中调用上述方法。策略内部负责体积归一化、价格按最小跳动对齐以及保护单的生成,最大限度复刻原面板的行为。
参数
| 名称 |
说明 |
备注 |
TradeCount |
每次动作提交的市价单数量。 |
不小于 0,默认 1。 |
StopLossPips |
止损距离(pips)。 |
0 表示不下止损,默认 2。 |
TakeProfitPips |
止盈距离(pips)。 |
0 表示不下止盈,默认 10。 |
VolumePerTrade |
单笔市价单的下单量。 |
会根据 VolumeStep 归一化,默认 0.01。 |
TargetSecurity |
可选的目标合约。 |
留空时使用 Strategy.Security。 |
全部参数都通过 StrategyParam<T> 定义,支持优化器和界面实时调整。
执行流程
- 确定实际交易的合约(
TargetSecurity 优先,否则 Strategy.Security)。
- 根据合约元数据计算 pip 大小:若小数位数 ≥ 3,则按照 MQL 的规则使用
PriceStep × 10,否则直接采用 PriceStep。
- 读取最新价格(优先最佳买/卖价,退化到最近成交价),并使用
Security.ShrinkPrice 对齐到交易所允许的价位。
- 计算预期下单量
TradeCount × VolumePerTrade,结合 MinVolume、MaxVolume、VolumeStep 做约束,同时如果当前持仓方向相反则追加对应的平仓量,实现“一键反手”。
- 调用
BuyMarket 或 SellMarket 发送市价单。
- 按设定的 pips 距离生成止损(Stop)和止盈(Limit)订单,并进行价格归一化。
- 当方向切换或策略停止时,自动撤销旧的保护单。
保护单规则
- 多头下单:使用
SellStop 作为止损,SellLimit 作为止盈。
- 空头下单:使用
BuyStop 作为止损,BuyLimit 作为止盈。
- 保护单的数量与本次面板请求的下单量一致。
- 在
OnStopped、OnReseted 以及换向时都会清理无效的保护单引用。
使用建议
- 调用面板方法之前务必设置
Strategy.Security 或 TargetSecurity,否则不会发送任何订单。
PlaceBuyOrders() 与 PlaceSellOrders() 需要由外部界面或脚本触发,用于替代 MT5 面板按钮。
- 如果当前没有盘口或成交价数据,策略会记录错误并跳过下单。
OnStarted 中调用了 StartProtection(),可以在重启后自动处理遗留持仓。
- 当合约缺少
PriceStep 信息时,pip 大小默认为 0.0001。如需其他精度,请手动补充合约元数据。
与 MQL 面板的区别
- 不再提供图形界面,需自行集成到应用程序中。
- 止损/止盈按照一次面板操作的总量下单,而不是为每张 MT5 订单单独附加,最终净仓位效果与原策略一致。
- 额外引入了基于
VolumeStep、MinVolume、MaxVolume、Security.ShrinkPrice 的规范检查,避免因交易所限制导致的拒单。
- 通过
LogInfo 和 LogError 输出日志,便于在 StockSharp 终端中监控。
快速上手
- 创建策略实例,设置投资组合并指定交易合约(或填入
TargetSecurity)。
- 启动策略,以便
StartProtection() 启用持仓保护。
- 在自定义界面中根据用户操作调用
PlaceBuyOrders() 或 PlaceSellOrders()。
- 关注日志输出,可根据需要实现更多的前端提示或校验。
该策略为 MT5 交易面板在 StockSharp 框架中的高保真移植,既保持了原有的手动下单体验,又充分利用了 StockSharp 的高层 API 能力。
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Trading Panel strategy: WMA crossover.
/// Buys when fast WMA crosses above slow WMA, sells on cross below.
/// </summary>
public class TradingPanelStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private decimal _prevFast;
private decimal _prevSlow;
private bool _hasPrev;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
public TradingPanelStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(60).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_fastPeriod = Param(nameof(FastPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Fast WMA", "Fast WMA period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 30)
.SetGreaterThanZero()
.SetDisplay("Slow WMA", "Slow WMA period", "Indicators");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = 0;
_prevSlow = 0;
_hasPrev = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = 0;
_prevSlow = 0;
_hasPrev = false;
var fast = new ExponentialMovingAverage { Length = FastPeriod };
var slow = new ExponentialMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fast, slow, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished) return;
if (_hasPrev)
{
if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
BuyMarket();
else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
SellMarket();
}
else
{
if (fastValue > slowValue && Position <= 0)
BuyMarket();
else if (fastValue < slowValue && Position >= 0)
SellMarket();
}
_prevFast = fastValue;
_prevSlow = slowValue;
_hasPrev = true;
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class trading_panel_strategy(Strategy):
def __init__(self):
super(trading_panel_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(60)))
self._fast_period = self.Param("FastPeriod", 10)
self._slow_period = self.Param("SlowPeriod", 30)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def FastPeriod(self):
return self._fast_period.Value
@FastPeriod.setter
def FastPeriod(self, value):
self._fast_period.Value = value
@property
def SlowPeriod(self):
return self._slow_period.Value
@SlowPeriod.setter
def SlowPeriod(self, value):
self._slow_period.Value = value
def OnReseted(self):
super(trading_panel_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(trading_panel_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev = False
fast = ExponentialMovingAverage()
fast.Length = self.FastPeriod
slow = ExponentialMovingAverage()
slow.Length = self.SlowPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast, slow, self._process_candle).Start()
def _process_candle(self, candle, fast_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
slow_val = float(slow_value)
if self._has_prev:
if self._prev_fast <= self._prev_slow and fast_val > slow_val and self.Position <= 0:
self.BuyMarket()
elif self._prev_fast >= self._prev_slow and fast_val < slow_val and self.Position >= 0:
self.SellMarket()
else:
if fast_val > slow_val and self.Position <= 0:
self.BuyMarket()
elif fast_val < slow_val and self.Position >= 0:
self.SellMarket()
self._prev_fast = fast_val
self._prev_slow = slow_val
self._has_prev = True
def CreateClone(self):
return trading_panel_strategy()