在 GitHub 上查看

FT Bill Williams Trader 策略

概述

FT Bill Williams Trader Strategy 是 MetaTrader 专家顾问 “FT_BillWillams_Trader” 的高层 StockSharp 移植版本。策略把比尔·威廉姆斯的分形指标和 Alligator 鳄鱼指标结合起来,捕捉趋势方向的突破机会。在入场之前,算法会验证 Alligator 三条线的排列,并根据需要附加距离、趋势一致性和反向信号等过滤条件。

交易逻辑

  1. 分形识别:策略维护最近 FractalPeriod 根 K 线的高点和低点。当中间那根 K 线创出窗口内的最高(或最低)点时,记录新的突破价位,并根据 IndentPoints 在该价位上方/下方加入偏移,以避免过早入场。
  2. 突破确认
    • PriceBreakout 模式在当前 K 线的最高价/最低价穿越突破价位时触发。
    • CloseBreakout 模式则要求上一根 K 线的收盘价已经站在突破价位之外。
  3. 距离过滤:若突破价位与上一根 K 线的鳄鱼“嘴唇”之间的距离超过 MaxDistancePoints(点),信号会被拒绝。将该参数设为 0 可关闭此检查。
  4. 齿线过滤:当 UseTeethFilter 打开时,上一根 K 线的收盘价必须位于鳄鱼“牙齿”之上(做多)或之下(做空)。
  5. 趋势一致性UseTrendAlignment = true 时,鳄鱼的“嘴唇”“牙齿”“下颚”之间的间距需要分别超过 TeethLipsDistancePointsJawTeethDistancePoints,以确认多头或空头趋势正在展开。
  6. 反向信号出场:若 ReverseExit = OppositeFractal,新的反向分形出现后立刻平仓。若为 OppositePosition,策略会先关闭当前持仓,再根据新的方向开仓。
  7. 下颚出场JawExit 决定价格重新触及鳄鱼下颚时是否平仓,以及使用盘中高低价还是收盘价作为判定依据。
  8. 移动止损EnableTrailing 启用时,且持仓已有浮盈,策略会比较鳄鱼“嘴唇”的斜率与 SlopeSmaPeriod 的 SMA:
    • 嘴唇斜率大于 SMA 斜率时,将止损上调/下调到嘴唇的位置;
    • 否则退守到牙齿位置。 初始止损和止盈距离由 StopLossPointsTakeProfitPoints 控制,值为 0 表示不设置。

参数

属性 说明 默认值
OrderVolume 下单使用的交易量。 0.1
FractalPeriod 分形窗口长度(建议为奇数)。 5
IndentPoints 在分形价位上增加的偏移点数。 1
EntryConfirmation 突破确认方式(PriceBreakoutCloseBreakout)。 CloseBreakout
UseTeethFilter 是否要求上一根收盘价站在鳄鱼牙齿的同侧。 true
MaxDistancePoints 突破价位与嘴唇之间的最大允许距离(点)。 1000
UseTrendAlignment 是否启用鳄鱼线排列过滤。 false
JawTeethDistancePoints 下颚与牙齿之间的最小距离。 10
TeethLipsDistancePoints 牙齿与嘴唇之间的最小距离。 10
JawExit 触及下颚时的平仓模式(DisabledPriceCrossCloseCross)。 CloseCross
ReverseExit 反向信号的处理方式(DisabledOppositeFractalOppositePosition)。 OppositePosition
EnableTrailing 是否启用鳄鱼线移动止损。 true
SlopeSmaPeriod 与嘴唇斜率对比的 SMA 周期。 5
StopLossPoints 止损距离(点,0 表示关闭)。 50
TakeProfitPoints 止盈距离(点,0 表示关闭)。 50
JawPeriod, TeethPeriod, LipsPeriod 鳄鱼三条线的周期。 13, 8, 5
JawShift, TeethShift, LipsShift 每条线向前平移的柱数。 8, 5, 3
MaMethod 使用的移动平均类型(SimpleExponentialSmoothedWeighted)。 Simple
AppliedPrice 提供给 Alligator 的价格类型。 CandlePrice.Median
CandleType 订阅的 K 线类型。 15 分钟 K 线

其他说明

  • 策略会在默认图表区域绘制 Alligator 三条线及成交记录。
  • 为保证分形识别正确,FractalPeriod 建议保持为奇数;默认值与原版 EA 一致。
  • 所有距离类参数(IndentPointsMaxDistancePointsJawTeethDistancePointsTeethLipsDistancePointsStopLossPointsTakeProfitPoints)均以品种的最小报价步长 (Security.PriceStep) 为单位。
  • 移动止损和下颚出场基于已完成的 K 线,与原始 MQL4 实现一致。
