Ver no GitHub

Estratégia de Scalping FX-CHAOS

Visão Geral

A estratégia de scalping FX-CHAOS replica o expert advisor MT5 que combina o Awesome Oscillator com níveis ZigZag baseados em fractais em múltiplos períodos. A versão StockSharp inscreve-se em velas horárias para execução de operações e velas diárias para um filtro de período superior. Rastreadores internos reconstroem a lógica do "ZigZag on Fractals" detectando padrões fractais de cinco velas e conectando-os em pontos de oscilação alternados.

Fluxo de Trading

  1. Coleta de dados
    • Velas horárias impulsionam as entradas e o gerenciamento de risco.
    • Velas diárias alimentam o filtro ZigZag de período superior.
    • Um Awesome Oscillator (5, 34) é calculado no feed horário.
  2. Rastreamento do ZigZag fractal
    • Cada vela concluída é inserida em uma janela deslizante de cinco elementos.
    • Quando a barra central forma um fractal ascendente/descendente, o último valor de oscilação é atualizado; oscilações consecutivas na mesma direção são substituídas apenas por valores mais extremos.
  3. Detecção de sinais no fechamento horário
    • Um sinal de compra aparece quando a vela abre abaixo da máxima anterior, fecha acima dela, permanece abaixo da última oscilação ZigZag horária, fica acima do nível ZigZag diário mais recente e o Awesome Oscillator é negativo.
    • Um sinal de venda espelha a lógica usando a mínima anterior e a polaridade oposta do oscilador.
  4. Execução de ordens
    • Posições opostas existentes são liquidadas antes de um novo entrada ser colocada com o volume configurado.
    • O preço de entrada é armazenado para o gerenciamento subsequente de stop loss e take profit.

Parâmetros

Nome Descrição
Volume Volume de trading em lotes. Aplicado a cada ordem de mercado.
Stop Loss (pts) Distância em pontos para o stop protetor. O valor é multiplicado pelo passo de preço do instrumento. Defina como 0 para desabilitar.
Take Profit (pts) Distância em pontos para o objetivo de lucro. Convertido com o passo de preço da mesma forma. Defina como 0 para desabilitar.
Trading Candle Período principal usado para entradas (padrão 1 hora).
Daily Candle Período superior usado para o filtro ZigZag (padrão 1 dia).

Gerenciamento de Risco

  • Em cada vela horária concluída, a estratégia verifica se o preço tocou o nível de stop loss ou take profit derivado do preço de entrada armazenado.
  • Uma ordem protetora preenchida fecha a posição imediatamente e reinicia o sinalizador de preço de entrada, evitando uma re-entrada no mesmo ciclo de vela.
  • As posições também são liquidadas quando um novo sinal na direção oposta aparece.

Notas de Implementação

  • A lógica ZigZag personalizada evita buffers diretos de indicadores e segue as diretrizes do repositório trabalhando em assinaturas de velas com estado local mínimo.
  • Os valores ZigZag permanecem null até que velas suficientes sejam processadas (duas barras em cada lado de um fractal potencial). O trading é suspenso até que ambos os rastreadores horários e diários produzam oscilações válidas.
  • O Awesome Oscillator é solicitado via BindEx, garantindo que a estratégia use apenas valores finais do indicador quando todas as entradas estão prontas.
  • As distâncias de preço são dimensionadas por Security.PriceStep. Se o instrumento não tiver um passo, a estratégia recorre a um multiplicador de um ponto.

Arquivos

  • CS/FxChaosScalpStrategy.cs – implementação da estratégia com o rastreador ZigZag, filtro Awesome Oscillator e lógica de ordens.
  • README_zh.md – documentação em chinês simplificado.
  • README_ru.md – documentação em russo.
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>
/// FX-CHAOS scalp strategy adapted to the StockSharp high-level API.
/// </summary>
public class FxChaosScalpStrategy : Strategy
{
	private readonly StrategyParam<decimal> _orderVolume;
	private readonly StrategyParam<decimal> _stopLossPoints;
	private readonly StrategyParam<decimal> _takeProfitPoints;
	private readonly StrategyParam<DataType> _tradingCandleType;
	private readonly StrategyParam<DataType> _dailyCandleType;

	private readonly StrategyParam<int> _zigZagWindowSize;

	private AwesomeOscillator _awesomeOscillator;
	private FractalZigZagTracker _hourlyTracker;
	private FractalZigZagTracker _dailyTracker;

	private decimal _previousHigh;
	private decimal _previousLow;
	private bool _hasPrevious;

	private decimal _entryPrice;
	private bool _hasEntry;

