feat: causal lab
This commit is contained in:
@@ -343,8 +343,31 @@ function NewsConfig({
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
const ECO_CURRENCIES = ['USD', 'EUR', 'GBP', 'JPY', 'AUD', 'CAD', 'NZD', 'CHF', 'CNY']
|
const ECO_COUNTRIES = [
|
||||||
const ECO_DEFAULT_CURRENCIES = ['USD', 'EUR', 'GBP', 'JPY']
|
{ code: 'USD', label: 'États-Unis' },
|
||||||
|
{ code: 'EUR', label: 'Zone Euro' },
|
||||||
|
{ code: 'GBP', label: 'Royaume-Uni' },
|
||||||
|
{ code: 'JPY', label: 'Japon' },
|
||||||
|
{ code: 'AUD', label: 'Australie' },
|
||||||
|
{ code: 'CAD', label: 'Canada' },
|
||||||
|
{ code: 'NZD', label: 'Nouvelle-Zélande' },
|
||||||
|
{ code: 'CHF', label: 'Suisse' },
|
||||||
|
{ code: 'CNY', label: 'Chine' },
|
||||||
|
]
|
||||||
|
const ECO_DEFAULT_COUNTRIES = ['USD', 'EUR', 'GBP', 'JPY']
|
||||||
|
|
||||||
|
const ECO_EVENT_TYPES = [
|
||||||
|
{ id: 'inflation', label: 'Inflation (CPI/PCE/PPI)' },
|
||||||
|
{ id: 'emploi', label: 'Emploi (NFP/Claims/ADP)' },
|
||||||
|
{ id: 'pmi', label: 'PMI (Mfg/Services)' },
|
||||||
|
{ id: 'pib', label: 'PIB / Croissance' },
|
||||||
|
{ id: 'taux', label: 'Décisions de taux' },
|
||||||
|
{ id: 'commerce', label: 'Balance commerciale' },
|
||||||
|
{ id: 'ventes', label: 'Ventes au détail' },
|
||||||
|
{ id: 'logement', label: 'Immobilier / Logement' },
|
||||||
|
{ id: 'confiance', label: 'Confiance / Sentiment' },
|
||||||
|
]
|
||||||
|
const ECO_DEFAULT_TYPES = ['inflation', 'emploi', 'pmi', 'pib', 'taux']
|
||||||
|
|
||||||
function EcoConfig({
|
function EcoConfig({
|
||||||
config,
|
config,
|
||||||
@@ -354,62 +377,70 @@ function EcoConfig({
|
|||||||
onChange: (c: Record<string, any>) => void
|
onChange: (c: Record<string, any>) => void
|
||||||
}) {
|
}) {
|
||||||
const set = (k: string, v: any) => onChange({ ...config, [k]: v })
|
const set = (k: string, v: any) => onChange({ ...config, [k]: v })
|
||||||
const currencies: string[] = config.currencies ?? ECO_DEFAULT_CURRENCIES
|
const countries: string[] = config.countries ?? ECO_DEFAULT_COUNTRIES
|
||||||
|
const eventTypes: string[] = config.event_types ?? ECO_DEFAULT_TYPES
|
||||||
|
|
||||||
const toggleCurrency = (ccy: string) => {
|
const toggleCountry = (code: string) => {
|
||||||
const next = currencies.includes(ccy)
|
const next = countries.includes(code)
|
||||||
? currencies.filter(c => c !== ccy)
|
? countries.filter(c => c !== code)
|
||||||
: [...currencies, ccy]
|
: [...countries, code]
|
||||||
set('currencies', next)
|
set('countries', next)
|
||||||
|
}
|
||||||
|
|
||||||
|
const toggleEventType = (id: string) => {
|
||||||
|
const next = eventTypes.includes(id)
|
||||||
|
? eventTypes.filter(t => t !== id)
|
||||||
|
: [...eventTypes, id]
|
||||||
|
set('event_types', next)
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="space-y-4">
|
<div className="space-y-4">
|
||||||
{/* Row 1: z-score + days */}
|
{/* Pays / Zones */}
|
||||||
<div className="grid grid-cols-2 gap-3">
|
|
||||||
<div>
|
|
||||||
<label className="text-xs text-slate-400 block mb-1">Seuil z-score (FRED)</label>
|
|
||||||
<input
|
|
||||||
type="number" min={0.5} max={5} step={0.1}
|
|
||||||
value={config.z_threshold ?? 1.5}
|
|
||||||
onChange={e => set('z_threshold', parseFloat(e.target.value))}
|
|
||||||
className="w-full bg-dark-900 border border-slate-700/40 rounded px-2 py-1.5 text-sm text-white"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
<label className="text-xs text-slate-400 block mb-1">Jours lookback</label>
|
|
||||||
<input
|
|
||||||
type="number" min={1} max={30}
|
|
||||||
value={config.days ?? 7}
|
|
||||||
onChange={e => set('days', parseInt(e.target.value))}
|
|
||||||
className="w-full bg-dark-900 border border-slate-700/40 rounded px-2 py-1.5 text-sm text-white"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Row 2: currencies */}
|
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs text-slate-400 block mb-1.5">Devises surveillées</label>
|
<label className="text-xs text-slate-400 block mb-1.5">Pays / Zones économiques</label>
|
||||||
<div className="flex flex-wrap gap-1.5">
|
<div className="flex flex-wrap gap-1.5">
|
||||||
{ECO_CURRENCIES.map(ccy => (
|
{ECO_COUNTRIES.map(c => (
|
||||||
<button
|
<button
|
||||||
key={ccy}
|
key={c.code}
|
||||||
onClick={() => toggleCurrency(ccy)}
|
onClick={() => toggleCountry(c.code)}
|
||||||
|
title={c.label}
|
||||||
className={clsx(
|
className={clsx(
|
||||||
'px-2.5 py-0.5 rounded text-xs border font-mono transition-colors',
|
'px-2.5 py-0.5 rounded text-xs border font-mono transition-colors',
|
||||||
currencies.includes(ccy)
|
countries.includes(c.code)
|
||||||
? 'bg-emerald-900/40 border-emerald-600/60 text-emerald-300'
|
? 'bg-emerald-900/40 border-emerald-600/60 text-emerald-300'
|
||||||
: 'bg-dark-800 border-slate-700/40 text-slate-500 hover:border-slate-600',
|
: 'bg-dark-800 border-slate-700/40 text-slate-500 hover:border-slate-600',
|
||||||
)}
|
)}
|
||||||
>
|
>
|
||||||
{ccy}
|
{c.code}
|
||||||
</button>
|
</button>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Row 3: impact + lookback_releases */}
|
{/* Types d'événements */}
|
||||||
<div className="grid grid-cols-2 gap-3">
|
<div>
|
||||||
|
<label className="text-xs text-slate-400 block mb-1.5">Types d'événements calendar</label>
|
||||||
|
<div className="flex flex-wrap gap-1.5">
|
||||||
|
{ECO_EVENT_TYPES.map(t => (
|
||||||
|
<button
|
||||||
|
key={t.id}
|
||||||
|
onClick={() => toggleEventType(t.id)}
|
||||||
|
className={clsx(
|
||||||
|
'px-2.5 py-1 rounded text-xs border transition-colors',
|
||||||
|
eventTypes.includes(t.id)
|
||||||
|
? 'bg-blue-900/40 border-blue-600/60 text-blue-300'
|
||||||
|
: 'bg-dark-800 border-slate-700/40 text-slate-500 hover:border-slate-600',
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
{t.label}
|
||||||
|
</button>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* Impact + seuil surprise + releases contexte */}
|
||||||
|
<div className="grid grid-cols-3 gap-3">
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs text-slate-400 block mb-1">Impact minimum</label>
|
<label className="text-xs text-slate-400 block mb-1">Impact minimum</label>
|
||||||
<select
|
<select
|
||||||
@@ -422,6 +453,15 @@ function EcoConfig({
|
|||||||
<option value="low">Tous niveaux</option>
|
<option value="low">Tous niveaux</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
<div>
|
||||||
|
<label className="text-xs text-slate-400 block mb-1">Seuil surprise z-score</label>
|
||||||
|
<input
|
||||||
|
type="number" min={0.5} max={5} step={0.1}
|
||||||
|
value={config.z_threshold ?? 1.5}
|
||||||
|
onChange={e => set('z_threshold', parseFloat(e.target.value))}
|
||||||
|
className="w-full bg-dark-900 border border-slate-700/40 rounded px-2 py-1.5 text-sm text-white"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
<div>
|
<div>
|
||||||
<label className="text-xs text-slate-400 block mb-1">Releases contexte</label>
|
<label className="text-xs text-slate-400 block mb-1">Releases contexte</label>
|
||||||
<input
|
<input
|
||||||
@@ -433,7 +473,7 @@ function EcoConfig({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Row 4: create_market_event toggle */}
|
{/* create_market_event toggle */}
|
||||||
<div className="flex items-center gap-3">
|
<div className="flex items-center gap-3">
|
||||||
<button
|
<button
|
||||||
onClick={() => set('create_market_event', !(config.create_market_event ?? true))}
|
onClick={() => set('create_market_event', !(config.create_market_event ?? true))}
|
||||||
@@ -737,13 +777,13 @@ function DeskEditor({
|
|||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Instruments / Gauges */}
|
{/* Instruments / Gauges — not shown for eco (link is through causal graph) */}
|
||||||
{d.type === 'sentiment' ? (
|
{d.type === 'sentiment' ? (
|
||||||
<MacroGaugeSelector
|
<MacroGaugeSelector
|
||||||
selected={d.instruments}
|
selected={d.instruments}
|
||||||
onChange={v => set('instruments', v)}
|
onChange={v => set('instruments', v)}
|
||||||
/>
|
/>
|
||||||
) : (
|
) : d.type !== 'eco' && (
|
||||||
<InstrumentSelector
|
<InstrumentSelector
|
||||||
selected={d.instruments}
|
selected={d.instruments}
|
||||||
onChange={v => set('instruments', v)}
|
onChange={v => set('instruments', v)}
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ interface ActionResult {
|
|||||||
|
|
||||||
const SOURCE_LABELS: Record<string, string> = {
|
const SOURCE_LABELS: Record<string, string> = {
|
||||||
news: 'News',
|
news: 'News',
|
||||||
eco: 'Éco. FRED',
|
eco: 'Éco. Calendar',
|
||||||
technical: 'Technique',
|
technical: 'Technique',
|
||||||
reports: 'Reports',
|
reports: 'Reports',
|
||||||
}
|
}
|
||||||
@@ -236,7 +236,7 @@ function CheckEventsPanel() {
|
|||||||
|
|
||||||
{/* Fenêtre de scan */}
|
{/* Fenêtre de scan */}
|
||||||
<div>
|
<div>
|
||||||
<div className="text-slate-400 mb-2 text-xs uppercase tracking-wide">Fenêtre de scan (news + éco)</div>
|
<div className="text-slate-400 mb-2 text-xs uppercase tracking-wide">Fenêtre de scan</div>
|
||||||
<div className="flex flex-wrap gap-1.5 mb-2">
|
<div className="flex flex-wrap gap-1.5 mb-2">
|
||||||
{SCAN_PRESETS.map(p => {
|
{SCAN_PRESETS.map(p => {
|
||||||
const { date_from, date_to } = presetDates(p.hours)
|
const { date_from, date_to } = presetDates(p.hours)
|
||||||
@@ -284,30 +284,6 @@ function CheckEventsPanel() {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Advanced */}
|
|
||||||
<div className="grid grid-cols-3 gap-3">
|
|
||||||
<label className="flex flex-col gap-1">
|
|
||||||
<span className="text-xs text-slate-400">News impact min</span>
|
|
||||||
<input type="number" step="0.05" min="0" max="1"
|
|
||||||
value={config.news_impact_min}
|
|
||||||
onChange={e => setConfig(c => ({ ...c, news_impact_min: +e.target.value }))}
|
|
||||||
className="bg-dark-900 border border-slate-700 rounded px-2 py-1 text-slate-200 text-sm w-full" />
|
|
||||||
</label>
|
|
||||||
<label className="flex flex-col gap-1">
|
|
||||||
<span className="text-xs text-slate-400">Éco z-score min</span>
|
|
||||||
<input type="number" step="0.25" min="0.5" max="5"
|
|
||||||
value={config.eco_z_threshold}
|
|
||||||
onChange={e => setConfig(c => ({ ...c, eco_z_threshold: +e.target.value }))}
|
|
||||||
className="bg-dark-900 border border-slate-700 rounded px-2 py-1 text-slate-200 text-sm w-full" />
|
|
||||||
</label>
|
|
||||||
<label className="flex flex-col gap-1">
|
|
||||||
<span className="text-xs text-slate-400">Report importance min</span>
|
|
||||||
<input type="number" step="1" min="1" max="5"
|
|
||||||
value={config.report_min_importance}
|
|
||||||
onChange={e => setConfig(c => ({ ...c, report_min_importance: +e.target.value }))}
|
|
||||||
className="bg-dark-900 border border-slate-700 rounded px-2 py-1 text-slate-200 text-sm w-full" />
|
|
||||||
</label>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user