Ver no GitHub

Estratégia Pipsover Chaikin Hedge

Visão geral

Esta estratégia reproduz o consultor especialista "Pipsover 2" do MetaTrader dentro do StockSharp. Ela busca condições de sobrevenda ou sobrecompra com o oscilador Chaikin enquanto o preço perfura uma média móvel e usa o corpo da vela anterior para confirmar a reversão. O port StockSharp mantém a lógica de hedge discrecionária do código original: quando um sinal oposto aparece enquanto já há uma posição, a estratégia imediatamente reverte a exposição líquida para seguir o novo viés.

Indicadores e dados

  • Oscilador Chaikin: construído a partir de uma linha de Acumulação/Distribuição suavizada por duas médias móveis. Ambas as médias são configuráveis e correspondem à implementação do MetaTrader (simples, exponencial, suavizada ou ponderada).
  • Média móvel de preço: comprimento, deslocamento e tipo configuráveis. Serve como a âncora de reversão à média que os máximos ou mínimos da vela anterior devem perfurar.
  • Período: a estratégia subscreve a um único fluxo de velas escolhido através do parâmetro CandleType.

Lógica de trading

  1. Trabalhar apenas com velas concluídas. O corpo da vela anterior (fechamento vs. abertura) fornece o viés direcional.
  2. Ler o valor do oscilador Chaikin da vela anterior. Valores negativos grandes sinalizam sobrevenda, valores positivos grandes marcam zonas de sobrecompra.
  3. Exigir que a vela anterior perfure o valor atual da média móvel (Low < MA para configurações de alta e High > MA para as de baixa).
  4. Entrar quando não há posição aberta:
    • Comprado: vela anterior de alta, mínimo abaixo da MA, Chaikin abaixo de -OpenLevel.
    • Vendido: vela anterior de baixa, máximo acima da MA, Chaikin acima de OpenLevel.
  5. Quando uma posição existe e uma configuração oposta aparece, o algoritmo reverte a posição líquida (SellMarket / BuyMarket com volume extra) para espelhar o comportamento de hedge da versão MT5.
  6. Stops e alvos são emulados dentro da estratégia usando máximos/mínimos de velas, porque o StockSharp trabalha com posições líquidas em vez de tickets individuais cobertos.

Gerenciamento de risco

  • Stop-loss e take-profit: distâncias em pips convertidas através do passo de preço do instrumento. Ambos podem ser desabilitados com zero.
  • Break-even: uma vez que o preço se move BreakevenPips a favor, o stop é movido para o preço de entrada.
  • Trailing: após o movimento exceder BreakevenPips + TrailingStopPips, o stop segue o preço à distância de trailing.
  • Redefinição do estado de posição: sempre que uma saída ocorre, todos os níveis de preço internos são limpos para preparação do próximo trade.

Parâmetros

Nome Descrição
OpenLevel Magnitude do Chaikin necessária para abrir uma nova posição (padrão 100).
CloseLevel Magnitude do Chaikin necessária para reverter uma posição existente (padrão 125).
StopLossPips Distância do stop-loss em pips (padrão 65).
TakeProfitPips Distância do take-profit em pips (padrão 100).
TrailingStopPips Distância de trailing em pips (padrão 30).
BreakevenPips Ganho em pips antes de mover o stop para break-even (padrão 15).
MaPeriod Comprimento da média móvel para o filtro de preço (padrão 20).
MaShift Barras para deslocar a média móvel (padrão 0).
MaType Tipo de média móvel (Simple, Exponential, Smoothed, Weighted).
ChaikinFastPeriod Comprimento de suavização rápida no oscilador Chaikin (padrão 3).
ChaikinSlowPeriod Comprimento de suavização lenta no oscilador Chaikin (padrão 10).
ChaikinMaType Tipo de média móvel usada para suavização do Chaikin.
CandleType Série de velas usada para cálculos.

