在 GitHub 上查看
Pedro Mod 策略
概述
本策略为 Pedroxxmod MetaTrader 4 智能交易系统的 StockSharp 版本。原始 EA 会在价格相对参考价偏离若干点
后建立逆势仓位,并在价格回撤既定距离时继续在同一方向加仓。移植后的实现保留了这一核心思想,同时
通过高层 Strategy API 暴露出强类型参数,便于回测与优化。
交易逻辑
- 订阅 Level1 买一/卖一报价,并缓存最新的买卖价。
- 当没有持仓时,把当前卖价作为参考价。只有当服务器时间位于
StartHour 与 EndHour 之间,并且年份
不早于 StartYear 时才允许交易。
- 如果卖价高于参考价
Gap 个 MetaTrader 点,则提交市价卖单;如果卖价低于参考价 Gap 点,则提交市
价买单。下单后立即通过 SetStopLoss/SetTakeProfit 设置与 EA 相同的止损和止盈距离。
- 一旦确定交易方向,策略会使用先进先出队列跟踪“虚拟仓位”,从而模拟 MetaTrader 的对冲账户。当篮子
中的交易数量小于
MaxTrades 时,只要卖价重新回到最近一次入场价 ReEntryGap 点以内,就会继续在相同
方向加仓。
- 仓位管理既可以使用固定的
Lots,也可以启用 money management:按照 floor(Equity / 20000) 计算手数,并
受 MaxLots 限制。最终下单量会根据交易品种的最小变动/最小手数进行规范化。
- 在交易时段之外到达的报价会重置参考价,防止下一交易时段误触发订单。
参数
| 名称 |
说明 |
Lots |
禁用 money management 时使用的固定下单量。 |
StopLoss |
MetaTrader 点为单位的止损距离,设为 0 表示不使用止损。 |
TakeProfit |
MetaTrader 点为单位的止盈距离,设为 0 表示不使用止盈。 |
Gap |
卖价相对参考价需要偏离的点数,满足后才会开出第一笔仓位。 |
MaxTrades |
同方向同时存在的最大订单数量(篮子大小)。 |
ReEntryGap |
触发继续加仓的回撤距离(MetaTrader 点)。 |
MoneyManagement |
置为 true 时启用 floor(Equity / 20000) 的动态手数算法。 |
MaxLots |
动态手数的上限。 |
StartHour / EndHour |
服务器时间下的交易时段(包含边界)。 |
StartYear |
允许交易的最早年份,更早的数据会被忽略。 |
备注
- 策略只订阅 Level1 行情,不请求任何 K 线,因此能够像 MT4 的
start() Tick 处理函数那样实时响应。
- 止损与止盈通过
Strategy 的辅助函数来换算实际价格,确保接入方正确提供 PriceStep、StepPrice 和
VolumeStep 等交易参数。
- 通过 FIFO 队列维护的“虚拟仓位”可以模拟对冲账户,即便 StockSharp 采用净头寸模型也不会丢失 EA 的行为。
局部成交或止损都会在
OnPositionChanged 回调中更新该队列。
- 根据仓库要求,暂不提供 Python 版本。
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using StockSharp.Algo;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Pedro Mod mean reversion strategy using Bollinger Bands.
/// Buy when price touches the lower band, sell when price touches the upper band.
/// </summary>
public class PedroModStrategy : Strategy
{
private readonly StrategyParam<int> _bollingerPeriod;
private readonly StrategyParam<decimal> _bollingerWidth;
private readonly StrategyParam<DataType> _candleType;
public int BollingerPeriod { get => _bollingerPeriod.Value; set => _bollingerPeriod.Value = value; }
public decimal BollingerWidth { get => _bollingerWidth.Value; set => _bollingerWidth.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public PedroModStrategy()
{
_bollingerPeriod = Param(nameof(BollingerPeriod), 20)
.SetDisplay("Bollinger Period", "Bollinger Bands period", "Indicators");
_bollingerWidth = Param(nameof(BollingerWidth), 1.5m)
.SetDisplay("Bollinger Width", "Standard deviation multiplier", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var bb = new BollingerBands { Length = BollingerPeriod, Width = BollingerWidth };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(bb, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue bbValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!bbValue.IsFinal)
return;
var bb = (BollingerBandsValue)bbValue;
if (bb.UpBand is not decimal upper || bb.LowBand is not decimal lower || bb.MovingAverage is not decimal middle)
return;
// Buy when price touches lower band (mean reversion)
if (Position <= 0 && candle.ClosePrice <= lower)
{
if (Position < 0)
BuyMarket();
BuyMarket();
}
// Sell when price touches upper band
else if (Position >= 0 && candle.ClosePrice >= upper)
{
if (Position > 0)
SellMarket();
SellMarket();
}
// Exit at middle band
else if (Position > 0 && candle.ClosePrice >= middle)
{
SellMarket();
}
else if (Position < 0 && candle.ClosePrice <= middle)
{
BuyMarket();
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import BollingerBands
from StockSharp.Algo.Strategies import Strategy
class pedro_mod_strategy(Strategy):
"""Pedro Mod mean reversion strategy using Bollinger Bands.
Buy when price touches the lower band, sell when price touches the upper band.
Exit at the middle band."""
def __init__(self):
super(pedro_mod_strategy, self).__init__()
self._bollinger_period = self.Param("BollingerPeriod", 20) \
.SetDisplay("Bollinger Period", "Bollinger Bands period", "Indicators")
self._bollinger_width = self.Param("BollingerWidth", 1.5) \
.SetDisplay("Bollinger Width", "Standard deviation multiplier", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def BollingerPeriod(self):
return self._bollinger_period.Value
@property
def BollingerWidth(self):
return self._bollinger_width.Value
def OnReseted(self):
super(pedro_mod_strategy, self).OnReseted()
def OnStarted2(self, time):
super(pedro_mod_strategy, self).OnStarted2(time)
bb = BollingerBands()
bb.Length = self.BollingerPeriod
bb.Width = self.BollingerWidth
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(bb, self._process_candle).Start()
def _process_candle(self, candle, bb_value):
if candle.State != CandleStates.Finished:
return
if not bb_value.IsFinal:
return
up_band = bb_value.UpBand if hasattr(bb_value, 'UpBand') else None
low_band = bb_value.LowBand if hasattr(bb_value, 'LowBand') else None
mid_band = bb_value.MovingAverage if hasattr(bb_value, 'MovingAverage') else None
if up_band is None or low_band is None or mid_band is None:
return
upper = float(up_band)
lower = float(low_band)
middle = float(mid_band)
close = float(candle.ClosePrice)
# Buy when price touches lower band (mean reversion)
if self.Position <= 0 and close <= lower:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
# Sell when price touches upper band
elif self.Position >= 0 and close >= upper:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
# Exit at middle band
elif self.Position > 0 and close >= middle:
self.SellMarket()
elif self.Position < 0 and close <= middle:
self.BuyMarket()
def CreateClone(self):
return pedro_mod_strategy()