Funk-lab/tools/finlab/finlab/plan.py
alkatrazz b649afd059 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>
2026-06-29 22:43:53 +02:00

88 lines
3 KiB
Python

"""Plan de trade chiffré : entrée, stop ATR, objectifs, taille, risque/rendement.
Le stop est placé sous le « bruit » du titre (multiple d'ATR), pas au hasard.
Le ratio R:R dit si le trade vaut le risque : viser +X% avec un stop plus large
que X% est structurellement perdant — l'outil le montre noir sur blanc.
"""
from __future__ import annotations
from . import data, indicators as ind
def plan(
ticker: str,
capital: float,
fee: float = 1.15,
targets=(5.0, 10.0),
stop_atr_mult: float = 1.5,
fractional: bool = True,
) -> dict:
df = data.history(ticker, period="6mo")
close = df["Close"]
entry = float(close.iloc[-1])
atr = float(ind.atr(df).iloc[-1])
stop = entry - stop_atr_mult * atr
stop_pct = (entry - stop) / entry * 100
# Taille : on déploie le capital dispo (moins le frais d'achat)
budget = capital - fee
shares = budget / entry if fractional else int(budget / entry)
invested = shares * entry
fees_round = 2 * fee # achat + revente
risk_eur = shares * (entry - stop) + fees_round # perte si stop touché
risk_pct_capital = risk_eur / capital * 100
tgs = []
for t in targets:
gain = shares * entry * (t / 100) - fees_round
tgs.append({
"cible_%": t,
"prix": round(entry * (1 + t / 100), 2),
"gain_net": round(gain, 2),
})
# R:R sur la cible médiane
mid_target = sum(targets) / len(targets)
reward = shares * entry * (mid_target / 100) - fees_round
rr = reward / risk_eur if risk_eur > 0 else 0
return {
"ticker": ticker,
"entry": round(entry, 2),
"atr": round(atr, 2),
"shares": round(shares, 4),
"invested": round(invested, 2),
"stop": round(stop, 2),
"stop_pct": round(stop_pct, 1),
"risk_eur": round(risk_eur, 2),
"risk_pct_capital": round(risk_pct_capital, 1),
"targets": tgs,
"rr": round(rr, 2),
}
def render(p: dict, ccy: str = "$") -> str:
L = [f"━━ {p['ticker']} @ {p['entry']}{ccy} (ATR {p['atr']}) ━━"]
L.append(f" Position : {p['shares']} actions → {p['invested']}{ccy} déployés")
L.append(f" STOP : {p['stop']}{ccy} (-{p['stop_pct']}%)")
L.append(f" ↳ perte si touché : -{p['risk_eur']}{ccy} ({p['risk_pct_capital']}% du capital)")
for t in p["targets"]:
L.append(f" Objectif +{t['cible_%']:>4}% : {t['prix']}{ccy} → gain net +{t['gain_net']}{ccy}")
verdict = "✅ favorable" if p["rr"] >= 1.5 else ("⚠️ limite" if p["rr"] >= 1 else "❌ défavorable")
L.append(f" RATIO R:R : {p['rr']}:1 {verdict}")
return "\n".join(L)
if __name__ == "__main__":
import sys
args = sys.argv[1:]
capital = float(args[0]) if args else 1427.0
tickers = args[1:] or ["CIEN", "GLW", "TLN"]
for tk in tickers:
try:
print(render(plan(tk, capital)))
except Exception as e:
print(f"{tk}: erreur {e}")
print()