Ver no GitHub

Estratégia de ajudante de empresa de apoio

Visão geral

Prop Firm Helper Strategy é um sistema de breakout de canal Donchian convertido do consultor especialista MetaTrader "Prop Firm Helper". A estratégia envia ordens de stop acima do intervalo recente para entradas longas e abaixo do intervalo para entradas curtas. Ele aplica automaticamente as regras de desafio da prop firme, interrompendo a negociação depois que o patrimônio alvo é atingido ou quando o limite de perda diária é violado.

Lógica de negociação

  • Assine velas definidas pelo parâmetro Candle Type.
  • Calcule dois canais Donchian:
    • Entry Period/Entry Shift para detectar interrupções.
    • Exit Period/Exit Shift para rastrear negociações abertas.
  • Coloque ordens de compra stop um tick acima da máxima superior deslocada Donchian quando estiver estável ou vendido.
  • Coloque ordens de stop de venda um tick abaixo da mínima inferior deslocada Donchian quando estiver plana ou longa.
  • Use a suavização Average True Range (ATR Period) para decidir quando avançar as ordens de stop.
  • Feche as posições longas se a vela ficar abaixo da mínima final Donchian. Feche as posições vendidas quando a vela fechar acima da máxima final de Donchian.

Gestão de risco

  • Risk Per Trade % calcula o volume do pedido a partir do patrimônio atual do portfólio, tamanho do passo do instrumento e preço do passo. O volume é arredondado para a etapa de volume de troca e limitado pelo volume mínimo/máximo.
  • As ordens de stop protetora rastreiam a posição usando o canal de saída Donchian mais um buffer ATR para evitar a rotatividade excessiva de ordens.

Regras do Desafio da Prop Firm

  • Use Challenge Rules permite verificações de desafio.
  • A negociação é interrompida quando Pass Criteria patrimônio é alcançado. Todas as ordens são canceladas e a posição fechada.
  • Rebaixamentos diários maiores que Daily Loss Limit acionam uma liquidação completa e desativam novos pedidos pelo restante da sessão. O patrimônio de referência é zerado no início de cada dia de negociação.

Parâmetros

Nome Descrição
Entry Period Lookback do canal de breakout Donchian.
Entry Shift Número de velas finalizadas ignoradas ao usar o canal de breakout.
Exit Period Lookback do canal Donchian final.
Exit Shift Número de velas concluídas ignoradas para trailing stops.
Risk Per Trade % Porcentagem do patrimônio do portfólio em relação ao risco em cada entrada.
ATR Period Lookback para filtro ATR usado ao mover paradas.
Use Challenge Rules Permite condições de desafio firmes.
Pass Criteria Nível de patrimônio que interrompe negociações adicionais.
Daily Loss Limit Rebaixamento diário permitido antes da interrupção da negociação.
Candle Type Assinatura de vela usada para cálculos.

Notas

  • A estratégia requer uma conexão de portfólio para calcular tamanhos de posição baseados em risco e métricas de desafio.
  • Os pedidos são cancelados e reenviados em cada vela concluída para manter os preços de gatilho alinhados com os níveis Donchian mais recentes.
  • Os parâmetros padrão reproduzem o comportamento do consultor especialista MetaTrader original.
using System;
using System.Collections.Generic;

using Ecng.Common;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Breakout strategy that recreates the MetaTrader "Prop Firm Helper" expert advisor.
/// Uses Donchian channel breakouts for entry signals with market orders.
/// </summary>
public class PropFirmHelperStrategy : Strategy
{
	private readonly StrategyParam<int> _entryPeriod;
	private readonly StrategyParam<int> _exitPeriod;
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _signalCooldownBars;

	private DonchianChannels _entryChannel;
	private DonchianChannels _exitChannel;

	private decimal _entryUpper;
	private decimal _entryLower;
	private decimal _exitLower;
	private decimal _exitUpper;
	private decimal _prevEntryUpper;
	private decimal _prevEntryLower;
	private bool _hasValues;
	private decimal _entryPrice;
	private int _cooldownRemaining;

