Python na negociação: análise de dados, automação e investigação

O Python ajuda a organizar dados de mercado, analisar um diário de negociação e repetir cálculos sem copiar fórmulas manualmente. É especialmente útil quando uma folha de cálculo se torna pequena ou difícil de manter. Este artigo leva da configuração de um ambiente a um script que carrega dados, verifica a sua qualidade e guarda os resultados. Explica também como a investigação num notebook difere de software ligado a uma conta de negociação e que proteções essa integração exige.

O Python permite análises repetíveis e verificações; o código não substitui qualidade de dados nem metodologia sólida.
O essencial em 60 segundos
  • Python na investigação e Python na execução são dois níveis de responsabilidade diferentes: um notebook que calcula estatísticas pode falhar sem consequências. O código que interage com a conta tem de ter limites, registo, tempos limite, modo de teste e paragem controlada
  • O ambiente tem de ser reproduzível: versão do Python, ambiente virtual isolado, versões fixadas das bibliotecas, fonte de dados, fuso horário e intervalo de datas. Sem isso, «funciona no meu computador» não é um resultado, é uma anedota
  • pandas e numpy substituem a folha de cálculo na análise OHLCV: reamostragem de M1 para H4, média móvel, perda máxima, correlações entre pares. A folha termina em 1 048 576 linhas; os dados de ticks começam muito acima disso
  • As chaves e palavras-passe nunca ficam no código: apenas numa variável de ambiente ou ficheiro de configuração fora do repositório. Uma chave escrita num script entra no histórico do Git e permanece lá depois de cada «eliminação»
  • Uma API não é acesso ilimitado: existe autenticação, limite de pedidos e códigos de erro. O ambiente de teste está por vezes, mas nem sempre, num host separado. Código sem tratamento de HTTP 429 e sem tempo limite acabará, mais cedo ou mais tarde, por ser interrompido no pior momento
  • A antecipação de informação é um único deslocamento no código: uma posição decidida no fecho de uma vela só atua sobre a rentabilidade da seguinte. Multiplicar o sinal pela rentabilidade da mesma linha produz um resultado que o mercado nunca entregou
  • Código que não executou é código que não verificou: compilar e «parecer correto» não equivale a executar com dados. Seis blocos executáveis foram verificados offline com dados e mocks declarados. Os blocos de saída mostram resultados efetivos desses testes; os comandos de instalação descrevem a configuração do ambiente, enquanto o envio de ordens continua a ser um esboço estrutural explicitamente não executável

1. Onde o Python ajuda realmente e onde apenas parece uma solução

Uma folha de cálculo suporta um diário de negociação enquanto a sua paciência o permitir. O limite depende do computador, do número de fórmulas e da construção da folha; nenhum número redondo de operações o descreve. Existe, contudo, um limite fixo e independente de tudo isso, escrito na especificação do formato: uma folha do Excel comporta 1 048 576 linhas e 16 384 colunas [16]. Um ano de histórico de ticks de um único par de moedas é um múltiplo desse número, e nenhum truque contorna o limite.

O Python não faz magia: resolve quatro problemas concretos que uma folha de cálculo não resolve ou só resolve à custa de horas de trabalho:

  • Escala dos dados: limitado pela memória do computador e pelo formato de armazenamento, não pelo limite de linhas de uma folha
  • Repetibilidade: o mesmo script executado amanhã com os mesmos dados produz o mesmo resultado. Uma folha com fórmulas arrastadas manualmente não o garante
  • Alterar o intervalo sem trabalho manual: agregar de M1 para H4 preservando OHLCV exige uma única chamada a resample em vez de uma coluna de fórmulas auxiliares
  • Automatizar a obtenção e limpeza dos dados: um script agendado descarrega, verifica e guarda dados sem a sua presença

É tudo. O Python não proporciona vantagem de mercado, não melhora a qualidade da estratégia nem altera as condições de execução na corretora. Encurta a distância entre a pergunta e a resposta e, precisamente por isso, mostra mais depressa que uma ideia não funciona.

Tabela de folhas de cálculo e Python segundo cinco critérios de trabalho
Escolha por escala e repetibilidade; avalie separadamente a qualidade da estratégia.

O que o Python não faz. Não é adequado à negociação com latência ultrabaixa: contam os microssegundos e uma linguagem interpretada com recolha automática de memória é uma má escolha. Mas a frase «Python é demasiado lento para negociar» é igualmente falsa: numa estratégia que entra na abertura da vela H1 seguinte, o tempo de cálculo em Python é insignificante face ao percurso da ordem pela rede e pelo servidor da corretora.

A aceleração pela vetorização tem um mecanismo concreto: uma operação sobre uma coluna inteira executa código numpy compilado em vez de um ciclo do interpretador. A ordem de grandeza da diferença depende dos dados, da operação e do equipamento. Se quiser citar um número concreto, meça-o no seu computador com o timeit módulo da biblioteca padrão [4] e registe-o juntamente com as versões das bibliotecas. Citar um teste de desempenho alheio sem o ambiente não é uma medição.

Comparação das ferramentas analíticas de um operador:

CaracterísticaExcelPythonPine ScriptMQL4/MQL5
Limite de dados1 048 576 linhas por folhaLimitado pela memória do computadorDepende do plano do TradingViewHistórico fornecido pela corretora
Onde o código é executadoLocalmenteLocalmente, num servidor ou na nuvemDentro do ambiente TradingViewDentro do terminal MetaTrader
Acesso à contaNenhumApenas através de API da corretora ou pacote intermediárioNenhum: um alerta pode ativar uma cadeia posteriorNativo no terminal
Fonte de dadosO que carregarQualquer uma, mas tem de a identificarFluxo de dados do TradingViewFluxo de dados da corretora
ReprodutibilidadeBaixa (fórmulas manuais)Elevada se fixar as versõesMédia (versão Pine e dados da plataforma)Média (compilação do terminal e dados da corretora)
Curva de aprendizagemBaixaMédiaBaixa a médiaMédia a elevada

A distinção na última linha da tabela importa mais do que parece. O Pine Script executa no ambiente TradingView, o MQL no terminal MetaTrader e o Python onde o executar. Essa liberdade implica responsabilidade pelo ambiente: versões das bibliotecas, fuso horário do sistema, disponibilidade e se o script tem sequer autorização para interagir com a conta.

Limites deste artigo. Não é um curso de Python nem de backtesting. A metodologia de teste — divisão em amostras de treino e validação, testes forward e modelo de custos — pertence ao artigo Contas de demonstração, backtesting e testes forward. O código executado dentro do terminal e a sua continuidade de funcionamento num servidor são tratados em Automação da negociação: EA, scripts e VPS. Os modelos de linguagem e os seus limites, em IA e LLM na negociação. Aqui tratamos exclusivamente o Python como camada de dados, cálculo e integração.
Não tem de abandonar a folha de cálculo. Pode exportar resultados do Python para CSV e abri-los numa folha, ou fazer o contrário: manter o seu diário de negociação numa folha e carregá-lo em pandas para análise. A biblioteca openpyxl permite ler e escrever ficheiros .xlsx diretamente a partir do Python; a versão mais recente data de junho de 2024 (3.1.5) [13] e, neste caso, isso não é um sinal de alarme: o formato .xlsx está estabilizado e a biblioteca não depende de NumPy nem de pandas. A regra da secção 2 não a exclui, apenas exige conhecer esse estado [À DATA DE: 2026-08-22]. O Python prolonga a folha onde esta termina.

2. Ambiente reproduzível: versões, venv, requirements

Uma razão muito frequente para um script alheio «não funcionar» não está no código, mas no facto de o autor ter utilizado versões diferentes das bibliotecas sem o registar. Na análise de dados de mercado, isto não é um incómodo: invalida o resultado porque não é reproduzível.

Um ambiente é reproduzível quando se podem indicar seis elementos: versão do Python, método de isolamento, versões fixadas das dependências, fonte de dados com intervalo de datas, fuso horário e semente aleatória sempre que o resultado dependa do acaso. Se faltar algum elemento, o resultado é uma história, não uma medição.

O ambiente virtual não é opcional. Instalar bibliotecas «globalmente» faz com que atualizar um projeto danifique outro. O isolamento é gratuito, demora cerca de uma dúzia de segundos e assenta no venv módulo da biblioteca padrão [4]:

# Windows, PowerShell
py -3.12 -m venv .venv
.venv\Scripts\Activate.ps1

# macOS / Linux
python3.12 -m venv .venv
source .venv/bin/activate

# comum aos dois sistemas
python -m pip install --upgrade pip
python -m pip install pandas numpy matplotlib requests python-dotenv
python -m pip freeze > requirements.txt

A última linha é a mais frequentemente omitida. pip freeze regista as versões exatas de tudo o que foi efetivamente instalado, incluindo dependências que desconhecia. Esse ficheiro fica no repositório junto do código. Recriar o ambiente noutro computador passa então por python -m pip install -r requirements.txt, não por adivinhação.

O ambiente utilizado na verificação offline atual dos exemplos de cálculo das secções 4 e 9: Windows 11, Python 3.12.10, pandas 3.0.5 e NumPy 2.5.3, verificado em 8 de outubro de 2026. A lista seguinte regista os pacotes associados a esses cálculos; é um inventário limitado, não uma exportação de todos os pacotes instalados no computador[1][3].

# Versões das dependências da verificação de cálculo local
numpy==2.5.3
pandas==3.0.5
python-dateutil==2.9.0.post0
six==1.17.0
tzdata==2026.4

# interpretador
Python 3.12.10

Note que pandas também utiliza outros pacotes: a descrição de um ambiente não termina na versão de uma biblioteca. As dependências podem causar diferenças entre computadores. Os comandos anteriores ilustram a configuração de um projeto, não registam uma instalação efetuada nesta auditoria. Crie o ficheiro completo requirements.txt no seu próprio ambiente com python -m pip freeze e guarde-o juntamente com o código.

Este ficheiro também não descreve o ambiente de todo o artigo, apenas o das secções de cálculo, e assim deve ser interpretado. O fragmento que verifica erros HTTP na secção 7 foi executado num ambiente ao qual se adicionou requests 2.34.2; o teste da barreira de segurança na secção 8 não precisa de pacotes fora da biblioteca padrão. As versões de matplotlib e python-dotenv não são indicadas porque não foram utilizadas em nenhum exemplo efetivamente executado. Listá-las «por organização» seria escrever um número que ninguém verificou. Se preparar um ambiente para o conjunto, use o comando anterior como ponto de partida e fixe as suas próprias versões com pip freeze; esse é o único ficheiro de dependências que diz algo sobre os seus resultados.

Notebook conceptual com dados UTC, verificações de valores em falta e exportação, sem envio de ordens.
Um notebook permite análise reproduzível. A saída exige validação antes de usar num sistema de execução separado.

Notebook ou ficheiro .py? Ambos, para coisas diferentes. O Jupyter é útil quando ainda não sabe o que procura: executa uma célula, observa o resultado e altera um parâmetro. Contudo, uma propriedade torna-o inadequado para código de execução: o estado das variáveis depende da ordem em que as células foram executadas, não da ordem escrita no ficheiro. Um notebook executado na ordem 1–3–2 parece idêntico a um executado sequencialmente. Código que interage com a conta tem de ser um ficheiro .py normal, executado de cima para baixo, com testes e registo.

