在 GitHub 上查看
Nextbar 策略
概述
Nextbar 策略是 MetaTrader 4 专家顾问 nextbar.mq4 的完整移植版本。原始 EA 会比较最新收盘价与若干根之前的收盘价,当价格单向移动超过阈值时,根据方向参数选择顺势或逆势入场。所有头寸都设置对称的止盈/止损,并在持仓达到固定的根数后强制平仓。
在 StockSharp 中我们使用高级策略 API 重建了同样的流程,只处理已经结束的 K 线,使得计算结果与 MT4 的“每根收盘时执行”逻辑完全一致。
原版 MQL 规则
- 动量距离:比较
Close[1] 与 Close[bars2check+1],若差值达到 minbar * Point 即视为有效信号。
- 方向参数:
direction = 1 表示顺势交易(上涨后买入、下跌后卖出),direction = 2 表示逆势交易(下跌后买入、上涨后卖出)。
- 入场限制:同一时间只允许一笔持仓,新的交易只会在信号形成后的下一根 K 线开盘时发送。
- 离场规则:多单在收盘价达到入场价上方的获利距离或下方的止损距离时平仓;空单条件相反。如果两者都未触发,则在持仓达到
bars2hold 根完成的 K 线后强制平仓。
StockSharp 实现要点
- 通过
SubscribeCandles() + Bind 订阅指定时间框架的已完成 K 线。
- 使用一个短期的收盘价缓冲区获取
bars2check + 1 对应的历史收盘价,与 MQL 的索引完全一致。
- 所有以“点”为单位的参数都乘以
Security.PriceStep,等同于 MetaTrader 的 Point 常量。
- 市价单使用策略的
Volume 作为下单手数,Direction 参数决定顺势 (Follow) 还是逆势 (Reverse) 入场。
- 止盈、止损以及持仓根数检查都只在每根已完成 K 线上执行一次,与原始脚本保持同步。
参数
| 参数 |
说明 |
默认值 |
备注 |
CandleType |
计算信号所使用的时间框架。 |
1 小时 |
需保证所选证券提供该类型的 K 线。 |
BarsToCheck |
最新收盘价与参考收盘价之间的完成 K 线数量。 |
8 |
对应 EA 中的 bars2check。 |
BarsToHold |
持仓允许经过的最大完成 K 线数量。 |
10 |
等同于 bars2hold,在达到该数值时强制平仓。 |
MinMovePoints |
两个收盘价之间的最小距离(点)。 |
77 |
对应 minbar,会乘以 Security.PriceStep。 |
TakeProfitPoints |
止盈距离(点)。 |
115 |
对应 profit 参数,置零可关闭。 |
StopLossPoints |
止损距离(点)。 |
115 |
对应 loss 参数,置零可关闭。 |
Direction |
交易模式:Follow 顺势,Reverse 逆势。 |
Follow |
与 direction 输入一致(1=顺势,2=逆势)。 |
Volume |
市价单的下单手数。 |
Strategy.Volume |
通过策略属性配置,等同 MT4 单笔订单的手数。 |
运行流程
- 等待一根 K 线收盘并记录其收盘价。
- 读取
BarsToCheck 根之前的收盘价并计算差值。
- 若绝对差值小于
MinMovePoints * PriceStep,则忽略该信号。
- 否则:
- Follow 模式:上涨后做多,下跌后做空。
- Reverse 模式:下跌后做多,上涨后做空。
- 在持仓期间的每根已完成 K 线:
- 多单:收盘价高于入场价
TakeProfitPoints 点或低于 StopLossPoints 点时平仓。
- 空单:收盘价低于入场价
TakeProfitPoints 点或高于 StopLossPoints 点时平仓。
- 当持仓经过
BarsToHold 根已完成 K 线后,立即平仓。
使用提示
- 点值转换依赖
Security.PriceStep(以及必要时的 StepPrice、成交量步长等元数据),请在运行前确保证券信息完整。
- 策略一次只管理一笔仓位,请根据实际需要设置
Volume 以匹配 MT4 中单笔订单的手数。
- 所有决策都基于已收盘的 K 线,因此需要使用能够提供完整 K 线的历史和实时数据源。
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class NextbarStrategy : Strategy
{
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<int> _cooldownCandles;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevOpen;
private decimal _prevClose;
private bool _hasPrev;
private int _cooldownRemaining;
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public NextbarStrategy()
{
_emaPeriod = Param(nameof(EmaPeriod), 50).SetDisplay("EMA Period", "EMA filter", "Indicators");
_cooldownCandles = Param(nameof(CooldownCandles), 200).SetDisplay("Cooldown", "Candles between signals", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevOpen = default;
_prevClose = default;
_hasPrev = default;
_cooldownRemaining = default;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevOpen = 0;
_prevClose = 0;
_hasPrev = false;
_cooldownRemaining = 0;
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ema, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal ema)
{
if (candle.State != CandleStates.Finished) return;
var close = candle.ClosePrice;
if (!_hasPrev) { _prevOpen = candle.OpenPrice; _prevClose = close; _hasPrev = true; return; }
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevOpen = candle.OpenPrice;
_prevClose = close;
return;
}
var prevBullish = _prevClose > _prevOpen;
var prevBearish = _prevClose < _prevOpen;
if (prevBullish && close > ema && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
_cooldownRemaining = CooldownCandles;
}
else if (prevBearish && close < ema && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
_cooldownRemaining = CooldownCandles;
}
_prevOpen = candle.OpenPrice;
_prevClose = close;
}
}
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 nextbar_strategy(Strategy):
def __init__(self):
super(nextbar_strategy, self).__init__()
self._ema_period = self.Param("EmaPeriod", 50) \
.SetDisplay("EMA Period", "EMA filter", "Indicators")
self._cooldown_candles = self.Param("CooldownCandles", 200) \
.SetDisplay("Cooldown", "Candles between signals", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_open = 0.0
self._prev_close = 0.0
self._has_prev = False
self._cooldown_remaining = 0
@property
def ema_period(self):
return self._ema_period.Value
@property
def cooldown_candles(self):
return self._cooldown_candles.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(nextbar_strategy, self).OnReseted()
self._prev_open = 0.0
self._prev_close = 0.0
self._has_prev = False
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(nextbar_strategy, self).OnStarted2(time)
self._prev_open = 0.0
self._prev_close = 0.0
self._has_prev = False
self._cooldown_remaining = 0
ema = ExponentialMovingAverage()
ema.Length = self.ema_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, self.process_candle).Start()
def process_candle(self, candle, ema):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
open_price = float(candle.OpenPrice)
ema_val = float(ema)
if not self._has_prev:
self._prev_open = open_price
self._prev_close = close
self._has_prev = True
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_open = open_price
self._prev_close = close
return
prev_bullish = self._prev_close > self._prev_open
prev_bearish = self._prev_close < self._prev_open
if prev_bullish and close > ema_val and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._cooldown_remaining = self.cooldown_candles
elif prev_bearish and close < ema_val and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._cooldown_remaining = self.cooldown_candles
self._prev_open = open_price
self._prev_close = close
def CreateClone(self):
return nextbar_strategy()