Ver no GitHub

Estratégia Aeron JJN Scalper EA

Visão geral

Esta estratégia é um port de alto nível no StockSharp do assessor especialista Aeron JJN Scalper. Observa velas terminadas, identifica situações específicas de reversão de duas barras e coloca ordens stop simuladas na abertura da vela oposta mais recente. Quando o mercado atinge o nível de stop armazenado, a estratégia entra com uma ordem a mercado, aplica metas de risco baseadas em ATR e gerencia a operação com um trailing stop baseado em pips.

Ideias-chave:

  • A direção da operação é decidida por um padrão de reversão de duas velas de alta/baixa.
  • Os níveis de entrada vêm do preço de abertura da última vela forte na direção oposta.
  • Um valor ATR(8) medido na barra de sinal define as distâncias de stop-loss e take-profit.
  • A lógica do trailing stop move o nível de proteção quando o preço avança pelos offsets de pip configurados.
  • Os níveis pendentes expiram automaticamente após o número configurado de minutos.

Regras de negociação

Detecção de sinal

  1. Trabalhar apenas com velas terminadas do período configurado (padrão: 1 minuto).
  2. Calcular o tamanho do pip a partir do passo de preço do instrumento e multiplicar por 10 para preços de 3 ou 5 decimais para imitar o comportamento de pip do MetaTrader.
  3. Manter uma janela deslizante das últimas 120 velas para buscar barras de referência.
  4. Detectar uma configuração de compra quando:
    • A vela atual fecha acima de sua abertura (de alta), e
    • A vela anterior é de baixa com tamanho de corpo maior que DojiDiff1Pips.
    • Procurar para trás a última vela de baixa cujo corpo exceda DojiDiff2Pips; seu preço de abertura torna-se o nível de buy stop.
  5. Detectar uma configuração de venda quando:
    • A vela atual fecha abaixo de sua abertura (de baixa), e
    • A vela anterior é de alta com tamanho de corpo maior que DojiDiff1Pips.
    • Procurar para trás a última vela de alta cujo corpo exceda DojiDiff2Pips; seu preço de abertura torna-se o nível de sell stop.
  6. Ignorar novas configurações se já houver um nível pendente na mesma direção, ou se o valor ATR para a vela ainda não estiver disponível.

Gestão de níveis pendentes

  • O nível armazenado é tratado como uma ordem stop pendente. É descartado se o preço permanecer abaixo (comprado) ou acima (vendido) do gatilho até o tempo de expiração ResetMinutes decorrer.
  • Quando o preço toca o nível em uma vela posterior (máxima ≥ nível de compra ou mínima ≤ nível de venda), a estratégia envia uma ordem a mercado dimensionada para reverter qualquer exposição existente e adicionar contratos Volume.
  • Entrar em uma posição comprada limpa qualquer nível de venda pendente e vice-versa.

Stop-loss, take-profit e trailing

  • Ao entrar, a estratégia registra o valor ATR(8) da vela de sinal.
    • Operações compradas: stop-loss = entry - ATR, take-profit = entry + ATR.
    • Operações vendidas: stop-loss = entry + ATR, take-profit = entry - ATR.
  • Em cada vela terminada, a estratégia:
    • Verifica se o preço atingiu o stop-loss ou take-profit e sai com uma ordem a mercado se tocado.
    • Aplica trailing quando o preço se moveu pelo menos TrailingStopPips + TrailingStepPips a favor da posição. O novo stop fica TrailingStopPips atrás do último fechamento. O stop nunca recua.
  • Se a posição for fechada manualmente, o estado interno é redefinido automaticamente.

Parâmetros

Parâmetro Padrão Descrição
Volume 0.1 Tamanho da posição líquida usado para entradas; a estratégia adiciona a posição atual absoluta para inverter a direção quando necessário.
TrailingStopPips 5 Distância base do trailing stop (convertida para unidades de preço).
TrailingStepPips 5 Avanço adicional necessário antes de mover o trailing stop novamente.
ResetMinutes 10 Tempo de expiração para um nível pendente armazenado (minutos).
DojiDiff1Pips 10 Tamanho mínimo de corpo (em pips) para a vela de reversão que precede o sinal.
DojiDiff2Pips 4 Tamanho mínimo de corpo (em pips) para a vela usada como nível de referência de entrada.
CandleType Período de 1 minuto Tipo de dados de vela usado para cálculos.