Anaconda e Google Colab são atalhos com custos próprios. O Anaconda instala muitos pacotes de uma vez e tem o seu próprio gestor de dependências. Facilita o início, mas dificulta descrever o ambiente com precisão se misturar conda com pip. O Colab funciona no navegador sem instalação e serve para aprender, mas o ambiente é recriado de raiz em cada sessão e as versões das bibliotecas mudam sem a sua intervenção. Para material que tenha de continuar a ser repetível daqui a um ano, ambos exigem disciplina adicional.

Bibliotecas para começar: quatro entradas, não quarenta (são cinco pacotes: a primeira entrada é um par):

  • pandas e numpy: estruturas de dados e cálculo
  • matplotlib: gráficos
  • requests: comunicação HTTP com uma API
  • python-dotenv: carregar configuração de um ficheiro fora do repositório

Adicione indicadores técnicos quando realmente precisar deles. Uma correção aos guias antigos: TA-Lib publica wheels binários oficiais com a biblioteca C no PyPI; para uma combinação suportada de sistema operativo, arquitetura e Python, basta um simples pip install TA-Lib sem exigir uma descarga não oficial nem compilação própria[9]. Verifique o wheel específico antes de instalar: não é uma promessa de funcionamento em todos os computadores. Os metadados verificados em 8 de outubro de 2026 indicam TA-Lib 0.8.1 e pandas-ta ainda em 0.4.71b0, publicada em setembro de 2025[13]. Esta última fixa numba==0.61.2, pelo que verificar apenas o requisito pandas não basta. Um teste histórico de instalação em Python 3.14.3, de 22 de agosto de 2026, falhou nessa dependência com a mensagem only versions >=3.10,<3.14 are supported. Essa observação não se aplica automaticamente aos exemplos atuais em Python 3.12.10. Esta auditoria leu metadados do PyPI; não instalou nem executou TA-Lib ou pandas-ta. A regra de verificação ao lado é, portanto, demasiado restrita: uma publicação recente e um requisito pandas compatível não provam que todo o conjunto de dependências possa ser instalado e executado corretamente. Verifique também as bibliotecas compiladas fixadas, a disponibilidade de wheels para o seu interpretador e um teste com poucos dados. Os metadados das duas últimas versões não descrevem toda a história do projeto; avalie pausas anteriores no repositório.

Antes de adicionar uma biblioteca, verifique a data da última versão. Um projeto sem desenvolvimento há muito tempo acabará por se afastar de uma nova versão de NumPy ou pandas; terá então de fixar versões antigas num ambiente separado ou reescrever o código. A data da última publicação está visível na página do pacote no PyPI [13] e diz mais do que o número de estrelas no GitHub. Isto aplica-se especialmente a estruturas antigas de backtesting.

A semente aleatória. Sempre que o resultado dependa do acaso — divisão da amostra, bootstrap, simulação ou inicialização do modelo — defina e registe a semente. Em numpy: rng = np.random.default_rng(20260822) [3], e depois utilize apenas esse objeto. Sem ele, duas execuções do mesmo script dão resultados diferentes e não fica claro qual está a descrever.

3. pandas e numpy: dados OHLCV, índice temporal e fusos horários

O numpy trata do cálculo em matrizes numéricas; pandas, da estrutura: um DataFrame é uma matriz com colunas nomeadas e um índice, normalmente temporal. Nos dados de mercado, o índice temporal importa mais do que os próprios cálculos, porque determina se está efetivamente a calcular o que pensa.

Carregamento – pd.read_csv() lida com ficheiros de milhões de linhas, reconhece separadores e interpreta as datas se indicar a coluna. Contudo, por predefinição, nada sabe sobre o fuso horário, e isso é a primeira questão a resolver.

Alteração do intervalo: agregar de M1 para H4 preservando OHLCV exige uma única chamada: df.resample('4h').agg({'open':'first', 'high':'max', 'low':'min', 'close':'last', 'tick_volume':'sum'}). Atenção a dois argumentos que quase ninguém define e que determinam a interpretação: label determina se a janela é etiquetada pelo início ou pelo fim, e closed determina que extremo do intervalo pertence à janela. Para a maioria das frequências, a etiqueta predefinida fica no extremo esquerdo [1], pelo que o carimbo temporal da vela indica a abertura; em pandas 3.0.5, resample('4h') sobre oito velas horárias desde a meia-noite produz as etiquetas 00:00 e 04:00. Se a convenção do sinal pressupõe o fecho da vela e os dados são etiquetados pela abertura, desloca todo o resultado um intervalo sem se aperceber.

Estatísticas móveis – df['close'].rolling(20).mean() dá uma média móvel, .rolling(20).std() o desvio utilizado nas bandas de Bollinger, .ewm(span=20).mean() uma média exponencial [1]. Todas são calculadas exclusivamente a partir de dados passados relativamente à linha atual; é dessa propriedade que depende a correção de tudo o resto.

Filtragem: uma condição composta escreve-se uma única vez: df[(df['sma20'] > df['sma50']) & (df['rsi'] < 30)]. Os parênteses em torno de cada condição são obrigatórios, porque & tem maior precedência do que as comparações em Python. Omiti-los causa um erro em vez de uma resposta errada silenciosa; aqui isso é uma boa notícia.

Tabela coerente de quatro velas OHLCV sintéticas e verificações
O formato DataFrame não prova qualidade; verifique valores, tempo e definição de volume.

O fuso horário é uma decisão, não um detalhe. Os dados da corretora costumam ter o carimbo temporal do seu servidor; os de fornecedores externos estão normalmente em UTC, e o sistema operativo pode utilizar hora local com as mudanças de relógio da zona escolhida. Se misturar estes três relógios, uma análise da «sessão de Londres» estará a estudar outra coisa.

A regra prática é estabelecer primeiro o fuso horário da origem e calcular em UTC. Para carimbos temporais com desvio explícito ou Z utilize pd.to_datetime(column, utc=True). Para carimbos do servidor sem desvio, interprete-os primeiro e atribua o fuso confirmado com tz_localize() tratando as horas ambíguas e inexistentes, e depois aplique tz_convert("UTC"). Usar apenas utc=True trata os valores sem fuso horário como UTC; não infere o fuso da corretora. Converta para Europe/London apenas para apresentação[1]. Um índice com fuso explícito e uma fonte de dados registada permitem combinar séries de forma coerente.

A hora do servidor da corretora é um valor separado. Muitas corretoras utilizam nos servidores um fuso diferente de UTC e, por vezes, alteram o desvio com a mudança da hora nos Estados Unidos ou na Europa. Não o assuma de memória: confirme na especificação da corretora e registe-o como parâmetro na configuração do projeto, não como número escrito no código. [FUNCIONALIDADE DEPENDE DA CORRETORA/CONTA]

Dados em falta. O histórico OHLCV tem lacunas: fins de semana, feriados, pausas técnicas ou instrumentos sem cotação. pandas oferece df.ffill(), que preenche lacunas com o último valor conhecido; em preços, costuma ser má ideia, porque cria velas com volatilidade nula e reduz o desvio-padrão. É preferível remover os períodos em falta e contar quantos existiam.

Correção de materiais antigos. A forma df.fillna(method='ffill'), habitual em guias de há alguns anos, foi retirada: o parâmetro method de fillna() foi marcado como obsoleto em pandas 2.1 [2] e removido em pandas 3.0. A diferença é prática: a forma antiga já não mostra um aviso, termina o programa com a mensagem TypeError: NDFrame.fillna() got an unexpected keyword argument 'method'. As formas corretas são df.ffill() e df.bfill(). A assinatura na documentação pandas 3.0.5 já não contém esse parâmetro: DataFrame.fillna(value, *, axis=None, inplace=False, limit=None) [1]. A assinatura e o texto da mensagem foram verificados através da execução em pandas 3.0.5, não pela leitura da documentação. [À DATA DE: 2026-08-22]
O volume de ticks não é o volume de mercado. No mercado cambial descentralizado, um cliente de retalho não recebe volume de negócios consolidado, mas o número de alterações de cotação no fluxo de uma corretora específica. Pode ser utilizado com cautela como aproximação da atividade, mas uma coluna chamada «volume» não é volume de mercado nem é comparável entre corretoras.

Vários instrumentos ao mesmo tempo. Para analisar carteiras cambiais, pandas oferece um índice hierárquico (MultiIndex): um DataFrame contém dados de vários pares, indexados por par e tempo. Permite calcular correlações num eixo temporal comum, desde que todas as séries sejam primeiro colocadas no mesmo fuso e na mesma grelha temporal. Sem isso, calcula a correlação entre dados deslocados uns dos outros.

Guardar resultados. df.to_csv() é legível e universal. df.to_parquet() produz ficheiros menores e leitura mais rápida, preservando os tipos das colunas e o fuso horário do índice [1] : CSV escreve tudo como texto e exige nova interpretação ao recarregar. Para grandes volumes de trabalho, Parquet é mais conveniente, mas exige um motor de escrita adicional; adicione-o a requirements.txt.

Contexto macroeconómico em Python. Os relatórios de posicionamento e os dados de calendário podem ser obtidos por script como os preços, com a mesma disciplina sobre fuso horário e momento de publicação. A sua interpretação pertence, contudo, a outra parte do curso: Relatórios COT e posicionamento.

4. Exemplo mínimo: CSV → UTC → rentabilidades → verificar lacunas → guardar

O seu primeiro script não deve enviar ordens nem sequer ligar-se a uma corretora. Deve fazer algo útil, verificável e completamente seguro: carregar um ficheiro, resolver as horas, calcular rentabilidades, indicar claramente os problemas dos dados e guardar o resultado.

Pressupostos. Um ficheiro CSV com as colunas time,open,high,low,close,tick_volume. A coluna time em formato ISO 8601 com indicador de fuso (Z significa UTC). O intervalo é declarado horário. O ficheiro do teste contém deliberadamente três defeitos presentes em exportações reais: um carimbo temporal duplicado, uma hora em falta e uma célula de volume vazia. As seis linhas estão diretamente no código para reproduzir exatamente o resultado sem descarregar ficheiros: um resultado não reproduzível não é um resultado. Ambiente: Python 3.12.10, pandas 3.0.5, numpy 2.5.3, Windows 11, [À DATA DE: 2026-10-08].

"""Exemplo mínimo: CSV -> UTC -> rentabilidade logarítmica -> lacunas -> guardar."""
import io
from pathlib import Path

import numpy as np
import pandas as pd

# Dados incluídos no código para reproduzir o resultado sem descarregar ficheiros.
# Seis linhas com três defeitos deliberados: carimbo 01:00 duplicado,
# hora 03:00 em falta e célula de volume vazia às 02:00.
CSV_DATA = """time,open,high,low,close,tick_volume
2026-06-01T00:00:00Z,1.08140,1.08170,1.08131,1.08153,812
2026-06-01T01:00:00Z,1.08153,1.08190,1.08150,1.08176,640
2026-06-01T01:00:00Z,1.08153,1.08195,1.08149,1.08182,655
2026-06-01T02:00:00Z,1.08182,1.08188,1.08130,1.08141,
2026-06-01T04:00:00Z,1.08120,1.08125,1.08040,1.08050,903
2026-06-01T05:00:00Z,1.08050,1.08061,1.07980,1.07993,774
"""

