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
Trabalhar apenas com velas terminadas do período configurado (padrão: 1 minuto).
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.
Manter uma janela deslizante das últimas 120 velas para buscar barras de referência.
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.
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.
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.
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
Anexar a estratégia ao instrumento e portfólio desejados.
Ajustar o período das velas, os offsets de pip e os filtros baseados em ATR para adequar ao instrumento.
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;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class aeron_jjn_scalper_ea_strategy(Strategy):
def __init__(self):
super(aeron_jjn_scalper_ea_strategy, self).__init__()
self._atr_length = self.Param("AtrLength", 14)
self._body_min_atr = self.Param("BodyMinAtr", 1.5)
self._cooldown_bars = self.Param("CooldownBars", 10)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._prev_open = 0.0
self._prev_close = 0.0
self._has_prev = False
self._cooldown = 0
@property
def AtrLength(self):
return self._atr_length.Value
@property
def BodyMinAtr(self):
return self._body_min_atr.Value
@property
def CooldownBars(self):
return self._cooldown_bars.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(aeron_jjn_scalper_ea_strategy, self).OnStarted2(time)
self._has_prev = False
self._cooldown = 0
atr = AverageTrueRange()
atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(atr, self._process_candle).Start()
self.StartProtection(
takeProfit=Unit(2, UnitTypes.Percent),
stopLoss=Unit(1, UnitTypes.Percent)
)
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle, atr_val):
if candle.State != CandleStates.Finished:
return
atr_value = float(atr_val)
if atr_value <= 0:
self._save_prev(candle)
return
if self._cooldown > 0:
self._cooldown -= 1
if self._has_prev and self._cooldown == 0 and float(self.Position) == 0:
prev_body = abs(self._prev_close - self._prev_open)
min_body = atr_value * float(self.BodyMinAtr)
if float(candle.ClosePrice) > float(candle.OpenPrice) and self._prev_close < self._prev_open and prev_body >= min_body:
self.BuyMarket()
self._cooldown = self.CooldownBars
elif float(candle.ClosePrice) < float(candle.OpenPrice) and self._prev_close > self._prev_open and prev_body >= min_body:
self.SellMarket()
self._cooldown = self.CooldownBars
self._save_prev(candle)
def _save_prev(self, candle):
self._prev_open = float(candle.OpenPrice)
self._prev_close = float(candle.ClosePrice)
self._has_prev = True
def OnReseted(self):
super(aeron_jjn_scalper_ea_strategy, self).OnReseted()
self._has_prev = False
self._cooldown = 0
self._prev_open = 0.0
self._prev_close = 0.0
def CreateClone(self):
return aeron_jjn_scalper_ea_strategy()