在 GitHub 上查看

Bread and Butter 2(ADX + AMA)

概览

该策略移植自 Ron Thompson 编写的 MetaTrader 5 智能交易程序 Breadandbutter2。原版算法在每根新 K 线收盘后,比较最新的平均趋向指数(ADX)与上一根的数值,同时判断考夫曼自适应移动平均线(KAMA/AMA)是上升还是下降。当趋势强度减弱而价格动能增强时开多仓;当趋势强度增强而价格动能走弱时开空仓。StockSharp 版本在开仓前会先平掉相反方向的持仓,并按照脚本中定义的固定止损和止盈点差执行风险管理。

指标

  • 平均趋向指数(ADX):衡量趋势强度,策略使用主线(Moving Average)并比较最近两根柱子的值来判断强弱变化。
  • 考夫曼自适应移动平均线(KAMA/AMA):通过快慢平滑参数适应波动率,策略比较最近两根柱子的值来判断动能方向。

交易逻辑

  1. 使用设置的 K 线类型(默认 1 小时)并仅在 K 线收盘后处理数据。
  2. 依据设定的长度、快速和平滑周期计算 KAMA。
  3. 计算 ADX 并提取主线的当前值。
  4. 比较当前值与上一根柱子:
    • 做多条件:ADX 下降而 KAMA 上升。
    • 做空条件:ADX 上升而 KAMA 下降。
  5. 出现信号时,先平掉相反方向的仓位,再以市场价建立与基础交易量相匹配的新仓位。
  6. 持仓期间持续监控价格,一旦触及以点差(pip)定义的止损或止盈水平(根据合约最小报价单位换算成价格距离)立即离场。

仓位管理

  • 止损:以点差表示,根据 PriceStep 自动换算成价格单位;对于 3 位或 5 位报价的品种,pip 等于价格最小变动的 10 倍,与 MetaTrader 的处理一致。
  • 止盈:同样以点差表示,并采用相同的换算方式。
  • 策略所有进出场均使用市价单,并在出现反向信号时翻转持仓。

参数

名称 默认值 说明
CandleType TimeSpan.FromHours(1).TimeFrame() 计算所用的 K 线类型。
AdxPeriod 14 ADX 主线的平滑周期。
AmaPeriod 9 KAMA 的基础长度。
AmaFastPeriod 2 KAMA 内部的快速 EMA 周期。
AmaSlowPeriod 30 KAMA 内部的慢速 EMA 周期。
StopLossPips 50 止损距离(pip)。设为 0 表示禁用。
TakeProfitPips 50 止盈距离(pip)。设为 0 表示禁用。

使用提示

  • 运行前请确保所选标的提供有效的 PriceStep 信息,以便正确计算 pip 大小,特别是对带有小数点后一位的外汇品种。
  • Volume 属性决定基础开仓手数。当出现反向信号时,算法会自动加仓以平掉旧仓并建立与 Volume 相等的净头寸。
  • 止损与止盈的判断基于 K 线的最高价与最低价,行为接近原版脚本下在交易服务器端挂出的止损/止盈单。

参考

  • 原始 MetaTrader 5 策略:MQL/22003/Breadandbutter2.mq5
  • StockSharp 指标:KaufmanAdaptiveMovingAverageAverageDirectionalIndex
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>
/// Bread and Butter 2 ADX AMA strategy. Uses KAMA direction with ADX filter.
/// </summary>
public class Breadandbutter2AdxAmaStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _kamaPeriod;
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private decimal? _prevKama;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int KamaPeriod { get => _kamaPeriod.Value; set => _kamaPeriod.Value = value; }
	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }

	public Breadandbutter2AdxAmaStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
		_kamaPeriod = Param(nameof(KamaPeriod), 10).SetGreaterThanZero().SetDisplay("KAMA Period", "KAMA lookback", "Indicators");
		_fastPeriod = Param(nameof(FastPeriod), 8).SetGreaterThanZero().SetDisplay("Fast EMA", "Fast EMA period", "Indicators");
		_slowPeriod = Param(nameof(SlowPeriod), 21).SetGreaterThanZero().SetDisplay("Slow EMA", "Slow EMA period", "Indicators");
	}

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

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

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

	private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!IsFormedAndOnlineAndAllowTrading()) { _prevKama = fast; return; }
		if (_prevKama == null) { _prevKama = fast; return; }
		var prevAbove = _prevKama.Value > slow;
		var currAbove = fast > slow;
		_prevKama = fast;
		if (!prevAbove && currAbove && Position <= 0) { if (Position < 0) BuyMarket(); BuyMarket(); }
		else if (prevAbove && !currAbove && Position >= 0) { if (Position > 0) SellMarket(); SellMarket(); }
	}
}