OUTPUT = Path("EURUSD_H1_returns.csv")
INTERVAL = "1h"  # frequência declarada dos dados de entrada


def load_data(source) -> pd.DataFrame:
    """Carrega CSV e define um índice com fuso horário UTC.

    Não ordena deliberadamente: a ordem das linhas informa por si só
    sobre a qualidade da exportação. Ordena-se depois do relatório.
    """
    df = pd.read_csv(source)
    # utc=True impõe um fuso; sem isso pandas pode devolver um índice sem fuso
    df["time"] = pd.to_datetime(df["time"], utc=True, format="ISO8601")
    return df.set_index("time")


def quality_report(df: pd.DataFrame, interval: str) -> dict:
    """Contadores a observar antes de calcular qualquer coisa."""
    expected = pd.date_range(df.index.min(), df.index.max(), freq=interval, tz="UTC")
    return {
        "rows": int(len(df)),
        "index_duplicates": int(df.index.duplicated().sum()),
        "missing_cells": int(df.isna().sum().sum()),
        "missing_timestamps": int(len(expected.difference(df.index))),
        # Medido ANTES de ordenar: depois de sort_index() a verificação seria sempre
        # verdadeira e não detetaria um ficheiro com cronologia desordenada.
        "file_chronology_ok": bool(df.index.is_monotonic_increasing),
    }


def add_returns(df: pd.DataFrame) -> pd.DataFrame:
    """Rentabilidade logarítmica = ln(close_t / close_(t-1)). Unidade: logaritmo natural, adimensional."""
    df = df[~df.index.duplicated(keep="last")].copy()
    df["log_return"] = np.log(df["close"]).diff()
    return df


def main() -> None:
    df = load_data(io.StringIO(CSV_DATA))  # na prática: load_data(Path("EURUSD_H1.csv"))
    print("verificação de qualidade:", quality_report(df, INTERVAL))
    df = add_returns(df.sort_index())
    print("velas após remover duplicados:", len(df))
    print("soma das rentabilidades logarítmicas:", round(float(df["log_return"].sum()), 6))
    print("desvio-padrão logarítmico (intervalos 1h e 2h):",
          round(float(df["log_return"].std()), 6))
    df.to_csv(OUTPUT)
    print("guardado:", OUTPUT)


if __name__ == "__main__":
    main()

Saída da execução com os dados incluídos no código e o ambiente descrito; pode reproduzi-la exatamente:

verificação de qualidade: {'rows': 6, 'index_duplicates': 1, 'missing_cells': 1, 'missing_timestamps': 1, 'file_chronology_ok': True}
velas após remover duplicados: 5
soma das rentabilidades logarítmicas: -0.00148
desvio-padrão logarítmico (intervalos 1h e 2h): 0.000467
guardado: EURUSD_H1_returns.csv

Interpretação. O script detetou os três defeitos introduzidos antes de calcular qualquer coisa: é esse todo o seu valor. A soma das rentabilidades logarítmicas foi −0,00148; com cinco velas após remover duplicados — quatro intervalos entre preços consecutivos observados — significa uma queda total do câmbio de cerca de 0,15 por cento na janela estudada. Seis é o número de linhas do ficheiro, não de velas: uma era duplicada. O desvio-padrão de 0,000467 é uma grandeza por vela H1 individual, não anual nem diária; compará-la sem indicar o intervalo não faz sentido. A rentabilidade logarítmica é adimensional e aditiva no tempo: por isso soma-se em vez de se multiplicar, ao contrário das rentabilidades simples.

Limitações. Cinco velas ilustram a mecânica, não constituem uma amostra estatística: nada se conclui sobre o mercado com tão poucas observações. O script não modela spread, comissão ou desvio de execução, porque não é um backtest: calcula rentabilidades dos preços, não o resultado de uma conta. Detetar carimbos temporais em falta pressupõe que os dados têm realmente a frequência horária declarada. Se abrangerem um fim de semana, haverá muitos carimbos «em falta» e isso será normal; o limiar de alarme define-se tendo em conta o calendário de negociação. Outra armadilha está na própria medida: o script deteta a lacuna, mas não a preenche; a rentabilidade calculada sobre a hora ausente abrange dois intervalos, não um. Aqui afeta uma das quatro rentabilidades e, ainda assim, o conjunto foi etiquetado «por vela H1»: é uma simplificação, não uma medida precisa. Existem duas vias honestas: reindexar numa grelha horária completa deixando a lacuna vazia, ou declarar explicitamente a mistura de horizontes como limitação da medição.

Porque se verifica a cronologia antes de ordenar. Se load_data() terminasse com uma chamada a sort_index(), o campo file_chronology_ok seria sempre verdadeiro: a verificação anunciada no relatório não poderia detetar nada e um ficheiro desordenado passaria sem deixar rasto. Uma verificação que não pode falhar é pior do que nenhuma, porque dá uma falsa sensação de validação. Por isso, a ordem é medida nos dados tal como são carregados do ficheiro e só se ordena depois do relatório.

Estado de verificação: este fragmento foi executado no ambiente indicado e a saída acima é a efetiva do programa. Noutras versões de pandas, o formato do dicionário ou o arredondamento podem ser diferentes.

5. Visualização: uma pergunta por gráfico

Um único número que resume o resultado esconde o essencial: quando a estratégia perdeu, durante quanto tempo e se todo o resultado depende de três operações. Um gráfico não decora o relatório: coloca aos dados uma pergunta a que uma tabela não responde.

matplotlib é a biblioteca básica de gráficos em Python: estática e com sintaxe algo trabalhosa. plotly oferece gráficos interativos: passar sobre um ponto para ver data e valor, ampliar um segmento ou exportar para HTML. Com conjuntos muito grandes pode ser mais lenta e exige instalação separada. Para explorar num notebook, plotly é mais conveniente; para gráficos guardados num relatório, matplotlib.

Convém saber desenhar quatro gráficos, porque cada um responde a uma pergunta diferente:

  1. Curva do património: resultado acumulado no tempo. Responde a «se cresceu e quando», não a «quanto vou ganhar»
  2. Gráfico de perda desde máximos: distância face ao máximo acumulado em cada momento. Mostra a perda mais profunda e o tempo de recuperação. Indique sempre se calcula sobre saldo ou património corrente, e em percentagem ou moeda
  3. Mapa de calor das sessões: resultado por hora e dia da semana. Exige resolver primeiro o fuso horário; caso contrário descreve horas inexistentes
  4. Distribuição das rentabilidades: histograma dos resultados individuais. Mostra as caudas pesadas e a assimetria que uma única métrica de risco não revela
Património e máximo acumulado sintéticos com perda de dez por cento
A perda mede-se desde o máximo acumulado, com tratamento explícito dos fluxos de capital.

Para gráficos de velas diretamente de um DataFrame existe mplfinance, uma camada sobre matplotlib, com uma ressalva decorrente da secção 2: a última publicação data de agosto de 2023 e mantém um número de pré-lançamento (0.12.10b0) [13] [À DATA DE: 2026-10-08]. Não a exclui à partida, mas antes de a adicionar a requirements.txt, verifique com dados próprios se funciona com as suas versões de matplotlib e pandas; uma nova publicação não o fará por si. Para transformar um script num painel simples com controlos deslizantes e listas existe streamlit, um projeto com publicações regulares [13], iniciado com streamlit run dashboard.py. Ambas as bibliotecas poupam tempo e adicionam dependências a requirements.txt, por isso adicione-as conscientemente.

Regra prática: se não consegue dizer numa frase a que pergunta responde um gráfico, não precisa dele. Um gráfico sem pergunta é decoração e, na análise de resultados, pode sair cara: sugere conclusões que os dados não contêm.

6. Backtesting em Python: o que pertence ao código e o que à metodologia

Aqui existe uma fronteira fácil de ignorar. O Python é responsável pela implementação do teste: carregar dados, calcular o sinal, atribuir o resultado ao período correto, aplicar custos e calcular métricas. Não é responsável pela metodologia: divisão da amostra, número de variantes, modelo de execução e avaliação da robustez. A metodologia é tratada num artigo separado: Contas de demonstração, backtesting e testes forward; nenhuma biblioteca melhor a pode substituir.

pandas, por si só, não é um backtest. Multiplicar sinais por rentabilidades produz uma série de resultados, mas não modela o que ocorre entre decisão e execução: momento e preço de entrada, spread, comissão, swap, execuções parciais ou rejeições. Pode ser uma simplificação aceitável na seleção inicial de ideias, desde que lhe chame simplificação, não resultado.

Custos de spread fixo e variável em três operações sintéticas
Nos pressupostos custam 3,00 USD ou 8,80 USD; não se afirmam spreads típicos.

Para além de um ciclo próprio, o ecossistema Python oferece três abordagens de ferramentas prontas. A tabela compara o funcionamento, não promessas de desempenho: os tempos dependem dos dados, equipamento e versão; se precisar deles, meça-os e registe o ambiente.

CaracterísticaAbordagem completa por eventosAbordagem vetorizadaAbordagem mínima por eventos
Biblioteca de exemplobacktradervectorbtbacktesting.py
Como calculaVela a vela, com modelo próprio de ordens e carteiraOperações em matrizes completas de uma vezVela a vela, modelo simplificado
Código para uma estratégia simplesMuito (classes, herança)Pouco, se pensar em colunasPouco
Utilização naturalLógica de carteira, muitos instrumentos e ordens complexasTriagem em massa de variantes e filtragem inicialAprendizagem e verificação rápida do sentido de uma ideia
Custo principalCurva de aprendizagem exigenteDepuração mais difícil; é fácil deslocar dados sem avisoLimitações com estratégias complexas
O que verificar antes de utilizarData da última versão no PyPI, compatibilidade com as suas versões pandas e NumPy, e modelação dos custos e do momento de execução
O ritmo de desenvolvimento destes três projetos difere muito. Não é um detalhe para colecionadores: uma estrutura que deixa de acompanhar pandas e NumPy acabará por obrigar a fixar versões antigas num ambiente separado. Antes de escolher, abra a página PyPI e verifique a data da última publicação e as versões Python suportadas. Conhecendo estes critérios, os três projetos diferem mais do que sugere uma tabela comum: backtrader tem no seu histórico pausas de vários anos entre versões e a última data de abril de 2023; existem também derivações comunitárias com manutenção variável; vectorbt existe como projeto aberto e como versão comercial desenvolvida separadamente (a versão aberta data de julho de 2026); confirme primeiro a qual se refere a documentação que está a ler; backtesting.py teve uma pausa de vários anos, mas interrompeu-a publicando outra versão em julho de 2026; o tempo passado importa aqui. As três datas vêm do registo de pacotes [13]. Nada disto exclui uma biblioteca à partida: o que a exclui é a incompatibilidade com as versões utilizadas. [À DATA DE: 2026-08-22]

A última linha não é uma formalidade. Cada estrutura assume um preço e um momento de execução; muitas vezes é esse pressuposto, não a lógica da estratégia, que determina o resultado. Antes de confiar numa biblioteca, procure três respostas na documentação: a que preço executa a entrada, como aplica custos e o que faz com um salto de preço entre velas.