Notas

  • Configurar a propriedade base Volume no StockSharp para controlar o tamanho da operação.
  • Os pips são calculados usando o PriceStep do instrumento. Se o passo corresponde a cotações de 3 ou 5 decimais (p.ex., 0.00001), a estratégia o multiplica por 10 para corresponder ao espaçamento de pips do MetaTrader.
  • Como o StockSharp usa posições líquidas, as ordens de hedge do consultor especialista MQL original são representadas como reversões imediatas da posição existente.
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>
/// Chaikin oscillator oversold/overbought strategy with optional reversal hedging and trailing management.
/// </summary>
public class PipsoverChaikinHedgeStrategy : Strategy
{
	private readonly StrategyParam<decimal> _openLevel;
	private readonly StrategyParam<decimal> _closeLevel;
	private readonly StrategyParam<decimal> _stopLossPips;
	private readonly StrategyParam<decimal> _takeProfitPips;
	private readonly StrategyParam<decimal> _trailingStopPips;
	private readonly StrategyParam<decimal> _breakevenPips;
	private readonly StrategyParam<int> _maPeriod;
	private readonly StrategyParam<int> _maShift;
	private readonly StrategyParam<MovingAverageTypeOptions> _maType;
	private readonly StrategyParam<int> _chaikinFastPeriod;
	private readonly StrategyParam<int> _chaikinSlowPeriod;
	private readonly StrategyParam<MovingAverageTypeOptions> _chaikinMaType;
	private readonly StrategyParam<DataType> _candleType;

	private AccumulationDistributionLine _adLine = null!;
	private IIndicator _priceMa = null!;
	private IIndicator _chaikinFast = null!;
	private IIndicator _chaikinSlow = null!;

	private readonly Queue<decimal> _maValues = new();

	private decimal _pipSize;
	private decimal? _entryPrice;
	private decimal? _stopPrice;
	private decimal? _takeProfitPrice;
	private decimal _prevOpen;
	private decimal _prevClose;
	private decimal _prevHigh;
	private decimal _prevLow;
	private bool _hasPrevCandle;
	private decimal _prevChaikin;
	private bool _hasPrevChaikin;

	/// <summary>
	/// Chaikin threshold for entries.
	/// </summary>
	public decimal OpenLevel
	{
		get => _openLevel.Value;
		set => _openLevel.Value = value;
	}

	/// <summary>
	/// Chaikin threshold for hedging reversals.
	/// </summary>
	public decimal CloseLevel
	{
		get => _closeLevel.Value;
		set => _closeLevel.Value = value;
	}

	/// <summary>
	/// Stop-loss distance in pips.
	/// </summary>
	public decimal StopLossPips
	{
		get => _stopLossPips.Value;
		set => _stopLossPips.Value = value;
	}

	/// <summary>
	/// Take-profit distance in pips.
	/// </summary>
	public decimal TakeProfitPips
	{
		get => _takeProfitPips.Value;
		set => _takeProfitPips.Value = value;
	}

	/// <summary>
	/// Trailing stop distance in pips.
	/// </summary>
	public decimal TrailingStopPips
	{
		get => _trailingStopPips.Value;
		set => _trailingStopPips.Value = value;
	}

	/// <summary>
	/// Breakeven activation distance in pips.
	/// </summary>
	public decimal BreakevenPips
	{
		get => _breakevenPips.Value;
		set => _breakevenPips.Value = value;
	}

	/// <summary>
	/// Moving average length.
	/// </summary>
	public int MaPeriod
	{
		get => _maPeriod.Value;
		set => _maPeriod.Value = value;
	}

	/// <summary>
	/// Moving average shift in bars.
	/// </summary>
	public int MaShift
	{
		get => _maShift.Value;
		set => _maShift.Value = value;
	}

	/// <summary>
	/// Moving average type for price filter.
	/// </summary>
	public MovingAverageTypeOptions MaType
	{
		get => _maType.Value;
		set => _maType.Value = value;
	}

	/// <summary>
	/// Fast Chaikin moving average length.
	/// </summary>
	public int ChaikinFastPeriod
	{
		get => _chaikinFastPeriod.Value;
		set => _chaikinFastPeriod.Value = value;
	}

