Financial indicators and analytics for global public companies. Live at sponda.capital.
Smooths earnings volatility by averaging 10 years of inflation-adjusted net income. More reliable than single-year P/E for identifying overvalued or undervalued companies.
PE10 = Market Cap / Average Inflation-Adjusted Annual Net Income (10 years)
Same 10-year averaging approach, but using free cash flow instead of earnings. Reveals whether reported profits translate into real cash generation.
PFCF10 = Market Cap / Average Inflation-Adjusted Annual FCF (10 years)
FCF = Operating Cash Flow + Investing Cash Flow
Point-in-time ratio measuring how much of the company's capital structure is financed by debt relative to shareholders' equity. Uses the most recent balance sheet.
Dívida Bruta / PL = Total Debt / Stockholders' Equity
Broader measure that considers all obligations (not just financial debt) relative to equity. Includes suppliers, taxes, provisions, etc.
Passivo / PL = Total Liabilities / Stockholders' Equity
Dual-panel chart showing historical adjusted prices alongside year-end P/L (Price/Earnings) and P/FCL (Price/Free Cash Flow) multiples. Helps visualize how a company's valuation has evolved over time relative to its stock price.
How it works:
- Fetches monthly historical prices from BRAPI (
range=max&interval=1mo) - Approximates shares outstanding as
market_cap / current_price - For each year with earnings/FCF data, calculates the year-end multiple:
(year_end_price × shares) / net_income(or FCF) - Years with negative earnings or FCF show as gaps in the chart
- Top panel shows adjusted prices (monthly); bottom panel shows the selected multiple (annual)
- Toggle between P/L and P/FCL with pill buttons
API endpoint: GET /api/quote/<ticker>/multiples-history/
- Trigram indexes (pg_trgm) on
Ticker.display_nameandsymbolfor sub-millisecond ILIKE search across 27K+ tickers - Composite indexes on
CompanyAnalysis(ticker, -generated_at),LookupLog(user, timestamp),LookupLog(session_key, timestamp) - PostgreSQL tuning for SSD + 2 GB RAM:
shared_buffers=512MB,work_mem=8MB,random_page_cost=1.1 - pg_stat_statements enabled for query performance monitoring
Three-layer caching strategy eliminates redundant external API calls:
Layer 1 · Provider cache (in providers.py): raw external API responses (BRAPI/FMP) are cached at the routing layer, so multiple views that need the same data (e.g. fetch_quote, fetch_historical_prices) share a single external call.
| Provider call | TTL |
|---|---|
fetch_quote |
15 min |
fetch_historical_prices |
1 hour |
fetch_dividends |
1 hour |
Layer 2 · View cache: computed results for each API endpoint.
| Endpoint | TTL | What it avoids |
|---|---|---|
| Ticker list (27K rows) | 1 hour | Full table scan on every page load |
| Search results | 2 min | Trigram query + sorting per keystroke |
| PE10 metrics | 4 hours | 6+ DB queries + external API call + inflation adjustment |
| Fundamentals | 6 hours | All balance sheets, earnings, cash flows + IPCA table + external API |
| Multiples history | 6 hours | 2 sequential external API calls (was 8s uncached) |
Layer 3 · Cache warming: python manage.py warm_cache pre-populates all three endpoints for the top 50 most-queried tickers. Run every 4 hours via cron so popular tickers are always served from cache.
- Search debounce at 300ms to reduce API calls during typing
- Dynamic imports via
next/dynamicfor CompanyMetricsCard, MultiplesChart (Recharts), CompareTab, FundamentalsTab, and CompanyAnalysis. Recharts (~100KB) only loads when the Charts tab is opened. - Prefetch on hover: hovering over Fundamentos or Graficos tabs triggers
queryClient.prefetchQuery(), so data is ready before the user clicks - Self-hosted Satoshi font: eliminates the 1.15s Fontshare external request
- 30-minute staleTime on React Query hooks; SSR revalidation at 1 hour
- Lazy-loaded images on all company logos; footer logo served via Next.js
<Image>with WebP optimization - useMemo on frequently recomputed derived state (excludeSet, sectorPeerLinks)
Locale-prefixed URLs serve region-specific metadata to search engines:
/pt/PETR4/fundamentos· Portuguese metadata,<html lang="pt-BR">, OG localept_BR/en/PETR4/fundamentals· English metadata,<html lang="en">, OG localeen_US- Bare URLs (
/PETR4) 301-redirect to the locale-prefixed version based onAccept-Language - Every page includes
<link rel="alternate" hreflang>cross-links between locales - Tab URL paths are localized: fundamentos/fundamentals, comparar/compare, graficos/charts
- Auto-generated sitemap with both locale variants for every ticker and tab
x-defaulthreflang points to English
- N+1 fix in AdminDashboard: replaced per-user loops with
annotate(Count(...))(1,200+ queries down to 1) - CompanyAnalysisView: 3 queries reduced to 1 with
.values()
- Backend: Django 5 + Django REST Framework + PostgreSQL + Redis
- Frontend: React 19 + TypeScript + Next.js 15 + TanStack Query
- Styling: Tailwind CSS v4 (
@applyonly -- no utility classes in JSX) - Deploy: GitHub Actions CI/CD → DigitalOcean VPS
- Python 3.12+
- Node.js 20+
- A BRAPI API key
cd backend
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
# Create .env from template (edit with your BRAPI key)
cp ../.env.example ../.env
# Run migrations and start server
python manage.py migrate
python manage.py refresh_ipca # fetch IPCA data
python manage.py refresh_tickers # fetch B3 ticker list
python manage.py runservercd frontend
npm install
npm run devThe Vite dev server proxies /api requests to Django on localhost:8000.
| Variable | Purpose |
|---|---|
DJANGO_SECRET_KEY |
Django secret key |
BRAPI_API_KEY |
BRAPI pro API key |
DATABASE_URL |
PostgreSQL connection string (production only) |
ALLOWED_HOSTS |
Comma-separated allowed hosts |
DEBUG |
True for development, False for production |
Pushes to main trigger a GitHub Actions workflow that SSHs to poe.ma, pulls the latest code, rebuilds Docker containers, runs migrations, and restarts services.
ssh root@poe.ma
cd /opt/sponda
git pull
docker compose build
docker compose run --rm web python manage.py migrate --noinput
docker compose up -dA systemd timer (sponda-refresh.timer) runs daily at 06:00 UTC to refresh IPCA and ticker data from BRAPI. It is installed automatically on deploy. To check status:
systemctl status sponda-refresh.timer # next run time
journalctl -u sponda-refresh.service # last run logs| Command | Purpose | Frequency |
|---|---|---|
refresh_ipca |
Sync IPCA inflation index | Daily |
refresh_tickers |
Sync B3 ticker list (~2,300 stocks) | Daily |
Free tier allows 3 lookups per day, tracked by session cookie. After the limit, users are prompted to create an account.