Duas curvas sintéticas com fronteira explícita dentro/fora da amostra
Separe seleção de variantes e avaliação posterior; declare todas as tentativas.

A liberdade do código também é um risco. Um testador integrado impõe regras e protege de alguns erros. O seu próprio código não protege de nada: não pergunta se separou treino e validação, não deteta um sinal baseado num preço então inexistente nem conta as variantes testadas. Toda a responsabilidade recai no autor; por isso as secções seguintes tratam precisamente o que pode falhar silenciosamente no código.

7. API de corretoras: autenticação, limites, erros e ambiente de teste

«A corretora fornece uma API» é por vezes interpretado como «o script tem acesso ilimitado à conta». Não funciona assim: uma API tem pontos de acesso com autenticação, permissões, limites de pedidos e códigos de erro definidos. Por vezes, não sempre, separa teste e produção; a forma dessa separação consulta-se na documentação, não se assume. Antes da primeira linha, responda a nove perguntas: como se autentica, que permissões tem o token, quantos pedidos por segundo são permitidos, como distingue teste e produção, que tipos de dados devolve, que erros define, se há restrições regionais, qual é a política de manutenção da ligação e se o token pode colocar ordens.

Arquiteturas MT5 IPC, TWS ou IB Gateway e API REST diretas
Uma biblioteca não elimina a necessidade de terminal ou aplicação intermediária.

Os limites são concretos e publicados, mas é necessário ler de que limite se trata. Esta distinção perde-se na maioria dos guias e determina como desenhar o ciclo. Na OANDA v20, dois números em dois documentos significam coisas diferentes. O guia do programador indica o valor imposto: «120 pedidos por segundo. Pedidos adicionais recebem o erro HTTP 429» para a API REST, além de vinte fluxos ativos e duas novas ligações por segundo, tudo contado por endereço IP de origem [15]. O documento de boas práticas, pelo contrário, apresenta uma recomendação: o fornecedor aconselha limitar-se a cem pedidos por segundo numa ligação estabelecida e a duas novas ligações por segundo [5]. [À DATA DE: 2026-08-22]

Cem por segundo é o limiar recomendado; acima de cento e vinte o servidor responde com erro 429. Assumir que «100/s é o máximo» não causa dano, mas tratá-lo como limite rígido sem gerir 429 faz o código falhar no primeiro excesso: o limite imposto não coincide com a recomendação. São números de um fornecedor, não uma regra de mercado: noutra corretora serão diferentes e [FUNCIONALIDADE DEPENDE DA CORRETORA/CONTA]. Verifique-os na documentação do seu fornecedor antes de escrever um ciclo de consultas.

Correção de guias populares e de uma versão anterior deste parágrafo. Muitos materiais citam «cerca de 30 pedidos por segundo» para a OANDA: está desatualizado. Contudo, um erro mais comum e difícil de detetar é citar os 100 recomendados como máximo. Foi isso que este artigo fez antes da correção. Uma recomendação e um limite imposto são duas grandezas de dois documentos do mesmo fornecedor [5] [15]. Confundi-los desperdiça capacidade ou leva a um bloqueio sem tratamento preparado. Verifique os limites na documentação, não num tutorial, e confirme se o número é conselho ou limiar de corte.

O ambiente de teste é outro host, não outro modo. Na OANDA, a separação é visível no endereço: api-fxpractice.oanda.com para o ambiente de prática e api-fxtrade.oanda.com para produção, com um par análogo para o fluxo de dados [15] [À DATA DE: 2026-08-22]. É positivo porque o código pode detetar o erro: o host de produção pode ser protegido por outra condição. É pior quando o fornecedor utiliza um só endereço e distingue ambientes apenas pelo token; nesse caso a única barreira é a sua configuração.

Quatro percursos habituais para ligar Python a uma conta: arquiteturas diferentes, não níveis de «sofisticação»:

SoluçãoPercurso efetivo da ligaçãoCondiçõesNotas
OANDA v20 (REST + streaming)Script → servidores da corretora, diretamente por HTTPConta nessa corretora; hosts separados para prática e produçãoDocumentação pública e limites explícitos. Não é uma norma do setor, mas a API de um fornecedor
Interactive Brokers (TWS / IB Gateway)Script → aplicação intermediária local → servidores da corretoraExige TWS ou IB Gateway em execução; modelo assíncronoFechar a aplicação intermediária interrompe a ligação do script. As bibliotecas de encapsulamento simplificam a utilização, mas não eliminam essa dependência. Atenção ao nome: a muito citada ib_insync não tem novas versões desde julho de 2023 e assenta numa versão antiga da API TWS; uma continuação com manutenção ativa é ib_async [13] [À DATA DE: 2026-10-08]
O pacote oficial MetaTrader5 de integraçãoScript → terminal MT5 instalado no mesmo computador → servidor da corretoraO terminal MT5 tem de estar instalado no mesmo computador; a chamada initialize() inicia-o se não estiver em execução e utiliza as credenciais guardadas no terminal ou passadas na chamada [18]; a integração está disponível desde a compilação 2085 (2019) [7]É comunicação com o terminal, não uma API independente da corretora [6]. A dependência do terminal é uma característica, não um defeito. O pacote oficial PyPI só se instala no Windows; em macOS e Linux exige executar terminal e Python sob Wine ou usar uma camada intermediária separada. [FUNCIONALIDADE DEPENDE DA VERSÃO/AMBIENTE]
Ponte externa na nuvem (p. ex., MetaApi)Script → serviço externo → terminal na nuvem do fornecedor → servidor da corretoraExige entregar acesso a terceiros; preços e limites dependem do planoAcrescenta um elo e um ponto de confiança. Uma ponte que coloque ordens precisa da palavra-passe principal, não da de investidor: poder de dispor da conta, não apenas de leitura. Confirme os preços no fornecedor [FUNCIONALIDADE DEPENDE DO PLANO]
A palavra-passe de investidor e a principal são duas decisões diferentes. Uma conta numa corretora MetaTrader tem duas palavras-passe. A palavra-passe de investidor dá acesso apenas de leitura: posições e histórico, sem direito a colocar ou alterar ordens. A palavra-passe principal dá controlo total da conta. A um serviço externo — diário, verificador de resultados ou painel estatístico — entregue, no máximo, a palavra-passe de investidor. Uma ponte na nuvem que deva enviar ordens não pode, por definição, funcionar com a palavra-passe de investidor: precisa da principal. Ligá-la é uma decisão qualitativamente diferente de ligar um diário. Verifique na documentação atual do fornecedor específico que acesso exige. Se já entregou a palavra-passe principal e quer revogar o acesso, altere-a no terminal ou portal da corretora; isso invalida o acesso do terceiro. Reveja o histórico à procura de operações desconhecidas e ative autenticação adicional onde seja oferecida. Mesmo o acesso de leitura revela estilo, horários e dimensões de posições: é uma decisão de privacidade, não uma formalidade.

Fora do mercado cambial também convém conhecer ccxt: uma biblioteca que unifica o acesso a bolsas de criptomoedas. Não se aplica a contas FX/CFD numa corretora de retalho, embora por vezes seja confundida com uma solução universal.

cTrader. Se utiliza cTrader, os algoritmos são escritos no cTrader Algo, que suporta nativamente C# e Python, incluindo cBots, indicadores e plugins [8] [À DATA DE: 2026-08-22]. É outro modelo: o código executa dentro da plataforma, não como processo separado ligado a uma API. Verifique funções e forma de arranque na documentação atual do fornecedor, porque o produto está em desenvolvimento ativo.

Chaves e palavras-passe: nunca no código

Uma chave API escrita num script passa para o repositório, cópias e histórico de versões; eliminar a linha não a remove de Git. Os repositórios públicos são pesquisados automaticamente por essas cadeias. Os únicos locais aceitáveis são uma variável de ambiente, um ficheiro de configuração fora do repositório e listado em .gitignore ou um gestor de segredos que forneça o valor por uma dessas vias.

"""Configuração e segredos: nada sensível no ficheiro de código."""
import os
from dataclasses import dataclass

HOSTS = {
    "practice": "https://api-fxpractice.oanda.com",
    "live": "https://api-fxtrade.oanda.com",
}


class MissingConfiguration(RuntimeError):
    """Lançada se faltarem dados sem os quais não se deve iniciar."""


@dataclass(frozen=True)
class Configuration:
    token: str
    account: str
    environment: str

    @property
    def host(self) -> str:
        return HOSTS[self.environment]


def _required(name: str) -> str:
    value = os.environ.get(name, "").strip()
    if not value:
        # Sem valor predefinido deliberadamente: um token vazio enviaria
        # sem autorização em vez de parar o programa.
        raise MissingConfiguration(f"Falta a variável de ambiente {name}")
    return value


def load_config() -> Configuration:
    environment = os.environ.get("BROKER_ENV", "practice").strip().lower()
    if environment not in HOSTS:
        raise MissingConfiguration(f"BROKER_ENV tem de ser um de {sorted(HOSTS)}")
    return Configuration(
        token=_required("BROKER_API_TOKEN"),
        account=_required("BROKER_ACCOUNT_ID"),
        environment=environment,
    )

Existem três decisões deliberadas: o ambiente predefinido é prática, não produção, pelo que um erro de configuração leva à demonstração, não à conta real. Uma variável em falta interrompe o programa em vez de inserir uma cadeia vazia. E o token nunca tem valor predefinido, nem comentado: um segredo comentado é um segredo divulgado.

A biblioteca python-dotenv mencionada antes não substitui este código: precede-o. A chamada load_dotenv() (após from dotenv import load_dotenv) carrega um ficheiro .env fora do repositório para as variáveis de ambiente do processo; só depois são lidas por load_config() Assim, a função de configuração não sabe nem precisa de saber de onde vem o valor: ficheiro, definições do sistema ou gestor de segredos. O próprio ficheiro .env fica em .gitignore juntamente com todo o diretório de configuração.

Obtenção de dados: tempo limite, tentativas e códigos

Um pedido sem tempo limite pode ficar bloqueado indefinidamente e parar o processo. Repetições ilimitadas podem esgotar a quota e provocar um corte. O fragmento mostra as proteções mínimas para ler dados e uma distinção que mais tarde protege a conta: só se repetem operações idempotentes, que repetidas não alteram o estado na corretora.

import logging
import time

import requests

TIMEOUT_S = 10.0        # segundos sem atividade ao ligar/ler, não prazo total da tarefa
MAX_ATTEMPTS = 4            # número de tentativas incluindo a primeira
BACKOFF_S = 1.0         # base do atraso exponencial em segundos

log = logging.getLogger("data")


class ApiError(RuntimeError):
    """Erro que não se corrige repetindo."""