	/// <summary>
	/// Slow Chaikin moving average length.
	/// </summary>
	public int ChaikinSlowPeriod
	{
		get => _chaikinSlowPeriod.Value;
		set => _chaikinSlowPeriod.Value = value;
	}

	/// <summary>
	/// Moving average type used in Chaikin oscillator.
	/// </summary>
	public MovingAverageTypeOptions ChaikinMaType
	{
		get => _chaikinMaType.Value;
		set => _chaikinMaType.Value = value;
	}

	/// <summary>
	/// Candle type used for calculations.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of the <see cref="PipsoverChaikinHedgeStrategy"/> class.
	/// </summary>
	public PipsoverChaikinHedgeStrategy()
	{
		_openLevel = Param(nameof(OpenLevel), 0.01m)
		.SetGreaterThanZero()
		.SetDisplay("Open Level", "Chaikin level for entries", "Chaikin");

		_closeLevel = Param(nameof(CloseLevel), 0.02m)
		.SetGreaterThanZero()
		.SetDisplay("Close Level", "Chaikin level for hedging", "Chaikin");

		_stopLossPips = Param(nameof(StopLossPips), 65m)
		.SetDisplay("Stop Loss (pips)", "Stop-loss distance in pips", "Risk");

		_takeProfitPips = Param(nameof(TakeProfitPips), 100m)
		.SetDisplay("Take Profit (pips)", "Take-profit distance in pips", "Risk");

		_trailingStopPips = Param(nameof(TrailingStopPips), 30m)
		.SetDisplay("Trailing Stop (pips)", "Trailing stop distance in pips", "Risk");

		_breakevenPips = Param(nameof(BreakevenPips), 15m)
		.SetDisplay("Breakeven (pips)", "Breakeven activation distance", "Risk");

		_maPeriod = Param(nameof(MaPeriod), 20)
		.SetGreaterThanZero()
		.SetDisplay("MA Period", "Price moving average length", "Trend");

		_maShift = Param(nameof(MaShift), 0)
		.SetDisplay("MA Shift", "Price moving average shift", "Trend");

		_maType = Param(nameof(MaType), MovingAverageTypeOptions.Simple)
		.SetDisplay("MA Type", "Price moving average type", "Trend");

		_chaikinFastPeriod = Param(nameof(ChaikinFastPeriod), 3)
		.SetGreaterThanZero()
		.SetDisplay("Chaikin Fast", "Fast Chaikin length", "Chaikin");

		_chaikinSlowPeriod = Param(nameof(ChaikinSlowPeriod), 10)
		.SetGreaterThanZero()
		.SetDisplay("Chaikin Slow", "Slow Chaikin length", "Chaikin");

		_chaikinMaType = Param(nameof(ChaikinMaType), MovingAverageTypeOptions.Exponential)
		.SetDisplay("Chaikin MA Type", "Chaikin moving average type", "Chaikin");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
		.SetDisplay("Candle Type", "Timeframe for analysis", "Data");
	}

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

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

