">
VICTOR SAAD
All buildsTodos os projetos
Python · dbt · PostgreSQL · Metabase

Investment data pipeline

Pipeline de dados de investimentos

My portfolio lived across a brokerage portal, a crypto exchange, government bonds, and a bank — four places, four formats, and no single answer to “how am I actually doing?”

Minha carteira vivia espalhada entre o portal de uma corretora, uma exchange de cripto, títulos públicos e um banco: quatro lugares, quatro formatos e nenhuma resposta única para “como eu estou indo, de verdade?”

PythonPlaywrightdbtPostgreSQLMetabasecronDiscord APIyfinance

The problem

O problema

Consolidated performance across four institutions is arithmetic nobody wants to do monthly, so it doesn't get done. And without it you can't answer basic questions: what's my real allocation, what's actually paying me, am I drifting away from the mix I chose.

Consolidar a rentabilidade de quatro instituições é uma conta que ninguém quer fazer todo mês, então ela não é feita. E sem ela não dá para responder o básico: qual é minha alocação real, o que está me pagando de fato, se estou me afastando do mix que escolhi.

Collection

Coleta

The brokerage side is the awkward part — there's an investor API, but reaching it means a real login. A headless browser drives the authentication and completes a PKCE token exchange; tokens are cached and refreshed rather than re-earned on every run, because logging in repeatedly is both slow and a good way to get rate-limited.

A corretora é a parte chata: existe uma API para o investidor, mas chegar nela exige um login de verdade. Um navegador headless conduz a autenticação e completa uma troca de token PKCE; os tokens ficam em cache e são renovados em vez de conquistados de novo a cada execução, porque logar várias vezes é lento e um bom jeito de levar rate limit.

Around that sit quieter collectors: daily quotes, USD/BRL and EUR/BRL from the central bank's PTAX series, Treasury bond pricing, exchange balances across spot and earn, and a monthly manual entry path for fixed income that has no API at all. Not everything can be automated, and pretending otherwise leaves a hole in the data.

Em volta ficam coletores mais tranquilos: cotações diárias, USD/BRL e EUR/BRL da série PTAX do Banco Central, preços do Tesouro Direto, saldos da exchange em spot e earn, e uma entrada manual mensal para a renda fixa que não tem API nenhuma. Nem tudo dá para automatizar, e fingir que dá deixa um buraco nos dados.

Modelling

Modelagem

Raw
Raw
collector output lands untouched — one table per source
a saída dos coletores chega intacta, uma tabela por fonte
Staging
Staging
dbt views that parse and normalise; tickers extracted, types coerced
views do dbt que fazem o parse e normalizam; tickers extraídos, tipos convertidos
Marts
Marts
the modelled layer everything downstream reads
a camada modelada que tudo o que vem depois lê
Serving
Consumo
Metabase dashboards, plus scheduled Discord digests
dashboards no Metabase, mais resumos agendados no Discord

Keeping raw immutable means a parsing bug is a re-run, not a re-collection — which matters when re-collection involves logging into a bank.

Manter o raw imutável faz um bug de parse virar só uma nova execução, não uma nova coleta, o que importa quando coletar de novo significa logar num banco.

The pipeline's schema viewer: 25 tables laid out as a graph, colour-coded by source — B3 raw, market data, dbt models, alternative sources, operations — with lineage edges between them.
The schema viewer, reading live from PostgreSQL. Colour is the source layer; script_runs, bottom right, is the execution log.
O visualizador de schema, lendo ao vivo do PostgreSQL. A cor indica a camada de origem; script_runs, no canto inferior direito, é o log de execuções.

The part that makes it useful

A parte que torna isso útil

Storage alone doesn't change behaviour. What does is the layer on top: a screener that scores roughly 170 tickers across Brazilian, US, European, and Asian markets daily against Graham, Greenblatt, Bazin, and Lynch criteria; twelve-month dividend projections per holding; rebalancing-drift alerts when allocation wanders from target; and weekly and monthly performance digests. All of it arrives in Discord, because a dashboard I have to remember to open is a dashboard I don't open.

Só guardar os dados não muda comportamento. O que muda é a camada de cima: um screener que pontua todo dia cerca de 170 tickers dos mercados brasileiro, americano, europeu e asiático pelos critérios de Graham, Greenblatt, Bazin e Lynch; projeções de dividendos de doze meses por ativo; alertas de desvio de rebalanceamento quando a alocação foge da meta; e resumos semanais e mensais de rentabilidade. Tudo chega no Discord, porque dashboard que eu preciso lembrar de abrir é dashboard que eu não abro.

