在 GitHub 上查看

Bulls & Bears Power Average 策略

概述

  • 基于 MQL/22016 中的 MetaTrader 5 专家顾问 MySystem.mq5 改写而成。
  • 通过计算 K 线最高价/最低价与 EMA 的差值来获得 Elder Bulls Power 与 Bears Power,再将二者求平均以判定动能反转。
  • 当平均值仍在零轴下方但开始上升时开多单;当平均值仍在零轴上方却开始下降时开空单。
  • 始终保持单一持仓;止损与止盈以点(pip)表示,可按需关闭。

指标逻辑

模块 说明
指数移动平均 (EMA) 对收盘价进行平滑,长度由 MaPeriod 参数控制(默认 5)。
Bulls Power(派生) 计算公式 High - EMA,衡量多方相对 EMA 的力度。
Bears Power(派生) 计算公式 Low - EMA,衡量空方相对 EMA 的力度。
平均动能 (Bulls Power + Bears Power) / 2,与上一根已完成 K 线的数值比较判断动量变化。

策略仅在 K 线收盘后进行评估,避免在未完成的 K 线中频繁波动。

入场条件

  1. EMA 必须已经形成(至少处理 MaPeriod 根 K 线)。
  2. 对最新收盘的 K 线计算 Bulls Power 与 Bears Power。
  3. 求平均值得到当前的动能读数。
  4. 与上一次的平均值比较:
    • 多头信号:上一值 < 当前值,并且当前值 < 0。若当前无持仓则市价买入。
    • 空头信号:上一值 > 当前值,并且当前值 > 0。若当前无持仓则市价卖出。
  5. 策略本身不产生额外的平仓条件,持仓由止损、止盈或人工干预退出。

风险管理

  • StopLossPips:止损距离(点)。为 0 表示不启用。根据交易品种的 PriceStep 转换为价格。
  • TakeProfitPips:止盈距离(点)。为 0 表示不启用。
  • 开仓后立即调用 StartProtection 注册防护单,并使用市价成交方式。

参数说明

参数 默认值 说明
OrderVolume 0.1 每次下单的数量。
StopLossPips 15 止损距离(点)。0 表示关闭。
TakeProfitPips 95 止盈距离(点)。0 表示关闭。
MaPeriod 5 计算 Bulls/Bears Power 的 EMA 周期。
CandleType 1 小时 计算所使用的 K 线类型,可根据数据源调整。

使用建议

  1. 在策略中选择目标品种,并确认 CandleType 与期望的时间框架一致。
  2. 根据账户规则调整下单量与止损/止盈距离。
  3. 启动策略后会自动订阅 K 线、计算 EMA,并在满足条件时发出市价订单。
  4. 策略在已有仓位时不会再次入场,直到仓位被止损/止盈或手动平仓。
  5. 原始 MQL 参数 InpBarCurrent = 1 在此版本中固定为使用最新两根已收盘 K 线,不进行盘中重新计算。

与原始 MQL 策略的差异

  • 采用 StockSharp 高阶 Strategy API,通过订阅 K 线与绑定指标实现逻辑,避免手动读取指针缓冲区。
  • 使用 PriceStep 自动换算点值,无需自行判断三位/五位报价。
  • 忽略了源代码中被注释掉的平仓逻辑,改用框架自带的保护机制。
  • 保留了“一次仅持有一笔仓位”的约束,忠实复刻原始行为。

测试建议

  • 使用包含高/低价的历史数据在目标品种与时间框架上回测,确保 Bulls/Bears Power 计算准确。
  • 在真实交易前核对品种的最小变动价位与下单步长,确保止损/止盈设置有效。
  • 可针对不同波动率尝试调整 MaPeriod,寻找更适合的敏感度。
using System;
using System.Collections.Generic;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Bulls Bears Power Average strategy. Uses EMA with bulls/bears power crossover.
/// </summary>
public class BullsBearsPowerAverageStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _emaPeriod;
	private decimal? _prevPower;

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

	public BullsBearsPowerAverageStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
		_emaPeriod = Param(nameof(EmaPeriod), 13).SetGreaterThanZero().SetDisplay("EMA Period", "EMA lookback for power calc", "Indicators");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();
		_prevPower = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_prevPower = null;
		var ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ema, ProcessCandle).Start();
		var area = CreateChartArea();
		if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, ema); DrawOwnTrades(area); }
	}

	private void ProcessCandle(ICandleMessage candle, decimal emaVal)
	{
		if (candle.State != CandleStates.Finished) return;
		var bullsPower = candle.HighPrice - emaVal;
		var bearsPower = candle.LowPrice - emaVal;
		var avgPower = (bullsPower + bearsPower) / 2m;
		if (!IsFormedAndOnlineAndAllowTrading()) { _prevPower = avgPower; return; }
		if (_prevPower == null) { _prevPower = avgPower; return; }
		if (_prevPower.Value < 0m && avgPower >= 0m && Position <= 0) { if (Position < 0) BuyMarket(); BuyMarket(); }
		else if (_prevPower.Value > 0m && avgPower <= 0m && Position >= 0) { if (Position > 0) SellMarket(); SellMarket(); }
		_prevPower = avgPower;
	}
}