← DeadCatFound
Courses Performance Live Trades
Track 04 โ€” Financial Dashboard M-01
Why You Need a Command Center
Most retail traders are flying blind. Before you write a single line of code, understand why a unified dashboard changes everything.

The Problem With Flying Blind

When you have capital deployed across multiple strategies, assets, and brokers, the answer to "how am I doing?" should take seconds โ€” not tabs. Most retail traders manage their portfolio from broker apps, spreadsheets, and gut instinct. That's not a system. That's chaos with a brokerage account attached.

A financial dashboard is your single source of truth. It answers the only questions that matter in real time: What do I own? What's it worth? What's at risk? What happened while I was away?

The DeadCatFound Standard
By the end of this course you will have a live dashboard that refreshes every 5 minutes, tracks positions across strategies, shows P&L in real time, and pushes alerts to your phone the moment something moves. All hosted free.

What You'll Actually Build

This isn't a theory course. The dashboard below is a replica of the exact architecture running the DeadCatFound trading firm live today:

Heartbeat OK  ยท  14:32 ET
Net Asset Value
$1,000,143
Paper account DUQ090929
Open Positions
9
Across 2 strategies
Today's P&L
+$2,840
+0.28% vs. prior close
Last Sync
14:30 ET
Next in ~3 min
SymbolQtyEntryLastP&LStrategyStatus
AAPL12$182.50$191.20+$104.40momentum_v10โ— Active
NVDA8$852.00$890.45+$307.60momentum_v10โ— Active
TQQQ50$56.40$58.32+$96.00levetf_v2โ— Active
SOXL80$23.10$22.15-$76.00levetf_v2โ— Active

The Four Pillars

  • Data Layer โ€” live feeds from your broker stored as JSON
  • Sync Engine โ€” scheduled Python scripts that pull data every 5 minutes
  • Display Layer โ€” clean HTML/CSS frontend that reads JSON and renders it
  • Alert Stack โ€” Telegram notifications pushed to your phone instantly
No Framework Required
This entire dashboard runs on vanilla HTML, CSS, and Python. No React. No Node. No paid services. Total monthly cost: $0.
Track 04 โ€” Financial Dashboard M-02
Architecture Overview
Understand how the pieces connect before writing any code. A five-minute diagram now saves five hours of debugging later.

The Full Stack, Mapped Out

System Architecture
ComponentWhat It DoesTechnology
sync_dashboard.pyPulls positions from broker every 5 minPython + IBKR API
trades.jsonStores current positions as structured dataJSON flat file
update_quotes.pyFetches live prices every 15 minPython + yfinance
quotes.jsonCurrent price for each symbol heldJSON flat file
index.htmlThe visual frontend โ€” reads JSON, renders UIHTML / CSS / JS
launchd plistsSchedules sync scripts automaticallymacOS launchd
Cloudflare WorkersHosts the dashboard publicly, freeWrangler CLI
Telegram BotPushes trade alerts to your phoneTelegram Bot API

The Data Flow

Data always travels one way: broker โ†’ JSON โ†’ dashboard. The frontend never talks to the broker directly โ€” that's what makes this architecture safe, fast, and hostable anywhere.

Design Principle
Separate your data collection from your display layer. If the sync script breaks, your dashboard still shows the last known state. Failure in one component never cascades to another.

Folder Structure

project structure
christs-first-firm/ โ”œโ”€โ”€ dashboard/ โ”‚ โ”œโ”€โ”€ index.html # The visual frontend โ”‚ โ”œโ”€โ”€ trades.json # Live positions from broker โ”‚ โ”œโ”€โ”€ quotes.json # Current prices โ”‚ โ””โ”€โ”€ strategy.html # Strategy overview โ”œโ”€โ”€ scripts/ โ”‚ โ”œโ”€โ”€ sync_dashboard_ibkr.py # Pulls IBKR positions โ”‚ โ”œโ”€โ”€ update_quotes.py # Pulls prices via yfinance โ”‚ โ””โ”€โ”€ telegram_listener.py # Alert dispatcher โ”œโ”€โ”€ config/ โ”‚ โ”œโ”€โ”€ ibkr.json # Broker connection config โ”‚ โ””โ”€โ”€ notifications.json # Telegram bot config โ””โ”€โ”€ ~/Library/LaunchAgents/ โ””โ”€โ”€ com.chris.sync.plist # Auto-schedule every 5 min
Track 04 โ€” Financial Dashboard M-03
Setting Up Your Data Layer
JSON flat files are the backbone. Structure your data correctly and everything downstream becomes simple.

Why JSON Flat Files?