def fetch_candles(session: requests.Session, host: str, token: str,
                   instrument: str, parameters: dict) -> dict:
    """Repete apenas GET: não altera o estado na corretora."""
    url = f"{host}/v3/instruments/{instrument}/candles"
    headers = {"Authorization": f"Bearer {token}"}

    for attempt in range(1, MAX_ATTEMPTS + 1):
        delay = BACKOFF_S * 2 ** (attempt - 1)
        try:
            response = session.get(url, headers=headers, params=parameters, timeout=TIMEOUT_S)
        except requests.RequestException as error:
            if attempt == MAX_ATTEMPTS:
                raise ApiError(f"Sem ligação após {MAX_ATTEMPTS} tentativas") from error
            log.warning("tentativa %s: %s", attempt, error)
            time.sleep(delay)
            continue

        if response.status_code == 429:
            # Limite excedido. O cabeçalho Retry-After, se devolvido pela corretora,
            # é mais fiável do que adivinhar.
            try:
                wait = float(response.headers.get("Retry-After", delay))
            except (TypeError, ValueError):
                # A especificação também permite data HTTP em vez de segundos;
                # sem este ramo float() interromperia toda a descarga.
                wait = delay
            log.warning("limite de pedidos, espera %.1f s", wait)
            time.sleep(wait)
            continue

        if 500 <= response.status_code < 600:
            log.warning("erro do servidor %s, nova tentativa", response.status_code)
            time.sleep(delay)
            continue

        if response.status_code != 200:
            raise ApiError(f"HTTP {response.status_code}: {response.text[:200]}")

        try:
            data = response.json()
        except ValueError as error:
            raise ApiError("Resposta JSON inválida") from error
        if not isinstance(data, dict):
            raise ApiError("A resposta JSON tem de ser um objeto")
        if not isinstance(data.get("candles"), list) or not data["candles"]:
            raise ApiError("Sem velas na resposta: verifique instrumento e datas")
        return data

    raise ApiError(f"Sem resposta válida após {MAX_ATTEMPTS} tentativas")

Estado de verificação: testado offline, sem ligação a uma corretora. A função publicada fetch_candles() foi executada com uma sessão HTTP simulada em memória, não com um servidor real. Nove cenários cobrem 429 com Retry-After em segundos, seguido no mesmo cenário por 503 e sucesso, uma lista de velas vazia, 404, um cabeçalho com data HTTP, tentativas esgotadas, uma exceção de ligação, JSON inválido, resposta que não é objeto e tipo inválido de candles. As chamadas a sleep() foram substituídas por um registo do atraso solicitado; o teste não abre sockets nem mede tempos de rede. Ambiente: Python 3.12.10, requests 2.34.2, Windows 11, verificado em 2026-10-08. Relatório efetivo dos cenários:

A: 429 segundos -> 503 -> 200: chamadas=3; esperas=[3.0, 2.0]; uma vela
B: lista de velas vazia: chamadas=1; esperas=[]; erro esperado
C: HTTP 404: chamadas=1; esperas=[]; erro esperado
D: data HTTP, alternativa: chamadas=2; esperas=[1.0]; uma vela
E: limite de tentativas: chamadas=4; esperas=[0.0, 0.0, 0.0, 0.0]; erro esperado
F: falha de transporte: chamadas=4; esperas=[1.0, 2.0, 4.0]; erro esperado
G: JSON inválido: chamadas=1; esperas=[]; erro esperado
H: JSON não objeto: chamadas=1; esperas=[]; erro esperado
I: tipo incorreto de candles: chamadas=1; esperas=[]; erro esperado

O que este resultado confirma e o que não confirma. Confirma o comportamento esperado em nove cenários preparados. A verifica que 429 escolhe os três segundos do servidor, não o segundo do código; D verifica a alternativa quando float() não pode interpretar uma data HTTP. F verifica que timeout=10 é passado, são feitas quatro tentativas e uma exceção termina as falhas de transporte simuladas. Não estabelece um prazo para ligações reais: um timeout de requests não é o prazo total da tarefa e aqui a espera foi simulada. B, C e G–I verificam a rejeição de respostas vazias ou inválidas. Não verifica disponibilidade da API, todos os campos ou a rede de uma corretora específica.

Nada confirma sobre a própria corretora. O percurso, os nomes dos parâmetros, a estrutura da resposta e se o fornecedor devolve Retry-After são propriedades de uma API específica [FUNCIONALIDADE DEPENDE DA CORRETORA/CONTA] e têm de ser verificadas na documentação e testadas numa conta de prática. A simulação prova a correção do código, não dos pressupostos sobre o servidor alheio; essa distinção é todo o valor do teste.

Consultas e fluxo com tempos separados de cotação, pedido e receção
Receber depressa não prova atualidade do preço nem momento de execução.

A latência mede-se, não se cita. Os intervalos «50–200 ms para esta API, 100–500 ms para aquela» não valem sem localização, ligação, hora e método. Separe quatro medidas: resposta do ponto de dados, tempo do envio da ordem à confirmação de aceitação, tempo à confirmação de execução e idade do dado na decisão. São grandezas diferentes e só uma se chama coloquialmente «latência». Meça cada uma na sua cadeia, registe-a e cite data, corretora e número de tentativas. Um ping ICMP não é nenhuma delas.

Consultas ou fluxo. Com REST periódico, consultar a cada cinco segundos pode significar decidir com um preço de há cinco segundos; consultar mais consome quota. Um fluxo — WebSocket ou HTTP consoante a API — inverte o modelo: o servidor envia as novidades. Serve para reagir dentro da vela ou evitar esgotar a quota. Para entrar na abertura H1 seguinte, as consultas bastam e são mais simples de manter. O fluxo acrescenta três problemas. Primeiro, a lacuna após voltar a ligar: após trinta segundos de interrupção, o socket não repete esse período; é necessário um pedido histórico separado antes de considerar a série completa. Segundo, a deteção de uma ligação morta: um socket sem dados parece um mercado parado; precisa de um sinal de presença (heartbeat ou keep-alive) e de um contador de silêncio para declarar a interrupção e restabelecer a ligação. Terceiro, a ordenação e os duplicados: após retomar pode chegar novamente uma mensagem já processada. Como nas ordens, resolve-se com um identificador de evento e verificando se já foi registado.

Limites separados para dados e ordens. Uma API séria separa preços e transações; os limites para alterar a conta podem ser muito inferiores aos de leitura. Tentar alterar um stop uma dúzia de vezes por segundo pode provocar um corte precisamente no percurso transacional, deixando a posição aberta temporariamente sem acesso programático. É motivo para limitar as alterações no próprio código, não esperar tolerância da corretora.

A automação não altera as condições de execução. O modelo de execução, o tratamento do fluxo e a qualidade dos dados influenciam o resultado quer envie uma pessoa quer um script; em estratégias sensíveis ao custo de entrada podem ser decisivos. São tratados na parte sobre escolha da corretora: modelos de execução e qualidade de execução. Para o código basta uma conclusão: a API acelera o envio de um pedido, mas não cria liquidez inexistente. Isto é especialmente visível em movimentos violentos. Em 15 de janeiro de 2015, o Banco Nacional Suíço retirou o câmbio mínimo de 1,20 francos por euro e reduziu simultaneamente a taxa para −0,75 por cento [17]. O mecanismo é geral e não exige acreditar em histórias: se não houver liquidez do outro lado no nível protetor, a ordem executa ao primeiro preço disponível, não ao indicado. A distância é determinada pelo livro, não pela ligação. A dimensão em corretoras específicas é tratada na escolha da corretora; aqui basta isto: enviar depressa não cria um preço ao qual ninguém quer negociar.

8. Do notebook ao serviço: como o código de execução difere de um script

Esta é a verdadeira fronteira do artigo. Um notebook estatístico pode falhar a meio: volta a executá-lo. O código autorizado na conta deixa estado após o mesmo erro: posição aberta, ordem parcialmente alterada ou entrada dupla. Não é uma diferença de qualidade do código, mas de consequências irreversíveis.

Notebook de investigação ≠ programa de execução. Antes de autorizar ordens, o código tem de ter tudo: configuração separada do código · segredos fora do repositório · registo de cada decisão e resposta do servidor · testes da lógica sem rede · novas tentativas limitadas e só para operações idempotentes · tempos limite em cada chamada de rede · estado persistente após reiniciar · monitorização com sinal de presença · interruptor de emergência que interrompa novas entradas · procedimento de implementação que descreva como lançar versões. Se faltar um elemento, é um protótipo, não um programa de execução; trate-o assim, por bonita que seja a curva do teste.

Três níveis de automação: distinguem-se por quem toma a decisão final, não pela tecnologia:

Nível 1: notificações. O script verifica condições e envia uma mensagem. Não acede à conta nem precisa de token para ordens; uma pessoa decide e executa. Só aqui um erro de código custa apenas irritação.

Nível 2: preparação com confirmação. O script prepara instrumento, direção e dimensão e espera confirmação explícita do operador antes de enviar. Deteta erros aritméticos antes de criar posições e mantém a pessoa na decisão. Para um operador de retalho que não pretenda manter software, é geralmente o destino, não uma transição; é juízo editorial, não resultado de investigação.

Nível 3: funcionamento sem confirmação. O script abre e fecha sozinho. Exige todos os elementos anteriores, testes primeiro em prática e depois com dimensão mínima, e supervisão inicial. Quantas operações bastam é uma questão de dimensão da amostra e potência estatística, não de números redondos; pertence a o artigo sobre testes.

Ramo analítico offline separado de barreiras e adaptador de execução
Primeiro correção analítica; enviar exige desenho, testes e confirmação de estado separados.

Camada de execução com barreiras de segurança

Se mostrar código que envia ordens, tem de mostrar o que o trava. O módulo é inteiramente executável e não chama a rede: prepara uma ordem, valida limites e para nas barreiras. O envio é outro fragmento, deliberadamente sem código executável.

"""Camada de execução: validação, modo de teste e barreira explícita antes de real."""
import logging
import os
import sys
import uuid

ALLOWED_INSTRUMENTS = {"EUR_USD"}
MAX_UNITS = 1_000          # unidades da moeda base, não lotes: depende da API

# A confirmação real não está neste ficheiro e não pode ser lida daqui.
# O programa lê-a de uma variável de ambiente e aceita como valor o
# número da conta escolhido conscientemente, escrito à mão na consola
# antes de iniciar. O alcance é explicado na caixa sob a saída.
CONFIRMATION_VARIABLE = "BROKER_LIVE_CONFIRMATION"

# Sem configurar não há entradas log.info: o nível raiz predefinido
# é WARNING e não há destino. O ficheiro deixa rasto após falhar e
# o fluxo mostra o mesmo em direto. Em programas maiores, configurar
# pertence ao ponto de entrada, não a um módulo.
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s %(levelname)s %(name)s %(message)s",
    handlers=[logging.FileHandler("execution.log", encoding="utf-8"),
              logging.StreamHandler(sys.stdout)],
)

log = logging.getLogger("execution")


class SafetyGateError(RuntimeError):
    """Para a ordem antes de sair do processo."""


def new_order_id() -> str:
    """Chamado UMA VEZ por intenção de ordem, guardado antes de enviar.

    O mesmo valor tem de regressar em cada tentativa dessa ordem;
    caso contrário não se reconhece um duplicado.
    """
    return str(uuid.uuid4())


