在 GitHub 上查看

Chaos Trader Lite 策略

Chaos Trader Lite 策略使用 StockSharp 的高级 API 复刻了 Bill Williams 的“三个智者”入场模型。系统会在所选时间框架(默认 1 小时)的每根完结 K 线后执行判定,并在任意条件满足时挂出止损订单:

  1. 第一智者——背离柱:识别多头或空头背离 K 线,并要求价格与 Alligator 嘴唇线之间保持最小距离。
  2. 第二智者——Awesome Oscillator 加速:等待五个连续的 AO 数值显示动能正在增强。
  3. 第三智者——分形突破:确认两根之前形成的分形,并检查价格是否远离 Alligator 牙齿线,然后准备突破挂单。

出现做多信号时,策略会取消现有的卖出止损、平掉空头、在前一根 K 线高点上方一个最小跳动处挂出买入止损,同时记录防守止损位于该柱低点下方。做空信号执行完全对称的流程。每根新 K 线都会检查保存的止损价位,一旦被触及便以市价平仓。

指标与计算

  • Alligator 嘴唇:对中位价进行 5 周期平滑移动平均(SMMA),并向前平移三根 K 线,通过队列保存以匹配 MetaTrader 的对齐方式。
  • Alligator 牙齿:对中位价进行 8 周期 SMMA 并向前平移五根 K 线,作为第三智者的过滤条件。
  • Awesome Oscillator:内置指标(中位价的 5 与 34 周期 SMA 差值)为第二智者提供动量序列。
  • 分形:检查位于当前柱左侧两个位置的 K 线,当其高点/低点高于(或低于)两侧各两根柱子时认定为有效分形。

交易流程

  1. 订阅指定类型的蜡烛,并仅处理状态为 Finished 的 K 线。
  2. 更新 Alligator 与 AO 指标,缓存平移后的结果。
  3. 依次评估三个智者条件:
    • 背离柱需收于上半区(多头)或下半区(空头),且与嘴唇线的距离大于 MagnitudePips * PriceStep
    • AO 加速要求五个值满足 AO[1] > AO[2] > AO[3] > AO[4]AO[4] < AO[5](空头条件为全体取反)。
    • 分形突破要求价格收在确认分形之上(或之下),并且超过 Alligator 牙齿加上距离阈值。
  4. 当条件满足时,按体积 Volume 在高点上方一个跳动(或低点下方一个跳动)注册 BuyStopSellStop,同时取消反向挂单并平掉反向持仓。
  5. 维护追踪止损:多头仅上移,空头仅下移;若被新蜡烛突破,则立即以市价平仓。

参数

  • MagnitudePips (默认 10) —— 背离柱与 Alligator 嘴唇之间的最小点差。
  • UseFirstWiseMan (默认 true) —— 启用/禁用背离柱信号。
  • UseSecondWiseMan (默认 true) —— 启用/禁用 AO 加速信号。
  • UseThirdWiseMan (默认 true) —— 启用/禁用分形突破信号。
  • Volume (默认 0.01) —— 止损订单的下单数量。
  • CandleType (默认 1 小时) —— 策略使用的蜡烛类型。

备注

  • 原始 MQL4 代码中的买卖价检查使用蜡烛收盘价近似实现。
  • MetaTrader 的保证金和手数校验已省略,因为 StockSharp 在提交订单时会自行验证。
  • 当出现新的反向信号时会取消对应挂单,以保持与原始 CloseAll 行为一致。
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Chaos Trader Lite strategy implementing Bill Williams' three wise men entry concepts.
/// Uses market orders when divergent bars, Awesome Oscillator accelerations or confirmed fractals appear.
/// </summary>
public class ChaosTraderLiteStrategy : Strategy
{
	private readonly StrategyParam<int> _lipsShift;
	private readonly StrategyParam<int> _teethShift;

	private readonly StrategyParam<int> _magnitudePips;
	private readonly StrategyParam<bool> _useFirstWiseMan;
	private readonly StrategyParam<bool> _useSecondWiseMan;
	private readonly StrategyParam<bool> _useThirdWiseMan;
	private readonly StrategyParam<DataType> _candleType;

	private SimpleMovingAverage _lipsSmma = null!;
	private SimpleMovingAverage _teethSmma = null!;
	private AwesomeOscillator _awesomeOscillator = null!;