Flat JSON files are readable by any browser, versionable with git, deployable to any static host, and debuggable in five seconds with a text editor. For a personal trading dashboard they are the right tool.

The trades.json Schema

dashboard/trades.json
{ "updated": "2026-05-19T14:32:00", "account": "DUQ090929", "nav": 1000143.00, "positions": [ { "symbol": "AAPL", "sec_type": "STK", "qty": 12, "avg_cost": 182.50, "last_price": 191.20, "pnl": 104.40, "strategy": "momentum_v10", "status": "active" } ], "heartbeat": [ {"ts": "2026-05-19T14:30:00", "status": "ok"} ] }

The quotes.json Schema

dashboard/quotes.json
{ "updated": "2026-05-19T14:30:00", "prices": { "AAPL": 191.20, "NVDA": 890.45, "TQQQ": 58.32, "SOXL": 22.15, "SPY": 528.70 } }
Track 04 โ€” Financial Dashboard M-04
Building the Sync Engine
The sync script is the heartbeat of your dashboard. It runs every 5 minutes, pulls from your broker, and writes clean JSON.

Core Sync Script

scripts/sync_dashboard_ibkr.py
#!/usr/bin/env python3 """ Pulls positions from IBKR paper account โ†’ dashboard/trades.json Runs every 5 min via launchd. """ import json from datetime import datetime from pathlib import Path from ibkr_executor import IBKRExecutor FIRM_ROOT = Path(__file__).parent.parent TRADES_OUT = FIRM_ROOT / "dashboard" / "trades.json" def build_positions(executor): raw = executor.get_positions() nav = executor.get_nav() positions = [] for p in raw: positions.append({ "symbol": p["symbol"], "sec_type": p["sec_type"], "qty": int(p["position"]), "avg_cost": round(p["avg_cost"], 2), "strategy": "momentum_v10", "status": "active", }) return { "updated": datetime.now().isoformat(timespec="seconds"), "account": executor._cfg["account"], "nav": round(nav, 2), "positions": positions, "heartbeat": [{"ts": datetime.now().isoformat(timespec="seconds"), "status": "ok"}], } if __name__ == "__main__": exc = IBKRExecutor() data = build_positions(exc) TRADES_OUT.write_text(json.dumps(data, indent=2)) print(f"Synced {len(data['positions'])} positions โ€” NAV ${data['nav']:,.2f}")

Automating With launchd

~/Library/LaunchAgents/com.chris.sync.plist
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Label</key> <string>com.chris.sync</string> <key>ProgramArguments</key> <array> <string>/usr/local/bin/python3.14</string> <string>/Users/christopherwhite/christs-first-firm/scripts/sync_dashboard_ibkr.py</string> </array> <key>StartInterval</key> <integer>300</integer> <key>KeepAlive</key> <false/> <key>StandardOutPath</key> <string>/tmp/sync.out</string> <key>StandardErrorPath</key> <string>/tmp/sync.err</string> </dict> </plist>
Track 04 โ€” Financial Dashboard M-05
Building the Frontend UI
Turn raw JSON into a clean, readable dashboard. No frameworks โ€” just HTML, CSS, and a hundred lines of JavaScript.

Page Structure

Frontend Layout
SectionData SourceFrequency
NAV & Account Summarytrades.json โ†’ navEvery 5 min
Heartbeat Statustrades.json โ†’ heartbeatEvery 5 min
Open Positions Tabletrades.json + quotes.jsonEvery 5 min
P&L SummaryCalculated client-sideOn load

Core Fetch & Render Pattern

