在 GitHub 上查看
VirtPO TestBed Scalp 策略
本策略将 MetaTrader 4 的 VirtPOTestBed_ScalpM1 EA 移植到 StockSharp 的高级 API。核心思路保持不变:利用 虚拟挂单,在随机指标穿越阈值时“布单”,在价格动量确认后立即转化为市价单执行。所有过滤条件、仓位管理和时间控制均按原始 EA 的逻辑改写。
策略逻辑
虚拟挂单阶段(无持仓时在每根完成的 K 线上运行):
- 使用 Level1 最优买卖价,确保点差低于
SpreadMaxPips;
- 最近三根 K 线的平均成交量必须大于
VolumeLimit;
VolatilityPeriod 根 K 线的平均实体长度(点数)大于 VolatilityLimit;
- 布林带宽度(周期
BollingerPeriod,宽度 2)必须处于 BollingerLowerLimit 与 BollingerUpperLimit 之间;
- 当前时间位于允许的交易窗口(
EntryHour 起始,持续 OpenHours 小时),并且不在禁止的周日 (Day1、Day2、周五 FridayEndHour 之后);
- 快速/慢速 SMA 的差值(点数)绝对值大于
SmaDifferencePips;
- 上一根 K 线的实体长度小于
LastBarLimitPips。
当上述过滤全部通过时,检测随机指标交叉:
%K 向上穿越 StochasticSetLevel → 在买价上方 PoThresholdPips 点布置虚拟 buy stop;
%K 向下穿越 100 - StochasticSetLevel → 在买价下方同样距离布置虚拟 sell stop。
虚拟挂单会记录到期时间 (PoTimeLimitMinutes) 以及对应的止损 (StopLossPips) 与止盈 (TakeProfitPips) 距离。
执行阶段 – 若启用 TickLevel,策略订阅逐笔成交,一旦最新成交价突破虚拟挂单价格立即触发市价单;若关闭 TickLevel,则在每根 K 线收盘时检查。触发后对应虚拟挂单被清除。
风险管理 – 持仓后持续监控:
- 固定的止损、止盈价格(按点数转换);
- 可选的移动止损
TrailingStopPips,跟随开仓后的最高/最低价;
- 持仓时间上限
CloseTimeMinutes,到期后根据 ProfitType 选择平仓全部/仅盈利/仅亏损仓位。
所有点数都会根据品种的 PriceStep 和小数位自动换算(兼容五位报价)。OrderVolume 用作每次下单的默认数量,持仓归零时内部状态会自动重置。
注意事项
- 必须订阅 Level1 数据,否则无法获得点差和触发价,过滤条件会阻止交易。
TickLevel=false 时执行以 K 线收盘为准,更适合回测;开启后可获得与原 EA 相近的逐笔执行行为。
- 策略只维护单一净头寸,对应 MT4 版本限制同一品种仅持有一笔市价单。
参数说明
| 分组 |
参数 |
说明 |
| General |
Candle Type |
订阅的蜡烛类型(默认 1 分钟)。 |
| Execution |
Tick Level |
是否使用逐笔成交触发虚拟挂单。 |
| Execution |
PO Threshold (pips) |
虚拟挂单距离买价的点数。 |
| Execution |
PO Lifetime (min) |
虚拟挂单的有效时间。 |
| Filters |
Max Spread (pips) |
允许的最大点差。 |
| Filters |
Volume Limit |
最近三根 K 线的平均成交量阈值。 |
| Filters |
Volatility Period |
计算平均实体长度的样本数量。 |
| Filters |
Volatility Limit |
平均实体长度的最小值(点)。 |
| Filters |
Bollinger Period |
布林带周期。 |
| Filters |
Bollinger Lower / Upper |
布林带宽度允许区间(点)。 |
| Filters |
Last Bar Limit |
上一根 K 线实体的最大长度。 |
| Trend |
Fast/Slow SMA |
趋势过滤所用的快/慢均线周期。 |
| Trend |
SMA Difference |
SMA 之间的最小差值(点)。 |
| Stochastic |
%K / %D / Smooth |
随机指标的周期设置。 |
| Stochastic |
Stochastic Set |
激活虚拟挂单的阈值。 |
| Stochastic |
Stochastic Go |
执行虚拟挂单的阈值。 |
| Trading |
Order Volume |
下单数量。 |
| Risk |
Take Profit / Stop Loss / Trailing Stop |
止盈、止损、移动止损的点数。 |
| Schedule |
Disable Days, Day1, Day2 |
禁止交易的星期(99 表示不禁用)。 |
| Schedule |
Entry Hour / Open Hours |
交易窗口开始时间与持续时长。 |
| Schedule |
Friday Cut-off |
周五停止交易的小时。 |
| Risk |
Max Lifetime |
持仓时间上限(≥5000 视为关闭)。 |
| Risk |
Profit Filter |
时间到期后的平仓规则:0 全部,1 盈利单,2 亏损单。 |
与原版差异
- MQL 中的
CPO 类被内部状态替代,当价格穿越阈值时直接调用 BuyMarket / SellMarket。
- 止损/止盈在回测中通过蜡烛高低价验证,实时模式下若有逐笔数据可获得更精细的触发;不支持部分成交与对冲仓位。
- 原 EA 的
GLots 动态手数未移植,改为使用固定的 OrderVolume。
这些调整确保策略在 StockSharp 的单净持仓模型中仍能重现原有交易思路。
namespace StockSharp.Samples.Strategies;
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;
/// <summary>
/// Virtual Pending Order Scalp strategy: ATR-based breakout scalper.
/// Buys when price breaks above recent high + ATR offset. Sells on break below low - ATR.
/// </summary>
public class VirtPoTestBedScalpStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _atrPeriod;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
public VirtPoTestBedScalpStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR period for breakout offset", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var atr = new AverageTrueRange { Length = AtrPeriod };
decimal? prevHigh = null;
decimal? prevLow = null;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, (candle, atrVal) =>
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
if (prevHigh.HasValue && prevLow.HasValue)
{
if (close > prevHigh.Value + atrVal * 0.5m && Position <= 0)
BuyMarket();
else if (close < prevLow.Value - atrVal * 0.5m && Position >= 0)
SellMarket();
}
prevHigh = candle.HighPrice;
prevLow = candle.LowPrice;
})
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
}
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 AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class virt_po_test_bed_scalp_strategy(Strategy):
def __init__(self):
super(virt_po_test_bed_scalp_strategy, self).__init__()
self._atr_period = self.Param("AtrPeriod", 14) \
.SetDisplay("ATR Period", "ATR period for breakout offset", "Indicators")
self._atr = None
self._prev_high = None
self._prev_low = None
@property
def atr_period(self):
return self._atr_period.Value
def OnReseted(self):
super(virt_po_test_bed_scalp_strategy, self).OnReseted()
self._atr = None
self._prev_high = None
self._prev_low = None
def OnStarted2(self, time):
super(virt_po_test_bed_scalp_strategy, self).OnStarted2(time)
self._atr = AverageTrueRange()
self._atr.Length = self.atr_period
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromHours(1)))
subscription.Bind(self._atr, self._process_candle)
subscription.Start()
def _process_candle(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
if not self._atr.IsFormed:
return
close = float(candle.ClosePrice)
atr_val = float(atr_value)
if self._prev_high is not None and self._prev_low is not None:
if close > self._prev_high + atr_val * 0.5 and self.Position <= 0:
self.BuyMarket()
elif close < self._prev_low - atr_val * 0.5 and self.Position >= 0:
self.SellMarket()
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
def CreateClone(self):
return virt_po_test_bed_scalp_strategy()