	private readonly List<decimal> _lipsShiftQueue = new();
	private readonly List<decimal> _teethShiftQueue = new();

	private CandleInfo? _bar0;
	private CandleInfo? _bar1;
	private CandleInfo? _bar2;
	private CandleInfo? _bar3;
	private CandleInfo? _bar4;

	private decimal? _lips0;
	private decimal? _teeth0;
	private decimal? _teeth1;

	private decimal? _ao0;
	private decimal? _ao1;
	private decimal? _ao2;
	private decimal? _ao3;
	private decimal? _ao4;
	private decimal? _ao5;

	private decimal? _longStopLoss;
	private decimal? _shortStopLoss;

	// Pending entry prices for stop-like behavior
	private decimal? _pendingBuyPrice;
	private decimal? _pendingSellPrice;
	private decimal? _pendingBuyStop;
	private decimal? _pendingSellStop;

	/// <summary>
	/// Magnitude threshold in pips between price and Alligator lips.
	/// </summary>
	public int MagnitudePips
	{
		get => _magnitudePips.Value;
		set => _magnitudePips.Value = value;
	}

	/// <summary>
	/// Number of candles used to shift the Alligator lips line.
	/// </summary>
	public int LipsShift
	{
		get => _lipsShift.Value;
		set => _lipsShift.Value = value;
	}

	/// <summary>
	/// Number of candles used to shift the Alligator teeth line.
	/// </summary>
	public int TeethShift
	{
		get => _teethShift.Value;
		set => _teethShift.Value = value;
	}

	/// <summary>
	/// Enable the first wise man divergent bar setup.
	/// </summary>
	public bool UseFirstWiseMan
	{
		get => _useFirstWiseMan.Value;
		set => _useFirstWiseMan.Value = value;
	}

	/// <summary>
	/// Enable the second wise man Awesome Oscillator acceleration setup.
	/// </summary>
	public bool UseSecondWiseMan
	{
		get => _useSecondWiseMan.Value;
		set => _useSecondWiseMan.Value = value;
	}

	/// <summary>
	/// Enable the third wise man fractal breakout setup.
	/// </summary>
	public bool UseThirdWiseMan
	{
		get => _useThirdWiseMan.Value;
		set => _useThirdWiseMan.Value = value;
	}

	/// <summary>
	/// Candle type processed by the strategy.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initialize <see cref="ChaosTraderLiteStrategy"/>.
	/// </summary>
	public ChaosTraderLiteStrategy()
	{
		_magnitudePips = Param(nameof(MagnitudePips), 10)
			.SetGreaterThanZero()
			.SetDisplay("Magnitude", "Distance from lips in pips", "General");

		_lipsShift = Param(nameof(LipsShift), 3)
			.SetNotNegative()
			.SetDisplay("Lips Shift", "Shift applied to Alligator lips", "Alligator");

		_teethShift = Param(nameof(TeethShift), 5)
			.SetNotNegative()
			.SetDisplay("Teeth Shift", "Shift applied to Alligator teeth", "Alligator");

		_useFirstWiseMan = Param(nameof(UseFirstWiseMan), true)
			.SetDisplay("First Wise Man", "Enable divergent bar setup", "General");

		_useSecondWiseMan = Param(nameof(UseSecondWiseMan), true)
			.SetDisplay("Second Wise Man", "Enable Awesome Oscillator setup", "General");

		_useThirdWiseMan = Param(nameof(UseThirdWiseMan), true)
			.SetDisplay("Third Wise Man", "Enable fractal breakout setup", "General");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
			.SetDisplay("Candle Type", "Type of candles", "General");
	}

	/// <inheritdoc />
	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

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

		_bar0 = _bar1 = _bar2 = _bar3 = _bar4 = null;

		_lipsShiftQueue.Clear();
		_teethShiftQueue.Clear();

		_lips0 = null;
		_teeth0 = null;
		_teeth1 = null;

		_ao0 = null;
		_ao1 = null;
		_ao2 = null;
		_ao3 = null;
		_ao4 = null;
		_ao5 = null;

		_longStopLoss = null;
		_shortStopLoss = null;

		_pendingBuyPrice = null;
		_pendingSellPrice = null;
		_pendingBuyStop = null;
		_pendingSellStop = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_lipsSmma = new SimpleMovingAverage { Length = 5 };
		_teethSmma = new SimpleMovingAverage { Length = 8 };
		_awesomeOscillator = new AwesomeOscillator { ShortMa = { Length = 5 }, LongMa = { Length = 34 } };

