Funk-lab/tools/finlab/finlab/cli.py
ALI YESILKAYA 54f6f7c634
feat(finlab): console Claude Code finance in-cluster + toolkit d'analyse (#64)
* feat(finlab): console Claude Code finance in-cluster + toolkit d'analyse

Intègre finlab (ex-projet Projets/Finance) au lab comme une console Claude Code
web spécialisée finance — l'esprit OpenAlice, mais c'est le vrai Claude Code sur
l'abonnement (login persisté, pas d'API facturée), agentique, avec la boîte à
outils finlab (Yahoo Finance) branchée en MCP.

- tools/finlab/ : source finlab rapatriée + Dockerfile (Python 3.12 + Node +
  claude-code + ttyd) + persona workspace/CLAUDE.md + branchement MCP + entrypoint
  (seed du workspace no-clobber sur le PVC)
- .github/workflows/build-finlab.yml : build GHCR funk-finlab + bump manifest (main)
- k8s/apps/finlab/ : Deployment/Service/PVC/IngressRoute (finance.lab.local) +
  Middleware basicAuth (shell web protégé) ; PVC = HOME (login) + workspace
- k8s/apps-of-apps/apps/finlab.yaml : Application ArgoCD
- .mcp.json (racine) : outils finlab dans les sessions Claude Code du lab
- admin/ia/finlab.md + READMEs + CLAUDE.md : doc + enregistrement

Analyse/aide à la décision uniquement — aucun ordre réel (paper trading Alpaca
fictif seul exécutable).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* fix(finlab): ttyd absent des dépôts bookworm → binaire statique GitHub

Le build amont échouait (`E: Package 'ttyd' has no installation candidate`) :
ttyd n'est pas packagé dans Debian bookworm. On récupère le binaire statique
(musl, pin TTYD_VERSION=1.7.7) depuis les releases GitHub. Build complet validé
en local (podman) : ttyd 1.7.7, claude-code 2.1.195, import finlab + seed OK.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-29 23:26:09 +02:00

97 lines
3.7 KiB
Python

"""CLI unifiée : python -m finlab.cli <commande>."""
from __future__ import annotations
import argparse
import pandas as pd
def main() -> None:
pd.set_option("display.max_columns", None, "display.max_colwidth", None, "display.width", 240)
parser = argparse.ArgumentParser(prog="finlab", description="Boîte à outils d'analyse de marché")
sub = parser.add_subparsers(dest="cmd", required=True)
sub.add_parser("portfolio", help="Suivi du portefeuille (valeur, P&L, secteurs)")
t = sub.add_parser("tech", help="Analyse technique")
t.add_argument("tickers", nargs="*", help="vide = tout le portefeuille")
t.add_argument("--period", default="1y")
f = sub.add_parser("fundamentals", help="Ratios fondamentaux")
f.add_argument("tickers", nargs="*", help="vide = tout le portefeuille")
s = sub.add_parser("scan", help="Scanner d'opportunités sur un thème")
s.add_argument("theme", nargs="?", default="all", help="thème de watchlists.yaml, 'all' ou 'portfolio'")
s.add_argument("--target", type=float, default=5.0, help="cible de perf hebdo en %%")
s.add_argument("--bullish", action="store_true", help="ne montrer que les setups haussiers")
d = sub.add_parser("digest", help="Digest compact (portefeuille + opportunités + alertes)")
d.add_argument("theme", nargs="?", default="all")
d.add_argument("--target", type=float, default=5.0)
a = sub.add_parser("alerts", help="Alertes déclenchées du jour")
a.add_argument("watch", nargs="?", default=None, help="thème, 'all' ou 'portfolio'")
p = sub.add_parser("plan", help="Plan de trade chiffré (entrée/stop/objectif/taille)")
p.add_argument("tickers", nargs="+")
p.add_argument("--capital", type=float, default=1427.0)
c = sub.add_parser("cache", help="Gestion du cache disque")
c.add_argument("action", choices=["clear", "info"], default="info", nargs="?")
args = parser.parse_args()
if args.cmd == "portfolio":
from . import tracker
print(tracker.report())
elif args.cmd == "tech":
from . import technical
df = technical.scan(args.tickers, args.period) if args.tickers else technical.scan_portfolio(args.period)
print(df.to_string(index=False))
elif args.cmd == "fundamentals":
from . import fundamental
df = fundamental.compare(args.tickers) if args.tickers else fundamental.compare_portfolio()
print(df.to_string(index=False))
elif args.cmd == "scan":
from . import scanner
if args.theme == "portfolio":
df = scanner.scan_portfolio(args.target)
else:
df = scanner.scan_theme(args.theme, args.target, bullish_only=args.bullish)
print(df.to_string(index=False))
elif args.cmd == "digest":
from . import digest
path = digest.write(args.theme, args.target)
print(path.read_text(encoding="utf-8"))
print(f"\n[écrit dans {path}]")
elif args.cmd == "alerts":
from . import alerts
print(alerts.render(alerts.run(args.watch)))
elif args.cmd == "plan":
from . import plan
for tk in args.tickers:
try:
print(plan.render(plan.plan(tk, args.capital)))
except Exception as e:
print(f"{tk}: erreur {e}")
print()
elif args.cmd == "cache":
from . import data
if args.action == "clear":
n = data.clear_cache()
print(f"Cache vidé : {n} fichier(s).")
else:
files = list(data.CACHE_DIR.glob("*.pkl")) if data.CACHE_DIR.exists() else []
print(f"{len(files)} fichier(s) en cache dans {data.CACHE_DIR}")
if __name__ == "__main__":
main()