Открыть на GitHub

Стратегия Chaos Trader Lite

Стратегия Chaos Trader Lite воспроизводит три техники входа Билла Уильямса («три мудреца») с использованием высокоуровневого API StockSharp. На каждой завершённой свече выбранного таймфрейма (по умолчанию 1 час) выполняются проверки и выставляются отложенные ордера при выполнении любого из условий:

  1. Первый мудрец — дивергентный бар: определяет бычьи или медвежьи дивергентные свечи и требует минимальный зазор между ценой и линией губ Alligator.
  2. Второй мудрец — ускорение Awesome Oscillator: ожидает пять последовательных значений индикатора, подтверждающих нарастание импульса.
  3. Третий мудрец — пробой фрактала: подтверждает фрактал двумя барами ранее и проверяет, что цена находится достаточно далеко от линии зубов Alligator, прежде чем подготовить ордер на пробой.

При появлении сигнала на покупку стратегия отменяет активные sell stop, закрывает короткие позиции, ставит новый buy stop чуть выше максимума свечи и фиксирует защитный стоп ниже минимума. Для продаж выполняется зеркальная последовательность. Защитные уровни проверяются на каждой свече: при пересечении цены открытая позиция закрывается рыночным ордером.

Индикаторы и расчёты

  • Губы Alligator: сглаженная средняя (SMMA) длиной 5 по медианной цене со смещением на три бара вперёд. Значения сохраняются в очереди, чтобы смещение совпадало с реализацией MetaTrader.
  • Зубы Alligator: сглаженная средняя длиной 8 по медиане со смещением на пять баров вперёд; используется как фильтр третьего мудреца.
  • Awesome Oscillator: встроенный индикатор (разница SMA 5 и SMA 34 от медианы) формирует последовательность импульса для второго мудреца.
  • Фракталы: анализируется свеча, находящаяся на две позиции левее текущей. Фрактал подтверждается, если её максимум (минимум) выше (ниже) максимумов и минимумов двух соседних баров.

Торговая логика

  1. Подписка на заданный тип свечей и обработка только завершённых баров.
  2. Обновление индикаторов Alligator и Awesome Oscillator и сохранение смещённых значений.
  3. Проверка условий мудрецов:
    • Дивергентный бар должен закрыться в верхней (для покупок) или нижней (для продаж) половине свечи, а расстояние до губ превышать MagnitudePips * PriceStep.
    • Ускорение AO требует пяти значений: AO[1] > AO[2] > AO[3] > AO[4] и AO[4] < AO[5] для лонга и зеркально для шорта.
    • Пробой фрактала требует закрытия выше (ниже) подтверждённого фрактала и выше (ниже) зубов Alligator плюс пороговое расстояние.
  4. При выполнении условия регистрируется BuyStop или SellStop объёмом Volume на уровне максимума свечи плюс один шаг цены (или минимума минус шаг). Противоположные стопы отменяются, противоположные позиции закрываются.
  5. Обновляются защитные стопы: для длинных они подтягиваются вверх, для коротких опускаются вниз. При пробое уровня позиция закрывается по рынку.

Параметры

  • MagnitudePips (по умолчанию 10) — минимальное расстояние в пунктах между дивергентной свечой и губами Alligator.
  • UseFirstWiseMan (по умолчанию true) — включение/отключение входа по дивергентным барам.
  • UseSecondWiseMan (по умолчанию true) — включение/отключение входа по ускорению Awesome Oscillator.
  • UseThirdWiseMan (по умолчанию true) — включение/отключение входа по пробою фрактала.
  • Volume (по умолчанию 0.01) — объём стоп-ордеров.
  • CandleType (по умолчанию 1 час) — тип свечей, обрабатываемых стратегией.

Примечания

  • Проверки bid/ask из оригинального 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; }
	}
}