initial commit — cassandra v0.1
Containerised macro-strategy dashboard: 4-panel web UI (indicators, portfolio, flash news, AI strategic log), MariaDB store, hourly ingestion jobs, OpenRouter-backed AI analysis. Ports the four prototype scripts in the parent dir (market_pulse, flash_news, trading212, strategic_log) into async services backed by a persistent DB and served via FastAPI + Jinja2 + HTMX. APScheduler runs as a separate compose service for crash-safety and easier restarts. Portfolio composition + position names come live from Trading 212; news per-ticker headlines reuse those names. Tone (NOVICE/INTERMEDIATE/ PRO) and analysis style (DRY/SPECULATIVE) are env-configurable and stored on each log row so historical entries show what produced them. Default model is deepseek/deepseek-v4-flash (overridable via env). Light/dark theme toggle, sans-serif for prose surfaces, monospace for data. Bearer-token auth, OpenRouter monthly cost cap, RSS feeds auto- disabled on consecutive failures. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
commit
a10409c02b
61 changed files with 4890 additions and 0 deletions
45
app/templates_env.py
Normal file
45
app/templates_env.py
Normal file
|
|
@ -0,0 +1,45 @@
|
|||
"""Shared Jinja2 environment with custom filters for the dashboard.
|
||||
Imported by both routers/pages.py and routers/api.py so the filters are
|
||||
registered exactly once."""
|
||||
from __future__ import annotations
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
from fastapi.templating import Jinja2Templates
|
||||
|
||||
|
||||
TEMPLATE_DIR = Path(__file__).resolve().parent / "templates"
|
||||
|
||||
|
||||
def _fmt_price(v: float | None) -> str:
|
||||
"""Format a price in a way that's readable in dense terminal tables.
|
||||
Avoids scientific notation for large round numbers (FTSE 25,962, not 2.596e+04)
|
||||
and keeps enough precision for FX rates like 0.8725 EUR/GBP."""
|
||||
if v is None:
|
||||
return "—"
|
||||
av = abs(v)
|
||||
if av >= 1000:
|
||||
return f"{v:,.2f}"
|
||||
if av >= 10:
|
||||
return f"{v:.2f}"
|
||||
if av >= 1:
|
||||
return f"{v:.4f}"
|
||||
return f"{v:.4f}"
|
||||
|
||||
|
||||
def _fmt_signed(v: float | None, decimals: int = 2) -> str:
|
||||
if v is None:
|
||||
return "—"
|
||||
return f"{v:+,.{decimals}f}"
|
||||
|
||||
|
||||
def _fmt_money(v: float | None) -> str:
|
||||
if v is None:
|
||||
return "—"
|
||||
return f"{v:,.2f}"
|
||||
|
||||
|
||||
templates = Jinja2Templates(directory=str(TEMPLATE_DIR))
|
||||
templates.env.filters["price"] = _fmt_price
|
||||
templates.env.filters["signed"] = _fmt_signed
|
||||
templates.env.filters["money"] = _fmt_money
|
||||
Loading…
Add table
Add a link
Reference in a new issue