	public decimal OrderVolume
	{
		get => _orderVolume.Value;
		set => _orderVolume.Value = value;
	}

	public decimal StopLossPoints
	{
		get => _stopLossPoints.Value;
		set => _stopLossPoints.Value = value;
	}

	public decimal TakeProfitPoints
	{
		get => _takeProfitPoints.Value;
		set => _takeProfitPoints.Value = value;
	}

	public DataType TradingCandleType
	{
		get => _tradingCandleType.Value;
		set => _tradingCandleType.Value = value;
	}

	public DataType DailyCandleType
	{
		get => _dailyCandleType.Value;
		set => _dailyCandleType.Value = value;
	}

	public int ZigZagWindowSize
	{
		get => _zigZagWindowSize.Value;
		set
		{
			var sanitized = Math.Max(3, value);
			if ((sanitized & 1) == 0)
				sanitized += 1;

			_zigZagWindowSize.Value = sanitized;
		}
	}

	public FxChaosScalpStrategy()
	{
		_orderVolume = Param(nameof(OrderVolume), 0.1m)
			.SetGreaterThanZero()
			.SetDisplay("Volume", "Order volume in lots", "Trading");

		_stopLossPoints = Param(nameof(StopLossPoints), 50m)
			.SetDisplay("Stop Loss (pts)", "Stop loss distance in points", "Risk");

		_takeProfitPoints = Param(nameof(TakeProfitPoints), 50m)
			.SetDisplay("Take Profit (pts)", "Take profit distance in points", "Risk");

		_tradingCandleType = Param(nameof(TradingCandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Trading Candle", "Primary trading timeframe", "General");

		_dailyCandleType = Param(nameof(DailyCandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Daily Candle", "Higher timeframe for ZigZag filter", "General");

		_zigZagWindowSize = Param(nameof(ZigZagWindowSize), 5)
			.SetRange(3, 20)
			.SetDisplay("ZigZag Window", "Candle count for ZigZag detection", "Indicators");

		_hourlyTracker = null;
		_dailyTracker = null;
	}

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

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

		Volume = OrderVolume;
		_hourlyTracker = null;
		_dailyTracker = null;
		_previousHigh = 0m;
		_previousLow = 0m;
		_hasPrevious = false;
		_entryPrice = 0m;
		_hasEntry = false;
	}

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

		Volume = OrderVolume;

		_awesomeOscillator = new AwesomeOscillator
		{
			ShortMa = { Length = 5 },
			LongMa = { Length = 34 }
		};
		_hourlyTracker = new FractalZigZagTracker(ZigZagWindowSize);
		_dailyTracker = new FractalZigZagTracker(ZigZagWindowSize);

		var dailySubscription = SubscribeCandles(DailyCandleType);
		dailySubscription.Bind(ProcessDailyCandle).Start();

		var tradingSubscription = SubscribeCandles(TradingCandleType);
		tradingSubscription.Bind(ProcessTradingCandleRaw).Start();

		StartProtection(
			takeProfit: new Unit(2, UnitTypes.Percent),
			stopLoss: new Unit(1, UnitTypes.Percent)
		);

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, tradingSubscription);
			DrawOwnTrades(area);
		}
	}

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

