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_DEFAULT_CURRENCIES = ['USD', 'EUR', 'GBP', 'JPY']
|
||||
const ECO_COUNTRIES = [
|
||||
{ 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({
|
||||
config,
|
||||
@@ -354,62 +377,70 @@ function EcoConfig({
|
||||
onChange: (c: Record<string, any>) => void
|
||||
}) {
|
||||
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 next = currencies.includes(ccy)
|
||||
? currencies.filter(c => c !== ccy)
|
||||
: [...currencies, ccy]
|
||||
set('currencies', next)
|
||||
const toggleCountry = (code: string) => {
|
||||
const next = countries.includes(code)
|
||||
? countries.filter(c => c !== code)
|
||||
: [...countries, code]
|
||||
set('countries', next)
|
||||
}
|
||||
|
||||
const toggleEventType = (id: string) => {
|
||||
const next = eventTypes.includes(id)
|
||||
? eventTypes.filter(t => t !== id)
|
||||
: [...eventTypes, id]
|
||||
set('event_types', next)
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="space-y-4">
|
||||
{/* Row 1: z-score + days */}
|
||||
<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 */}
|
||||
{/* Pays / Zones */}
|
||||
<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">
|
||||
{ECO_CURRENCIES.map(ccy => (
|
||||
{ECO_COUNTRIES.map(c => (
|
||||
<button
|
||||
key={ccy}
|
||||
onClick={() => toggleCurrency(ccy)}
|
||||
key={c.code}
|
||||
onClick={() => toggleCountry(c.code)}
|
||||
title={c.label}
|
||||
className={clsx(
|
||||
'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-dark-800 border-slate-700/40 text-slate-500 hover:border-slate-600',
|
||||
)}
|
||||
>
|
||||
{ccy}
|
||||
{c.code}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Row 3: impact + lookback_releases */}
|
||||
<div className="grid grid-cols-2 gap-3">
|
||||
{/* Types d'événements */}
|
||||
<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>
|
||||
<label className="text-xs text-slate-400 block mb-1">Impact minimum</label>
|
||||
<select
|
||||
@@ -422,6 +453,15 @@ function EcoConfig({
|
||||
<option value="low">Tous niveaux</option>
|
||||
</select>
|
||||
</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>
|
||||
<label className="text-xs text-slate-400 block mb-1">Releases contexte</label>
|
||||
<input
|
||||
@@ -433,7 +473,7 @@ function EcoConfig({
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Row 4: create_market_event toggle */}
|
||||
{/* create_market_event toggle */}
|
||||
<div className="flex items-center gap-3">
|
||||
<button
|
||||
onClick={() => set('create_market_event', !(config.create_market_event ?? true))}
|
||||
@@ -737,13 +777,13 @@ function DeskEditor({
|
||||
/>
|
||||
</div>
|
||||
|
||||
{/* Instruments / Gauges */}
|
||||
{/* Instruments / Gauges — not shown for eco (link is through causal graph) */}
|
||||
{d.type === 'sentiment' ? (
|
||||
<MacroGaugeSelector
|
||||
selected={d.instruments}
|
||||
onChange={v => set('instruments', v)}
|
||||
/>
|
||||
) : (
|
||||
) : d.type !== 'eco' && (
|
||||
<InstrumentSelector
|
||||
selected={d.instruments}
|
||||
onChange={v => set('instruments', v)}
|
||||
|
||||
@@ -36,7 +36,7 @@ interface ActionResult {
|
||||
|
||||
const SOURCE_LABELS: Record<string, string> = {
|
||||
news: 'News',
|
||||
eco: 'Éco. FRED',
|
||||
eco: 'Éco. Calendar',
|
||||
technical: 'Technique',
|
||||
reports: 'Reports',
|
||||
}
|
||||
@@ -236,7 +236,7 @@ function CheckEventsPanel() {
|
||||
|
||||
{/* Fenêtre de scan */}
|
||||
<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">
|
||||
{SCAN_PRESETS.map(p => {
|
||||
const { date_from, date_to } = presetDates(p.hours)
|
||||
@@ -284,30 +284,6 @@ function CheckEventsPanel() {
|
||||
</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>
|
||||
)}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user