	/// <summary>
	/// Initializes a new instance of <see cref="PropFirmHelperStrategy"/>.
	/// </summary>
	public PropFirmHelperStrategy()
	{
		_entryPeriod = Param(nameof(EntryPeriod), 20)
			.SetGreaterThanZero()
			.SetDisplay("Entry Period", "Number of candles used for breakout Donchian channel", "Entries");

		_exitPeriod = Param(nameof(ExitPeriod), 10)
			.SetGreaterThanZero()
			.SetDisplay("Exit Period", "Number of candles used for trailing Donchian channel", "Exits");

		_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe used for Donchian calculations", "General");

		_signalCooldownBars = Param(nameof(SignalCooldownBars), 4)
			.SetNotNegative()
			.SetDisplay("Signal Cooldown Bars", "Closed candles to wait before a new breakout entry", "General");
	}

	/// <summary>
	/// Donchian breakout lookback length.
	/// </summary>
	public int EntryPeriod
	{
		get => _entryPeriod.Value;
		set => _entryPeriod.Value = value;
	}

	/// <summary>
	/// Donchian trailing lookback length.
	/// </summary>
	public int ExitPeriod
	{
		get => _exitPeriod.Value;
		set => _exitPeriod.Value = value;
	}

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

	public int SignalCooldownBars
	{
		get => _signalCooldownBars.Value;
		set => _signalCooldownBars.Value = value;
	}

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

		_entryChannel = null;
		_exitChannel = null;
		_entryUpper = 0m;
		_entryLower = 0m;
		_exitLower = 0m;
		_exitUpper = 0m;
		_prevEntryUpper = 0m;
		_prevEntryLower = 0m;
		_hasValues = false;
		_entryPrice = 0m;
		_cooldownRemaining = 0;
	}

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

		_hasValues = false;
		_cooldownRemaining = 0;

		_entryChannel = new DonchianChannels { Length = EntryPeriod };
		_exitChannel = new DonchianChannels { Length = ExitPeriod };

		var subscription = SubscribeCandles(CandleType);

		subscription
			.Bind(ProcessCandle)
			.Start();

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

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

		if (_cooldownRemaining > 0)
			_cooldownRemaining--;

		var entryValue = _entryChannel.Process(new CandleIndicatorValue(_entryChannel, candle));
		var exitValue = _exitChannel.Process(new CandleIndicatorValue(_exitChannel, candle));

		if (!_entryChannel.IsFormed || !_exitChannel.IsFormed)
			return;

		if (entryValue is not DonchianChannelsValue entryBands || exitValue is not DonchianChannelsValue exitBands)
			return;

		if (entryBands.UpperBand is not decimal entryUpper || entryBands.LowerBand is not decimal entryLower)
			return;

		if (exitBands.UpperBand is not decimal exitUpper || exitBands.LowerBand is not decimal exitLower)
			return;

		_prevEntryUpper = _entryUpper;
		_prevEntryLower = _entryLower;
		_entryUpper = entryUpper;
		_entryLower = entryLower;
		_exitUpper = exitUpper;
		_exitLower = exitLower;

		if (!_hasValues)
		{
			_hasValues = true;
			return;
		}

		if (!IsFormedAndOnlineAndAllowTrading())
			return;

		ProcessSignal(candle);
	}

	private void ProcessSignal(ICandleMessage candle)
	{
		var close = candle.ClosePrice;

		// Exit logic
		if (Position > 0 && close < _exitLower)
		{
			SellMarket(Position);
			_cooldownRemaining = SignalCooldownBars;
			return;
		}
		else if (Position < 0 && close > _exitUpper)
		{
			BuyMarket(-Position);
			_cooldownRemaining = SignalCooldownBars;
			return;
		}

		// Entry logic - breakout above previous entry channel upper
		if (_cooldownRemaining == 0 && _prevEntryUpper > 0 && close > _prevEntryUpper && Position <= 0)
		{
			BuyMarket(Volume + (Position < 0 ? -Position : 0m));
			_entryPrice = close;
			_cooldownRemaining = SignalCooldownBars;
		}
		else if (_cooldownRemaining == 0 && _prevEntryLower > 0 && close < _prevEntryLower && Position >= 0)
		{
			SellMarket(Volume + (Position > 0 ? Position : 0m));
			_entryPrice = close;
			_cooldownRemaining = SignalCooldownBars;
		}
	}
}