feat: translate all UI strings to English for international release
Complete French→English translation across all frontend pages and backend services — every label, button, header, empty state, toast, and nav item is now in English. Build verified clean (tsc + vite). No i18n library added; direct string replacement throughout. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -86,7 +86,7 @@ function AddPositionModal({ prefill, onClose }: AddModalProps) {
|
||||
onSuccess: onClose,
|
||||
onError: (err: unknown) => {
|
||||
const detail = (err as { response?: { data?: { detail?: string } } })?.response?.data?.detail
|
||||
setAddError(detail || 'Erreur lors de l\'ajout de la position')
|
||||
setAddError(detail || 'Error adding position')
|
||||
}
|
||||
})
|
||||
}
|
||||
@@ -96,38 +96,38 @@ function AddPositionModal({ prefill, onClose }: AddModalProps) {
|
||||
<div className="card w-full max-w-lg max-h-[90vh] overflow-y-auto">
|
||||
<div className="flex items-center justify-between mb-4">
|
||||
<h2 className="text-base font-bold text-white flex items-center gap-2">
|
||||
<Plus className="w-4 h-4 text-blue-400" /> Ajouter au portefeuille
|
||||
<Plus className="w-4 h-4 text-blue-400" /> Add to portfolio
|
||||
</h2>
|
||||
<button onClick={onClose} className="text-slate-500 hover:text-slate-200"><X className="w-4 h-4" /></button>
|
||||
</div>
|
||||
<div className="space-y-3">
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="text-xs text-slate-500 mb-1 block">Titre</label>
|
||||
<label className="text-xs text-slate-500 mb-1 block">Title</label>
|
||||
<input value={form.title} onChange={e => set('title', e.target.value)}
|
||||
placeholder="Ex: Long Call GLD Q3 2026"
|
||||
className="w-full bg-dark-700 border border-slate-700 rounded px-2 py-1.5 text-sm text-white focus:outline-none focus:border-blue-500" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500 mb-1 block">
|
||||
Sous-jacent <span className="text-slate-600">(ticker Yahoo Finance)</span>
|
||||
Underlying <span className="text-slate-600">(Yahoo Finance ticker)</span>
|
||||
</label>
|
||||
<input value={form.underlying} onChange={e => { set('underlying', e.target.value.toUpperCase()); setAddError(null) }}
|
||||
placeholder="^GSPC, GLD, CL=F..."
|
||||
className="w-full bg-dark-700 border border-slate-700 rounded px-2 py-1.5 text-sm text-white focus:outline-none focus:border-blue-500" />
|
||||
<div className="text-xs text-slate-600 mt-0.5">S&P 500: ^GSPC · Or: GC=F · WTI: CL=F · GLD · USO</div>
|
||||
<div className="text-xs text-slate-600 mt-0.5">S&P 500: ^GSPC · Gold: GC=F · WTI: CL=F · GLD · USO</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="text-xs text-slate-500 mb-1 block">Stratégie</label>
|
||||
<label className="text-xs text-slate-500 mb-1 block">Strategy</label>
|
||||
<select value={form.strategy} onChange={e => set('strategy', e.target.value)}
|
||||
className="w-full bg-dark-700 border border-slate-700 rounded px-2 py-1.5 text-sm text-white focus:outline-none focus:border-blue-500">
|
||||
{STRATEGIES.map(s => <option key={s}>{s}</option>)}
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500 mb-1 block">Classe d'actif</label>
|
||||
<label className="text-xs text-slate-500 mb-1 block">Asset class</label>
|
||||
<select value={form.asset_class} onChange={e => set('asset_class', e.target.value)}
|
||||
className="w-full bg-dark-700 border border-slate-700 rounded px-2 py-1.5 text-sm text-white focus:outline-none focus:border-blue-500">
|
||||
{ASSET_CLASSES.map(s => <option key={s}>{s}</option>)}
|
||||
@@ -138,7 +138,7 @@ function AddPositionModal({ prefill, onClose }: AddModalProps) {
|
||||
<div>
|
||||
<label className="text-xs text-slate-500 mb-1 block">Strike</label>
|
||||
<input type="number" value={form.strike} onChange={e => set('strike', e.target.value)}
|
||||
placeholder="Prix d'exercice"
|
||||
placeholder="Exercise price"
|
||||
className="w-full bg-dark-700 border border-slate-700 rounded px-2 py-1.5 text-sm text-white focus:outline-none focus:border-blue-500" />
|
||||
</div>
|
||||
<div>
|
||||
@@ -150,45 +150,45 @@ function AddPositionModal({ prefill, onClose }: AddModalProps) {
|
||||
</select>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500 mb-1 block">Prime payée</label>
|
||||
<label className="text-xs text-slate-500 mb-1 block">Premium paid</label>
|
||||
<input type="number" value={form.premium} onChange={e => set('premium', e.target.value)}
|
||||
placeholder="Prix option"
|
||||
placeholder="Option price"
|
||||
className="w-full bg-dark-700 border border-slate-700 rounded px-2 py-1.5 text-sm text-white focus:outline-none focus:border-blue-500" />
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
<div>
|
||||
<label className="text-xs text-slate-500 mb-1 block">Capital investi (€)</label>
|
||||
<label className="text-xs text-slate-500 mb-1 block">Capital invested (€)</label>
|
||||
<input type="number" value={form.capital_invested} onChange={e => set('capital_invested', Number(e.target.value))}
|
||||
className="w-full bg-dark-700 border border-slate-700 rounded px-2 py-1.5 text-sm text-white focus:outline-none focus:border-blue-500" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500 mb-1 block">Durée (jours)</label>
|
||||
<label className="text-xs text-slate-500 mb-1 block">Duration (days)</label>
|
||||
<input type="number" value={form.expiry_days} onChange={e => set('expiry_days', Number(e.target.value))}
|
||||
className="w-full bg-dark-700 border border-slate-700 rounded px-2 py-1.5 text-sm text-white focus:outline-none focus:border-blue-500" />
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500 mb-1 block">Déclencheur géopolitique</label>
|
||||
<label className="text-xs text-slate-500 mb-1 block">Geopolitical trigger</label>
|
||||
<input value={form.geo_trigger} onChange={e => set('geo_trigger', e.target.value)}
|
||||
placeholder="Ex: Middle East escalation → oil spike"
|
||||
className="w-full bg-dark-700 border border-slate-700 rounded px-2 py-1.5 text-sm text-white focus:outline-none focus:border-blue-500" />
|
||||
</div>
|
||||
<div>
|
||||
<label className="text-xs text-slate-500 mb-1 block">Raisonnement</label>
|
||||
<label className="text-xs text-slate-500 mb-1 block">Rationale</label>
|
||||
<textarea value={form.rationale} onChange={e => set('rationale', e.target.value)}
|
||||
rows={2} placeholder="Pourquoi ce trade..."
|
||||
rows={2} placeholder="Why this trade..."
|
||||
className="w-full bg-dark-700 border border-slate-700 rounded px-2 py-1.5 text-sm text-white focus:outline-none focus:border-blue-500 resize-none" />
|
||||
</div>
|
||||
{isStraddle && (
|
||||
<div className="bg-blue-900/30 border border-blue-700/30 rounded p-2 text-xs text-blue-300">
|
||||
Straddle : 2 legs seront créés automatiquement (CALL + PUT au même strike)
|
||||
Straddle: 2 legs will be created automatically (CALL + PUT at the same strike)
|
||||
</div>
|
||||
)}
|
||||
<div className="bg-dark-700 rounded p-2 text-xs text-slate-500">
|
||||
<div className="font-semibold text-slate-400 mb-1">Frais IB simulés</div>
|
||||
<div>Options: $0.65/contrat (min $1.00) à l'entrée + sortie</div>
|
||||
<div>1 contrat = <span className="text-white">$0.65</span> · 5 contrats = <span className="text-white">$3.25</span></div>
|
||||
<div className="font-semibold text-slate-400 mb-1">Simulated IB fees</div>
|
||||
<div>Options: $0.65/contract (min $1.00) at entry + exit</div>
|
||||
<div>1 contract = <span className="text-white">$0.65</span> · 5 contracts = <span className="text-white">$3.25</span></div>
|
||||
</div>
|
||||
{addError && (
|
||||
<div className="bg-red-900/40 border border-red-700/40 rounded p-2 text-xs text-red-300">
|
||||
@@ -197,7 +197,7 @@ function AddPositionModal({ prefill, onClose }: AddModalProps) {
|
||||
)}
|
||||
<button onClick={submit} disabled={isPending || !form.underlying}
|
||||
className="w-full bg-blue-600 hover:bg-blue-500 disabled:opacity-40 text-white rounded py-2 text-sm font-semibold">
|
||||
{isPending ? 'Ajout...' : '+ Ajouter au portefeuille'}
|
||||
{isPending ? 'Adding...' : '+ Add to portfolio'}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -233,7 +233,7 @@ function PositionCard({ pos }: { pos: Record<string, any> }) {
|
||||
<button
|
||||
onClick={() => navigate(`/markets?symbol=${pos.underlying}`)}
|
||||
className="text-xs text-blue-400 hover:text-blue-300 flex items-center gap-0.5 transition-colors"
|
||||
title="Voir dans les marchés"
|
||||
title="View in markets"
|
||||
>
|
||||
{pos.underlying} <ExternalLink className="w-2.5 h-2.5" />
|
||||
</button>
|
||||
@@ -253,7 +253,7 @@ function PositionCard({ pos }: { pos: Record<string, any> }) {
|
||||
</div>
|
||||
</div>
|
||||
<button onClick={() => deletePos(pos.id)} disabled={deleting}
|
||||
className="text-slate-600 hover:text-red-400 transition-colors" title="Supprimer">
|
||||
className="text-slate-600 hover:text-red-400 transition-colors" title="Delete">
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</div>
|
||||
@@ -262,29 +262,29 @@ function PositionCard({ pos }: { pos: Record<string, any> }) {
|
||||
{/* KPI grid */}
|
||||
<div className="grid grid-cols-4 gap-2 mb-3">
|
||||
<div className="card-sm text-center">
|
||||
<div className="text-xs text-slate-600">{hasPremium ? 'Prime payée' : 'Capital investi'}</div>
|
||||
<div className="text-xs text-slate-600">{hasPremium ? 'Premium paid' : 'Capital invested'}</div>
|
||||
<div className="text-xs text-white font-mono mt-0.5">{entryRef.toFixed(2)}€</div>
|
||||
</div>
|
||||
<div className="card-sm text-center">
|
||||
<div className="text-xs text-slate-600">Valeur BS actuelle</div>
|
||||
<div className="text-xs text-slate-600">Current BS value</div>
|
||||
<div className="text-xs text-white font-mono mt-0.5">
|
||||
{pos.current_value != null ? `${pos.current_value.toFixed(2)}€` : '—'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-sm text-center">
|
||||
<div className="text-xs text-slate-600">Spot sous-jacent</div>
|
||||
<div className="text-xs text-slate-600">Underlying spot</div>
|
||||
<div className="text-xs text-white font-mono mt-0.5">
|
||||
{pos.current_underlying != null ? `$${pos.current_underlying}` : '—'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="card-sm text-center">
|
||||
<div className="text-xs text-slate-600">Jours restants</div>
|
||||
<div className="text-xs text-slate-600">Days remaining</div>
|
||||
<div className={clsx('text-xs font-mono mt-0.5', {
|
||||
'text-red-400': (pos.days_remaining ?? 99) < 14,
|
||||
'text-yellow-400': (pos.days_remaining ?? 99) < 30,
|
||||
'text-white': (pos.days_remaining ?? 99) >= 30,
|
||||
})}>
|
||||
{pos.days_remaining != null ? `${pos.days_remaining}j` : '—'}
|
||||
{pos.days_remaining != null ? `${pos.days_remaining}d` : '—'}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -294,9 +294,9 @@ function PositionCard({ pos }: { pos: Record<string, any> }) {
|
||||
<div className="flex items-center gap-4 text-xs mb-2">
|
||||
<span className="text-slate-600">Greeks:</span>
|
||||
<span>Δ <span className="text-slate-300 font-mono">{pos.greeks.net_delta?.toFixed(3)}</span></span>
|
||||
<span>Θ <span className="text-red-400 font-mono">{pos.greeks.net_theta?.toFixed(3)}</span>/j</span>
|
||||
<span>Θ <span className="text-red-400 font-mono">{pos.greeks.net_theta?.toFixed(3)}</span>/d</span>
|
||||
<span>ν <span className="text-blue-400 font-mono">{pos.greeks.net_vega?.toFixed(3)}</span></span>
|
||||
<span className="ml-auto text-slate-600">Frais IB: <span className="text-orange-400">{(pos.ib_fees_entry || 0).toFixed(2)}€</span></span>
|
||||
<span className="ml-auto text-slate-600">IB fees: <span className="text-orange-400">{(pos.ib_fees_entry || 0).toFixed(2)}€</span></span>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -307,7 +307,7 @@ function PositionCard({ pos }: { pos: Record<string, any> }) {
|
||||
className="flex items-center gap-1 text-xs text-slate-600 hover:text-slate-400 mb-2 transition-colors"
|
||||
>
|
||||
{showDetails ? <ChevronUp className="w-3 h-3" /> : <ChevronDown className="w-3 h-3" />}
|
||||
Détail de la simulation Black-Scholes
|
||||
Black-Scholes simulation detail
|
||||
</button>
|
||||
)}
|
||||
|
||||
@@ -316,14 +316,14 @@ function PositionCard({ pos }: { pos: Record<string, any> }) {
|
||||
<div className="mb-3 bg-dark-700/40 rounded p-2.5 text-xs border border-slate-700/30">
|
||||
<div className="flex flex-wrap items-center gap-3 mb-2 text-slate-500 border-b border-slate-700/30 pb-1.5">
|
||||
{pos.entry_underlying_price != null && (
|
||||
<span>Spot entrée: <span className="text-slate-300 font-mono">${Number(pos.entry_underlying_price).toFixed(2)}</span></span>
|
||||
<span>Entry spot: <span className="text-slate-300 font-mono">${Number(pos.entry_underlying_price).toFixed(2)}</span></span>
|
||||
)}
|
||||
{pos.sigma_used != null && (
|
||||
<span>σ (IV histo): <span className="text-slate-300 font-mono">{(pos.sigma_used * 100).toFixed(1)}%</span></span>
|
||||
<span>σ (hist. IV): <span className="text-slate-300 font-mono">{(pos.sigma_used * 100).toFixed(1)}%</span></span>
|
||||
)}
|
||||
<span>r: <span className="text-slate-300">5%</span></span>
|
||||
{pos.expiry_days != null && (
|
||||
<span>Durée initiale: <span className="text-slate-300">{pos.expiry_days}j</span></span>
|
||||
<span>Initial duration: <span className="text-slate-300">{pos.expiry_days}d</span></span>
|
||||
)}
|
||||
</div>
|
||||
<table className="w-full mb-2">
|
||||
@@ -331,9 +331,9 @@ function PositionCard({ pos }: { pos: Record<string, any> }) {
|
||||
<tr className="text-slate-600">
|
||||
<th className="text-left pb-1 font-normal">Leg</th>
|
||||
<th className="text-left pb-1 font-normal">Strike</th>
|
||||
<th className="text-right pb-1 font-normal">Qté</th>
|
||||
<th className="text-right pb-1 font-normal">Prime/contrat</th>
|
||||
<th className="text-right pb-1 font-normal">Coût total</th>
|
||||
<th className="text-right pb-1 font-normal">Qty</th>
|
||||
<th className="text-right pb-1 font-normal">Premium/contract</th>
|
||||
<th className="text-right pb-1 font-normal">Total cost</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
@@ -370,7 +370,7 @@ function PositionCard({ pos }: { pos: Record<string, any> }) {
|
||||
</tbody>
|
||||
</table>
|
||||
<div className="text-slate-600 italic">
|
||||
Black-Scholes · 1 contrat = 100 actions · Prix calculé au moment de l'ajout (spot, IV historique, r=5%)
|
||||
Black-Scholes · 1 contract = 100 shares · Price computed at entry time (spot, historical IV, r=5%)
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
@@ -403,29 +403,29 @@ function PositionCard({ pos }: { pos: Record<string, any> }) {
|
||||
<div className="flex gap-2">
|
||||
<button onClick={() => setShowClose(!showClose)}
|
||||
className="flex-1 text-xs border border-slate-700 hover:border-emerald-600 text-slate-400 hover:text-emerald-400 rounded py-1.5 transition-colors">
|
||||
{showClose ? 'Annuler' : '💰 Clôturer la position'}
|
||||
{showClose ? 'Cancel' : '💰 Close position'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
{showClose && (
|
||||
<div className="mt-2 space-y-1">
|
||||
<div className="text-xs text-slate-500">Valeur de revente (€) — ex: {pos.current_value?.toFixed(2)}</div>
|
||||
<div className="text-xs text-slate-500">Resale value (€) — e.g.: {pos.current_value?.toFixed(2)}</div>
|
||||
<div className="flex gap-2">
|
||||
<input type="number" value={closeVal} onChange={e => setCloseVal(e.target.value)}
|
||||
placeholder={`Valeur actuelle: ~${pos.current_value?.toFixed(2)}€`}
|
||||
placeholder={`Current value: ~${pos.current_value?.toFixed(2)}€`}
|
||||
className="flex-1 bg-dark-700 border border-slate-700 rounded px-2 py-1 text-sm text-white focus:outline-none focus:border-blue-500" />
|
||||
<button
|
||||
onClick={() => closePos({ id: pos.id, close_value: parseFloat(closeVal) || 0 },
|
||||
{ onSuccess: () => setShowClose(false) })}
|
||||
disabled={closing || !closeVal}
|
||||
className="bg-emerald-700 hover:bg-emerald-600 disabled:opacity-40 text-white px-3 rounded text-xs font-semibold">
|
||||
{closing ? '...' : 'Confirmer'}
|
||||
{closing ? '...' : 'Confirm'}
|
||||
</button>
|
||||
</div>
|
||||
{closeVal && (
|
||||
<div className={clsx('text-xs', parseFloat(closeVal) - entryRef - (pos.ib_fees_entry||0) - 1 >= 0 ? 'positive' : 'negative')}>
|
||||
P&L net IB: {(parseFloat(closeVal) - entryRef - (pos.ib_fees_entry||0) - 1).toFixed(2)}€
|
||||
(frais sortie: ~$1.00)
|
||||
Net IB P&L: {(parseFloat(closeVal) - entryRef - (pos.ib_fees_entry||0) - 1).toFixed(2)}€
|
||||
(exit fees: ~$1.00)
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
@@ -449,7 +449,7 @@ function ClosedRow({ pos, fees, pnl }: { pos: Record<string, any>; fees: number;
|
||||
<td className="py-1.5 text-slate-500">{pos.close_date?.slice(0, 10)}</td>
|
||||
<td className="py-1.5 pl-2">
|
||||
<button onClick={() => deletePos(pos.id)} disabled={isPending}
|
||||
className="opacity-0 group-hover:opacity-100 text-slate-600 hover:text-red-400 transition-all" title="Supprimer">
|
||||
className="opacity-0 group-hover:opacity-100 text-slate-600 hover:text-red-400 transition-all" title="Delete">
|
||||
<Trash2 className="w-3.5 h-3.5" />
|
||||
</button>
|
||||
</td>
|
||||
@@ -470,19 +470,19 @@ export default function Portfolio() {
|
||||
<div className="flex items-center justify-between">
|
||||
<div>
|
||||
<h1 className="text-xl font-bold text-white flex items-center gap-2">
|
||||
<DollarSign className="w-5 h-5 text-blue-400" /> Portefeuille
|
||||
<DollarSign className="w-5 h-5 text-blue-400" /> Portfolio
|
||||
</h1>
|
||||
<p className="text-xs text-slate-500 mt-0.5">
|
||||
Suivi temps réel · Mark-to-market Black-Scholes · Frais IB simulés
|
||||
Real-time tracking · Mark-to-market Black-Scholes · Simulated IB fees
|
||||
</p>
|
||||
</div>
|
||||
<div className="flex gap-2">
|
||||
<button onClick={() => refetch()} className="flex items-center gap-1 text-xs text-slate-400 hover:text-slate-200">
|
||||
<RefreshCw className="w-3.5 h-3.5" /> Actualiser
|
||||
<RefreshCw className="w-3.5 h-3.5" /> Refresh
|
||||
</button>
|
||||
<button onClick={() => setShowModal(true)}
|
||||
className="flex items-center gap-1.5 bg-blue-600 hover:bg-blue-500 text-white px-3 py-1.5 rounded text-sm font-semibold">
|
||||
<Plus className="w-4 h-4" /> Nouvelle position
|
||||
<Plus className="w-4 h-4" /> New position
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
@@ -491,11 +491,11 @@ export default function Portfolio() {
|
||||
{summary && (
|
||||
<div className="grid grid-cols-5 gap-3">
|
||||
{[
|
||||
{ label: 'Positions ouvertes', value: summary.open_positions, color: 'text-white' },
|
||||
{ label: 'Capital engagé', value: `${summary.total_invested}€`, color: 'text-white' },
|
||||
{ label: 'P&L latent', value: `${summary.unrealized_pnl >= 0 ? '+' : ''}${summary.unrealized_pnl}€`, color: summary.unrealized_pnl >= 0 ? 'text-emerald-400' : 'text-red-400' },
|
||||
{ label: 'P&L réalisé', value: `${summary.realized_pnl >= 0 ? '+' : ''}${summary.realized_pnl}€`, color: summary.realized_pnl >= 0 ? 'text-emerald-400' : 'text-red-400' },
|
||||
{ label: 'Frais IB total', value: `${summary.total_fees_paid}€`, color: 'text-orange-400' },
|
||||
{ label: 'Open positions', value: summary.open_positions, color: 'text-white' },
|
||||
{ label: 'Capital deployed', value: `${summary.total_invested}€`, color: 'text-white' },
|
||||
{ label: 'Unrealized P&L', value: `${summary.unrealized_pnl >= 0 ? '+' : ''}${summary.unrealized_pnl}€`, color: summary.unrealized_pnl >= 0 ? 'text-emerald-400' : 'text-red-400' },
|
||||
{ label: 'Realized P&L', value: `${summary.realized_pnl >= 0 ? '+' : ''}${summary.realized_pnl}€`, color: summary.realized_pnl >= 0 ? 'text-emerald-400' : 'text-red-400' },
|
||||
{ label: 'Total IB fees', value: `${summary.total_fees_paid}€`, color: 'text-orange-400' },
|
||||
].map(({ label, value, color }) => (
|
||||
<div key={label} className="card-sm text-center">
|
||||
<div className="stat-label">{label}</div>
|
||||
@@ -508,9 +508,9 @@ export default function Portfolio() {
|
||||
{/* Tabs */}
|
||||
<div className="flex gap-1 bg-dark-700 p-1 rounded w-fit">
|
||||
{[
|
||||
{ key: 'open', label: `Positions ouvertes (${positions?.length ?? 0})` },
|
||||
{ key: 'closed', label: `Clôturées (${closed?.length ?? 0})` },
|
||||
{ key: 'history', label: 'Courbe P&L' },
|
||||
{ key: 'open', label: `Open positions (${positions?.length ?? 0})` },
|
||||
{ key: 'closed', label: `Closed (${closed?.length ?? 0})` },
|
||||
{ key: 'history', label: 'P&L curve' },
|
||||
].map(({ key, label }) => (
|
||||
<button key={key} onClick={() => setTab(key as any)}
|
||||
className={clsx('px-3 py-1.5 rounded text-sm transition-colors', {
|
||||
@@ -538,8 +538,8 @@ export default function Portfolio() {
|
||||
) : (
|
||||
<div className="card text-center py-12 text-slate-500">
|
||||
<DollarSign className="w-10 h-10 mx-auto mb-3 opacity-20" />
|
||||
<div>Aucune position ouverte</div>
|
||||
<div className="text-xs mt-1">Ajouter un trade depuis les idées ou manuellement</div>
|
||||
<div>No open positions</div>
|
||||
<div className="text-xs mt-1">Add a trade from ideas or manually</div>
|
||||
</div>
|
||||
)}
|
||||
</>
|
||||
@@ -552,12 +552,12 @@ export default function Portfolio() {
|
||||
<thead>
|
||||
<tr className="text-slate-600 border-b border-slate-700/40">
|
||||
<th className="text-left pb-2">Position</th>
|
||||
<th className="text-left pb-2">Stratégie</th>
|
||||
<th className="text-right pb-2">Investi</th>
|
||||
<th className="text-right pb-2">Clôture</th>
|
||||
<th className="text-right pb-2">Frais IB</th>
|
||||
<th className="text-right pb-2">P&L net</th>
|
||||
<th className="text-left pb-2">Date clôture</th>
|
||||
<th className="text-left pb-2">Strategy</th>
|
||||
<th className="text-right pb-2">Invested</th>
|
||||
<th className="text-right pb-2">Close</th>
|
||||
<th className="text-right pb-2">IB fees</th>
|
||||
<th className="text-right pb-2">Net P&L</th>
|
||||
<th className="text-left pb-2">Close date</th>
|
||||
<th className="pb-2"></th>
|
||||
</tr>
|
||||
</thead>
|
||||
@@ -570,7 +570,7 @@ export default function Portfolio() {
|
||||
)
|
||||
})}
|
||||
{(!closed || closed.length === 0) && (
|
||||
<tr><td colSpan={8} className="py-8 text-center text-slate-600">Aucun trade clôturé</td></tr>
|
||||
<tr><td colSpan={8} className="py-8 text-center text-slate-600">No closed trades</td></tr>
|
||||
)}
|
||||
</tbody>
|
||||
</table>
|
||||
@@ -580,7 +580,7 @@ export default function Portfolio() {
|
||||
{/* P&L history */}
|
||||
{tab === 'history' && (
|
||||
<div className="card">
|
||||
<div className="section-title">Courbe de P&L cumulé (trades réalisés)</div>
|
||||
<div className="section-title">Cumulative P&L curve (realized trades)</div>
|
||||
{pnlHistory && pnlHistory.length > 0 ? (
|
||||
<>
|
||||
<ResponsiveContainer width="100%" height={250}>
|
||||
@@ -596,7 +596,7 @@ export default function Portfolio() {
|
||||
<YAxis tick={{ fill: '#475569', fontSize: 9 }} tickLine={false} axisLine={false}
|
||||
tickFormatter={v => `${v.toFixed(0)}€`} />
|
||||
<Tooltip contentStyle={{ background: '#0f1623', border: '1px solid #1e2d4d', fontSize: 11 }}
|
||||
formatter={(v: number) => [`${v.toFixed(2)}€`, 'P&L cumulé']} />
|
||||
formatter={(v: number) => [`${v.toFixed(2)}€`, 'Cumulative P&L']} />
|
||||
<ReferenceLine y={0} stroke="#475569" strokeDasharray="4 4" />
|
||||
<Area type="monotone" dataKey="cumulative" stroke="#10b981" fill="url(#pnl-grad)" strokeWidth={2} dot={false} />
|
||||
</AreaChart>
|
||||
@@ -615,7 +615,7 @@ export default function Portfolio() {
|
||||
</>
|
||||
) : (
|
||||
<div className="h-40 flex items-center justify-center text-slate-600 text-sm">
|
||||
Clôturer des positions pour voir la courbe P&L
|
||||
Close positions to see the P&L curve
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user