		_maValues.Clear();
		_pipSize = 0m;
		_entryPrice = null;
		_stopPrice = null;
		_takeProfitPrice = null;
		_prevOpen = 0m;
		_prevClose = 0m;
		_prevHigh = 0m;
		_prevLow = 0m;
		_prevChaikin = 0m;
		_hasPrevCandle = false;
		_hasPrevChaikin = false;
	}

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

		_pipSize = CalculatePipSize();

		_adLine = new AccumulationDistributionLine();
		_priceMa = CreateMovingAverage(MaType, MaPeriod);
		_chaikinFast = CreateMovingAverage(ChaikinMaType, ChaikinFastPeriod);
		_chaikinSlow = CreateMovingAverage(ChaikinMaType, ChaikinSlowPeriod);

		var subscription = SubscribeCandles(CandleType);
		subscription
		.Bind(_priceMa, _adLine, ProcessCandle)
		.Start();
	}

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

		var prevOpen = _prevOpen;
		var prevClose = _prevClose;
		var prevHigh = _prevHigh;
		var prevLow = _prevLow;
		var prevChaikin = _prevChaikin;
		var hasPrevCandle = _hasPrevCandle;
		var hasPrevChaikin = _hasPrevChaikin;

		var fastValue = _chaikinFast.Process(new DecimalIndicatorValue(_chaikinFast, adValue, candle.OpenTime) { IsFinal = true });
		var slowValue = _chaikinSlow.Process(new DecimalIndicatorValue(_chaikinSlow, adValue, candle.OpenTime) { IsFinal = true });

		if (!fastValue.IsFinal || !slowValue.IsFinal)
		{
			_prevChaikin = fastValue.ToDecimal() - slowValue.ToDecimal();
			_hasPrevChaikin = true;
			StorePreviousCandle(candle);
			return;
		}

		var chaikin = fastValue.ToDecimal() - slowValue.ToDecimal();
		var shiftedMa = UpdateShiftedMa(maValue);

		if (shiftedMa is null)
		{
			_prevChaikin = chaikin;
			_hasPrevChaikin = true;
			StorePreviousCandle(candle);
			return;
		}

		var hasPrevData = hasPrevCandle && hasPrevChaikin;
		var positionClosed = HandleStopsAndTargets(candle);
		var reversed = false;

		if (Position == 0m)
		{
			if (hasPrevData)
			{
				var bullishPrev = prevClose > prevOpen;
				var bearishPrev = prevClose < prevOpen;

				if (bullishPrev && prevLow < shiftedMa && prevChaikin < -OpenLevel)
				{
					BuyMarket(Volume);
					SetupLongTargets(candle.ClosePrice);
				}
				else if (bearishPrev && prevHigh > shiftedMa && prevChaikin > OpenLevel)
				{
					SellMarket(Volume);
					SetupShortTargets(candle.ClosePrice);
				}
			}
		}
		else if (!positionClosed)
		{
			if (hasPrevData)
			{
				var bearishPrev = prevClose < prevOpen;
				var bullishPrev = prevClose > prevOpen;

				if (Position > 0m && bearishPrev && prevHigh > shiftedMa && prevChaikin > CloseLevel)
				{
					var size = Math.Abs(Position) + Volume;
					SellMarket(size);
					SetupShortTargets(candle.ClosePrice);
					reversed = true;
				}
				else if (Position < 0m && bullishPrev && prevLow < shiftedMa && prevChaikin < -CloseLevel)
				{
					var size = Math.Abs(Position) + Volume;
					BuyMarket(size);
					SetupLongTargets(candle.ClosePrice);
					reversed = true;
				}
			}

			if (!reversed)
			UpdateTrailing(candle);
		}

		_prevChaikin = chaikin;
		_hasPrevChaikin = true;
		StorePreviousCandle(candle);
	}
	private decimal? UpdateShiftedMa(decimal maValue)
	{
		var shift = Math.Max(0, MaShift);
		_maValues.Enqueue(maValue);

		while (_maValues.Count > shift + 1)
		_maValues.Dequeue();

		var values = _maValues.ToArray();
		if (values.Length < shift + 1)
		return null;

		return values[0];
	}

	private void StorePreviousCandle(ICandleMessage candle)
	{
		_prevOpen = candle.OpenPrice;
		_prevClose = candle.ClosePrice;
		_prevHigh = candle.HighPrice;
		_prevLow = candle.LowPrice;
		_hasPrevCandle = true;
	}

	private bool HandleStopsAndTargets(ICandleMessage candle)
	{
		if (Position > 0m)
		{
			if (_stopPrice is decimal stop && candle.LowPrice <= stop)
			{
				SellMarket(Math.Abs(Position));
				ResetPositionState();
				return true;
			}

			if (_takeProfitPrice is decimal take && candle.HighPrice >= take)
			{
				SellMarket(Math.Abs(Position));
				ResetPositionState();
				return true;
			}
		}
		else if (Position < 0m)
		{
			if (_stopPrice is decimal stop && candle.HighPrice >= stop)
			{
				BuyMarket(Math.Abs(Position));
				ResetPositionState();
				return true;
			}

			if (_takeProfitPrice is decimal take && candle.LowPrice <= take)
			{
				BuyMarket(Math.Abs(Position));
				ResetPositionState();
				return true;
			}
		}

		return false;
	}

	private void SetupLongTargets(decimal price)
	{
		_entryPrice = price;

		if (StopLossPips > 0m)
		_stopPrice = price - StopLossPips * _pipSize;
		else
		_stopPrice = null;

		if (TakeProfitPips > 0m)
		_takeProfitPrice = price + TakeProfitPips * _pipSize;
		else
		_takeProfitPrice = null;
	}

	private void SetupShortTargets(decimal price)
	{
		_entryPrice = price;

		if (StopLossPips > 0m)
		_stopPrice = price + StopLossPips * _pipSize;
		else
		_stopPrice = null;

		if (TakeProfitPips > 0m)
		_takeProfitPrice = price - TakeProfitPips * _pipSize;
		else
		_takeProfitPrice = null;
	}

	private void UpdateTrailing(ICandleMessage candle)
	{
		if (_entryPrice is not decimal entry)
		return;

		var breakevenDist = BreakevenPips > 0m ? BreakevenPips * _pipSize : 0m;
		var trailingDist = TrailingStopPips > 0m ? TrailingStopPips * _pipSize : 0m;

		if (Position > 0m)
		{
			var move = candle.ClosePrice - entry;

			if (breakevenDist > 0m && move > breakevenDist)
			{
				if (_stopPrice is null || _stopPrice < entry)
				_stopPrice = entry;
			}

			if (trailingDist > 0m)
			{
				var activation = breakevenDist + trailingDist;
				if (move > activation)
				{
					var newStop = candle.ClosePrice - trailingDist;
					if (_stopPrice is null || newStop > _stopPrice)
					_stopPrice = newStop;
				}
			}
		}
		else if (Position < 0m)
		{
			var move = entry - candle.ClosePrice;

			if (breakevenDist > 0m && move > breakevenDist)
			{
				if (_stopPrice is null || _stopPrice > entry)
				_stopPrice = entry;
			}

			if (trailingDist > 0m)
			{
				var activation = breakevenDist + trailingDist;
				if (move > activation)
				{
					var newStop = candle.ClosePrice + trailingDist;
					if (_stopPrice is null || newStop < _stopPrice)
					_stopPrice = newStop;
				}
			}
		}
	}

	private void ResetPositionState()
	{
		_entryPrice = null;
		_stopPrice = null;
		_takeProfitPrice = null;
	}

	private decimal CalculatePipSize()
	{
		var step = Security?.PriceStep ?? 0.0001m;
		if (step <= 0m)
		step = 0.0001m;

		var tmp = step;
		var decimals = 0;

		while (tmp < 1m && decimals < 10)
		{
			tmp *= 10m;
			decimals++;
		}

		return decimals == 3 || decimals == 5 ? step * 10m : step;
	}

	private static IIndicator CreateMovingAverage(MovingAverageTypeOptions type, int length)
	{
		return type switch
		{
			MovingAverageTypeOptions.Simple => new SimpleMovingAverage { Length = length },
			MovingAverageTypeOptions.Exponential => new ExponentialMovingAverage { Length = length },
			MovingAverageTypeOptions.Smoothed => new SmoothedMovingAverage { Length = length },
			MovingAverageTypeOptions.Weighted => new WeightedMovingAverage { Length = length },
			_ => new SimpleMovingAverage { Length = length }
		};
	}

	/// <summary>
	/// Moving average options matching the MetaTrader configuration.
	/// </summary>
	public enum MovingAverageTypeOptions
	{
		Simple,
		Exponential,
		Smoothed,
		Weighted
	}
}