在 GitHub 上查看

Bobnaley 策略

概述

Bobnaley 策略在 StockSharp 高层 API 中复刻了 MetaTrader 5 专家顾问“bobnaley”。它把简单移动平均线趋势过滤与随机指标结合起来寻找反转机会。原脚本在每个 Tick 上计算,这里改为在收盘 K 线后执行,同时保留原有的订单管理规则。

工作原理

  1. 指标
    • 使用可配置周期的简单移动平均线判断价格在最近样本中的斜率。
    • 随机指标(主线与信号线)用来确认超买和超卖。交易信号只依赖主线,信号线仅用于保持与原版一致。
  2. 入场条件
    • 策略仅在蜡烛收盘并且两个指标都完成计算后才会评估信号。
    • 多头:最近三次均线值必须严格递减,且当前收盘价高于最新均线;随机主线位于超卖阈值以下,同时上一笔主线值要高于再前一笔(对应原脚本的 stochVal[1] > stochVal[2])。
    • 空头:最近三次均线值必须严格递增,且当前收盘价低于最新均线;随机主线位于超买阈值以上,并且上一笔主线值低于再前一笔。
    • 只有在没有持仓时才会开仓,与 MetaTrader 中的 PositionSelect 检查一致。
  3. 风控
    • 每当开仓,策略会通过 StockSharp 的保护服务设置固定点差的止盈与止损(默认分别为 0.007 与 0.0035),完全对应原脚本的输入参数。
    • 在提交订单前会读取投资组合的当前市值,与 Minimum Balance 参数比较,复制原脚本对可用保证金大于 5000 的限制;当账户价值已知但低于阈值时会跳过入场。
  4. 仓位规模
    • 所有订单都使用固定的 Base Volume,对应原脚本在 Money_M 函数中得到的手数。

参数

分类 名称 说明 默认值
通用 Candle Type 计算指标所用的蜡烛类型。 5 分钟周期
交易 Base Volume 每次下单使用的固定手数。 5
指标 MA Period 简单移动平均线的周期。 76
指标 Stochastic Period 随机指标主线的回看长度。 5
指标 Stochastic %K %K 线的平滑周期。 3
指标 Stochastic %D %D 线的平滑周期。 3
指标 Stochastic Oversold 随机主线的超卖阈值。 30
指标 Stochastic Overbought 随机主线的超买阈值。 70
风险控制 Take Profit 入场价与止盈价之间的距离(绝对价格单位)。 0.007
风险控制 Stop Loss 入场价与止损价之间的距离(绝对价格单位)。 0.0035
风险控制 Minimum Balance 允许开仓所需的最低账户价值。 5000

备注

  • 原始脚本依赖买卖价,这里使用蜡烛收盘价近似成交价。
  • 没有实现移动止损,仓位只会由保护单来平仓。
  • 随机指标的默认设置为 5/3/3,可通过参数进行优化。
namespace StockSharp.Samples.Strategies;

using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.Messages;

/// <summary>
/// Bobnaley strategy: EMA trend with ATR volatility filter.
/// </summary>
public class BobnaleyStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _atrPeriod;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
	public int AtrPeriod { get => _atrPeriod.Value; set => _atrPeriod.Value = value; }

	public BobnaleyStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
			.SetDisplay("Candle Type", "Candle timeframe", "General");
		_emaPeriod = Param(nameof(EmaPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("EMA Period", "EMA period", "Indicators");
		_atrPeriod = Param(nameof(AtrPeriod), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Period", "ATR period", "Indicators");
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var atr = new AverageTrueRange { Length = AtrPeriod };

		decimal? prevClose = null;
		decimal? prevEma = null;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(ema, atr, (candle, emaVal, atrVal) =>
			{
				if (candle.State != CandleStates.Finished)
					return;

				if (!IsFormedAndOnlineAndAllowTrading())
					return;

				var close = candle.ClosePrice;

				if (prevClose.HasValue && prevEma.HasValue)
				{
					var crossUp = prevClose.Value <= prevEma.Value && close > emaVal;
					var crossDown = prevClose.Value >= prevEma.Value && close < emaVal;

					if (crossUp && Position <= 0)
						BuyMarket();
					else if (crossDown && Position >= 0)
						SellMarket();
				}

				prevClose = close;
				prevEma = emaVal;
			})
			.Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, subscription);
			DrawIndicator(area, ema);
			DrawOwnTrades(area);
		}
	}
}