Ver no GitHub

Estratégia de canal de preços de vinte pips

Visão geral

A Estratégia de Canal de Preço de Vinte Pips é uma conversão do consultor especialista original MetaTrader 20 pips que combina um canal de preço estilo Donchian com filtros de média móvel de curto prazo. O algoritmo abre negociações somente quando a vela atual abre oposta à anterior, filtra a direção com médias móveis calculadas sobre preços típicos e gerencia saídas através de um alvo fixo de vinte pips apoiado por um trailing stop dinâmico baseado em canal.

A versão StockSharp mantém o espírito da abordagem original enquanto adapta o gerenciamento de pedidos ao API de alto nível. As ordens de mercado são usadas para entradas e saídas, as metas de lucro são monitoradas internamente e os níveis de stop são emulados com condições de canal de preço.

Lógica de negociação

  1. Pilha de indicadores

    • Uma média móvel simples de um período do preço típico (H+L+C)/3 atua como uma linha de base rápida que reflete o preço típico da vela anterior.
    • Uma média móvel simples lenta configurável (padrão 20) calculada nos preços de fechamento desempenha o papel do filtro MA_Low do EA.
    • Os indicadores mais altos e mais baixos com o mesmo período do canal de preço (padrão 20) emulam os buffers originais do indicador personalizado.
  2. Condições de entrada

    • Configuração longa: o preço típico rápido anterior está acima da média móvel lenta anterior ** e ** a vela atual abre abaixo da abertura anterior. Após uma negociação perdida, o volume é multiplicado pelo fator de recuperação (padrão 2). O preço de entrada é registrado para acompanhar lucros e perdas.
    • Configuração curta: o preço típico rápido anterior está abaixo da média móvel lenta anterior ** e ** a vela atual abre acima da abertura anterior. O escalonamento de volume segue a mesma lógica de recuperação das negociações longas.
  3. Gerenciamento de saídas

    • Uma meta fixa de lucro igual a TakeProfitPips multiplicada pela etapa do preço do instrumento é colocada quando a posição é aberta.
    • Um trailing stop orientado por canal imita a chamada OrderModify original. Quando a barra anterior ultrapassa o canal de preço (mudança de duas barras da lógica MT4), o stop de proteção é movido para o extremo anterior menos/mais o deslocamento final em pips. Se a próxima vela ultrapassar esse extremo, a posição sai imediatamente ao preço de abertura.
    • As saídas de take-profit, trailing stop e gap são todas executadas por meio de ordens de mercado enquanto rastreiam o preço de saída real para atualizar o sinalizador de vitória/perda para a escala do estilo martingale.
  4. Martingale recuperação

    • Após cada posição perdedora fechada, o próximo tamanho da entrada é multiplicado por RecoveryMultiplier. As negociações lucrativas redefinem a bandeira e revertem para o volume base.

Parâmetros

Nome Descrição Padrão
CandleType Período principal usado para cálculos. Velas de 1 hora
ChannelPeriod Período de lookback para o canal estilo Donchian. 20
SlowMaPeriod Comprimento do filtro de média móvel lenta. 20
TakeProfitPips Distância em pips para a meta de lucro fixo. 20
TrailingOffsetPips Offset usado ao apertar o batente até o extremo anterior. 10
RecoveryMultiplier Multiplicador de volume aplicado após uma perda. 2
Volume Volume base de negociação antes do escalonamento de recuperação. 0,1

Notas de uso

  • A estratégia espera que Security.PriceStep reflita o valor do pip do instrumento negociado. Ajuste TakeProfitPips e TrailingOffsetPips se o símbolo usar uma definição de pip diferente.
  • Como StockSharp usa ordens de mercado para saídas, os backtests podem mostrar derrapagens em comparação com as ordens de stop e limite MT4 originais. A lógica ainda reproduz os mesmos limites de preços.
  • Os valores do canal são alterados para emular as chamadas iCustom(..., shift=2); tenha isso em mente ao modificar o comportamento final.
  • O multiplicador de recuperação pode ser definido como 1 para desativar o escalonamento no estilo martingale.
