
Sistemas neurossimbólicos no algotrading: Unindo regras simbólicas e redes neurais
Introdução aos sistemas neurossimbólicos: princípios da união entre regras e redes neurais
Imagine que você está tentando ensinar um computador a operar na bolsa. De um lado, temos regras clássicas e padrões, como os famosos "ombro-cabeça-ombro", "fundo duplo" e centenas de outras figuras conhecidas de qualquer trader. Muitos de nós já programamos EAs no MQL5 tentando codificar essas estruturas. Mas o mercado é um organismo vivo, está sempre mudando, e regras rígidas muitas vezes falham.
Do outro lado, temos as redes neurais, redes essas que são modernas, potentes, mas por vezes completamente opacas nas suas decisões. Alimente uma rede LSTM com dados históricos e ela vai gerar previsões com boa precisão. Mas por que ela tomou determinada decisão, geralmente permanece um mistério. E no trading, cada erro pode custar dinheiro de verdade.
Lembro de alguns anos atrás, quando eu travava essa luta no meu algoritmo de trading. Os padrões clássicos geravam muitos falsos positivos, enquanto a rede neural às vezes soltava previsões incríveis, mas sem qualquer lógica. E então tive um estalo: e se a gente unisse as duas abordagens? Usar regras claras como estrutura, o esqueleto do sistema, e a rede neural como o mecanismo adaptativo, que reage ao estado atual do mercado.
Assim nasceu a ideia do sistema neurossimbólico para algotrading. Pense nele como um trader experiente que conhece todas as figuras clássicas e regras, mas que também sabe se adaptar ao mercado, levando em conta nuances e correlações sutis. Um sistema desses tem um "esqueleto" de regras bem definidas e "músculos" na forma de uma rede neural, trazendo flexibilidade e adaptabilidade.
Neste artigo, vou mostrar como nossa equipe desenvolveu esse sistema em Python e explicar como unir a análise clássica de padrões com métodos modernos de aprendizado de máquina. Vamos explorar a arquitetura desde os componentes básicos até os mecanismos complexos de decisão e, claro, vou compartilhar código real e resultados de testes.
Pronto para mergulhar num mundo onde as regras clássicas do trading encontram as redes neurais? Então vamos nessa!
Regras simbólicas no trading: padrões e suas estatísticas
Vamos começar pelo básico: o que é um padrão no mercado? Na análise técnica clássica, é uma figura específica no gráfico, como um "fundo duplo" ou uma "bandeira". Mas quando falamos em programar sistemas de trading, precisamos pensar de forma mais abstrata. No nosso código, um padrão é uma sequência de movimentos de preço codificada em binário: 1 para alta, 0 para queda.
Parece primitivo? Nada disso. Essa representação nos dá uma ferramenta poderosa de análise. Pegue a sequência [1, 1, 0, 1, 0]. Isso não é só um monte de números, senão que é um mini-tendência codificada. No Python, podemos procurar por esses padrões com um código simples, porém eficiente:
pattern = tuple(np.where(data['close'].diff() > 0, 1, 0))
Mas a verdadeira mágica começa quando analisamos as estatísticas. Para cada padrão, podemos calcular três parâmetros chave:
- Frequência de ocorrência (frequency) — quantas vezes o padrão apareceu no histórico
- Taxa de acerto (winrate) — com que frequência, após o padrão, o preço seguiu na direção prevista
- Confiabilidade (reliability) — um indicador composto que leva em conta tanto a frequência quanto o winrate
Aqui vai um exemplo real da minha prática: o padrão [1, 1, 1, 0, 0] no gráfico de 4 horas do EURUSD mostrou um winrate de 68% com frequência de mais de 200 ocorrências ao longo de um ano. Soa promissor, certo? Mas é aqui que mora o perigo da reotimização.
Por isso, adicionamos um filtro dinâmico de confiabilidade:
reliability = frequency * winrate * (1 - abs(0.5 - winrate))
Essa fórmula é surpreendentemente simples. Ela não só leva em conta frequência e winrate, mas também penaliza padrões com desempenho suspeitamente alto — que geralmente não passam de uma anomalia estatística.
Outro ponto importante é o comprimento dos padrões. Padrões curtos (3–4 barras) são frequentes, mas geram muito ruído. Os longos (20–25 barras) são mais confiáveis, mas raros. O ponto ideal geralmente está entre 5 e 8 barras. Embora, admito, para alguns ativos eu vi excelentes resultados com padrões de 12 barras.
Um aspecto crucial é o horizonte de previsão. No nosso sistema usamos o parâmetro forecast_horizon, que define quantas barras à frente tentamos prever o movimento. Empiricamente, chegamos ao valor 6, pois ele oferece o melhor equilíbrio entre precisão na previsão e viabilidade de execução nas operações.
Mas o mais interessante acontece quando começamos a analisar padrões em diferentes condições de mercado. Um mesmo padrão pode se comportar de forma totalmente diferente dependendo da volatilidade ou do horário do dia. Por isso, a estatística simples é só o primeiro passo. É aí que entram as redes neurais, mas isso veremos na próxima parte.
Arquitetura da rede neural para análise de dados de mercado
Agora vamos dar uma olhada no "cérebro" do nosso sistema — a rede neural. Depois de muitos experimentos, optamos por uma arquitetura híbrida que combina camadas LSTM para lidar com séries temporais e camadas totalmente conectadas para processar os atributos estatísticos dos padrões.
Por que LSTM? Porque os dados de mercado não são apenas uma coleção de números, mas uma sequência, onde cada valor depende dos anteriores. Redes LSTM são excelentes para captar essas dependências de longo prazo. Veja como é a estrutura básica da nossa rede:
model = tf.keras.Sequential([ tf.keras.layers.LSTM(256, input_shape=input_shape, return_sequences=True), tf.keras.layers.Dropout(0.4), tf.keras.layers.LSTM(128), tf.keras.layers.Dropout(0.3), tf.keras.layers.Dense(64, activation='relu'), tf.keras.layers.Dense(1, activation='sigmoid') ])
Repare nas camadas Dropout, camadas essas que são a nossa defesa contra o sobreajuste. Nas versões iniciais do sistema, não usávamos isso, e a rede funcionava muito bem com dados históricos, mas "derretia" no mercado real. O Dropout desativa aleatoriamente parte dos neurônios durante o aprendizado, forçando a rede a encontrar padrões mais robustos.
Um ponto importante é a dimensionalidade dos dados de entrada. O parâmetro input_shape é definido por três fatores principais:
- Tamanho da janela de análise (no nosso caso, 10 passos temporais)
- Quantidade de atributos básicos (preço, volume, indicadores técnicos)
- Quantidade de atributos extraídos dos padrões
O resultado é um tensor com dimensão (batch_size, 10, features), onde features é o número total de atributos. Esse é exatamente o formato de dados esperado pela primeira camada LSTM.
Preste atenção ao parâmetro return_sequences=True na primeira camada LSTM. Isso significa que ela retorna uma sequência de saídas para cada passo de tempo, e não apenas a saída final. Isso permite que a segunda camada LSTM tenha acesso a uma visão mais detalhada da dinâmica temporal. Já essa segunda LSTM retorna apenas o estado final, e sua saída segue para as camadas totalmente conectadas.
As camadas Dense atuam como um "intérprete", transformando os padrões complexos encontrados pela LSTM em uma decisão concreta. A primeira camada Dense com ativação ReLU lida com as dependências não lineares, e a camada final, com ativação sigmoide, gera a probabilidade de movimento de preço para cima.
Vale destacar o processo de compilação do modelo:
model.compile( optimizer='adam', loss='binary_crossentropy', metrics=['accuracy', tf.keras.metrics.Precision(), tf.keras.metrics.Recall()] )
Usamos o otimizador Adam, já que ele tem bom desempenho com dados não estacionários, como é o caso dos preços de mercado. A função de perda binary crossentropy é perfeita para nossa tarefa de classificação binária (prever a direção do movimento de preço). E o conjunto de métricas nos ajuda a acompanhar não só a acurácia, mas também a qualidade das previsões em termos de falsos positivos e falsos negativos.
Durante o desenvolvimento, experimentamos diferentes configurações de rede. Testamos a adição de camadas convolucionais (CNN) para identificar padrões locais, experimentamos com o mecanismo de atenção (Attention), mas no fim das contas percebemos que a simplicidade e a transparência da arquitetura são mais importantes. Quanto mais complexa a rede, mais difícil se torna interpretar suas decisões; e no trading, entender a lógica por trás do sistema é absolutamente crucial.
Integração de padrões na rede neural: enriquecendo os dados de entrada
Agora vem a parte mais interessante: como "fundimos" os padrões clássicos com a rede neural. Não se trata apenas de concatenar atributos, e sim de um sistema completo de pré-processamento e análise dos dados.
Começamos com um conjunto básico de dados de entrada. Para cada ponto no tempo, formamos um vetor multidimensional de atributos que inclui:
base_features = [ 'close', # Цена закрытия 'volume', # Объём 'rsi', # Relative Strength Index 'macd', # MACD 'bb_upper', 'bb_lower' # Границы Bollinger Bands ]
Mas isso é só o começo. A verdadeira inovação é a inclusão de estatísticas dos padrões. Para cada padrão, calculamos três indicadores principais:
pattern_stats = { 'winrate': np.mean(outcomes), # Процент успешных сработок 'frequency': len(outcomes), # Частота появления 'reliability': len(outcomes) * np.mean(outcomes) * (1 - abs(0.5 - np.mean(outcomes))) # Надёжность }
Damos atenção especial à última métrica, a reliability. Essa é uma criação nossa, que leva em conta não só a frequência e o winrate, mas também a "suspeição" da estatística. Se o winrate estiver perto demais de 100% ou for muito volátil, o índice de confiabilidade é reduzido.
O processo de integrar esses dados à rede neural exige cuidado especial.
def prepare_data(df): # Базовые признаки нормализуем через MinMaxScaler X_base = self.scaler.fit_transform(df[base_features].values) # Для статистик паттернов используем специальную нормализацию pattern_features = self.pattern_analyzer.extract_pattern_features( df, lookback=len(df) ) return np.column_stack((X_base, pattern_features))
A solução para o problema da variação no tamanho dos padrões:
def extract_pattern_features(self, data, lookback=100): features_per_length = 5 # фиксированное количество признаков на паттерн total_features = len(self.pattern_lengths) * features_per_length features = np.zeros((len(data) - lookback, total_features)) # ... заполнение массива признаков
Cada padrão, independentemente do seu comprimento, é transformado em um vetor de dimensão fixa. Isso resolve o problema do número variável de padrões ativos e permite que a rede trabalhe com uma entrada de tamanho constante.
Um capítulo à parte é a consideração do contexto de mercado. Adicionamos atributos especiais que caracterizam o estado atual do mercado:
market_features = { 'volatility': calculate_atr(data), # Волатильность через ATR 'trend_strength': calculate_adx(data), # Сила тренда через ADX 'market_phase': identify_market_phase(data) # Фаза рынка }
Isso ajuda o sistema a se adaptar a diferentes condições de mercado. Por exemplo, em períodos de alta volatilidade, aumentamos automaticamente os requisitos de confiabilidade dos padrões.
Um ponto importante é o tratamento de dados ausentes. No trading real, isso é um problema comum, especialmente ao lidar com múltiplos timeframes. Resolvemos isso com uma combinação de métodos:
# Заполнение пропусков с учётом специфики каждого признака df['close'] = df['close'].fillna(method='ffill') # для цен df['volume'] = df['volume'].fillna(df['volume'].rolling(24).mean()) # для объёмов pattern_features = np.nan_to_num(pattern_features, nan=-1) # для признаков паттернов
O resultado é que a rede neural recebe um conjunto de dados completo e coerente, onde os padrões técnicos clássicos se integram de forma natural com os indicadores de mercado básicos. Isso dá ao sistema uma vantagem única: ele pode se apoiar tanto em estruturas consagradas pelo tempo quanto em relações complexas descobertas durante o aprendizado.
Sistema de tomada de decisão: da análise aos sinais
Vamos falar sobre como o sistema realmente toma decisões. Esqueça por um momento as redes neurais e os padrões. No final do dia, precisamos de uma decisão clara: entrar no mercado ou não. E se entrar, com qual volume.
A lógica básica que seguimos é simples: usamos dois fluxos de dados, a previsão da rede neural e a estatística dos padrões. A rede neural nos dá a probabilidade de movimento para cima/baixo, e os padrões confirmam ou refutam essa previsão. Mas, como sempre, o diabo mora nos detalhes.
Veja o que acontece por baixo do capô:
def get_trading_decision(self, market_data): # Получаем прогноз от нейросети prediction = self.model.predict(market_data) # Вытаскиваем активные паттерны patterns = self.pattern_analyzer.get_active_patterns(market_data) # Базовая проверка условий рынка if not self._market_conditions_ok(): return None # Не торгуем если что-то не так # Проверяем согласованность сигналов if not self._signals_aligned(prediction, patterns): return None # Нет консенсуса - нет сделки # Рассчитываем уверенность в сигнале confidence = self._calculate_confidence(prediction, patterns) # Определяем размер позиции size = self._get_position_size(confidence) return TradingSignal( direction='BUY' if prediction > 0.5 else 'SELL', size=size, confidence=confidence, patterns=patterns )
A primeira coisa que verificamos são as condições básicas do mercado. Nada de rocket science aqui, e sim de bom senso:
def _market_conditions_ok(self): # Проверяем время if not self.is_trading_session(): return False # Смотрим спред if self.current_spread > self.MAX_ALLOWED_SPREAD: return False # Проверяем волатильность if self.current_atr > self.volatility_threshold: return False return True
Depois, vem a verificação da consistência dos sinais. Aqui há um ponto importante: não exigimos que todos os sinais estejam perfeitamente alinhados. Basta que os principais indicadores não entrem em contradição entre si:
def _signals_aligned(self, ml_prediction, pattern_signals): # Определяем базовое направление ml_direction = ml_prediction > 0.5 # Считаем, сколько паттернов его подтверждают confirming_patterns = sum(1 for p in pattern_signals if p.predicted_direction == ml_direction) # Нужно подтверждение хотя бы 60% паттернов return confirming_patterns / len(pattern_signals) >= 0.6
A parte mais difícil é calcular a confiança no sinal. Após muitos testes e análise de diferentes abordagens, adotamos uma métrica combinada que leva em conta tanto a confiabilidade estatística da previsão da rede neural quanto a eficácia histórica dos padrões identificados:
def _calculate_confidence(self, prediction, patterns): # Базовая уверенность от ML-модели base_confidence = abs(prediction - 0.5) * 2 # Учитываем подтверждающие паттерны pattern_confidence = self._get_pattern_confidence(patterns) # Взвешенное среднее с эмпирически подобранными коэффициентами return (base_confidence * 0.7 + pattern_confidence * 0.3)
Essa arquitetura de tomada de decisão demonstra a eficácia do nosso modelo híbrido, onde métodos clássicos da análise técnica se integram de forma fluida com o aprendizado de máquina. Cada componente da estrutura contribui para a decisão final, e a presença de um sistema de verificações em múltiplos níveis garante o grau necessário de confiabilidade e resistência às diferentes condições do mercado.
Considerações finais
Unir padrões clássicos com análise baseada em redes neurais gera um resultado de outra ordem: a rede neural capta conexões de mercado muito sutis, enquanto os padrões testados ao longo do tempo fornecem uma estrutura confiável para a tomada de decisões de trading. Nos nossos testes, essa abordagem demonstrou resultados consistentemente superiores — tanto em comparação com a análise técnica pura quanto com o uso isolado de aprendizado de máquina.
Uma das descobertas mais importantes foi entender que simplicidade e interpretabilidade são cruciais. Fizemos questão de abandonar arquiteturas mais complexas em favor de um sistema claro e compreensível. Isso não só facilita o controle sobre as decisões de trading, como também permite ajustes rápidos conforme o mercado muda. Em um cenário onde muitos correm atrás da complexidade, foi a simplicidade que nos deu vantagem competitiva.
Espero que nossa experiência seja útil para quem também está explorando os limites do possível na fronteira entre o trading clássico e a inteligência artificial. Porque é justamente nessas áreas interdisciplinares que surgem as soluções mais interessantes e aplicáveis. Continuem experimentando, mas lembrem-se: no trading, não existe bala de prata. Só existe o caminho do desenvolvimento contínuo e da evolução constante das ferramentas.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/16894
Aviso: Todos os direitos sobre esses materiais pertencem à MetaQuotes Ltd. É proibida a reimpressão total ou parcial.
Esse artigo foi escrito por um usuário do site e reflete seu ponto de vista pessoal. A MetaQuotes Ltd. não se responsabiliza pela precisão das informações apresentadas nem pelas possíveis consequências decorrentes do uso das soluções, estratégias ou recomendações descritas.





- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso
Artigo publicado Neurosymbolic Systems in Algorithm Trading: Combining Symbolic Rules and Neural Networks:
Autor: Yevgeniy Koshtenko