在 GitHub 上查看

Broadening Top 策略

概述

Broadening Top 策略源自 MetaTrader 平台上的 "Broadening top" 智能交易系统。该策略通过趋势过滤、动量确认和 MACD 指示,在价格形成扩张形态后寻找突破机会。核心组件包括两条线性加权移动平均线(LWMA)、Momentum 指标以及 MACD 过滤器。

交易逻辑

  1. 趋势过滤:只有当快速 LWMA 位于慢速 LWMA 上方时才允许做多,当快速 LWMA 位于慢速 LWMA 下方时才允许做空。
  2. 动量确认:策略检查最近三根完成的 K 线上的 Momentum 数值,只要其中任何一个值与基准水平 100 的偏差达到预设阈值(多空分别设置)即可视为有效。
  3. MACD 过滤:做多要求 MACD 主线高于信号线;做空要求 MACD 主线低于信号线。
  4. 仓位管理:在开仓之前会自动平掉相反方向的持仓,保证账户中最多只有一个方向的仓位。

风险管理

通过 StartProtection 统一设置保护性订单:

  • 可选的止损和止盈距离(以最小价格变动为单位)。
  • 可选的追踪止损以及追踪步长。

参数

参数 说明 默认值
OrderVolume 下单量(手数或合约数)。 1
FastMaLength 快速线性加权均线周期。 6
SlowMaLength 慢速线性加权均线周期。 85
MomentumPeriod Momentum 指标周期。 14
MomentumBuyThreshold 允许开多仓所需的 Momentum 偏差(相对于 100)。 0.3
MomentumSellThreshold 允许开空仓所需的 Momentum 偏差(相对于 100)。 0.3
MacdFast MACD 中快速 EMA 的周期。 12
MacdSlow MACD 中慢速 EMA 的周期。 26
MacdSignal MACD 信号线的 EMA 周期。 9
TakeProfitPips 止盈距离(价格步长数)。 50
StopLossPips 止损距离(价格步长数)。 20
TrailingStopPips 追踪止损距离(价格步长数)。 40
TrailingStepPips 追踪止损的调整步长。 10
CandleType 计算使用的 K 线类型/时间框架。 15 分钟
EnableLongs 是否允许做多。 true
EnableShorts 是否允许做空。 true

指标

  • LinearWeightedMovingAverage:用于趋势方向判定的快慢线。
  • Momentum:确认价格动能是否足够强劲。
  • MovingAverageConvergenceDivergenceSignal:通过 MACD 主线与信号线的关系进行方向过滤。

使用提示

  • Momentum 阈值以最近三根已完成 K 线的数值为准,以模拟原始 MQL 策略的行为。
  • 将止损、止盈或追踪止损参数设置为零即可关闭相应的保护机制。
  • 策略需要交易品种提供价格最小变动和小数位信息,才能正确计算点值。
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

public class BroadeningTopStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private ExponentialMovingAverage _fast;
	private ExponentialMovingAverage _slow;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public BroadeningTopStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 14).SetGreaterThanZero().SetDisplay("Fast Period", "Fast EMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 50).SetGreaterThanZero().SetDisplay("Slow Period", "Slow EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new ExponentialMovingAverage { Length = FastPeriod };
		_slow = new ExponentialMovingAverage { Length = SlowPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 100; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 100; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 100; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}