mirror of
https://github.com/Alkatrazz24/Funk-lab.git
synced 2026-07-08 19:24:42 +02:00
Procédure de mise à jour du portefeuille « à chaque trade » à partir des relevés Revolut, sans IA (exact). - finlab/revolut.py : reconstruit les positions depuis le « trading account statement » (toutes transactions) par COÛT MOYEN (achats − ventes, PRU pondéré), mappe les tickers Revolut→Yahoo (ASME→ASML.AS, TOTB→TTE.PA, BNP→BNP.PA, AXA→CS.PA, FTE→ORA.PA, AIR1→AI.PA) et vérifie les cours. Idempotent, ne touche qu'un compte, préserve les autres comptes/cash/commentaires (round-trip ruamel.yaml) - CLI : python -m finlab.cli import-revolut <csv> [--write] [--account] - Dashboard : le bouton « Importer un relevé » accepte les CSV → endpoints /api/revolut/preview (aperçu) + /api/revolut/apply (écrit), avec garde anti-évasion de chemin ; le front affiche les positions + bouton « Appliquer » - portfolio.yaml : compte Revolut reconstruit depuis l'historique complet → ajoute AMAT (achetée le 29/06, manquait) ; valeurs alignées au centime - requirements : + ruamel.yaml ; persona console + admin/ia/finlab.md documentés Validé sur l'historique réel : 11 positions reconstruites = portfolio.yaml existant (cours vérifiés, 0 warning) + AMAT. Flux dashboard upload→preview→apply testé (TestClient), JS OK. Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
118 lines
5 KiB
Python
118 lines
5 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="?")
|
|
|
|
ir = sub.add_parser("import-revolut", help="Reconstruit les positions depuis un relevé de compte Revolut")
|
|
ir.add_argument("csv", help="trading account statement Revolut (CSV, export sur toute la période)")
|
|
ir.add_argument("--account", default="Revolut", help="compte à mettre à jour dans portfolio.yaml")
|
|
ir.add_argument("--write", action="store_true", help="écrire dans portfolio.yaml (sinon aperçu)")
|
|
ir.add_argument("--no-verify", action="store_true", help="ne pas vérifier les cours Yahoo")
|
|
|
|
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 == "import-revolut":
|
|
from . import revolut
|
|
positions, warns = revolut.to_positions(
|
|
revolut.reconstruct(revolut.parse_statement(args.csv)), verify=not args.no_verify)
|
|
print(f"=== {len(positions)} positions (compte « {args.account} ») ===")
|
|
for p in positions:
|
|
print(f" {p['ticker']:9} {p['shares']:>14.8f} @ {p['avg_price']:>10.4f} {p['avg_currency']}")
|
|
for w in warns:
|
|
print(f" ⚠ {w}")
|
|
if args.write:
|
|
revolut.update_portfolio(positions, account=args.account)
|
|
print(f"\n✅ portfolio.yaml mis à jour (compte « {args.account} »). Autres comptes/cash préservés.")
|
|
else:
|
|
print("\n(aperçu — ajoute --write pour écrire dans portfolio.yaml)")
|
|
|
|
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()
|