using System;
using System.Linq;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Converted "20 pips" price channel strategy.
/// Uses Donchian channel breakouts with MA filter, trailing stop, and recovery multiplier.
/// </summary>
public class TwentyPipsPriceChannelStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _channelPeriod;
	private readonly StrategyParam<int> _slowMaPeriod;
	private readonly StrategyParam<decimal> _stopLoss;
	private readonly StrategyParam<decimal> _takeProfit;

	private readonly List<decimal> _highs = new();
	private readonly List<decimal> _lows = new();
	private decimal? _prevChannelUpper;
	private decimal? _prevChannelLower;

	/// <summary>
	/// Candle type to process.
	/// </summary>
	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	/// <summary>
	/// Donchian channel lookback period.
	/// </summary>
	public int ChannelPeriod
	{
		get => _channelPeriod.Value;
		set => _channelPeriod.Value = value;
	}

	/// <summary>
	/// Slow moving average length.
	/// </summary>
	public int SlowMaPeriod
	{
		get => _slowMaPeriod.Value;
		set => _slowMaPeriod.Value = value;
	}

	/// <summary>
	/// Stop loss distance in absolute price units.
	/// </summary>
	public decimal StopLoss
	{
		get => _stopLoss.Value;
		set => _stopLoss.Value = value;
	}

	/// <summary>
	/// Take profit distance in absolute price units.
	/// </summary>
	public decimal TakeProfit
	{
		get => _takeProfit.Value;
		set => _takeProfit.Value = value;
	}

	/// <summary>
	/// Initializes a new instance of <see cref="TwentyPipsPriceChannelStrategy"/>.
	/// </summary>
	public TwentyPipsPriceChannelStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Primary candle type", "General");

		_channelPeriod = Param(nameof(ChannelPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Channel Period", "Donchian channel lookback", "Parameters");

		_slowMaPeriod = Param(nameof(SlowMaPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Slow MA Period", "Slow moving average length", "Parameters");

		_stopLoss = Param(nameof(StopLoss), 500m)
			.SetNotNegative()
			.SetDisplay("Stop Loss", "Stop loss distance", "Risk");

		_takeProfit = Param(nameof(TakeProfit), 500m)
			.SetNotNegative()
			.SetDisplay("Take Profit", "Take profit distance", "Risk");
	}

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

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

		_highs.Clear();
		_lows.Clear();
		_prevChannelUpper = null;
		_prevChannelLower = null;
	}

	/// <inheritdoc />
	protected override void OnStarted2(DateTime time)
	{
		var slowMa = new SMA { Length = SlowMaPeriod };

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

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

		// Use StartProtection for SL/TP
		var tp = TakeProfit > 0 ? new Unit(TakeProfit, UnitTypes.Absolute) : null;
		var sl = StopLoss > 0 ? new Unit(StopLoss, UnitTypes.Absolute) : null;
		StartProtection(tp, sl);

		base.OnStarted2(time);
	}

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

		// Track highs and lows for manual Donchian channel
		_highs.Add(candle.HighPrice);
		_lows.Add(candle.LowPrice);

		while (_highs.Count > ChannelPeriod)
			_highs.RemoveAt(0);
		while (_lows.Count > ChannelPeriod)
			_lows.RemoveAt(0);

		if (_highs.Count < ChannelPeriod)
		{
			_prevChannelUpper = null;
			_prevChannelLower = null;
			return;
		}

		var channelUpper = _highs.Max();
		var channelLower = _lows.Min();

		if (!IsFormedAndOnlineAndAllowTrading())
		{
			_prevChannelUpper = channelUpper;
			_prevChannelLower = channelLower;
			return;
		}

		// Channel breakout with MA filter
		if (_prevChannelUpper.HasValue && _prevChannelLower.HasValue)
		{
			// Breakout above the previous channel high -> buy signal
			if (candle.ClosePrice > _prevChannelUpper.Value && candle.ClosePrice > slowMaValue && Position <= 0)
			{
				if (Position < 0)
					BuyMarket(Math.Abs(Position));
				BuyMarket(Volume);
			}
			// Breakout below the previous channel low -> sell signal
			else if (candle.ClosePrice < _prevChannelLower.Value && candle.ClosePrice < slowMaValue && Position >= 0)
			{
				if (Position > 0)
					SellMarket(Position);
				SellMarket(Volume);
			}
		}

		_prevChannelUpper = channelUpper;
		_prevChannelLower = channelLower;
	}
}