using System;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Bill Williams Fractal Breakout strategy filtered by Alligator alignment.
/// Buys on up-fractal breakout when price is above alligator teeth,
/// sells on down-fractal breakout when price is below alligator teeth.
/// Exits on opposite signal (reverse position).
/// </summary>
public class FTBillWillamsTraderStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _jawPeriod;
	private readonly StrategyParam<int> _teethPeriod;
	private readonly StrategyParam<int> _lipsPeriod;
	private readonly StrategyParam<int> _fractalLen;

	private SMA _jaw = null!;
	private SMA _teeth = null!;
	private SMA _lips = null!;

	private decimal[] _highBuf = Array.Empty<decimal>();
	private decimal[] _lowBuf = Array.Empty<decimal>();
	private int _bufCount;

	private decimal? _pendingBuyLevel;
	private decimal? _pendingSellLevel;
	private decimal _prevJaw;
	private decimal _prevTeeth;
	private decimal _prevLips;
	private decimal _entryPrice;

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

	public int JawPeriod
	{
		get => _jawPeriod.Value;
		set => _jawPeriod.Value = value;
	}

	public int TeethPeriod
	{
		get => _teethPeriod.Value;
		set => _teethPeriod.Value = value;
	}

	public int LipsPeriod
	{
		get => _lipsPeriod.Value;
		set => _lipsPeriod.Value = value;
	}

	public int FractalLen
	{
		get => _fractalLen.Value;
		set => _fractalLen.Value = value;
	}

	public FTBillWillamsTraderStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
			.SetDisplay("Candle Type", "Candle type for strategy", "General");

		_jawPeriod = Param(nameof(JawPeriod), 13)
			.SetDisplay("Jaw Period", "Alligator jaw SMA period", "Alligator");

		_teethPeriod = Param(nameof(TeethPeriod), 8)
			.SetDisplay("Teeth Period", "Alligator teeth SMA period", "Alligator");

		_lipsPeriod = Param(nameof(LipsPeriod), 5)
			.SetDisplay("Lips Period", "Alligator lips SMA period", "Alligator");

		_fractalLen = Param(nameof(FractalLen), 5)
			.SetDisplay("Fractal Length", "Number of bars for fractal detection", "Signals");
	}

	/// <inheritdoc />
	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_jaw = new SMA { Length = JawPeriod };
		_teeth = new SMA { Length = TeethPeriod };
		_lips = new SMA { Length = LipsPeriod };
		_highBuf = new decimal[FractalLen];
		_lowBuf = new decimal[FractalLen];
		_bufCount = 0;
		_pendingBuyLevel = null;
		_pendingSellLevel = null;
		_prevJaw = 0;
		_prevTeeth = 0;
		_prevLips = 0;
		_entryPrice = 0;
	}

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

		_jaw = new SMA { Length = JawPeriod };
		_teeth = new SMA { Length = TeethPeriod };
		_lips = new SMA { Length = LipsPeriod };

		_highBuf = new decimal[FractalLen];
		_lowBuf = new decimal[FractalLen];
		_bufCount = 0;
		_pendingBuyLevel = null;
		_pendingSellLevel = null;
		_prevJaw = 0;
		_prevTeeth = 0;
		_prevLips = 0;
		_entryPrice = 0;

		var subscription = SubscribeCandles(CandleType);
		subscription
			.Bind(_jaw, _teeth, _lips, OnProcess)
			.Start();

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

	private void OnProcess(ICandleMessage candle, decimal jawVal, decimal teethVal, decimal lipsVal)
	{
		if (candle.State != CandleStates.Finished)
			return;

		// Update fractal buffers
		UpdateFractals(candle);

		var close = candle.ClosePrice;
		var high = candle.HighPrice;
		var low = candle.LowPrice;

		// Manage existing positions - exit on opposite signal
		if (Position > 0)
		{
			// Close long if price breaks below pending sell level or close drops below teeth
			if (_pendingSellLevel is decimal sellLvl && low < sellLvl)
			{
				SellMarket();
				_entryPrice = 0;
			}
		}
		else if (Position < 0)
		{
			// Close short if price breaks above pending buy level or close rises above teeth
			if (_pendingBuyLevel is decimal buyLvl && high > buyLvl)
			{
				BuyMarket();
				_entryPrice = 0;
			}
		}

		// Enter long: price breaks above up-fractal level, close above teeth (bullish)
		if (Position <= 0 && _pendingBuyLevel is decimal pendBuy)
		{
			if (high > pendBuy && close > teethVal)
			{
				if (Position < 0)
					BuyMarket(); // close short first

				BuyMarket();
				_entryPrice = close;
			}
		}

		// Enter short: price breaks below down-fractal level, close below teeth (bearish)
		if (Position >= 0 && _pendingSellLevel is decimal pendSell)
		{
			if (low < pendSell && close < teethVal)
			{
				if (Position > 0)
					SellMarket(); // close long first

				SellMarket();
				_entryPrice = close;
			}
		}

		_prevJaw = jawVal;
		_prevTeeth = teethVal;
		_prevLips = lipsVal;
	}

	private void UpdateFractals(ICandleMessage candle)
	{
		var len = _highBuf.Length;
		if (len < 3)
			return;

		// Shift buffers
		Array.Copy(_highBuf, 1, _highBuf, 0, len - 1);
		_highBuf[len - 1] = candle.HighPrice;
		Array.Copy(_lowBuf, 1, _lowBuf, 0, len - 1);
		_lowBuf[len - 1] = candle.LowPrice;

		_bufCount++;
		if (_bufCount < len)
			return;

		var wing = (len - 1) / 2;
		var center = len - 1 - wing;

		// Check up fractal
		var centerHigh = _highBuf[center];
		var isUp = true;
		for (var i = 0; i < len; i++)
		{
			if (i != center && _highBuf[i] >= centerHigh)
			{
				isUp = false;
				break;
			}
		}

		if (isUp)
			_pendingBuyLevel = centerHigh;

		// Check down fractal
		var centerLow = _lowBuf[center];
		var isDown = true;
		for (var i = 0; i < len; i++)
		{
			if (i != center && _lowBuf[i] <= centerLow)
			{
				isDown = false;
				break;
			}
		}

		if (isDown)
			_pendingSellLevel = centerLow;
	}
}