Notas de implementação

  • A estratégia opera puramente em velas terminadas e usa níveis em memória em vez de ordens stop reais; quando o nível é violado, uma ordem a mercado é enviada imediatamente. Isso reflete o comportamento original do EA dentro da API de alto nível do StockSharp.
  • ATR(8) é calculado com AverageTrueRange e armazenado em cache para que as distâncias originais de stop/alvo permaneçam constantes para cada operação.
  • A conversão de pip reproduz o ajuste do MetaTrader para cotações de 3 e 5 dígitos. Se o instrumento não tiver PriceStep, um passo padrão de 1 é usado.
  • Até 120 velas históricas são armazenadas para replicar o look-back original de CopyRates de 100 barras com alguma margem de segurança.
  • Não há port em Python para esta estratégia.

Uso

  1. Anexar a estratégia ao instrumento e portfólio desejados.
  2. Ajustar o período das velas, os offsets de pip e os filtros baseados em ATR para adequar ao instrumento.
  3. Iniciar a estratégia; ela rastreará sinais, enviará ordens a mercado quando os níveis de gatilho forem tocados e gerenciará as saídas automaticamente.
using System;
using System.Collections.Generic;

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

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Port of the Aeron JJN Scalper expert advisor.
/// Detects reversal candle patterns (bullish candle after strong bearish, and vice versa)
/// and enters at breakout of the prior candle's open level.
/// </summary>
public class AeronJjnScalperEaStrategy : Strategy
{
	private readonly StrategyParam<int> _atrLength;
	private readonly StrategyParam<decimal> _bodyMinAtr;
	private readonly StrategyParam<int> _cooldownBars;
	private readonly StrategyParam<DataType> _candleType;

	private AverageTrueRange _atr;
	private decimal _prevOpen;
	private decimal _prevClose;
	private bool _hasPrev;
	private int _cooldown;

	public int AtrLength
	{
		get => _atrLength.Value;
		set => _atrLength.Value = value;
	}

	public decimal BodyMinAtr
	{
		get => _bodyMinAtr.Value;
		set => _bodyMinAtr.Value = value;
	}

	public int CooldownBars
	{
		get => _cooldownBars.Value;
		set => _cooldownBars.Value = value;
	}

	public DataType CandleType
	{
		get => _candleType.Value;
		set => _candleType.Value = value;
	}

	public AeronJjnScalperEaStrategy()
	{
		_atrLength = Param(nameof(AtrLength), 14)
			.SetGreaterThanZero()
			.SetDisplay("ATR Length", "ATR indicator period", "Indicators");

		_bodyMinAtr = Param(nameof(BodyMinAtr), 1.5m)
			.SetDisplay("Body Min ATR", "Minimum candle body size as ATR multiple", "Indicators");

		_cooldownBars = Param(nameof(CooldownBars), 10)
			.SetGreaterThanZero()
			.SetDisplay("Cooldown Bars", "Bars to wait between trades", "Trading");

		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Candle Type", "General");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
		=> [(Security, CandleType)];

	protected override void OnReseted()
	{
		base.OnReseted();
		_hasPrev = false;
		_cooldown = 0;
		_prevOpen = 0;
		_prevClose = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		_atr = new AverageTrueRange { Length = AtrLength };
		_hasPrev = false;
		_cooldown = 0;

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

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

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

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

		if (atrVal <= 0)
		{
			SavePrev(candle);
			return;
		}

		if (_cooldown > 0)
			_cooldown--;

		if (_hasPrev && _cooldown == 0 && Position == 0)
		{
			var prevBody = Math.Abs(_prevClose - _prevOpen);
			var minBody = atrVal * BodyMinAtr;

			// Bullish candle after strong bearish candle => buy
			if (candle.ClosePrice > candle.OpenPrice && _prevClose < _prevOpen && prevBody >= minBody)
			{
				BuyMarket();
				_cooldown = CooldownBars;
			}
			// Bearish candle after strong bullish candle => sell
			else if (candle.ClosePrice < candle.OpenPrice && _prevClose > _prevOpen && prevBody >= minBody)
			{
				SellMarket();
				_cooldown = CooldownBars;
			}
		}

		SavePrev(candle);
	}

	private void SavePrev(ICandleMessage candle)
	{
		_prevOpen = candle.OpenPrice;
		_prevClose = candle.ClosePrice;
		_hasPrev = true;
	}
}