		// Update higher timeframe ZigZag filter.
		_dailyTracker.Update(candle);
	}

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

		// Track ZigZag swings for the trading timeframe.
		_hourlyTracker.Update(candle);

		var aoValue = _awesomeOscillator.Process(candle);

		if (!_hasPrevious)
		{
			UpdatePreviousLevels(candle);
			return;
		}

		if (aoValue.IsEmpty || !_awesomeOscillator.IsFormed)
		{
			UpdatePreviousLevels(candle);
			return;
		}

		var ao = aoValue.ToDecimal();
		var open = candle.OpenPrice;
		var close = candle.ClosePrice;

		// Evaluate breakout conditions relative to previous levels and AO.
		var longSignal = open < _previousHigh && close > _previousHigh && ao < 0m;
		var shortSignal = open > _previousLow && close < _previousLow && ao > 0m;

		if (longSignal && Position == 0)
		{
			BuyMarket();
			_entryPrice = close;
			_hasEntry = true;
		}
		else if (shortSignal && Position == 0)
		{
			SellMarket();
			_entryPrice = close;
			_hasEntry = true;
		}

		if (Position == 0)
		{
			_hasEntry = false;
			_entryPrice = 0m;
		}

		UpdatePreviousLevels(candle);
	}

	private void UpdatePreviousLevels(ICandleMessage candle)
	{
		_previousHigh = candle.HighPrice;
		_previousLow = candle.LowPrice;
		_hasPrevious = true;
	}

	private bool ManageRisk(ICandleMessage candle)
	{
		if (Position == 0)
		{
			_hasEntry = false;
			_entryPrice = 0m;
			return false;
		}

		if (!_hasEntry)
			return false;

		var step = GetPriceStep();

		if (Position > 0)
		{
			var stop = StopLossPoints > 0m ? _entryPrice - StopLossPoints * step : (decimal?)null;
			var take = TakeProfitPoints > 0m ? _entryPrice + TakeProfitPoints * step : (decimal?)null;

			if (stop is decimal stopPrice && candle.LowPrice <= stopPrice)
			{
				if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(Math.Abs(Position));
				_hasEntry = false;
				_entryPrice = 0m;
				return true;
			}

			if (take is decimal takePrice && candle.HighPrice >= takePrice)
			{
				if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(Math.Abs(Position));
				_hasEntry = false;
				_entryPrice = 0m;
				return true;
			}
		}
		else if (Position < 0)
		{
			var stop = StopLossPoints > 0m ? _entryPrice + StopLossPoints * step : (decimal?)null;
			var take = TakeProfitPoints > 0m ? _entryPrice - TakeProfitPoints * step : (decimal?)null;

			if (stop is decimal stopPrice && candle.HighPrice >= stopPrice)
			{
				if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(Math.Abs(Position));
				_hasEntry = false;
				_entryPrice = 0m;
				return true;
			}

			if (take is decimal takePrice && candle.LowPrice <= takePrice)
			{
				if (Position > 0) SellMarket(Position); else if (Position < 0) BuyMarket(Math.Abs(Position));
				_hasEntry = false;
				_entryPrice = 0m;
				return true;
			}
		}

		return false;
	}

	private decimal GetPriceStep()
	{
		var step = Security?.PriceStep;
		return step is decimal value && value > 0m ? value : 1m;
	}

	private sealed class FractalZigZagTracker
	{
		private readonly int _windowSize;
		private readonly CandleInfo[] _window;
		private int _count;
		private decimal? _lastValue;
		private int _direction;

		public FractalZigZagTracker(int windowSize)
		{
			if (windowSize < 3)
				windowSize = 3;

			if ((windowSize & 1) == 0)
				windowSize += 1;

			_windowSize = windowSize;
			_window = new CandleInfo[_windowSize];
		}

		public decimal? LastValue => _lastValue;

		public void Reset()
		{
			Array.Clear(_window, 0, _window.Length);
			_count = 0;
			_lastValue = null;
			_direction = 0;
		}

		public decimal? Update(ICandleMessage candle)
		{
			if (_count < _windowSize)
			{
				_window[_count++] = new CandleInfo(candle.HighPrice, candle.LowPrice);
				if (_count < _windowSize)
					return _lastValue;

				Evaluate();
				return _lastValue;
			}

			for (var i = 0; i < _windowSize - 1; i++)
				_window[i] = _window[i + 1];

			_window[_windowSize - 1] = new CandleInfo(candle.HighPrice, candle.LowPrice);

			Evaluate();
			return _lastValue;
		}

		private void Evaluate()
		{
			if (_count < _windowSize)
				return;

			var centerIndex = _windowSize / 2;
			var center = _window[centerIndex];

			var isUp = true;
			var isDown = true;

			for (var i = 0; i < _windowSize; i++)
			{
				if (i == centerIndex)
					continue;

				var candle = _window[i];

				if (i < centerIndex)
				{
					if (center.High <= candle.High)
						isUp = false;

					if (center.Low >= candle.Low)
						isDown = false;
				}
				else
				{
					if (center.High < candle.High)
						isUp = false;

					if (center.Low > candle.Low)
						isDown = false;
				}

				if (!isUp && !isDown)
					break;
			}

			if (isUp)
			{
				if (_direction == 1)
				{
					if (_lastValue == null || center.High > _lastValue.Value)
						_lastValue = center.High;
				}
				else
				{
					_lastValue = center.High;
					_direction = 1;
				}
			}
			else if (isDown)
			{
				if (_direction == -1)
				{
					if (_lastValue == null || center.Low < _lastValue.Value)
						_lastValue = center.Low;
				}
				else
				{
					_lastValue = center.Low;
					_direction = -1;
				}
			}
		}

		private readonly struct CandleInfo
		{
			public CandleInfo(decimal high, decimal low)
			{
				High = high;
				Low = low;
			}

			public decimal High { get; }

			public decimal Low { get; }
		}
	}
}