def build_order(instrument: str, units: int, order_id: str) -> dict:
    if instrument not in ALLOWED_INSTRUMENTS:
        raise SafetyGateError(f"Instrumento não permitido: {instrument}")
    # bool deriva de int, logo isinstance(True, int) é verdadeiro; sem rejeição
    # explícita, um booleano passaria na validação e chegaria ao conteúdo.
    if isinstance(units, bool) or not isinstance(units, int) or units == 0:
        raise SafetyGateError("A dimensão tem de ser um inteiro diferente de zero")
    if abs(units) > MAX_UNITS:
        raise SafetyGateError(
            f"Dimensão {abs(units)} excede o limite {MAX_UNITS}")
    return {
        "order": {
            "type": "MARKET",
            "instrument": instrument,
            "units": str(units),
            # O identificador vem de fora e não muda entre tentativas.
            # Se fosse criado aqui, cada tentativa levaria outro valor.
            # O nome e as restrições do campo pertencem à API concreta.
            "clientExtensions": {"id": order_id},
        }
    }


def _operator_confirmation() -> str:
    """Lê um valor externo; o código não o conhece nem o guarda."""
    return os.environ.get(CONFIRMATION_VARIABLE, "").strip()


def submit_order(session, cfg, instrument: str, units: int, order_id: str,
                  test_mode: bool = True) -> dict | None:
    """Não envia por predefinição. O host de produção exige duas decisões."""
    order = build_order(instrument, units, order_id)

    if test_mode:
        log.info("MODO DE TESTE [%s] ordem preparada, nada enviado: %s",
                 cfg.environment, order)
        return None

    if cfg.environment == "live" and _operator_confirmation() != cfg.account:
        raise SafetyGateError(
            "O host de produção exige confirmação explícita do operador")

    # Barreiras superadas: aqui termina o módulo. Não envia nada,
    # porque não há forma honesta de o executar; a estrutura segue no esboço.
    raise NotImplementedError(
        "Envio não implementado aqui: consulte o esboço")

O identificador é criado uma vez, não uma vez por tentativa. Para reconhecer duplicados após uma interrupção, o valor calcula-se na intenção de colocar a ordem, guarda-se de forma duradoura antes de enviar e reutiliza-se em cada tentativa dessa ordem. Se uuid.uuid4() estivesse na construção, cada tentativa enviaria um valor diferente e não seria reconhecível como repetição. Por isso build_order() recebe o identificador como argumento em vez de o gerar.

O que este identificador não resolve. O campo clientExtensions no conteúdo anterior é solução de um fornecedor, não uma norma nem garantia documentada de idempotência. A OANDA descreve-o como dados personalizados opcionais e avisa: «Não defina, altere ou elimine tradeClientExtensions se a conta estiver associada a MT4» [14] (verificado em 2026-08-22). Não promete rejeitar como duplicada uma ordem de mercado com o mesmo identificador; executa imediatamente e a janela de deteção é mínima. Reconhecer duplicados exige duas medidas próprias: registo duradouro da intenção antes de enviar e reconciliação com as ordens e transações obtidas da corretora após falhar. Confirme nome e restrições na sua API; noutro fornecedor muda ou não existe.

Enviar uma ordem: esboço estrutural

Não se segue uma função para copiar, mas um esboço: passos e operações transferíveis para código próprio, não para colar e executar. A escolha é deliberada. A redação não executou este ramo nem pretende fazê-lo: enviar ordens exige uma conta; testar transações numa alheia é risco alheio. Uma função completa seria um artefacto nunca executado e ensinaria confiança em código não verificado. Foi a mesma escolha para os esboços MQL no art. 11.5 e para o de Pine Script no art. 11.2.

ESBOÇO ESTRUTURAL - enviar uma ordem e tratar uma resposta ausente
Orientação para a camada HTTP. Não é código completo nem executável.

declaração: StanNieznany - a resposta não chegou; não se sabe se existe a ordem.
            Tipo separado, não subclasse de erro de rede: um erro de rede
            pode ser repetido, este estado nunca deve ser repetido.

entrada: ordem (construída e validada), order_id, configuração, sessão HTTP
saída: resposta da corretora ou StanNieznany; nunca sucesso silencioso

1. REGISTO DURADOURO - antes de tudo
   escrever fora da memória do processo: order_id, hora UTC,
   instrument, units, environment, status := INTENT
   forçar a escrita no disco antes de continuar
   sem isso, reiniciar perde a intenção e o programa
   não sabe o que já tentou antes de voltar a arrancar

2. DIÁRIO - também antes de enviar
   registar: order_id + environment + conteúdo da ordem
   o token nunca entra no diário
   mascarar a conta salvo os quatro últimos carateres
   uma pessoa lê o diário depois; o programa lê o registo do passo 1

3. UM PEDIDO, ZERO REPETIÇÕES
   POST ao percurso de ordens indicado pela documentação da sua API
   cabeçalho de autorização com token da configuração, nunca do código
   tempo limite obrigatório; sem ele pode bloquear para sempre

4. A resposta não chegou (tempo esgotado ou erro de transporte)
   NÃO repetir; POST não é idempotente
   status no registo := UNKNOWN
   lançar StanNieznany
   RECONCILIAÇÃO: consultar ordens e transações à corretora
   dos últimos minutos, procurar order_id e só depois de
   estabelecer se a ordem existe tomar uma nova decisão
   (o esboço não contém esse procedimento; veja o parágrafo seguinte)

5. A resposta chegou
   registar: código HTTP + corpo truncado
   código fora de (200, 201) -> status := REJECTED -> lançar erro
   corpo que não é JSON     -> status := UNKNOWN  -> lançar StanNieznany
   caso contrário           -> status := ACCEPTED -> devolver resposta

O que não contém nem pode conter:
   nenhuma função pronta a colar e executar numa conta,
   nem o nome do campo protetor: vem da documentação de uma
   API específica, não de um exemplo alheio.

O que este esboço não faz, tão importante como o que descreve. Não adiciona stop loss nem take profit. Não é descuido: cada API os expressa de forma diferente — campos, distância ou preço absoluto, validade própria —, pelo que o fragmento vem da documentação da sua corretora, não de outro exemplo. A consequência prática afeta o passo 4: após um StanNieznany pode existir uma posição aberta sem proteção e sem certeza de que exista. No código autorizado a enviar, o nível protetor viaja no mesmo pedido da entrada; caso contrário abre-se uma janela sem stop.

Não faltam apenas proteções. Da lista «Notebook de investigação ≠ programa de execução», nem módulo nem esboço têm ainda monitorização, interruptor de emergência ou implementação de versões. O estado persistente surge no passo 1 como requisito, não implementação. A secção de registos, estado e testes fala desses elementos, não os mostra em código. A conclusão mantém-se: material para aprender arquitetura, não um programa de execução.

As barreiras foram testadas offline: sem rede, corretora ou segredos, com configuração simulada. Segue-se o teste completo, não apenas a saída; caso contrário teria de acreditar, precisamente a confiança contra a qual o artigo alerta.

"""Teste de barreiras sem rede: configuração simulada, sem segredo nem POST."""
from dataclasses import dataclass


@dataclass(frozen=True)
class MockConfiguration:
    environment: str = "practice"
    host: str = "https://api-fxpractice.oanda.com"
    token: str = "dummy-token-for-offline-test"
    account: str = "000-000-0000000-000"


def test_gates() -> None:
    cfg = MockConfiguration()
    order_id = new_order_id()
    print("configuração:", cfg.environment, cfg.host, "token_length=", len(cfg.token))

    # session=None é deliberado: se tentasse enviar POST,
    # falharia com AttributeError em vez de passar silenciosamente.
    result = submit_order(None, cfg, "EUR_USD", 100, order_id)
    assert result is None
    print("1. modo de teste: nada enviado - OK")

    for description, instrument, units in [
        ("instrumento não permitido", "GBP_JPY", 100),
        ("dimensão acima do limite", "EUR_USD", 5000),
        ("dimensão zero", "EUR_USD", 0),
        ("booleano em vez de número", "EUR_USD", True),
    ]:
        try:
            build_order(instrument, units, order_id)
        except SafetyGateError as error:
            print(f"2. barreira [{description}]:", error)
        else:
            raise AssertionError(f"A barreira esperada não foi ativada: {description}")

    cfg_live = MockConfiguration(environment="live", host="https://api-fxtrade.oanda.com")
    try:
        submit_order(None, cfg_live, "EUR_USD", 100, order_id, test_mode=False)
    except SafetyGateError as error:
        print("3. barreira [real sem confirmação]:", error)
    else:
        raise AssertionError("O modo real passou sem confirmação")

    print("todas as barreiras superadas")


if __name__ == "__main__":
    test_gates()

Saída com Python 3.12.10 como na secção 2, sem rede nem pacotes externos: retirado o envio, o módulo já não precisa de requests. A segunda linha é um registo: o mesmo conteúdo segue simultaneamente para o ficheiro execution.log e para a saída padrão. A hora e o identificador mudam em cada execução; o restante é repetível caráter a caráter.

configuração: practice https://api-fxpractice.oanda.com token_length= 28
1. modo de teste: nada enviado - OK
2. barreira [instrumento não permitido]: Instrumento não permitido: GBP_JPY
2. barreira [dimensão acima do limite]: Dimensão 5000 excede o limite 1000
2. barreira [dimensão zero]: A dimensão tem de ser um inteiro diferente de zero
2. barreira [booleano em vez de número]: A dimensão tem de ser um inteiro diferente de zero
3. barreira [real sem confirmação]: O host de produção exige confirmação explícita do operador
todas as barreiras superadas

O que esta saída confirma e o que não confirma. Cinco casos negativos ativaram as barreiras: instrumento não permitido, dimensão excessiva, zero, booleano e modo real sem confirmação. As asserções também verificam o modo de teste; foram ainda verificados limites ±1000, excessos e tipos inválidos. O primeiro argumento é None, e o envio não está implementado, pelo que não pode colocar ordens. Não testa a API, a resistência do registo nem a ocultação de todos os campos sensíveis. Só mostra o comprimento do token fictício, não o token. Verifique separadamente escrita e recuperação de registos, regras de ocultação e falhas do disco.

Não confirma nem pode confirmar o comportamento numa API real: o ramo de envio não existe. Um esboço substituiu-o, eliminando o único ponto de envio. É a diferença entre «não o executámos» e «não há nada para executar»: o primeiro deixa código pronto a colar; o segundo, uma estrutura que terá de implementar e confrontar com a documentação da sua corretora.

O teste também tem de detetar uma barreira em falta. Por isso verifica por asserção o resultado do modo de teste e lança AssertionError após cada caso negativo se faltar a exceção esperada. A mensagem final só aparece após aprovação. É um exemplo pequeno: não cobre formatos da corretora, todos os tipos nem recuperação do estado. Em projetos maiores, passe os casos para pytest, adicione casos positivos e execute-os sempre que alterar o validador.

A confirmação protege de um engano, não de um programa. Convém explicitar o alcance. O valor não é uma constante do ficheiro: é lido da variável de ambiente BROKER_LIVE_CONFIRMATION e comparado com a conta; o operador tem de escrever conscientemente o número na consola. Protege de três erros: «experimentar» produção, confundir a conta e desligar acidentalmente o modo de teste. Não protege de mais: código no mesmo processo pode definir a variável e atravessar a barreira. Uma confirmação lida do próprio ambiente não protege de um programa. Os limites reais de permissões ficam fora do código: token sem direito a ordens, onde a corretora o distinga, e limites de posição na conta. [FUNCIONALIDADE DEPENDE DA CORRETORA/CONTA].