		var subscription = SubscribeCandles(CandleType);
		subscription.Bind(ProcessCandle).Start();

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

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		// Check pending stop-like entries first
		CheckPendingEntries(candle);

		UpdateBarHistory(candle);

		var median = (candle.HighPrice + candle.LowPrice) / 2m;
		var candleInput = new CandleIndicatorValue(_lipsSmma, candle);

		var lipsValue = _lipsSmma.Process(new DecimalIndicatorValue(_lipsSmma, median, candle.ServerTime) { IsFinal = true });
		var teethValue = _teethSmma.Process(new DecimalIndicatorValue(_teethSmma, median, candle.ServerTime) { IsFinal = true });
		var awesomeValue = _awesomeOscillator.Process(new CandleIndicatorValue(_awesomeOscillator, candle));

		if (lipsValue.IsFinal)
		{
			var lips = lipsValue.ToDecimal();
			_lipsShiftQueue.Add(lips);
			if (_lipsShiftQueue.Count > LipsShift)
			{
				_lips0 = _lipsShiftQueue[0];
				try { _lipsShiftQueue.RemoveAt(0); } catch { }
			}
		}

		if (teethValue.IsFinal)
		{
			var teeth = teethValue.ToDecimal();
			_teethShiftQueue.Add(teeth);
			if (_teethShiftQueue.Count > TeethShift)
			{
				_teeth1 = _teeth0;
				_teeth0 = _teethShiftQueue[0];
				try { _teethShiftQueue.RemoveAt(0); } catch { }
			}
		}

		if (awesomeValue.IsFinal)
		{
			var ao = awesomeValue.ToDecimal();
			_ao5 = _ao4;
			_ao4 = _ao3;
			_ao3 = _ao2;
			_ao2 = _ao1;
			_ao1 = _ao0;
			_ao0 = ao;
		}

		var upFractal = GetUpFractal();
		var downFractal = GetDownFractal();

		if (_lipsSmma.IsFormed && _teethSmma.IsFormed)
			EvaluateSignals(candle, upFractal, downFractal);

