feat(finlab): dashboard web (graphiques/portefeuille/actions) + fix seed NFS (#65)

Ajoute une interface graphique à finlab, en plus de la console Claude Code (les
deux cohabitent sur finance.lab.local) :

- dashboard/ : backend FastAPI (sans LLM/clé) exposant les données finlab en JSON
  (/api/portfolio, /api/scan, /api/ohlc, /api/plan, /api/alerts) + front statique
  (lightweight-charts) : graphiques chandeliers MM50/200 + volume, portefeuille
  (P&L/poids/secteurs), watchlists thématiques scannées, alertes, plan de trade R:R
- Découplage code/data : finlab lit ses configs depuis FINLAB_HOME si défini
  (workspace persistant) ; repli sur le dossier du package en dév local
- Image double usage (un build, deux conteneurs) : code → /opt/app, seed minimal
  (configs + persona + MCP) → workspace
- Fix seed NFS (cause racine du workspace vide) : WORKDIR n'est plus sur le volume
  (le runtime le créait en root sur NFS) ; seed déplacé dans un initContainer
  robuste (recrée le dossier s'il est non-inscriptible)
- Deployment : initContainer seed + 2 conteneurs (console ttyd -b /console, dashboard
  uvicorn) ; Service 2 ports ; IngressRoute 2 routes (/console + dashboard), basicAuth

Validé en local (podman) : seed OK, dashboard /api/portfolio → 16k€/10 positions
via FINLAB_HOME, endpoints ohlc/scan/plan/alerts OK, ttyd/claude/finlab présents.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
ALI YESILKAYA 2026-06-30 00:57:23 +02:00 committed by GitHub
parent 607e5e6c0a
commit 56cb32eacc
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
19 changed files with 700 additions and 149 deletions

View file

@ -5,6 +5,7 @@ que ce qui vient de basculer (croisement MACD, cassure MM50, survente...).
"""
from __future__ import annotations
import os
from pathlib import Path
import pandas as pd
@ -12,7 +13,8 @@ import yaml
from . import data, indicators as ind, scanner
ALERTS_FILE = Path(__file__).resolve().parent.parent / "alerts.yaml"
# Config lue depuis FINLAB_HOME (workspace persistant) si défini — cf. data.ROOT.
ALERTS_FILE = Path(os.environ.get("FINLAB_HOME") or Path(__file__).resolve().parent.parent) / "alerts.yaml"
def _events(symbol: str) -> dict:

View file

@ -6,6 +6,7 @@ pour que tous les outils partagent la même source.
from __future__ import annotations
import functools
import os
import pickle
import time
from pathlib import Path
@ -14,7 +15,10 @@ import pandas as pd
import yaml
import yfinance as yf
ROOT = Path(__file__).resolve().parent.parent
# FINLAB_HOME découple le CODE (package) de la DONNÉE (configs éditables + cache). En
# déploiement, le code vit dans l'image (/opt/app) et les configs dans le workspace persistant
# (PVC). Non défini → repli sur le dossier parent du package (dév local inchangé).
ROOT = Path(os.environ.get("FINLAB_HOME") or Path(__file__).resolve().parent.parent)
PORTFOLIO_FILE = ROOT / "portfolio.yaml"
CACHE_DIR = ROOT / ".cache"

View file

@ -7,11 +7,13 @@ alertes déclenchées. Écrit dans reports/ et renvoyé comme texte court.
from __future__ import annotations
import datetime as dt
import os
from pathlib import Path
from . import alerts, data, scanner, tracker
REPORTS_DIR = Path(__file__).resolve().parent.parent / "reports"
# Écrit dans FINLAB_HOME/reports si défini (workspace inscriptible) — cf. data.ROOT.
REPORTS_DIR = Path(os.environ.get("FINLAB_HOME") or Path(__file__).resolve().parent.parent) / "reports"
def build(theme: str = "all", target_pct: float = 5.0, top: int = 6) -> str:

View file

@ -10,6 +10,7 @@ faire +8% est tout aussi capable de faire -8%.
from __future__ import annotations
import math
import os
from pathlib import Path
import pandas as pd
@ -17,7 +18,8 @@ import yaml
from . import data, indicators as ind
WATCHLISTS_FILE = Path(__file__).resolve().parent.parent / "watchlists.yaml"
# Config lue depuis FINLAB_HOME (workspace persistant) si défini — cf. data.ROOT.
WATCHLISTS_FILE = Path(os.environ.get("FINLAB_HOME") or Path(__file__).resolve().parent.parent) / "watchlists.yaml"
def load_theme(theme: str | None = None) -> list[str]: