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:
OpenSquared
2026-06-22 09:06:37 +02:00
parent f8a0a6d023
commit dcbc9f19fc
24 changed files with 1443 additions and 1444 deletions

View File

@@ -7,39 +7,39 @@ import clsx from 'clsx'
const API = ''
const SOURCE_CATEGORIES = {
'Flux RSS actifs': ['reuters_world', 'reuters_business', 'reuters_energy', 'ap_top', 'aljazeera', 'ft', 'bloomberg'],
'Données économiques': ['newsapi', 'gdelt', 'eia', 'fred', 'usda'],
'Santé & Catastrophes': ['who', 'emdat'],
'Réseaux sociaux': ['twitter_trump'],
'Active RSS Feeds': ['reuters_world', 'reuters_business', 'reuters_energy', 'ap_top', 'aljazeera', 'ft', 'bloomberg'],
'Economic Data': ['newsapi', 'gdelt', 'eia', 'fred', 'usda'],
'Health & Disasters': ['who', 'emdat'],
'Social Media': ['twitter_trump'],
}
const SOURCE_DOCS: Record<string, { description: string; link?: string; cost: string }> = {
reuters_world: { description: 'Actualités mondiales Reuters', cost: 'Gratuit' },
reuters_business: { description: 'Business et marchés Reuters', cost: 'Gratuit' },
reuters_energy: { description: 'Énergie et commodités Reuters', cost: 'Gratuit' },
ap_top: { description: 'Associated Press — Top Stories', cost: 'Gratuit' },
aljazeera: { description: 'Al Jazeera — couverture Moyen-Orient/Monde', cost: 'Gratuit' },
ft: { description: 'Financial Times — finance internationale', cost: 'Gratuit' },
bloomberg: { description: 'Bloomberg Markets RSS', cost: 'Gratuit' },
newsapi: { description: '100 req/jour gratuit — actualités mondiales multi-sources', link: 'https://newsapi.org', cost: '100 req/j gratuit' },
gdelt: { description: 'Base de données géopolitique mondiale — 300K events/jour', link: 'https://gdeltproject.org', cost: 'Gratuit total' },
eia: { description: 'US Energy Information Administration — données pétrole/gaz hebdo', link: 'https://www.eia.gov/opendata', cost: 'Gratuit avec clé' },
fred: { description: 'Federal Reserve St. Louis — macro US (PIB, inflation, emploi)', link: 'https://fred.stlouisfed.org/docs/api/fred/', cost: 'Gratuit avec clé' },
usda: { description: 'USDA — rapports agricoles officiels US', cost: 'Gratuit' },
who: { description: 'OMS — alertes sanitaires mondiales RSS', cost: 'Gratuit' },
emdat: { description: 'EM-DAT — base de données catastrophes naturelles', link: 'https://www.emdat.be', cost: 'Inscription gratuite' },
twitter_trump: { description: 'Flux X/Twitter Trump — discours, annonces tarifs', cost: 'API payante ($100/mois+)' },
reuters_world: { description: 'Reuters World News', cost: 'Free' },
reuters_business: { description: 'Reuters Business & Markets', cost: 'Free' },
reuters_energy: { description: 'Reuters Energy & Commodities', cost: 'Free' },
ap_top: { description: 'Associated Press — Top Stories', cost: 'Free' },
aljazeera: { description: 'Al Jazeera — Middle East/World coverage', cost: 'Free' },
ft: { description: 'Financial Times — international finance', cost: 'Free' },
bloomberg: { description: 'Bloomberg Markets RSS', cost: 'Free' },
newsapi: { description: '100 req/day free — multi-source world news', link: 'https://newsapi.org', cost: '100 req/d free' },
gdelt: { description: 'Global geopolitical database — 300K events/day', link: 'https://gdeltproject.org', cost: 'Totally free' },
eia: { description: 'US Energy Information Administration — weekly oil/gas data', link: 'https://www.eia.gov/opendata', cost: 'Free with key' },
fred: { description: 'Federal Reserve St. Louis — US macro (GDP, inflation, employment)', link: 'https://fred.stlouisfed.org/docs/api/fred/', cost: 'Free with key' },
usda: { description: 'USDA — official US agricultural reports', cost: 'Free' },
who: { description: 'WHO — global health alerts RSS', cost: 'Free' },
emdat: { description: 'EM-DAT — natural disasters database', link: 'https://www.emdat.be', cost: 'Free registration' },
twitter_trump: { description: 'X/Twitter Trump feed — speeches, tariff announcements', cost: 'Paid API ($100/mo+)' },
}
// ── Risk Profiles Component ───────────────────────────────────────────────────
const PROFILE_COLORS = [
{ value: '#22c55e', label: 'Vert' },
{ value: '#3b82f6', label: 'Bleu' },
{ value: '#22c55e', label: 'Green' },
{ value: '#3b82f6', label: 'Blue' },
{ value: '#f97316', label: 'Orange' },
{ value: '#ef4444', label: 'Rouge' },
{ value: '#8b5cf6', label: 'Violet' },
{ value: '#eab308', label: 'Jaune' },
{ value: '#ef4444', label: 'Red' },
{ value: '#8b5cf6', label: 'Purple' },
{ value: '#eab308', label: 'Yellow' },
]
function NextRunCountdown({ nextRunAt }: { nextRunAt: string }) {
@@ -64,16 +64,16 @@ function NextRunCountdown({ nextRunAt }: { nextRunAt: string }) {
return (
<div className="flex items-center gap-2 text-emerald-400/80">
<span className="inline-block w-1.5 h-1.5 rounded-full bg-emerald-400 animate-pulse" />
<span>Prochain cycle dans <span className="font-mono font-semibold text-emerald-300">{remaining}</span></span>
<span className="text-slate-600">({absTime} heure locale)</span>
<span>Next cycle in <span className="font-mono font-semibold text-emerald-300">{remaining}</span></span>
<span className="text-slate-600">({absTime} local time)</span>
</div>
)
}
function evNetLabel(evNet: number): { text: string; cls: string } {
if (evNet > 0.05) return { text: `EV nette +${(evNet * 100).toFixed(0)}%`, cls: 'text-emerald-400' }
if (evNet >= -0.01) return { text: 'EV nette ≈ 0', cls: 'text-yellow-400' }
return { text: `EV nette ${(evNet * 100).toFixed(0)}%`, cls: 'text-slate-600' }
if (evNet > 0.05) return { text: `Net EV +${(evNet * 100).toFixed(0)}%`, cls: 'text-emerald-400' }
if (evNet >= -0.01) return { text: 'Net EV ≈ 0', cls: 'text-yellow-400' }
return { text: `Net EV ${(evNet * 100).toFixed(0)}%`, cls: 'text-slate-600' }
}
function ProfileRow({
@@ -113,7 +113,7 @@ function ProfileRow({
<div className="flex-1 min-w-0">
<div className="flex items-center gap-2 flex-wrap">
<span className={clsx('text-sm font-semibold', enabled ? 'text-slate-200' : 'text-slate-600')}>{profile.name}</span>
{!enabled && <span className="text-[10px] text-slate-700 italic">désactivé</span>}
{!enabled && <span className="text-[10px] text-slate-700 italic">disabled</span>}
<span className="text-xs text-slate-500">Score <span className="text-slate-300 font-mono">{profile.min_score}</span></span>
<span className="text-xs text-slate-500">Gain <span className="text-slate-300 font-mono">{profile.min_gain_pct}%</span></span>
<span className={clsx('text-[11px] font-mono', fevc)}>{fev}</span>
@@ -138,7 +138,7 @@ function ProfileRow({
<div className="bg-dark-700/60 rounded-lg p-3 border border-slate-600/40 space-y-3">
<div className="grid grid-cols-3 gap-3">
<div>
<label className="text-[10px] text-slate-500 mb-1 block">Nom du profil</label>
<label className="text-[10px] text-slate-500 mb-1 block">Profile name</label>
<input value={name} onChange={e => setName(e.target.value)}
className="w-full bg-dark-800 border border-slate-700 rounded px-2 py-1 text-sm text-white" />
</div>
@@ -158,7 +158,7 @@ function ProfileRow({
{/* Live EV preview */}
<div className="bg-dark-800 rounded px-3 py-2 text-xs flex items-center gap-4 flex-wrap">
<span className="text-slate-500">À la frontière (score={score}, gain={gain}%)</span>
<span className="text-slate-500">At the frontier (score={score}, gain={gain}%)</span>
<span className={clsx('font-mono font-bold', evCls)}>{evText}</span>
<span className="text-slate-600">Trade Score = <span className="text-slate-400">{trade_score.toFixed(1)}</span>/100</span>
<span className="text-slate-600">
@@ -179,14 +179,14 @@ function ProfileRow({
<button onClick={() => setEnabled(!enabled)}
className={clsx('text-xs px-2 py-0.5 rounded border transition-all',
enabled ? 'border-emerald-600/40 text-emerald-400 bg-emerald-900/20' : 'border-slate-700 text-slate-600')}>
{enabled ? '✓ Activé' : '○ Désactivé'}
{enabled ? '✓ Enabled' : '○ Disabled'}
</button>
<div className="ml-auto flex gap-2">
<button onClick={() => setEditing(false)}
className="text-xs text-slate-500 hover:text-slate-300 px-2 py-1">Annuler</button>
className="text-xs text-slate-500 hover:text-slate-300 px-2 py-1">Cancel</button>
<button onClick={save}
className="text-xs bg-blue-600 hover:bg-blue-500 text-white px-3 py-1 rounded font-semibold">
Sauvegarder
Save
</button>
</div>
</div>
@@ -227,22 +227,22 @@ function RiskProfilesCard() {
<div className="card bg-dark-700/20 mb-4">
<div className="flex items-center justify-between mb-3">
<div>
<div className="text-sm font-semibold text-slate-300">Profils de risque</div>
<div className="text-sm font-semibold text-slate-300">Risk Profiles</div>
<div className="text-[10px] text-slate-600 mt-0.5">
Un trade est loggé dans le journal s'il passe <span className="text-slate-500">au moins un</span> profil activé
· Formule : EV nette = (score/100 × gain/100) (1 score/100)
A trade is logged in the journal if it passes <span className="text-slate-500">at least one</span> enabled profile
· Formula: Net EV = (score/100 × gain/100) (1 score/100)
</div>
</div>
<button onClick={() => setShowNew(true)}
className="flex items-center gap-1 text-xs border border-blue-500/40 text-blue-400 hover:bg-blue-900/20 px-2.5 py-1 rounded">
<Plus className="w-3 h-3" /> Nouveau profil
<Plus className="w-3 h-3" /> New profile
</button>
</div>
{isLoading ? (
<div className="h-16 animate-pulse bg-dark-700 rounded" />
) : profiles.length === 0 ? (
<div className="text-center py-6 text-slate-600 text-sm">Aucun profil — tous les trades seront ignorés</div>
<div className="text-center py-6 text-slate-600 text-sm">No profiles all trades will be ignored</div>
) : (
<div className="space-y-1">
{profiles.map((p: any) => (
@@ -257,13 +257,13 @@ function RiskProfilesCard() {
{showNew && (
<div className="bg-dark-700/60 rounded-lg p-3 border border-blue-700/30 space-y-3 mt-3">
<div className="flex items-center justify-between">
<span className="text-xs font-semibold text-blue-400">Nouveau profil</span>
<span className="text-xs font-semibold text-blue-400">New profile</span>
<button onClick={() => setShowNew(false)} className="text-slate-600 hover:text-slate-400"><X className="w-3.5 h-3.5" /></button>
</div>
<div className="grid grid-cols-3 gap-3">
<div>
<label className="text-[10px] text-slate-500 mb-1 block">Nom</label>
<input value={newName} onChange={e => setNewName(e.target.value)} placeholder="ex: Ultra-risqué"
<label className="text-[10px] text-slate-500 mb-1 block">Name</label>
<input value={newName} onChange={e => setNewName(e.target.value)} placeholder="e.g. Ultra-aggressive"
className="w-full bg-dark-800 border border-slate-700 rounded px-2 py-1 text-sm text-white placeholder:text-slate-700" />
</div>
<div>
@@ -282,7 +282,7 @@ function RiskProfilesCard() {
<div className="bg-dark-800 rounded px-3 py-2 text-xs flex items-center gap-4 flex-wrap">
<span className={clsx('font-mono font-bold', evNetLabel(nEvNet).cls)}>{evNetLabel(nEvNet).text}</span>
<span className="text-slate-600">Trade Score = <span className="text-slate-400">{nTradeScore.toFixed(1)}</span>/100</span>
<span className="text-slate-700">Score min pour EV=0 avec gain {newGain}% : <span className="text-slate-500 font-mono">{nMinScore}</span>/100</span>
<span className="text-slate-700">Min score for EV=0 with gain {newGain}% : <span className="text-slate-500 font-mono">{nMinScore}</span>/100</span>
</div>
<div className="flex items-center gap-3">
@@ -295,10 +295,10 @@ function RiskProfilesCard() {
))}
</div>
<div className="ml-auto flex gap-2">
<button onClick={() => setShowNew(false)} className="text-xs text-slate-500 hover:text-slate-300 px-2 py-1">Annuler</button>
<button onClick={() => setShowNew(false)} className="text-xs text-slate-500 hover:text-slate-300 px-2 py-1">Cancel</button>
<button onClick={saveNew} disabled={!newName.trim()}
className="text-xs bg-blue-600 hover:bg-blue-500 disabled:opacity-40 text-white px-3 py-1 rounded font-semibold">
Créer
Create
</button>
</div>
</div>
@@ -308,7 +308,7 @@ function RiskProfilesCard() {
{/* Frontier visualization */}
{profiles.length > 0 && (
<div className="mt-3 pt-3 border-t border-slate-700/30">
<div className="text-[10px] text-slate-600 mb-2">Frontière d'acceptation chaque point représente le minimum requis par profil</div>
<div className="text-[10px] text-slate-600 mb-2">Acceptance frontier each point represents the minimum required per profile</div>
<div className="flex flex-wrap gap-2">
{profiles.filter((p: any) => p.enabled).map((p: any) => {
const { text: ev, cls } = evNetLabel(p.ev_net_at_frontier ?? 0)
@@ -412,15 +412,15 @@ export default function Config() {
mutationFn: (cfg: any) => fetch(`${API}/api/var/scheduler/config`, {
method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(cfg),
}).then(r => r.json()),
onSuccess: () => { refetchSched(); setSavedMsg('Schedulers sauvegardés'); setTimeout(() => setSavedMsg(''), 2000) },
onSuccess: () => { refetchSched(); setSavedMsg('Schedulers saved'); setTimeout(() => setSavedMsg(''), 2000) },
})
const { mutate: triggerVarNow, isPending: triggeringVar } = useMutation({
mutationFn: () => fetch(`${API}/api/var/run-now`, { method: 'POST' }).then(r => r.json()),
onSuccess: () => { setSavedMsg('Snapshot VaR sauvegardé'); setTimeout(() => setSavedMsg(''), 2000) },
onSuccess: () => { setSavedMsg('VaR snapshot saved'); setTimeout(() => setSavedMsg(''), 2000) },
})
const { mutate: triggerPnlNow, isPending: triggeringPnl } = useMutation({
mutationFn: () => fetch(`${API}/api/var/pnl/run-now`, { method: 'POST' }).then(r => r.json()),
onSuccess: () => { setSavedMsg('Snapshot PnL sauvegardé'); setTimeout(() => setSavedMsg(''), 2000) },
onSuccess: () => { setSavedMsg('PnL snapshot saved'); setTimeout(() => setSavedMsg(''), 2000) },
})
// Auto-cycle local state
@@ -486,7 +486,7 @@ export default function Config() {
const saveSources = () => {
updateSources(displaySources, {
onSuccess: () => { setSavedMsg('Sources sauvegardées'); setTimeout(() => setSavedMsg(''), 2000) }
onSuccess: () => { setSavedMsg('Sources saved'); setTimeout(() => setSavedMsg(''), 2000) }
})
}
@@ -498,7 +498,7 @@ export default function Config() {
if (fredKey) keys.fred_api_key = fredKey
updateApiKeys(keys, {
onSuccess: () => {
setSavedMsg('Clés API sauvegardées')
setSavedMsg('API keys saved')
setTimeout(() => setSavedMsg(''), 2000)
setOpenaiKey(''); setNewsapiKey(''); setEiaKey(''); setFredKey('')
}
@@ -506,11 +506,11 @@ export default function Config() {
}
const CONFIG_TABS = [
{ key: 'ia', label: 'IA & Analyse', icon: Brain },
{ key: 'ia', label: 'AI & Analysis', icon: Brain },
{ key: 'cycle', label: 'Auto-Cycle & Logging', icon: Zap },
{ key: 'sources', label: 'Sources', icon: Globe },
{ key: 'options', label: 'Options — Paramètres', icon: TrendingUp },
{ key: 'profiles', label: 'Profils de risque', icon: SlidersHorizontal },
{ key: 'options', label: 'Options — Settings', icon: TrendingUp },
{ key: 'profiles', label: 'Risk Profiles', icon: SlidersHorizontal },
] as const
return (
@@ -520,7 +520,7 @@ export default function Config() {
<h1 className="text-xl font-bold text-white flex items-center gap-2">
<Settings className="w-5 h-5 text-blue-400" /> Configuration
</h1>
<p className="text-xs text-slate-500 mt-0.5">Clés API, sources, paramètres IA &amp; cycle</p>
<p className="text-xs text-slate-500 mt-0.5">API keys, sources, AI &amp; cycle settings</p>
</div>
{savedMsg && (
<div className="flex items-center gap-2 text-emerald-400 text-sm bg-emerald-900/10 border border-emerald-700/30 rounded px-3 py-1.5">
@@ -549,30 +549,30 @@ export default function Config() {
<div className="space-y-4">
{/* AI Status */}
<div className={clsx('card', aiStatus?.enabled ? 'border-emerald-700/40' : 'border-slate-700/40')}>
<div className="section-title flex items-center gap-1"><Key className="w-3 h-3" /> Statut IA</div>
<div className="section-title flex items-center gap-1"><Key className="w-3 h-3" /> AI Status</div>
<div className="flex items-center gap-3 mb-3">
{aiStatus?.enabled ? (
<><CheckCircle className="w-5 h-5 text-emerald-400" />
<div><div className="text-sm text-emerald-400 font-semibold">OpenAI Connecté</div>
<div><div className="text-sm text-emerald-400 font-semibold">OpenAI Connected</div>
<div className="text-xs text-slate-500">GPT-4o + GPT-4o-mini</div></div></>
) : (
<><XCircle className="w-5 h-5 text-red-400" />
<div><div className="text-sm text-red-400 font-semibold">OpenAI Non configuré</div>
<div className="text-xs text-slate-500">Entrer la clé ci-dessous</div></div></>
<div><div className="text-sm text-red-400 font-semibold">OpenAI Not configured</div>
<div className="text-xs text-slate-500">Enter key below</div></div></>
)}
</div>
<div className="text-xs text-slate-600 space-y-1">
<div> Analyse de discours (Trump, Powell...)</div>
<div> Classification IA des actualités</div>
<div> Évaluation de patterns</div>
<div> Top 10 idées contextualisées</div>
<div> Speech analysis (Trump, Powell...)</div>
<div> AI news classification</div>
<div> Pattern evaluation</div>
<div> Top 10 contextualized ideas</div>
</div>
</div>
{/* OpenAI API Key */}
<div className="card">
<div className="flex items-center justify-between mb-3">
<div className="section-title mb-0 flex items-center gap-1"><Key className="w-3 h-3" /> Clé OpenAI</div>
<div className="section-title mb-0 flex items-center gap-1"><Key className="w-3 h-3" /> OpenAI Key</div>
<button onClick={() => setShowKeys(!showKeys)} className="text-slate-500 hover:text-slate-300">
{showKeys ? <EyeOff className="w-3.5 h-3.5" /> : <Eye className="w-3.5 h-3.5" />}
</button>
@@ -582,7 +582,7 @@ export default function Config() {
<div className="flex items-center justify-between mb-1">
<label className="text-xs text-slate-500">OpenAI API Key</label>
<span className={clsx('text-xs', aiStatus?.enabled ? 'text-emerald-400' : 'text-slate-600')}>
{aiStatus?.enabled ? '✓ Actif' : '○ Inactif'}
{aiStatus?.enabled ? '✓ Active' : '○ Inactive'}
</span>
</div>
<input
@@ -599,7 +599,7 @@ export default function Config() {
className="w-full bg-blue-600 hover:bg-blue-500 disabled:opacity-40 text-white rounded py-1.5 text-xs font-semibold flex items-center justify-center gap-1.5"
>
<Save className="w-3.5 h-3.5" />
{savingKeys ? 'Sauvegarde...' : 'Sauvegarder la clé'}
{savingKeys ? 'Saving...' : 'Save key'}
</button>
</div>
</div>
@@ -608,11 +608,11 @@ export default function Config() {
{/* Right: Analysis params */}
<div className="card">
<h2 className="text-base font-bold text-white flex items-center gap-2 mb-4">
<SlidersHorizontal className="w-4 h-4 text-blue-400" /> Paramètres d'analyse IA
<SlidersHorizontal className="w-4 h-4 text-blue-400" /> AI Analysis Settings
</h2>
<div className="grid grid-cols-2 gap-4 mb-4">
<div>
<label className="text-xs text-slate-500 mb-1 block">Top N résultats par défaut</label>
<label className="text-xs text-slate-500 mb-1 block">Default top N results</label>
<div className="flex gap-1">
{[5, 10, 15, 20].map(n => (
<button key={n} onClick={() => setAnalysisTopN(n)}
@@ -626,46 +626,46 @@ export default function Config() {
</div>
</div>
<div>
<label className="text-xs text-slate-500 mb-1 block">Catégorie par défaut</label>
<label className="text-xs text-slate-500 mb-1 block">Default category</label>
<select value={analysisCategoryDefault} onChange={e => setAnalysisCategoryDefault(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">
<option value="all">Toutes les catégories</option>
<option value="energy">Énergie</option>
<option value="metals">Métaux</option>
<option value="all">All categories</option>
<option value="energy">Energy</option>
<option value="metals">Metals</option>
<option value="agriculture">Agriculture</option>
<option value="indices">Indices</option>
<option value="equities">Actions</option>
<option value="equities">Equities</option>
<option value="forex">Forex</option>
</select>
</div>
</div>
<div className="mb-4">
<div className="flex items-center justify-between mb-1">
<label className="text-xs text-slate-500">Template d'analyse (prompt GPT-4o)</label>
<label className="text-xs text-slate-500">Analysis template (GPT-4o prompt)</label>
<button onClick={() => setAnalysisTemplate('')} className="text-xs text-slate-600 hover:text-slate-400">
Par faut
Default
</button>
</div>
<textarea
value={analysisTemplate}
onChange={e => setAnalysisTemplate(e.target.value)}
rows={8}
placeholder="Laisser vide pour utiliser le template par défaut..."
placeholder="Leave empty to use default template..."
className="w-full bg-dark-700 border border-slate-700 rounded px-3 py-2 text-sm text-white font-mono focus:outline-none focus:border-blue-500 resize-y"
/>
<div className="text-xs text-slate-600 mt-1">
Contexte marché (prix, IV, variation 1j) et news filtrées sont injectés automatiquement.
Market context (prices, IV, 1d change) and filtered news are automatically injected.
</div>
</div>
<button
onClick={() => saveAnalysis(
{ top_n: analysisTopN, category_filter: analysisCategoryDefault, template: analysisTemplate || undefined },
{ onSuccess: () => { setSavedMsg('Paramètres d\'analyse sauvegardés'); setTimeout(() => setSavedMsg(''), 2000) } }
{ onSuccess: () => { setSavedMsg('Analysis settings saved'); setTimeout(() => setSavedMsg(''), 2000) } }
)}
disabled={savingAnalysis}
className="flex items-center gap-1.5 bg-blue-600 hover:bg-blue-500 disabled:opacity-40 text-white px-4 py-2 rounded text-sm font-semibold">
<Save className="w-4 h-4" />
{savingAnalysis ? 'Sauvegarde...' : 'Sauvegarder'}
{savingAnalysis ? 'Saving...' : 'Save'}
</button>
</div>
</div>
@@ -677,11 +677,11 @@ export default function Config() {
{/* Score min threshold — prominent */}
<div className="card border-blue-700/40 bg-blue-900/5">
<h2 className="text-base font-bold text-white flex items-center gap-2 mb-1">
<SlidersHorizontal className="w-4 h-4 text-blue-400" /> Score minimum pour le cycle
<SlidersHorizontal className="w-4 h-4 text-blue-400" /> Minimum score for the cycle
</h2>
<p className="text-xs text-slate-500 mb-4">
Pré-filtre du cycle les patterns avec un score inférieur ne sont pas soumis à l'analyse.
Les profils de risque déterminent ensuite quels trades sont loggés.
Cycle pre-filter patterns with a score below this are not submitted for analysis.
Risk profiles then determine which trades get logged.
</p>
<div className="grid grid-cols-2 gap-6">
<div>
@@ -694,12 +694,12 @@ export default function Config() {
onChange={e => setMinScore(parseInt(e.target.value))}
className="w-full accent-blue-500" />
<div className="flex justify-between text-[10px] text-slate-600 mt-1">
<span>0 (tous)</span><span>80 (très sélectif)</span>
<span>0 (all)</span><span>80 (very selective)</span>
</div>
<div className="text-xs text-slate-500 mt-2">
{minScore === 0
? 'Tous les patterns analysés par le cycle'
: `Seuls les patterns avec score ≥ ${minScore} sont soumis au cycle`}
? 'All patterns processed by the cycle'
: `Only patterns with score ≥ ${minScore} are submitted to the cycle`}
</div>
</div>
<div>
@@ -716,10 +716,10 @@ export default function Config() {
</div>
<div className="text-xs text-slate-500 mt-2">
{minEv === 0
? 'Toute EV nette acceptée'
? 'Any net EV accepted'
: minEv > 0
? `EV nette ≥ +${(minEv * 100).toFixed(0)}% requise`
: `Filtre désactivé (EV < ${(minEv * 100).toFixed(0)}%)`}
? `Net EV ≥ +${(minEv * 100).toFixed(0)}% required`
: `Filter disabled (EV < ${(minEv * 100).toFixed(0)}%)`}
</div>
</div>
</div>
@@ -735,38 +735,38 @@ export default function Config() {
<div className="flex items-center gap-2 text-xs">
{cs.running ? (
<span className="flex items-center gap-1 text-blue-400 bg-blue-900/30 border border-blue-700/30 px-2 py-0.5 rounded animate-pulse">
<RefreshCw className="w-3 h-3 animate-spin" /> En cours...
<RefreshCw className="w-3 h-3 animate-spin" /> Running...
</span>
) : cs.scheduler_alive ? (
<span className="text-emerald-400 bg-emerald-900/30 border border-emerald-700/30 px-2 py-0.5 rounded">
Scheduler actif
Scheduler active
</span>
) : (
<span className="text-slate-600 bg-dark-700 border border-slate-700/30 px-2 py-0.5 rounded">
Scheduler inactif
Scheduler inactive
</span>
)}
</div>
)}
</div>
<p className="text-xs text-slate-500 mb-4">
Toutes les N heures : suggère de nouveaux patterns, filtre les doublons, score tout, log les prix et génère un commentaire IA.
Every N hours: suggests new patterns, deduplicates, scores everything, logs prices, and generates an AI comment.
</p>
<div className="grid grid-cols-3 gap-4 mb-4">
<div>
<label className="text-xs text-slate-500 mb-2 block">Activer l'auto-cycle</label>
<label className="text-xs text-slate-500 mb-2 block">Enable auto-cycle</label>
<button
onClick={() => setCycleEnabled(!cycleEnabled)}
className={clsx('w-full py-2 rounded border text-sm font-semibold transition-all', {
'bg-blue-600 border-blue-500 text-white': cycleEnabled,
'bg-dark-700 border-slate-700 text-slate-400 hover:border-slate-500': !cycleEnabled,
})}>
{cycleEnabled ? '✓ Activé' : '○ Désactivé'}
{cycleEnabled ? '✓ Enabled' : '○ Disabled'}
</button>
</div>
<div>
<label className="text-xs text-slate-500 mb-2 block">Intervalle (heures)</label>
<label className="text-xs text-slate-500 mb-2 block">Interval (hours)</label>
<div className="flex gap-1">
{[1, 3, 6, 12].map(h => (
<button key={h} onClick={() => setCycleHours(h)}
@@ -781,7 +781,7 @@ export default function Config() {
</div>
<div>
<label className="text-xs text-slate-500 mb-2 block">
Similarité max ({Math.round(cycleSimilarity * 100)}%)
Max similarity ({Math.round(cycleSimilarity * 100)}%)
</label>
<input type="range" min="0.1" max="0.8" step="0.05"
value={cycleSimilarity}
@@ -796,7 +796,7 @@ export default function Config() {
<div className="grid grid-cols-2 gap-4 mb-4">
<div>
<label className="text-xs text-slate-500 mb-2 block">
Rétention Journal ({retentionDays}j)
Journal retention ({retentionDays}d)
</label>
<div className="flex gap-1">
{[30, 60, 90, 180].map(d => (
@@ -812,7 +812,7 @@ export default function Config() {
</div>
<div>
<label className="text-xs text-slate-500 mb-2 block">
Seuil maturité ({maturityThreshold}%)
Maturity threshold ({maturityThreshold}%)
</label>
<div className="flex gap-1">
{[20, 30, 35, 50].map(p => (
@@ -832,8 +832,8 @@ export default function Config() {
<div className="border border-slate-700/50 rounded-lg p-3 mb-4 bg-dark-700/30">
<div className="flex items-center justify-between mb-3">
<div>
<span className="text-xs font-semibold text-slate-300">Cycles weekend</span>
<p className="text-[10px] text-slate-500 mt-0.5">Sam/Dim Globex ouvre dim ~18h UTC. Heures en UTC.</p>
<span className="text-xs font-semibold text-slate-300">Weekend cycles</span>
<p className="text-[10px] text-slate-500 mt-0.5">Sat/Sun Globex opens Sun ~18h UTC. Times in UTC.</p>
</div>
<button
onClick={() => setWeekendEnabled(!weekendEnabled)}
@@ -841,12 +841,12 @@ export default function Config() {
'bg-amber-600/20 border-amber-500/50 text-amber-300': weekendEnabled,
'bg-dark-700 border-slate-700 text-slate-500 hover:border-slate-500': !weekendEnabled,
})}>
{weekendEnabled ? '✓ Activé' : '○ Désactivé'}
{weekendEnabled ? '✓ Enabled' : '○ Disabled'}
</button>
</div>
{weekendEnabled && (
<div>
<label className="text-[10px] text-slate-500 mb-1.5 block">Horaires UTC (cliquer pour activer/désactiver)</label>
<label className="text-[10px] text-slate-500 mb-1.5 block">UTC times (click to enable/disable)</label>
<div className="flex gap-1.5 flex-wrap">
{['06:00', '08:00', '12:00', '18:00', '22:00', '00:00'].map(t => {
const active = weekendTimes.includes(t)
@@ -865,7 +865,7 @@ export default function Config() {
})}
</div>
{weekendTimes.length === 0 && (
<p className="text-[10px] text-red-400 mt-1">Sélectionner au moins un horaire</p>
<p className="text-[10px] text-red-400 mt-1">Select at least one time</p>
)}
</div>
)}
@@ -874,14 +874,14 @@ export default function Config() {
{cs?.last_cycle && (
<div className="card bg-dark-700/50 mb-2 text-xs space-y-2">
<div className="flex items-center gap-4 flex-wrap">
<span className="text-slate-500">Dernier cycle :</span>
<span className="text-slate-500">Last cycle:</span>
<span className="text-slate-300">{cs.last_cycle.started_at?.slice(0, 16)} UTC</span>
<span className={clsx('badge', cs.last_cycle.status === 'completed' ? 'badge-green' : 'badge-red')}>
{cs.last_cycle.status}
</span>
<span className="text-slate-500">+{cs.last_cycle.patterns_added} patterns</span>
<span className="text-slate-500">{cs.last_cycle.patterns_scored} scorés</span>
<span className="text-slate-500">Géo: {cs.last_cycle.geo_score}</span>
<span className="text-slate-500">{cs.last_cycle.patterns_scored} scored</span>
<span className="text-slate-500">Geo: {cs.last_cycle.geo_score}</span>
<span className="text-slate-500 capitalize">{cs.last_cycle.dominant_regime}</span>
</div>
</div>
@@ -906,19 +906,19 @@ export default function Config() {
weekend_cycle_enabled: weekendEnabled,
weekend_cycle_times: weekendTimes.length > 0 ? weekendTimes.join(',') : '08:00,22:00',
},
{ onSuccess: () => { refetchCycle(); setSavedMsg('Auto-cycle configuré'); setTimeout(() => setSavedMsg(''), 2000) } }
{ onSuccess: () => { refetchCycle(); setSavedMsg('Auto-cycle configured'); setTimeout(() => setSavedMsg(''), 2000) } }
)}
disabled={savingCycle}
className="flex items-center gap-1.5 bg-blue-600 hover:bg-blue-500 disabled:opacity-40 text-white px-4 py-2 rounded text-sm font-semibold">
<Save className="w-4 h-4" />
{savingCycle ? 'Sauvegarde...' : 'Appliquer'}
{savingCycle ? 'Saving...' : 'Apply'}
</button>
<button
onClick={() => triggerCycle(undefined, { onSuccess: () => { refetchCycle(); setSavedMsg('Cycle lancé !'); setTimeout(() => setSavedMsg(''), 3000) } })}
onClick={() => triggerCycle(undefined, { onSuccess: () => { refetchCycle(); setSavedMsg('Cycle started!'); setTimeout(() => setSavedMsg(''), 3000) } })}
disabled={triggeringCycle || !aiStatus?.enabled}
className="flex items-center gap-1.5 border border-blue-500/50 text-blue-400 hover:bg-blue-900/20 disabled:opacity-40 px-4 py-2 rounded text-sm font-semibold">
<RefreshCw className={clsx('w-4 h-4', triggeringCycle && 'animate-spin')} />
Lancer maintenant
Run now
</button>
</div>
</div>
@@ -929,8 +929,8 @@ export default function Config() {
<Gauge className="w-4 h-4 text-red-400" /> Schedulers VaR &amp; PnL
</h2>
<p className="text-xs text-slate-500 mb-4">
Calcule et sauvegarde automatiquement les snapshots VaR (Black-Scholes delta) et PnL à intervalles définis.
Les données sont accessibles depuis la page VaR Analyse avec un historique complet.
Automatically computes and saves VaR (Black-Scholes delta) and PnL snapshots at defined intervals.
Data is accessible from the VaR Analysis page with full history.
</p>
<div className="grid grid-cols-2 gap-6 mb-4">
{/* VaR scheduler */}
@@ -939,21 +939,21 @@ export default function Config() {
<Gauge className="w-3.5 h-3.5 text-red-400" />
<span className="text-sm font-semibold text-slate-300">Scheduler VaR</span>
{schedStatus?.var?.alive
? <span className="text-[10px] bg-emerald-900/30 text-emerald-400 border border-emerald-700/30 px-1.5 py-0.5 rounded ml-auto"> Actif</span>
: <span className="text-[10px] bg-dark-700 text-slate-600 border border-slate-700/30 px-1.5 py-0.5 rounded ml-auto"> Inactif</span>}
? <span className="text-[10px] bg-emerald-900/30 text-emerald-400 border border-emerald-700/30 px-1.5 py-0.5 rounded ml-auto"> Active</span>
: <span className="text-[10px] bg-dark-700 text-slate-600 border border-slate-700/30 px-1.5 py-0.5 rounded ml-auto"> Inactive</span>}
</div>
<div>
<label className="text-xs text-slate-500 mb-1 block">Activer</label>
<label className="text-xs text-slate-500 mb-1 block">Enable</label>
<button onClick={() => setVarSchedEnabled(!varSchedEnabled)}
className={clsx('w-full py-2 rounded border text-sm font-semibold transition-all', {
'bg-red-700 border-red-600 text-white': varSchedEnabled,
'bg-dark-700 border-slate-700 text-slate-400': !varSchedEnabled,
})}>
{varSchedEnabled ? '✓ Activé' : '○ Désactivé'}
{varSchedEnabled ? '✓ Enabled' : '○ Disabled'}
</button>
</div>
<div>
<label className="text-xs text-slate-500 mb-1 block">Intervalle</label>
<label className="text-xs text-slate-500 mb-1 block">Interval</label>
<div className="flex gap-1">
{[1, 3, 6, 12, 24].map(h => (
<button key={h} onClick={() => setVarSchedHours(h)}
@@ -974,21 +974,21 @@ export default function Config() {
<DollarSign className="w-3.5 h-3.5 text-emerald-400" />
<span className="text-sm font-semibold text-slate-300">Scheduler PnL</span>
{schedStatus?.pnl?.alive
? <span className="text-[10px] bg-emerald-900/30 text-emerald-400 border border-emerald-700/30 px-1.5 py-0.5 rounded ml-auto"> Actif</span>
: <span className="text-[10px] bg-dark-700 text-slate-600 border border-slate-700/30 px-1.5 py-0.5 rounded ml-auto"> Inactif</span>}
? <span className="text-[10px] bg-emerald-900/30 text-emerald-400 border border-emerald-700/30 px-1.5 py-0.5 rounded ml-auto"> Active</span>
: <span className="text-[10px] bg-dark-700 text-slate-600 border border-slate-700/30 px-1.5 py-0.5 rounded ml-auto"> Inactive</span>}
</div>
<div>
<label className="text-xs text-slate-500 mb-1 block">Activer</label>
<label className="text-xs text-slate-500 mb-1 block">Enable</label>
<button onClick={() => setPnlSchedEnabled(!pnlSchedEnabled)}
className={clsx('w-full py-2 rounded border text-sm font-semibold transition-all', {
'bg-emerald-700 border-emerald-600 text-white': pnlSchedEnabled,
'bg-dark-700 border-slate-700 text-slate-400': !pnlSchedEnabled,
})}>
{pnlSchedEnabled ? '✓ Activé' : '○ Désactivé'}
{pnlSchedEnabled ? '✓ Enabled' : '○ Disabled'}
</button>
</div>
<div>
<label className="text-xs text-slate-500 mb-1 block">Intervalle</label>
<label className="text-xs text-slate-500 mb-1 block">Interval</label>
<div className="flex gap-1">
{[1, 2, 4, 8, 24].map(h => (
<button key={h} onClick={() => setPnlSchedHours(h)}
@@ -1010,21 +1010,21 @@ export default function Config() {
disabled={savingSched}
className="flex items-center gap-1.5 bg-blue-600 hover:bg-blue-500 disabled:opacity-40 text-white px-4 py-2 rounded text-sm font-semibold">
<Save className="w-4 h-4" />
{savingSched ? 'Sauvegarde…' : 'Appliquer'}
{savingSched ? 'Saving…' : 'Apply'}
</button>
<button
onClick={() => triggerVarNow()}
disabled={triggeringVar}
className="flex items-center gap-1.5 border border-red-500/50 text-red-400 hover:bg-red-900/20 disabled:opacity-40 px-4 py-2 rounded text-sm font-semibold">
<Gauge className={clsx('w-4 h-4', triggeringVar && 'animate-spin')} />
Snapshot VaR maintenant
VaR snapshot now
</button>
<button
onClick={() => triggerPnlNow()}
disabled={triggeringPnl}
className="flex items-center gap-1.5 border border-emerald-500/50 text-emerald-400 hover:bg-emerald-900/20 disabled:opacity-40 px-4 py-2 rounded text-sm font-semibold">
<DollarSign className={clsx('w-4 h-4', triggeringPnl && 'animate-spin')} />
Snapshot PnL maintenant
PnL snapshot now
</button>
</div>
</div>
@@ -1037,16 +1037,16 @@ export default function Config() {
{/* Data API Keys */}
<div className="card">
<div className="flex items-center justify-between mb-3">
<div className="section-title mb-0 flex items-center gap-1"><Key className="w-3 h-3" /> Clés API données</div>
<div className="section-title mb-0 flex items-center gap-1"><Key className="w-3 h-3" /> Data API Keys</div>
<button onClick={() => setShowKeys(!showKeys)} className="text-slate-500 hover:text-slate-300">
{showKeys ? <EyeOff className="w-3.5 h-3.5" /> : <Eye className="w-3.5 h-3.5" />}
</button>
</div>
<div className="grid grid-cols-3 gap-4">
{[
{ label: 'NewsAPI Key', value: newsapiKey, setter: setNewsapiKey, placeholder: 'Obtenir sur newsapi.org' },
{ label: 'EIA API Key', value: eiaKey, setter: setEiaKey, placeholder: 'Obtenir sur eia.gov' },
{ label: 'FRED API Key', value: fredKey, setter: setFredKey, placeholder: 'Obtenir sur fred.stlouisfed.org' },
{ label: 'NewsAPI Key', value: newsapiKey, setter: setNewsapiKey, placeholder: 'Get at newsapi.org' },
{ label: 'EIA API Key', value: eiaKey, setter: setEiaKey, placeholder: 'Get at eia.gov' },
{ label: 'FRED API Key', value: fredKey, setter: setFredKey, placeholder: 'Get at fred.stlouisfed.org' },
].map(({ label, value, setter, placeholder }) => (
<div key={label}>
<label className="text-xs text-slate-500 mb-1 block">{label}</label>
@@ -1065,7 +1065,7 @@ export default function Config() {
disabled={savingKeys || (!newsapiKey && !eiaKey && !fredKey)}
className="mt-3 flex items-center gap-1.5 bg-blue-600 hover:bg-blue-500 disabled:opacity-40 text-white px-4 py-2 rounded text-sm font-semibold">
<Save className="w-4 h-4" />
{savingKeys ? 'Sauvegarde...' : 'Sauvegarder les clés'}
{savingKeys ? 'Saving...' : 'Save keys'}
</button>
</div>
@@ -1093,14 +1093,14 @@ export default function Config() {
<div className="flex items-center gap-2">
<span className="text-xs text-white font-semibold">{src.name || key}</span>
<span className={clsx('badge text-xs', {
'badge-green': doc?.cost === 'Gratuit' || doc?.cost === 'Gratuit total',
'badge-blue': doc?.cost?.includes('gratuit') || doc?.cost?.includes('Inscription'),
'badge-yellow': doc?.cost?.includes('clé'),
'badge-red': doc?.cost?.includes('payante'),
'badge-green': doc?.cost === 'Free' || doc?.cost === 'Totally free',
'badge-blue': doc?.cost?.includes('free') || doc?.cost?.includes('registration'),
'badge-yellow': doc?.cost?.includes('key') || doc?.cost?.includes('key'),
'badge-red': doc?.cost?.includes('Paid'),
})}>
{doc?.cost}
</span>
{requiresKey && !keySet && <span className="badge badge-orange text-xs">Clé requise</span>}
{requiresKey && !keySet && <span className="badge badge-orange text-xs">Key required</span>}
</div>
<div className="text-xs text-slate-500 mt-0.5">{doc?.description}</div>
</div>
@@ -1121,7 +1121,7 @@ export default function Config() {
<button onClick={saveSources} disabled={savingSources || !localSources}
className="flex items-center gap-2 bg-blue-600 hover:bg-blue-500 disabled:opacity-40 text-white px-4 py-2 rounded text-sm font-semibold">
<Save className="w-4 h-4" />
{savingSources ? 'Sauvegarde...' : 'Appliquer les changements'}
{savingSources ? 'Saving...' : 'Apply changes'}
</button>
</div>
</>
@@ -1137,7 +1137,7 @@ export default function Config() {
<div className="card border-blue-700/30 bg-blue-900/5">
<div className="flex items-center justify-between mb-1">
<h2 className="text-base font-bold text-white flex items-center gap-2">
<ShieldAlert className="w-4 h-4 text-blue-400" /> IV Gate Filtres d'entrée
<ShieldAlert className="w-4 h-4 text-blue-400" /> IV Gate Entry Filters
</h2>
<button
onClick={() => setIvGateEnabled(!ivGateEnabled)}
@@ -1145,13 +1145,13 @@ export default function Config() {
'bg-blue-600 border-blue-500 text-white': ivGateEnabled,
'bg-dark-700 border-slate-700 text-slate-400': !ivGateEnabled,
})}>
{ivGateEnabled ? ' Gate actif' : ' Gate désactivé'}
{ivGateEnabled ? '✓ Gate active' : '○ Gate disabled'}
</button>
</div>
<p className="text-xs text-slate-500 mb-5">
Bloque automatiquement les trades avec verdict <span className="text-red-400 font-semibold">ALERT</span> avant
qu'ils soient loggés dans le journal. Un trade ALERT est une stratégie incompatible avec le régime de volatilité actuel
(ex: Long Call à IVR 100%). Les trades bloqués apparaissent dans les trades skippés avec la raison <span className="text-slate-400 font-mono">[IV_GATE]</span>.
Automatically blocks trades with verdict <span className="text-red-400 font-semibold">ALERT</span> before
they are logged in the journal. An ALERT trade is a strategy incompatible with the current volatility regime
(e.g. Long Call at IVR 100%). Blocked trades appear in skipped trades with reason <span className="text-slate-400 font-mono">[IV_GATE]</span>.
</p>
<div className={clsx('space-y-5 transition-opacity', !ivGateEnabled && 'opacity-40 pointer-events-none')}>
@@ -1160,7 +1160,7 @@ export default function Config() {
<div>
<div className="flex items-center justify-between mb-2">
<label className="text-xs text-slate-400 font-medium">
Seuil vol chère (IVR High)
High vol threshold (IVR High)
</label>
<span className={clsx('font-mono font-bold text-base', ivGateHigh >= 70 ? 'text-orange-400' : 'text-yellow-400')}>
{ivGateHigh}%
@@ -1186,7 +1186,7 @@ export default function Config() {
<div>
<div className="flex items-center justify-between mb-2">
<label className="text-xs text-slate-400 font-medium">
Seuil vol extrême (IVR Extreme)
Extreme vol threshold (IVR Extreme)
</label>
<span className="font-mono font-bold text-base text-red-400">{ivGateExtreme}%</span>
</div>
@@ -1202,8 +1202,8 @@ export default function Config() {
<span>50%</span><span>100%</span>
</div>
<div className="text-[10px] text-slate-500 mt-1.5 space-y-0.5">
<div>IVR &gt; {ivGateExtreme}% <span className="text-red-400 font-semibold">BLOQUÉ</span> naked long vol ALERT</div>
<div>Straddle / Strangle ALERT dès IVR &gt; {ivGateExtreme}% (pénalité ×1.3)</div>
<div>IVR &gt; {ivGateExtreme}% <span className="text-red-400 font-semibold">BLOCKED</span> naked long vol ALERT</div>
<div>Straddle / Strangle ALERT when IVR &gt; {ivGateExtreme}% (penalty ×1.3)</div>
</div>
</div>
</div>
@@ -1213,7 +1213,7 @@ export default function Config() {
<div>
<div className="flex items-center justify-between mb-2">
<label className="text-xs text-slate-400 font-medium">
Seuil skew put (protection chère)
Put skew threshold (expensive protection)
</label>
<span className="font-mono font-bold text-base text-orange-400">{ivGateSkew} pts</span>
</div>
@@ -1222,31 +1222,31 @@ export default function Config() {
onChange={e => setIvGateSkew(parseInt(e.target.value))}
className="w-full accent-orange-500" />
<div className="flex justify-between text-[10px] text-slate-600 mt-1">
<span>2pts (très sensible)</span><span>20pts (très tolérant)</span>
<span>2pts (very sensitive)</span><span>20pts (very tolerant)</span>
</div>
<div className="text-[10px] text-slate-500 mt-1.5">
Skew put &gt; {ivGateSkew}pts puts très chers, signal WARN sur Long Put
Put skew &gt; {ivGateSkew}pts puts very expensive, WARN signal on Long Put
</div>
</div>
{/* Visual summary */}
<div className="bg-dark-700/60 rounded-lg px-4 py-3 text-xs space-y-2">
<div className="text-slate-500 font-semibold mb-2 text-[10px] uppercase tracking-wide">Résumé du gate actuel</div>
<div className="text-slate-500 font-semibold mb-2 text-[10px] uppercase tracking-wide">Current gate summary</div>
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-emerald-500" />
<span className="text-slate-400">IVR &lt; <span className="text-emerald-400 font-mono">{ivGateHigh}%</span> Toute stratégie OK</span>
<span className="text-slate-400">IVR &lt; <span className="text-emerald-400 font-mono">{ivGateHigh}%</span> Any strategy OK</span>
</div>
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-yellow-500" />
<span className="text-slate-400">IVR <span className="text-yellow-400 font-mono">{ivGateHigh}{ivGateExtreme}%</span> Spreads préférés</span>
<span className="text-slate-400">IVR <span className="text-yellow-400 font-mono">{ivGateHigh}{ivGateExtreme}%</span> Spreads preferred</span>
</div>
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-red-500 animate-pulse" />
<span className="text-slate-400">IVR &gt; <span className="text-red-400 font-mono">{ivGateExtreme}%</span> Long naked <strong className="text-red-400">BLOQUÉ</strong></span>
<span className="text-slate-400">IVR &gt; <span className="text-red-400 font-mono">{ivGateExtreme}%</span> Long naked <strong className="text-red-400">BLOCKED</strong></span>
</div>
<div className="flex items-center gap-2">
<div className="w-2 h-2 rounded-full bg-orange-500" />
<span className="text-slate-400">Skew &gt; <span className="text-orange-400 font-mono">{ivGateSkew}pts</span> Puts chers (WARN)</span>
<span className="text-slate-400">Skew &gt; <span className="text-orange-400 font-mono">{ivGateSkew}pts</span> Expensive puts (WARN)</span>
</div>
</div>
</div>
@@ -1257,12 +1257,12 @@ export default function Config() {
onClick={() => saveOptionsGate(
{ iv_gate_enabled: ivGateEnabled, iv_gate_ivr_high: ivGateHigh,
iv_gate_ivr_extreme: ivGateExtreme, iv_gate_skew_threshold: ivGateSkew },
{ onSuccess: () => { setSavedMsg('IV Gate sauvegardé'); setTimeout(() => setSavedMsg(''), 2000) } }
{ onSuccess: () => { setSavedMsg('IV Gate saved'); setTimeout(() => setSavedMsg(''), 2000) } }
)}
disabled={savingOptionsGate}
className="flex items-center gap-1.5 bg-blue-600 hover:bg-blue-500 disabled:opacity-40 text-white px-4 py-2 rounded text-sm font-semibold">
<Save className="w-4 h-4" />
{savingOptionsGate ? 'Sauvegarde...' : 'Sauvegarder le gate'}
{savingOptionsGate ? 'Saving...' : 'Save gate'}
</button>
</div>
</div>
@@ -1270,16 +1270,16 @@ export default function Config() {
{/* ── Paramètres de sortie ── */}
<div className="card">
<h2 className="text-base font-bold text-white flex items-center gap-2 mb-1">
<Lock className="w-4 h-4 text-amber-400" /> Paramètres de sortie des trades
<Lock className="w-4 h-4 text-amber-400" /> Trade Exit Settings
</h2>
<p className="text-xs text-slate-500 mb-5">
Valeurs par défaut pour les objectifs et stop-loss. Chaque trade peut avoir ses propres seuils (Journal Ouverts).
Default values for targets and stop-losses. Each trade can have its own thresholds (Journal Open).
</p>
<div className="grid grid-cols-2 gap-6 mb-4">
<div>
<label className="text-xs text-slate-500 mb-2 block">
Objectif de gain : <span className="text-emerald-400 font-mono font-bold">+{exitTarget}%</span>
<span className="text-slate-600 ml-1">(P&L sous-jacent)</span>
Profit target: <span className="text-emerald-400 font-mono font-bold">+{exitTarget}%</span>
<span className="text-slate-600 ml-1">(underlying P&L)</span>
</label>
<input type="range" min="5" max="150" step="5"
value={exitTarget}
@@ -1291,8 +1291,8 @@ export default function Config() {
</div>
<div>
<label className="text-xs text-slate-500 mb-2 block">
Stop-loss : <span className="text-red-400 font-mono font-bold">{exitStop}%</span>
<span className="text-slate-600 ml-1">(P&L sous-jacent)</span>
Stop-loss: <span className="text-red-400 font-mono font-bold">{exitStop}%</span>
<span className="text-slate-600 ml-1">(underlying P&L)</span>
</label>
<input type="range" min="-90" max="-5" step="5"
value={exitStop}
@@ -1305,11 +1305,11 @@ export default function Config() {
</div>
<div className="grid grid-cols-2 gap-6 mb-5">
<div>
<label className="text-xs text-slate-500 mb-2 block">Mode signal IA de retournement</label>
<label className="text-xs text-slate-500 mb-2 block">AI reversal signal mode</label>
<div className="flex gap-2">
{[
{ value: 'badge_only', label: '🔔 Badge uniquement' },
{ value: 'auto', label: '⚡ Auto (futur)' },
{ value: 'badge_only', label: '🔔 Badge only' },
{ value: 'auto', label: '⚡ Auto (future)' },
].map(opt => (
<button key={opt.value} onClick={() => setExitSignalMode(opt.value)}
className={clsx('flex-1 py-2 rounded text-xs border transition-all', {
@@ -1321,19 +1321,19 @@ export default function Config() {
))}
</div>
<div className="text-[10px] text-slate-600 mt-1">
Badge only : alerte visible, vous confirmez manuellement. Auto : fermeture proposée.
Badge only: visible alert, you confirm manually. Auto: closure suggested.
</div>
</div>
<div>
<label className="text-xs text-slate-500 mb-2 block">
Seuil retournement signal : <span className="text-blue-400 font-mono font-bold">{exitSignalThreshold}pts</span>
Signal reversal threshold: <span className="text-blue-400 font-mono font-bold">{exitSignalThreshold}pts</span>
</label>
<input type="range" min="10" max="60" step="5"
value={exitSignalThreshold}
onChange={e => setExitSignalThreshold(parseFloat(e.target.value))}
className="w-full accent-blue-500" />
<div className="flex justify-between text-[10px] text-slate-600 mt-1">
<span>10pts (sensible)</span><span>60pts (tolérant)</span>
<span>10pts (sensitive)</span><span>60pts (tolerant)</span>
</div>
</div>
</div>
@@ -1341,12 +1341,12 @@ export default function Config() {
onClick={() => saveExitDefaults(
{ target_pct: exitTarget, stop_loss_pct: exitStop,
signal_reversal_mode: exitSignalMode, signal_reversal_threshold: exitSignalThreshold },
{ onSuccess: () => { setSavedMsg('Paramètres de sortie sauvegardés'); setTimeout(() => setSavedMsg(''), 2000) } }
{ onSuccess: () => { setSavedMsg('Exit settings saved'); setTimeout(() => setSavedMsg(''), 2000) } }
)}
disabled={savingExitDefaults}
className="flex items-center gap-1.5 bg-amber-600 hover:bg-amber-500 disabled:opacity-40 text-white px-4 py-2 rounded text-sm font-semibold">
<Save className="w-4 h-4" />
{savingExitDefaults ? 'Sauvegarde...' : 'Sauvegarder les seuils'}
{savingExitDefaults ? 'Saving...' : 'Save thresholds'}
</button>
</div>
@@ -1354,7 +1354,7 @@ export default function Config() {
<div className="card border-purple-700/30 bg-purple-900/5">
<div className="flex items-center justify-between mb-1">
<h2 className="text-base font-bold text-white flex items-center gap-2">
<TrendingUp className="w-4 h-4 text-purple-400" /> Indicateurs techniques du sous-jacent
<TrendingUp className="w-4 h-4 text-purple-400" /> Underlying Technical Indicators
</h2>
<button
onClick={() => setTechEnabled(!techEnabled)}
@@ -1362,23 +1362,23 @@ export default function Config() {
'bg-purple-600 border-purple-500 text-white': techEnabled,
'bg-dark-700 border-slate-700 text-slate-400': !techEnabled,
})}>
{techEnabled ? '✓ Activé' : '○ Désactivé'}
{techEnabled ? '✓ Enabled' : '○ Disabled'}
</button>
</div>
<p className="text-xs text-slate-500 mb-4">
Injecte RSI, MA, Bollinger et ATR dans le contexte IA à chaque cycle. Les périodes sont calibrées automatiquement selon l'horizon de l'option analysée.
Injects RSI, MA, Bollinger and ATR into the AI context at each cycle. Periods are automatically calibrated based on the option's horizon.
</p>
<div className={clsx('space-y-4 transition-opacity', !techEnabled && 'opacity-40 pointer-events-none')}>
{/* Indicateurs à activer */}
<div>
<label className="text-xs text-slate-400 font-medium block mb-2">Indicateurs actifs</label>
<label className="text-xs text-slate-400 font-medium block mb-2">Active indicators</label>
<div className="flex flex-wrap gap-2">
{[
{ key: 'rsi', label: 'RSI', desc: 'Momentum / surachat-survente' },
{ key: 'ma', label: 'MA fast/slow', desc: 'Tendance & golden/death cross' },
{ key: 'bollinger', label: 'Bollinger', desc: 'Position dans les bandes de vol' },
{ key: 'atr', label: 'ATR', desc: 'Volatilité réalisée récente' },
{ key: 'rsi', label: 'RSI', desc: 'Momentum / overbought-oversold' },
{ key: 'ma', label: 'MA fast/slow', desc: 'Trend & golden/death cross' },
{ key: 'bollinger', label: 'Bollinger', desc: 'Position in vol bands' },
{ key: 'atr', label: 'ATR', desc: 'Recent realized volatility' },
].map(({ key, label, desc }) => {
const active = techList.includes(key)
return (
@@ -1397,13 +1397,13 @@ export default function Config() {
)
})}
</div>
<p className="text-[10px] text-slate-600 mt-1.5">Survolez pour voir la description de chaque indicateur.</p>
<p className="text-[10px] text-slate-600 mt-1.5">Hover to see each indicator's description.</p>
</div>
{/* Calibration auto */}
<div className="flex items-center justify-between">
<div>
<p className="text-xs text-slate-300 font-medium">Calibration automatique selon horizon</p>
<p className="text-xs text-slate-300 font-medium">Auto-calibration by horizon</p>
<p className="text-[10px] text-slate-500">30j RSI14, MA20/50 | 90j RSI21, MA50/100 | &gt;90j RSI28, MA100/200</p>
</div>
<button
@@ -1412,7 +1412,7 @@ export default function Config() {
'bg-purple-600 border-purple-500 text-white': techAutoCalibrate,
'bg-dark-700 border-slate-700 text-slate-400': !techAutoCalibrate,
})}>
{techAutoCalibrate ? '✓ Auto' : '○ Fixe'}
{techAutoCalibrate ? '✓ Auto' : '○ Fixed'}
</button>
</div>
</div>
@@ -1420,12 +1420,12 @@ export default function Config() {
<button
onClick={() => saveTechIndicators(
{ tech_indicators_enabled: techEnabled, tech_indicators_list: techList.join(','), tech_indicators_auto_calibrate: techAutoCalibrate },
{ onSuccess: () => { setSavedMsg('Indicateurs techniques sauvegardés'); setTimeout(() => setSavedMsg(''), 2000) } }
{ onSuccess: () => { setSavedMsg('Technical indicators saved'); setTimeout(() => setSavedMsg(''), 2000) } }
)}
disabled={savingTechIndicators}
className="mt-4 flex items-center gap-1.5 bg-purple-700 hover:bg-purple-600 disabled:opacity-40 text-white px-4 py-2 rounded text-sm font-semibold">
<Save className="w-4 h-4" />
{savingTechIndicators ? 'Sauvegarde...' : 'Sauvegarder les indicateurs'}
{savingTechIndicators ? 'Saving...' : 'Save indicators'}
</button>
</div>
</div>