		UpdateProtection(candle);
	}

	private void CheckPendingEntries(ICandleMessage candle)
	{
		// Simulate buy stop: if candle high reaches or exceeds pending buy price, enter long
		if (_pendingBuyPrice is decimal buyPrice && candle.HighPrice >= buyPrice)
		{
			if (Position <= 0)
			{
				if (Position < 0)
				{
					BuyMarket();
					_shortStopLoss = null;
				}
				if (Volume > 0)
				{
					BuyMarket();
					_longStopLoss = _pendingBuyStop;
				}
			}
			_pendingBuyPrice = null;
			_pendingBuyStop = null;
		}

		// Simulate sell stop: if candle low reaches or goes below pending sell price, enter short
		if (_pendingSellPrice is decimal sellPrice && candle.LowPrice <= sellPrice)
		{
			if (Position >= 0)
			{
				if (Position > 0)
				{
					SellMarket();
					_longStopLoss = null;
				}
				if (Volume > 0)
				{
					SellMarket();
					_shortStopLoss = _pendingSellStop;
				}
			}
			_pendingSellPrice = null;
			_pendingSellStop = null;
		}
	}

	private void EvaluateSignals(ICandleMessage candle, decimal? upFractal, decimal? downFractal)
	{
		if (_bar0 is not CandleInfo current || _bar1 is not CandleInfo previous)
			return;

		var point = Security?.PriceStep ?? 1m;
		var magnitudeThreshold = MagnitudePips * point;

		if (UseFirstWiseMan && _lips0 is decimal lips)
		{
			if (IsBullishDivergent(current, previous))
			{
				var distance = lips - current.High;
				if (distance > magnitudeThreshold)
					PlaceBuySetup(current, point);
			}

			if (IsBearishDivergent(current, previous))
			{
				var distance = current.Low - lips;
				if (distance > magnitudeThreshold)
					PlaceSellSetup(current, point);
			}
		}

		if (UseSecondWiseMan && _ao1.HasValue && _ao2.HasValue && _ao3.HasValue && _ao4.HasValue && _ao5.HasValue)
		{
			var currentAo = _ao1.Value;
			var bar2Ao = _ao2.Value;
			var bar3Ao = _ao3.Value;
			var bar4Ao = _ao4.Value;
			var bar5Ao = _ao5.Value;

			var bullishAcceleration = currentAo > bar2Ao && bar2Ao > bar3Ao && bar3Ao > bar4Ao && bar4Ao < bar5Ao;
			if (bullishAcceleration)
				PlaceBuySetup(current, point);

			var bearishAcceleration = currentAo < bar2Ao && bar2Ao < bar3Ao && bar3Ao < bar4Ao && bar4Ao > bar5Ao;
			if (bearishAcceleration)
				PlaceSellSetup(current, point);
		}

		if (UseThirdWiseMan && _teeth0.HasValue)
		{
			var teeth = _teeth0.Value;
			var offset = MagnitudePips * point;

			if (upFractal.HasValue && candle.ClosePrice > teeth + offset)
				PlaceBuySetup(current, point);

			if (downFractal.HasValue && candle.ClosePrice < teeth - offset)
				PlaceSellSetup(current, point);
		}
	}

	private void UpdateProtection(ICandleMessage candle)
	{
		if (Position > 0 && _longStopLoss is decimal longStop)
		{
			if (candle.LowPrice <= longStop)
			{
				SellMarket();
				_longStopLoss = null;
			}
		}
		else if (Position < 0 && _shortStopLoss is decimal shortStop)
		{
			if (candle.HighPrice >= shortStop)
			{
				BuyMarket();
				_shortStopLoss = null;
			}
		}
	}

	private void PlaceBuySetup(CandleInfo bar, decimal point)
	{
		if (Volume <= 0)
			return;

		var entryPrice = bar.High + point;
		if (entryPrice <= 0m)
			return;

		var stopPrice = bar.Low - point;

		// Cancel pending sell
		_pendingSellPrice = null;
		_pendingSellStop = null;

		// Set pending buy entry (simulates buy stop order)
		_pendingBuyPrice = entryPrice;
		_pendingBuyStop = stopPrice;
	}

	private void PlaceSellSetup(CandleInfo bar, decimal point)
	{
		if (Volume <= 0)
			return;

		var entryPrice = bar.Low - point;
		if (entryPrice <= 0m)
			return;

		var stopPrice = bar.High + point;

		// Cancel pending buy
		_pendingBuyPrice = null;
		_pendingBuyStop = null;

		// Set pending sell entry (simulates sell stop order)
		_pendingSellPrice = entryPrice;
		_pendingSellStop = stopPrice;
	}

	private static bool IsBullishDivergent(CandleInfo current, CandleInfo previous)
	{
		var median = (current.High + current.Low) / 2m;
		return current.Low < previous.Low && current.Close > median;
	}

	private static bool IsBearishDivergent(CandleInfo current, CandleInfo previous)
	{
		var median = (current.High + current.Low) / 2m;
		return current.High > previous.High && current.Close < median;
	}

	private decimal? GetUpFractal()
	{
		if (_bar0 is not CandleInfo bar0 || _bar1 is not CandleInfo bar1 || _bar2 is not CandleInfo bar2 ||
			_bar3 is not CandleInfo bar3 || _bar4 is not CandleInfo bar4)
			return null;

		return bar2.High > bar3.High && bar2.High > bar4.High && bar2.High > bar1.High && bar2.High > bar0.High
			? bar2.High
			: null;
	}

	private decimal? GetDownFractal()
	{
		if (_bar0 is not CandleInfo bar0 || _bar1 is not CandleInfo bar1 || _bar2 is not CandleInfo bar2 ||
			_bar3 is not CandleInfo bar3 || _bar4 is not CandleInfo bar4)
			return null;

		return bar2.Low < bar3.Low && bar2.Low < bar4.Low && bar2.Low < bar1.Low && bar2.Low < bar0.Low
			? bar2.Low
			: null;
	}

	private void UpdateBarHistory(ICandleMessage candle)
	{
		_bar4 = _bar3;
		_bar3 = _bar2;
		_bar2 = _bar1;
		_bar1 = _bar0;

		_bar0 = new CandleInfo
		{
			Open = candle.OpenPrice,
			High = candle.HighPrice,
			Low = candle.LowPrice,
			Close = candle.ClosePrice
		};
	}

	private struct CandleInfo
	{
		public decimal Open { get; init; }
		public decimal High { get; init; }
		public decimal Low { get; init; }
		public decimal Close { get; init; }
	}
}