dashboard/index.html โ€” core JS
async function loadDashboard() { const [tradesRes, quotesRes] = await Promise.all([ fetch('trades.json'), fetch('quotes.json'), ]); const trades = await tradesRes.json(); const quotes = await quotesRes.json(); // Render NAV document.getElementById('nav').textContent = '$' + trades.nav.toLocaleString('en-US', {minimumFractionDigits: 2}); // Render positions const tbody = document.getElementById('positions-tbody'); trades.positions.forEach(pos => { const price = quotes.prices[pos.symbol] ?? pos.avg_cost; const pnl = (price - pos.avg_cost) * pos.qty; const pnlPct = ((price - pos.avg_cost) / pos.avg_cost) * 100; const cls = pnl >= 0 ? 'td-green' : 'td-red'; tbody.innerHTML += ` <tr> <td>${pos.symbol}</td> <td>${pos.qty}</td> <td>$${pos.avg_cost.toFixed(2)}</td> <td>$${price.toFixed(2)}</td> <td class="${cls}">${pnl >= 0 ? '+' : ''}${pnl.toFixed(2)}</td> <td class="${cls}">${pnlPct.toFixed(2)}%</td> </tr>`; }); } loadDashboard(); setInterval(loadDashboard, 300_000); // refresh every 5 min
Track 04 โ€” Financial Dashboard M-06
Live Positions & P&L
The positions table is the dashboard's core. Here's how to build it so it's always accurate and instantly readable.

Live Positions Table

Live Positions โ€” Paper Account DUQ090929
SymbolTypeQtyAvg CostLastP&LP&L %Strategy
AAPLSTK12$182.50$191.20+$104.40+4.77%momentum_v10
NVDASTK8$852.00$890.45+$307.60+4.51%momentum_v10
MSFTSTK10$415.20$421.80+$66.00+1.59%momentum_v10
TQQQSTK50$56.40$58.32+$96.00+3.40%levetf_v2
SOXLSTK80$23.10$22.15-$76.00-4.11%levetf_v2
SPXLSTK30$142.80$148.20+$162.00+3.78%levetf_v2

Computing Unrealized P&L

Formula
Unrealized P&L = (Current Price − Average Cost) × Quantity
P&L % = ((Current Price − Average Cost) / Average Cost) × 100

Price Fallback Pattern

safe price lookup
// Fall back to avg_cost if quote is missing (market closed / API limit) const price = quotes.prices[pos.symbol] ?? pos.avg_cost; // Never let a missing price crash the render
Track 04 โ€” Financial Dashboard M-07
Telegram Alerts & Notifications
Your dashboard should come to you โ€” not the other way around. Wire up Telegram so every trade and fill pushes to your phone.

Setup: Get Your Bot Token

  • Open Telegram and search for @BotFather
  • Send /newbot and follow the prompts
  • Copy the token โ€” looks like 123456789:ABC-xyz...
  • Start a chat with your bot, send any message
  • Visit the getUpdates URL to find your chat_id

The Alert Function

scripts/telegram_alerts.py
import requests import json from pathlib import Path CONFIG = json.loads((Path(__file__).parent.parent / "config" / "notifications.json").read_text()) TOKEN = CONFIG["telegram_token"] CHAT_ID = CONFIG["telegram_chat_id"] def send_alert(message: str) -> bool: url = f"https://api.telegram.org/bot{TOKEN}/sendMessage" resp = requests.post(url, json={ "chat_id": CHAT_ID, "text": message, "parse_mode": "HTML", }, timeout=10) return resp.status_code == 200 # Usage examples: send_alert("BUY AAPL โ€” 12 shares @ $182.50\nStrategy: momentum_v10") send_alert("Heartbeat OK โ€” NAV $1,000,143 โ€” 9 positions open")
Security
Store your token and chat_id in config/notifications.json โ€” never hardcode credentials. Add the file to .gitignore so it's never committed.
Track 04 โ€” Financial Dashboard M-08
Hosting & Going Live
Deploy your dashboard to Cloudflare Workers in under 5 minutes โ€” free, fast, globally distributed.

Step-by-Step Deployment

  • Install Wrangler: npm install -g wrangler
  • Authenticate: npx wrangler login
  • Create a wrangler.jsonc in your dashboard folder
  • Run npx wrangler deploy
  • Your dashboard is live at your-name.workers.dev
dashboard/wrangler.jsonc
{ "$schema": "node_modules/wrangler/config-schema.json", "name": "firm-dashboard", "compatibility_date": "2026-05-19", "assets": { "directory": "." } }

Auto-Deploy on Every Sync

end of sync_dashboard_ibkr.py
import subprocess # After writing trades.json, redeploy automatically result = subprocess.run( ["npx", "wrangler", "deploy"], cwd=str(FIRM_ROOT / "dashboard"), capture_output=True, text=True ) if result.returncode == 0: print("Dashboard deployed to Cloudflare") else: print(f"Deploy failed: {result.stderr}")

You've Got the Blueprint. Now Build It.

Everything you need to track your finances like a professional firm operator is in your hands. Start with the sync script โ€” the rest follows.

Continue Learning on DeadCatFound
Back to DeadCatFound โ†’
โš  Important Disclaimer

DeadCatFound is an educational platform only. We are not a registered financial advisor, broker-dealer, investment advisor, or financial institution of any kind. Nothing in this course constitutes financial advice, investment advice, or any recommendation to buy or sell any security.

All content is for educational and informational purposes only. Trading involves substantial risk of loss. Always consult a licensed financial professional. By accessing this course you acknowledge that DeadCatFound bears no liability for any financial outcomes.