As unidades não são lotes. O comentário junto de MAX_UNITS não é decoração: as API expressam dimensão em unidades base, lotes ou contratos. Um erro não gera erro de sintaxe nem exceção, mas exposição duas ou três ordens de grandeza maior com código correto. Confirme a convenção na documentação e com uma operação mínima de prática. Dois limites do validador: uma dimensão calculada em pandas ou NumPy tem tipo numpy.int64, que não deriva do tipo integrado int. O validador rejeita o inteiro com uma mensagem sobre inteiros: seguro, mas confuso. Passe os números lidos do DataFrame por int(...) antes de chegarem a build_order(). O segundo limite: order_id não é verificado e uma anotação não impõe nada. Como a deteção de duplicados depende dele, confirme comprimento e formato na API e imponha-os como o limite de dimensão.

Registos, estado e testes

Registo em vez de print(). O módulo logging oferece níveis, hora e escrita em ficheiro, mas nada se ativa sozinho. Sem configurar, o nível predefinido é WARNING e não há destino, pelo que as chamadas log.info(...) são descartadas silenciosamente: parece registar, mas nada guarda. Por isso o registo costuma estar vazio após falhar. Configure uma vez no ponto de entrada, não num módulo importado. A chamada logging.basicConfig(...) está no módulo apenas porque ali é todo o programa; o comentário declara que em projetos maiores seria incorreto. Configurar ao importar assume o registo de todo o processo, e uma chamada posterior a logging.basicConfig(...) no ponto de entrada real nada faz quando o registo raiz já tem destinos (salvo com force=True) sem erro [4]. As definições desaparecem e as entradas vão para onde o módulo decidiu: a mesma falha silenciosa pelo outro lado. Registe decisão e fundamentos, pedido e resposta. Trunque a resposta para não inundar com erros HTML e aumente o limite ao diagnosticar. Nunca guarde o token. Sem resposta não distinguirá falha de lógica, rede ou servidor.

O registo de execução também é sensível. O conteúdo completo da ordem vai para execution.log, e num programa completo também a resposta: conta, identificador, dimensão e preços. O token não está nem deve estar: dez testes produziram onze entradas e zero ocorrências do token. O restante basta para o tratar como configuração secreta: inclua execution.log em .gitignore junto de .env. E .gitignore não controla o acesso: não restringe permissões, não elimina passado um prazo nem oculta conteúdos. Por isso o esboço exige mascarar a conta salvo os últimos quatro carateres e registar campos de uma lista própria, não tudo o que o servidor devolve. Antes do funcionamento sem supervisão faltam duas questões. O percurso: execution.log é relativo: o ficheiro nasce no diretório de trabalho do processo, não junto do script. Ao iniciar pelo Agendador de Tarefas ou cron — como se explica adiante — pode ser muito diferente, e o registo essencial para recuperar não estará onde o procura. Use um percurso absoluto baseado na localização do script ou na configuração. A dimensão: com a recomendação anterior cresce sem limite porque o exemplo não tem rotação. logging.handlers.RotatingFileHandler pertence à mesma biblioteca padrão e, após adicionar import logging.handlers— porque import logging por si só não expõe o submódulo — substitui FileHandler sem alterar o restante da configuração.

O estado tem de sobreviver ao reinício. O processo acabará por parar devido a atualização, falha de energia ou ao seu próprio Ctrl+C. Se a informação de ordens vive apenas na memória, após reiniciar não sabe o que fez. O mínimo é guardar identificadores num ficheiro ou base de dados antes de enviar e compará-los no arranque com o estado da conta obtido pela API. Isso evita entradas duplas após uma interrupção.

Testes sem rede. A lógica e os limites testam-se com dados preparados sem rede, como as barreiras anteriores. Um teste que exige API não é unitário nem pode executar com cada alteração. Comece pela condição de entrada, cálculo da dimensão e comportamento com dados vazios ou lacunas.

Paragem controlada. O programa deve reagir ao sinal de fim: terminar a operação, guardar estado e sair, não interromper-se durante o envio. Separadamente, convém parar novas entradas após exceder a perda diária. O limiar e cálculo são tratados em a parte sobre gestão do risco; no código é uma condição antes de cada entrada, não uma função de biblioteca.

Onde executar. Servidor contínuo, sincronização do relógio, continuidade após reinícios e recuperação são tratados no artigo Automação da negociação: EA, scripts e VPS. As formas de arranque diferem pelo que ocorre após falhar, não por conveniência: Agendador de Tarefas do Windows ou cron em Unix iniciam execuções periódicas e terminam-nas; um serviço do sistema (por exemplo, uma unidade systemd) mantém o processo e pode reiniciá-lo; um gestor faz o mesmo na aplicação. Escolher execução breve ou processo contínuo decide onde guardar estado e quem garante uma só instância. Um script agendado tem de tolerar que a execução anterior continue ativa: verificar estado antes de agir, não assumir um início do zero.

Consulte também: Negociação algorítmica: arquitetura do sistema e papel da automação em todo o processo de decisão.

9. Antecipação e fuga de dados no código Python

A antecipação não é um erro de metodologia, mas de uma única linha de código; por isso pertence aqui, não ao texto metodológico. Um resultado atribuído a um momento utiliza informação que ainda não existia.

A forma mais comum em pandas é trivial: sinal por rentabilidade da mesma linha. A rentabilidade da linha t ocorreu entre o fecho anterior e o da vela t. Um sinal calculado com o fecho da vela t só é conhecido depois dessa rentabilidade. Multiplicá-los atribui um movimento desconhecido no momento da decisão.

Pressupostos do exemplo: oito observações horárias sintéticas, índice UTC, valores ilustrativos e sem custos; mostra-se o deslocamento, não se avalia uma regra. Ambiente como na secção 2: Python 3.12.10, pandas 3.0.5.

"""Antecipação numa linha: mesma regra, dois resultados."""
import pandas as pd

index = pd.date_range("2026-06-01", periods=8, freq="1h", tz="UTC")
df = pd.DataFrame(
    {"close": [1.0800, 1.0810, 1.0825, 1.0815, 1.0830, 1.0845, 1.0840, 1.0855]},
    index=index,
)
df["sma"] = df["close"].rolling(3).mean()
df["return"] = df["close"].pct_change()

# Regra: long se o fecho superar a média de três velas.
df["position"] = (df["close"] > df["sma"]).astype(int)

# ERRADO: posição da linha t por rentabilidade da mesma linha.
# A rentabilidade t surge entre fecho t-1 e fecho t; a decisão
# usa um preço que ainda não existia na entrada.
df["result_with_lookahead"] = df["position"] * df["return"]

# CORRETO: uma posição decidida no fecho t atua na rentabilidade t+1.
df["result_without_lookahead"] = df["position"].shift(1) * df["return"]

print(df[["result_with_lookahead", "result_without_lookahead"]].sum().round(6))

Saída da execução:

result_with_lookahead       0.005082
result_without_lookahead    0.001384
dtype: float64

Interpretação. A mesma regra, os mesmos dados, uma chamada a shift(1) de diferença: a soma das rentabilidades simples desce de 0,005082 para 0,001384, menos de 30 por cento inicial. Oito observações ilustram, não medem; em anos de dados o erro pode tornar uma estratégia média espetacular e uma espetacular impossível. Os valores são adimensionais, sem spread nem comissão; com custos a versão correta cairia mais.

Uma ressalva aritmética. pct_change() dá rentabilidades simples que, como indicado na secção 4, se compõem multiplicando, não somando; só as logarítmicas podem ser somadas. Aqui a soma é uma aproximação deliberada: com valores da ordem dos milésimos, a diferença entre somar e compor corretamente é inferior a 0,00001, mais de duas ordens de grandeza menor do que a diferença que se pretende mostrar. Para descrever o resultado de uma conta, não o mecanismo do deslocamento, componha os fatores (1 + r) ou passe para logarítmicas e só depois some.

Cuidado com deslocar duas vezes. Desloque num só lugar: posição ou dados do sinal, nunca ambos. Deslocar preços e depois posição atrasa dois períodos e subestima silenciosamente o resultado. É menos perigoso do que antecipar, mas igualmente falso.

Tabela de preços com alinhamento enviesado e deslocado de rentabilidades
Usar um fecho futuro altera aqui −0,98% para um aparente +2,00%.

Outras formas de antecipação em Python: sempre a mesma pergunta: esse valor era conhecido ao decidir?

  • Usar máximo ou mínimo da vela atual para entrar, embora só sejam conhecidos no fecho
  • Escalonar ou normalizar tudo antes de dividir treino e validação; os parâmetros transportam informação futura
  • Indicadores que suavizam ou marcam inversões com velas posteriores; o valor histórico difere do observado em direto
  • Dados macro revistos em vez dos publicados originalmente
  • Executar no fecho da vela que gerou o sinal

Fuga de dados na preparação do conjunto. O segundo ponto merece clareza: em Python sai por reflexo. Aplicar um escalonador a todo o DataFrame antes de dividir parece inocente, mas transfere a distribuição futura para validação. Determine transformações apenas nos dados anteriores e aplique aos posteriores, nunca ao contrário.

Duas versões atribuem o sinal à rentabilidade atual ou seguinte
Deslocar corrige esse erro temporal neste modelo; não prova rentabilidade.

Testes múltiplos. Quantas mais variantes, maior a probabilidade de a melhor ser casual. Num modelo nulo explícito, 10 000 testes com probabilidade de falso positivo 0,05 dão uma expectativa de 0,05 × 10 000 = 500 resultados «significativos». Somam-se probabilidades sem exigir independência: a dependência altera a distribuição da contagem, não a média nesses pressupostos. Por isso se declara o número de tentativas.

Existe uma correção formal pelas tentativas: o rácio de Sharpe deflacionado, proposto por Bailey e López de Prado em 2014 [12]. Antes de usar uma implementação, verifique estado e licença: alguns projetos alteraram a distribuição. A fórmula está na publicação original e pode implementar-se independentemente.

O que esta secção não cobre. Sobreajuste, seleção de amostras, walk-forward, viés de sobrevivência e robustez aos custos são metodologia, descrita no artigo de backtesting e testes forward. Aqui só importa o que pode falhar e verificar-se no código.

Verifique o código antes de confiar:

  1. O resultado da linha t usa apenas dados disponíveis antes da decisão?
  2. Deslocou uma vez: sinal ou dados, não ambos?
  3. Momento e preço de execução são explícitos?
  4. Inclui custos ou omite-os consciente e explicitamente?
  5. Treino e validação dividem-se cronologicamente?
  6. Transformações determinadas apenas na parte anterior?
  7. Regista o número de variantes junto do resultado?
  8. Índice com fuso explícito, crescente e sem duplicados?
Regra prática: um resultado surpreendente sugere um erro antes de uma vantagem. Antes de declarar uma descoberta, siga a lista e execute dados de resposta conhecida: preços constantes dão rentabilidade de preço nula antes de custos, mas podem perder depois. Isso deteta mais erros do que olhar para uma curva.

10. De quanto código precisa realmente

Não precisa de se tornar programador, mas de saber o suficiente para não ser enganado pelo próprio código ou scripts descarregados.