What it looks like

Como ele é

The daily view is a single page: where the money is, how far each class has drifted from its target, and what to do about it. The table at the bottom is the decision itself: which class to trim and which to top up, and by how much, to get back to the mix I chose.

A visão diária é uma página só: onde está o dinheiro, quanto cada classe se afastou da meta e o que fazer a respeito. A tabela no fim é a própria decisão: qual classe reduzir, qual reforçar e quanto, para voltar ao mix que escolhi.

The personal finance dashboard: headline cards for net worth, total return, passive income and dividends this month, a 180-day net worth line, and allocation-versus-target bars for each asset class.
The finance view. Every currency value is masked; percentages are real.
A visão de finanças. Todo valor em moeda está mascarado; os percentuais são reais.
A table titled 'what to do now' listing each asset class with current share, target share, drift in percentage points and a suggested action to reduce or top up.
“What to do now”: drift per class and the suggested move, recalculated every evening after the market closes.
“O que fazer agora”: o desvio por classe e o movimento sugerido, recalculados toda noite depois do fechamento do mercado.

The analysts that run on their own

Os analistas que rodam sozinhos

Fourteen scheduled jobs do the work a person would otherwise have to remember. Each one has a single question to answer and reports in Discord only when there is something to say.

Catorze jobs agendados fazem o trabalho que, de outro jeito, alguém teria que lembrar de fazer. Cada um responde a uma única pergunta e só se manifesta no Discord quando tem algo a dizer.

  • Collectors · quotes, FX, crypto, Treasury bonds and benchmarks (CDI, IBOV, IFIX) every weekday at 18:30, then dbt run.
  • Watchdog · price drops over 5%, allocation drifting more than 5 points from target, ex-dividend dates in the next seven days.
  • Screener · around 160 liquid stocks in Brazil, the US, Europe and Asia scored daily against Graham, Greenblatt, Bazin and Lynch, with each day's score stored so recommendations can be checked against what happened next.
  • Income · the next twelve months of dividends projected per holding from 24 months of payment history.
  • Reports · a weekly and a monthly digest: change, best and worst performers, dividends received, drift, benchmarks.
  • Coletores · cotações, câmbio, cripto, Tesouro Direto e benchmarks (CDI, IBOV, IFIX) todo dia útil às 18:30, e depois dbt run.
  • Vigia · quedas de preço acima de 5%, alocação desviando mais de 5 pontos da meta, datas ex-dividendo nos próximos sete dias.
  • Screener · cerca de 160 ações líquidas no Brasil, nos EUA, na Europa e na Ásia pontuadas todo dia por Graham, Greenblatt, Bazin e Lynch, com a nota de cada dia guardada para conferir as recomendações contra o que aconteceu depois.
  • Renda · os próximos doze meses de dividendos projetados por ativo a partir de 24 meses de histórico de pagamentos.
  • Relatórios · um resumo semanal e um mensal: variação, melhores e piores ativos, dividendos recebidos, desvio, benchmarks.
The daily screener digest, from a real run on 19 Oct 2026 (public market data only, nothing from the portfolio; labels translated).
O resumo diário do screener, de uma execução real em 19 out. 2026 (só dados públicos de mercado, nada da carteira).

Operating it

Operação

Every script runs inside a context manager that writes start, end, and outcome to a script_runs table. This sounds like bookkeeping and is actually the most important file in the project: the failure mode of a personal pipeline isn't a crash, it's a collector that silently returns nothing for three weeks while the dashboards keep rendering yesterday's numbers with total confidence.

Todo script roda dentro de um context manager que grava início, fim e resultado numa tabela script_runs. Parece burocracia e, na prática, é o arquivo mais importante do projeto: o jeito de um pipeline pessoal falhar não é um crash, é um coletor que passa três semanas sem trazer nada, em silêncio, enquanto os dashboards seguem mostrando os números de ontem com total confiança.

Not shown here: any actual figures. The screens are real, with every currency value masked and holdings left out; the dashboards themselves stay on my LAN.
O que não aparece aqui: nenhum valor real. As telas são reais, com todo valor em moeda mascarado e as posições de fora; os dashboards em si ficam na minha rede local.

Want to talk about this? I'm happy to walk through how it was built.Quer conversar sobre isso? Mostro com prazer como foi construído.