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:
@@ -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 & cycle</p>
|
||||
<p className="text-xs text-slate-500 mt-0.5">API keys, sources, AI & 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 dé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 & 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 > {ivGateExtreme}% → <span className="text-red-400 font-semibold">BLOQUÉ</span> — naked long vol → ALERT</div>
|
||||
<div>Straddle / Strangle → ALERT dès IVR > {ivGateExtreme}% (pénalité ×1.3)</div>
|
||||
<div>IVR > {ivGateExtreme}% → <span className="text-red-400 font-semibold">BLOCKED</span> — naked long vol → ALERT</div>
|
||||
<div>Straddle / Strangle → ALERT when IVR > {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 > {ivGateSkew}pts → puts très chers, signal WARN sur Long Put
|
||||
Put skew > {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 < <span className="text-emerald-400 font-mono">{ivGateHigh}%</span> → Toute stratégie OK</span>
|
||||
<span className="text-slate-400">IVR < <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 > <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 > <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 > <span className="text-orange-400 font-mono">{ivGateSkew}pts</span> → Puts chers (WARN)</span>
|
||||
<span className="text-slate-400">Skew > <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 | >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>
|
||||
|
||||
Reference in New Issue
Block a user