NívelCompetênciasO que proporcionaTempo aproximado
0 – sem códigoNenhumasAnalisa o que a plataforma permite, da forma que impõe—
1 – ler códigoEntender scripts alheios, alterar parâmetros e executar isoladamenteVerificar antecipação e custos em scripts de repositóriosAlgumas semanas
2 – análise própriapandas, numpy, matplotlib e scripts analíticos própriosAnalisar diário e mercado sem os limites da folhaAlguns meses
3 – integraçãoAPI, erros, registos, agendamento e testesAvisos e preparação de ordens com confirmaçãoSeis meses ou mais
4 – código de execuçãoTestes automatizados, estado, implementações e monitorizaçãoPrograma sem confirmar cada decisãoAnos de prática

O tempo é aproximado e depende do ponto de partida e das horas semanais efetivas: ordem de grandeza, não promessa.

Os níveis 1 e 2 não dão vantagem de mercado. Permitem avaliar soluções alheias e deixar de comprar sistemas impossíveis de verificar. É valor real, diferente do que os fornecedores vendem. O nível 4 serve para manter software de execução, não para negociar melhor. O manual de Hilpisch apresenta Python nas finanças [11], com a ressalva de qualquer livro de código: verifique fragmentos com as versões atuais, porque algumas interfaces mudaram.

Código gerado por um modelo de linguagem. Os assistentes escrevem grande parte do código analítico mais depressa, mas também antecipação aparentemente correta ou funções inexistentes na sua versão. Sem nível 1 não deteta nenhuma. Possibilidades e limites estão noutro artigo: IA e LLM na negociação.

Ordem sensata de aprendizagem: cada passo assenta no anterior e termina com algo que funciona:

  1. Fundamentos da linguagem: variáveis, listas, funções e condições; suficiente para ler sem adivinhar
  2. pandas com dados próprios: carregue o diário e calcule por dia e instrumento. Se prefere livros à documentação, McKinney, autor de pandas, cobre esta etapa por completo [10]
  3. Um gráfico: desenhe património e perda com os cálculos anteriores
  4. Verificar qualidade dos dados: repita a secção 4 no seu ficheiro: fuso, duplicados e lacunas
  5. Integração apenas de leitura: dados API em prática com token sem permissões transacionais, se o fornecedor as distinguir

A ordem é deliberada: o quinto passo surge quando consegue verificar dados; caso contrário automatiza um erro, não o trabalho.

A aplicação mais valiosa é a menos espetacular. Analisar o diário mostra de onde vem realmente o resultado e onde apenas parece. Costuma dar mais do que construir execução e exige nível 2; não porque foi medido, mas porque é a única análise dos seus próprios dados.

Comece por carregar dados próprios, calcular algo verificável à mão em três linhas e comparar. Se coincide com contar pelos dedos, tem base; se não, encontrou o primeiro erro: melhor do que um script que funciona sem o entender.

O Python não corrige uma estratégia má; no máximo mostra-o mais depressa.

Perguntas frequentes: Python na negociação

O Python é suficientemente rápido para automatizar a negociação?
Depende do estrangulamento. Para entrar na abertura H1 seguinte, o cálculo em Python é insignificante face ao percurso pela rede e servidor. Dentro de uma vela de um minuto conta toda a cadeia: idade do dado na decisão, resposta do ponto de acesso e confirmação de execução. Não cite nenhuma de memória: meça-as na sua cadeia e corretora, com data e tentativas. O Python não serve para latência ultrabaixa, mas essa é outra categoria de problema face à negociação de retalho.
Quanto demora aprender Python a um nível útil para negociar?
Ler código alheio e alterar parâmetros leva algumas semanas regulares; analisar o diário com pandas e gráficos, alguns meses. Depende do início e das horas semanais: são ordens de grandeza. Não precisa de toda a linguagem: classes, decoradores ou concorrência não são necessários para carregar e calcular estatísticas. Aprende mais depressa com dados próprios porque reconhece logo se o resultado faz sentido.
Posso ligar Python a uma conta MT4 ou MT5?
Para MT5 existe o pacote oficial MetaTrader5 instalável por pip, mas só no Windows. Em macOS e Linux não existe instalação oficial: terá de executar terminal e Python sob Wine ou preparar uma camada intermediária. [FUNCIONALIDADE DEPENDE DA VERSÃO/AMBIENTE]. O pacote comunica com um terminal MetaTrader 5 no mesmo computador, não diretamente com a corretora. Tem de estar instalado, não previamente iniciado: a documentação diz que a chamada initialize() inicia-o se necessário; os parâmetros de acesso são opcionais e, se faltarem, utiliza os dados guardados no terminal. [18]. Não é pormenor: um script agendado pode abrir o terminal e iniciar sessão sem si. É conveniente, mas «o terminal estava fechado» não protege. A integração surgiu em 2019, compilação 2085 [7]. Não é uma API REST independente da corretora. A alternativa é uma ponte na nuvem com mais um elo e acesso entregue a terceiros. O início mais simples e seguro continua unidirecional: exportar, analisar em Python e deixar decisão e execução à pessoa.
Onde guardar chaves API e o que fazer se forem expostas?
Só em variáveis de ambiente ou configuração fora do repositório e listada em .gitignore. Nunca no código, comentários ou notebooks: a célula guarda saídas e o token impresso fica no ficheiro. Se entrou no repositório, eliminar a linha não elimina o histórico. Revogue a chave na corretora, gere outra e só depois limpe o histórico. Convém também limitar dimensão na conta como segunda proteção independente do código.
O código deste artigo foi verificado?
Sim, cada bloco de código, com o estado explicado individualmente. O exemplo CSV da secção 4 e a comparação do deslocamento da secção 9 foram executados no ambiente indicado (Python 3.12.10, pandas 3.0.5); as saídas são as efetivas do programa. O carregamento da configuração foi testado nos cinco ramos, de variáveis ausentes à rejeição de nomes de ambiente desconhecidos. As barreiras de execução foram verificadas offline, sem rede nem segredos. A obtenção API foi testada com uma sessão HTTP simulada em memória, sem servidor local nem ligação de rede, nem a 127.0.0.1 nem a uma corretora. Foram verificados os cenários documentados de resposta, incluindo esgotar o limite de tentativas e uma exceção de ligação. Contudo, o artigo não contém uma única linha que envie uma ordem: o envio foi substituído por um esboço estrutural, porque não se testam transações em contas alheias e uma função pronta sem teste só ensinaria confiança em código não verificado. Executar código não confirma pressupostos sobre outro servidor: verifique percursos, estruturas de resposta e limites na documentação da sua corretora e numa conta de prática.
Posso confiar numa estratégia de um repositório público?
Não sem verificar, e não se pressupõe má-fé. Costumam faltar deslocamento verificado, custos, ambiente e versões e número de variantes. Antes de executar, leia a origem do sinal e atribuição do período, confirme custos, recrie dependências e isole a execução. Sem versões não é reproduzível e, portanto, não pode verificar-se nem refutar-se.

Fontes e referências

  1. Documentação pandas 3.0.5 — pandas.pydata.org/docs: estruturas de dados, índice temporal e fusos, resample, valores em falta; versão verificada em 2026-08-22
  2. pandas — notas das versões 2.2.0 e 3.0.0 — pandas.pydata.org/docs/whatsnew: obsolescência do parâmetro method em fillna() e alterações do comportamento predefinido
  3. NumPy — documentação e notas de versões — numpy.org/doc: vetorização, geradores aleatórios e sementes
  4. Documentação Python — docs.python.org/3: módulos venv, logging e timeit e tratamento de exceções
  5. OANDA v20 — boas práticas — developer.oanda.com/rest-live-v20/best-practices: frequência recomendada de pedidos em ligação estabelecida (100/s) e novas ligações (2/s); verificado em 2026-08-22
  6. MQL5 — integração Python — mql5.com/en/docs/python_metatrader5: pacote MetaTrader5 e comunicação com o terminal
  7. MetaTrader 5 — notas da compilação 2085 — metatrader5.com/en/releasenotes/terminal/2085: introdução da integração Python no terminal
  8. cTrader Algo — documentação — help.ctrader.com/ctrader-algo: suporte nativo C# e Python para cBots, indicadores e plugins; à data de 2026-08-22
  9. TA-Lib (ligações Python) — pypi.org/project/TA-Lib: pacotes binários no PyPI e instalação sem compilar C
  10. McKinney, W. – Python for Data Analysis, O'Reilly, 3.ª edição, 2022 — pandas, numpy e séries temporais
  11. Hilpisch, Y. – Python for Finance, O'Reilly, 2.ª edição, 2018 — Python nas finanças; verificar código com versões atuais
  12. Bailey, D. H., López de Prado, M. – The Deflated Sharpe Ratio, Journal of Portfolio Management, 2014 — correção Sharpe pelas variantes testadas
  13. PyPI — páginas de pacotes e interface JSON do registo — pypi.org: datas e números das últimas versões citadas (mplfinance, streamlit, openpyxl, pandas-ta, backtrader, vectorbt, backtesting.py, ib_insync, ib_async); verificado em 2026-10-08
  14. OANDA v20 — API de ordens e transações — developer.oanda.com/rest-live-v20/transaction-df – clientExtensions como dados personalizados opcionais da ordem e aviso sobre contas MT4; sem garantia documentada de idempotência para ordens de mercado; verificado em 2026-08-22
  15. OANDA v20 — guia do programador — developer.oanda.com/rest-live-v20/development-guide: limite imposto REST de 120 pedidos por segundo com HTTP 429, 20 fluxos ativos e 2 novas ligações por segundo, por IP de origem; verificado em 2026-08-22
  16. Microsoft — especificações e limites Excel — support.microsoft.com: folha de 1 048 576 linhas por 16 384 colunas; verificado em 2026-08-22
  17. Banco Nacional Suíço — comunicado de 15 de janeiro de 2015 — snb.ch: retirada do câmbio mínimo de 1,20 francos por euro e corte para −0,75 por cento
  18. MQL5 — MetaTrader5.initialize() – mql5.com/en/docs/python_metatrader5/mt5initialize_py: início do terminal ao ligar se necessário e parâmetros opcionais path, login, password e server com dados guardados no terminal; verificado em 2026-08-22

O seu progresso de aprendizagem

Terminou esta lição? Marque-a você. Abrir ou deslocar uma página nunca a marca como concluída.

O progresso requer JavaScript e armazenamento do navegador.

Guardado apenas neste navegador. Sem conta nem sincronização entre dispositivos. Limpar os dados do site pode apagar o seu progresso.

Jarosław Wasiński

Editor-chefe, ForexMechanics.com • Analista macro e FX

Analista independente e profissional com mais de 20 anos de experiência nos mercados financeiros. Atua no Forex desde 2007, com foco em análise fundamentalista, estrutura do mercado OTC e gestão disciplinada do risco de capital.

  • Criador e editor-chefe do MyBank.pl, oferecendo educação financeira confiável desde 2004.
  • Autor de centenas de comentários de mercado aprofundados, análises estruturais e materiais educacionais para traders e investidores.
  • Defensor da transparência do mercado, promovendo uma educação baseada em dados concretos e relatórios institucionais (BIS, ESMA, CFTC).

Todo o conteúdo tem caráter exclusivamente educacional e informativo – não constitui recomendação de investimento. Lembre-se: operar com alavancagem (Forex/CFD) envolve alto risco de perda rápida de capital.