在 GitHub 上查看
Peter Panel 策略
Peter Panel 策略 将 MetaTrader 5 上的手动交易面板 “Peter Panel” 移植到 StockSharp。原始 EA 在图表上绘制三条水平线(入场、止盈、止损)以及一个按钮矩阵,交易者只需点击按钮即可利用这些价格快速下单。本 C# 版本保留原有的决策流程,但通过策略参数来模拟面板按钮。只要将某个布尔参数设为 true,对应的操作就会立即执行,随后参数自动回到 false。
核心概念
- 人工辅助 – 策略本身不产生信号,交易者通过参数开关来决定何时下单。
- 共用价格线 – 水色入场线、绿色止盈线、红色止损线分别由三个十进制参数表示,可手动输入,也可以通过
ResetCommand 开关按当前中间价重新计算。
- 覆盖全部订单类型 – 面板中的六种订单(买入市价、卖出市价、买入止损、买入限价、卖出止损、卖出限价)全部实现。成交后策略会自动挂出与原 EA 相同的止盈/止损保护单。
- 批量修改 –
ModifyCommand 参数会把当前三条价格线重新应用到所有挂单以及当前持仓的保护单上。
- 一键清仓 –
CloseCommand 会撤掉所有挂单、删除保护单,并以市价平掉净持仓。
原版与 StockSharp 版的对照
| 功能 |
MetaTrader 5 Peter Panel |
StockSharp Peter Panel Strategy |
| 界面呈现 |
图表上的对话框、按钮和输入框 |
策略参数界面中的开关与数值输入 |
| 价格线调整 |
拖动水平线或点击 “Reset” |
直接修改参数或触发 ResetCommand |
| 下单方式 |
按钮触发同步的 OrderSend 调用 |
参数开关调用 Buy/Sell 辅助方法并保存订单引用 |
| 止盈/止损 |
每个订单通过 MqlTradeRequest.tp/sl 设置 |
成交后立即注册单独的止损与止盈订单 |
| 订单修改 |
选中列表中的票据并按 “Modify” |
ModifyCommand 取消并重建所有挂单,同时刷新保护单 |
| 订单关闭 |
对选中票据按 “Close” |
CloseCommand 平掉整体持仓并清空所有挂单与保护单 |
| 订单列表 |
图形化的票据与价格表格 |
使用 StockSharp 的订单跟踪与日志,无单独列表 |
提示: 原 EA 支持在列表中选择单个票据。策略参数无法直接表达这一交互,因此 C# 版本对所有由策略创建的订单统一执行修改或关闭。
参数说明
| 参数 |
含义 |
Volume |
交易手数,会根据品种的最小手数和步长自动校验。 |
EntryLevel |
挂单使用的入场价(水色线)。 |
TakeProfitLevel |
绿色线价格:多头的止盈价,空头的保护止损价。 |
StopLossLevel |
红色线价格:多头的止损价,空头的止盈目标。 |
BuyMarketCommand |
置为 true 时发送买入市价单,执行后自动复位。 |
BuyStopCommand |
在 EntryLevel 处挂买入止损单。 |
BuyLimitCommand |
在 EntryLevel 处挂买入限价单。 |
SellMarketCommand |
发送卖出市价单。 |
SellStopCommand |
在 EntryLevel 处挂卖出止损单。 |
SellLimitCommand |
在 EntryLevel 处挂卖出限价单。 |
ModifyCommand |
将三条价格线重新应用到所有挂单和保护单。 |
CloseCommand |
撤挂单、撤保护单,并以市价平掉净持仓。 |
ResetCommand |
根据最新买卖价中值重新计算三条价格线。 |
操作流程
- 连接好账户与标的后启动策略。策略会订阅 Level1 数据,用于
ResetCommand 估算中间价。
- 通过
ResetCommand 或直接编辑参数来设定水色、绿色、红色的价格线。
- 将任一操作参数切换为
true 以执行下单动作,执行完成后参数会自动回到 false。
- 每次成交后策略都会根据持仓方向自动挂出止损与止盈单:多头使用红线止损、绿线止盈;空头则相反。
- 如需调整价格线,可修改参数并触发
ModifyCommand,无需重启策略即可刷新挂单与保护单。
- 交易结束时,触发
CloseCommand 即可整体清仓并清除所有挂单。
与原面板的差异
- 策略没有票据列表,所有订单状态可通过日志或自定义界面查看。如需逐单控制,可在外部 UI 中调用这些参数。
- StockSharp 无法像 MetaTrader 那样在主订单中直接附加止盈/止损,因此策略通过额外的止损/止盈订单来实现同样的保护效果。
- 修改订单采用“先撤后下”的方式,可兼容不支持原地修改的交易通道。
使用建议
- 可结合 StockSharp 的自定义面板或图表,将策略参数与按钮绑定,重现原有的操作体验。
- 策略不会排队执行多个动作。若需要依次执行多个步骤(例如重置价格线后立即挂单),请等待前一个参数恢复为
false 再触发下一个参数。
- 只有在持仓非零时才会创建保护单。如果先挂单后成交,可在成交后再次触发
ModifyCommand 以确保最新价格被应用。
风险提示
- 下单前请确认已分配投资组合、品种,以及品种的价格步长信息;缺少信息时策略会在日志中给出警告。
- 若调整后的交易手数因最小手数或步长限制而变为 0,策略不会下单,并会输出警告信息。
- 执行
CloseCommand 时,策略的顺序为:先撤保护单,再撤挂单,最后以市价平仓,与原 EA 的防御流程保持一致。
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Peter Panel strategy: MACD histogram crossover.
/// Buys when MACD histogram turns positive, sells when it turns negative.
/// </summary>
public class PeterPanelStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<int> _signalPeriod;
private decimal _prevMacd;
private decimal _prevSignal;
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 int SignalPeriod { get => _signalPeriod.Value; set => _signalPeriod.Value = value; }
public PeterPanelStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_fastPeriod = Param(nameof(FastPeriod), 12)
.SetGreaterThanZero()
.SetDisplay("Fast Period", "MACD fast period", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 26)
.SetGreaterThanZero()
.SetDisplay("Slow Period", "MACD slow period", "Indicators");
_signalPeriod = Param(nameof(SignalPeriod), 9)
.SetGreaterThanZero()
.SetDisplay("Signal Period", "MACD signal period", "Indicators");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevMacd = 0m;
_prevSignal = 0m;
_hasPrev = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
var macd = new MovingAverageConvergenceDivergenceSignal
{
Macd =
{
ShortMa = { Length = FastPeriod },
LongMa = { Length = SlowPeriod },
},
SignalMa = { Length = SignalPeriod }
};
var subscription = SubscribeCandles(CandleType);
subscription.BindEx(macd, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue)
{
if (candle.State != CandleStates.Finished) return;
if (!macdValue.IsFinal) return;
var macdTyped = (MovingAverageConvergenceDivergenceSignalValue)macdValue;
if (macdTyped.Macd is not decimal macdLine || macdTyped.Signal is not decimal signal) return;
var histogram = macdLine - signal;
if (_hasPrev)
{
var prevHist = _prevMacd - _prevSignal;
if (prevHist <= 0 && histogram > 0 && Position <= 0)
BuyMarket();
else if (prevHist >= 0 && histogram < 0 && Position >= 0)
SellMarket();
}
_prevMacd = macdLine;
_prevSignal = signal;
_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 MovingAverageConvergenceDivergenceSignal
from StockSharp.Algo.Strategies import Strategy
class peter_panel_strategy(Strategy):
def __init__(self):
super(peter_panel_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30)))
self._fast_period = self.Param("FastPeriod", 12)
self._slow_period = self.Param("SlowPeriod", 26)
self._signal_period = self.Param("SignalPeriod", 9)
self._prev_macd = 0.0
self._prev_signal = 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
@property
def SignalPeriod(self):
return self._signal_period.Value
@SignalPeriod.setter
def SignalPeriod(self, value):
self._signal_period.Value = value
def OnReseted(self):
super(peter_panel_strategy, self).OnReseted()
self._prev_macd = 0.0
self._prev_signal = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(peter_panel_strategy, self).OnStarted2(time)
self._has_prev = False
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = self.FastPeriod
macd.Macd.LongMa.Length = self.SlowPeriod
macd.SignalMa.Length = self.SignalPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(macd, self._process_candle).Start()
def _process_candle(self, candle, macd_value):
if candle.State != CandleStates.Finished:
return
if not macd_value.IsFinal:
return
macd_line = macd_value.Macd
signal = macd_value.Signal
if macd_line is None or signal is None:
return
macd_line = float(macd_line)
signal = float(signal)
histogram = macd_line - signal
if self._has_prev:
prev_hist = self._prev_macd - self._prev_signal
if prev_hist <= 0 and histogram > 0 and self.Position <= 0:
self.BuyMarket()
elif prev_hist >= 0 and histogram < 0 and self.Position >= 0:
self.SellMarket()
self._prev_macd = macd_line
self._prev_signal = signal
self._has_prev = True
def CreateClone(self):
return peter_panel_strategy()