refactor: separate Sentiment and Technical desks — no signal overlap

- Mark 6 price signals (ma_cross, rsi_extreme, bb_squeeze, new_52w_extreme, price_gap, volume_spike) as desk_type=technical so they no longer appear in the Sentiment desk
- Sentiment desk now shows MacroGaugeSelector (32 gauges grouped by bloc: Liquidité, Crédit, Volatilité, Métaux…) instead of the stock/ETF instrument picker
- Sentiment desk instruments seed updated to macro gauge keys (vix, vvix, skew, hyg, dxy, slope_10y3m, gold_copper_ratio)
- Signal init useEffect extended to cover sentiment desk as well as technical

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OpenSquared
2026-06-26 13:48:14 +02:00
parent 85eb864584
commit 27ee4410cf
3 changed files with 121 additions and 8 deletions

View File

@@ -19,6 +19,7 @@ SIGNAL_CATALOG: List[Dict[str, Any]] = [
"id": "ma_cross",
"label": "Croisement de moyennes mobiles",
"description": "Détecte les croisements bullish/bearish entre deux MAs",
"desk_type": "technical",
"params": {
"pairs": {
"type": "pairs", "label": "Paires MA",
@@ -31,6 +32,7 @@ SIGNAL_CATALOG: List[Dict[str, Any]] = [
"id": "rsi_extreme",
"label": "RSI extrême",
"description": "Signal quand le RSI dépasse les seuils de surachat/survente",
"desk_type": "technical",
"params": {
"period": {"type": "int", "label": "Période", "default": 14, "min": 5, "max": 50},
"oversold": {"type": "int", "label": "Survente", "default": 30, "min": 10, "max": 45},
@@ -41,6 +43,7 @@ SIGNAL_CATALOG: List[Dict[str, Any]] = [
"id": "bb_squeeze",
"label": "Squeeze Bollinger Bands",
"description": "Détecte quand les bandes BB se resserrent en dessous d'un seuil",
"desk_type": "technical",
"params": {
"period": {"type": "int", "label": "Période", "default": 20, "min": 10, "max": 50},
"std": {"type": "float", "label": "Std dev", "default": 2.0, "min": 1.0, "max": 3.0},
@@ -51,6 +54,7 @@ SIGNAL_CATALOG: List[Dict[str, Any]] = [
"id": "new_52w_extreme",
"label": "Nouveau 52 semaines extrême",
"description": "Nouveau plus haut ou plus bas sur 52 semaines (avec buffer)",
"desk_type": "technical",
"params": {
"buffer_pct": {"type": "float", "label": "Buffer %", "default": 0.5, "min": 0.0, "max": 5.0},
},
@@ -59,6 +63,7 @@ SIGNAL_CATALOG: List[Dict[str, Any]] = [
"id": "price_gap",
"label": "Gap de prix",
"description": "Gap d'ouverture significatif par rapport à la clôture précédente",
"desk_type": "technical",
"params": {
"min_gap_pct": {"type": "float", "label": "Gap min %", "default": 1.5, "min": 0.5, "max": 10.0},
},
@@ -67,6 +72,7 @@ SIGNAL_CATALOG: List[Dict[str, Any]] = [
"id": "volume_spike",
"label": "Spike de volume",
"description": "Volume anormalement élevé par rapport à la moyenne mobile",
"desk_type": "technical",
"params": {
"ma_period": {"type": "int", "label": "Période MA vol", "default": 20, "min": 5, "max": 50},
"spike_factor": {"type": "float", "label": "Facteur spike", "default": 2.5, "min": 1.5, "max": 10.0},

View File

@@ -1098,7 +1098,7 @@ def init_db():
"SKEW > 140 = marché paye cher pour la protection downside. "
"Inversion terme structure VIX (front > back) = peur concentrée court terme."
),
"instruments": json.dumps(["SPY","QQQ","VXX","TLT","HYG","GLD"]),
"instruments": json.dumps(["vix","vvix","skew","hyg","dxy","slope_10y3m","gold_copper_ratio"]),
"config": json.dumps({
"lookback_days": 5,
"signals": {

View File

@@ -38,6 +38,54 @@ const ALL_INSTRUMENTS = [
'VXX','AAPL','NVDA','GS','XOM','BTC-USD',
]
// Macro regime gauges from MACRO_GAUGE_CONFIG (backend data_fetcher.py)
const MACRO_GAUGE_LIST: { id: string; label: string; bloc: string }[] = [
// Liquidité / Taux
{ id: 'dxy', label: 'Dollar DXY', bloc: 'Liquidité' },
{ id: 'us10y', label: 'UST 10Y', bloc: 'Liquidité' },
{ id: 'us3m', label: 'UST 3M', bloc: 'Liquidité' },
{ id: 'tips', label: 'TIPS ETF', bloc: 'Liquidité' },
{ id: 'tlt', label: 'TLT 20Y+', bloc: 'Liquidité' },
// Crédit
{ id: 'vix', label: 'VIX', bloc: 'Crédit' },
{ id: 'hyg', label: 'HYG (HY Bonds)', bloc: 'Crédit' },
{ id: 'lqd', label: 'LQD (IG Bonds)', bloc: 'Crédit' },
{ id: 'ief', label: 'IEF (7-10Y)', bloc: 'Crédit' },
// Énergie
{ id: 'brent', label: 'Brent', bloc: 'Énergie' },
{ id: 'ng', label: 'Gaz naturel', bloc: 'Énergie' },
// Métaux
{ id: 'gold', label: 'Or', bloc: 'Métaux' },
{ id: 'silver', label: 'Argent', bloc: 'Métaux' },
{ id: 'copper', label: 'Cuivre', bloc: 'Métaux' },
// Croissance
{ id: 'spx', label: 'S&P 500', bloc: 'Croissance' },
{ id: 'iwm', label: 'Russell 2000', bloc: 'Croissance' },
{ id: 'xli', label: 'Industriels XLI', bloc: 'Croissance' },
// Secteurs
{ id: 'xlk', label: 'Tech XLK', bloc: 'Secteurs' },
{ id: 'xlf', label: 'Financières XLF', bloc: 'Secteurs' },
{ id: 'xlp', label: 'Conso. défensif', bloc: 'Secteurs' },
{ id: 'xlu', label: 'Utilities XLU', bloc: 'Secteurs' },
// Volatilité
{ id: 'vvix', label: 'VVIX (vol-of-vol)', bloc: 'Volatilité' },
{ id: 'skew', label: 'CBOE SKEW', bloc: 'Volatilité' },
{ id: 'ovx', label: 'OVX (Oil Vol)', bloc: 'Volatilité' },
{ id: 'gvz', label: 'GVZ (Gold Vol)', bloc: 'Volatilité' },
// Global / EM
{ id: 'eem', label: 'EM Actions EEM', bloc: 'Global' },
{ id: 'emb', label: 'EM Bonds EMB', bloc: 'Global' },
{ id: 'fxi', label: 'Chine FXI', bloc: 'Global' },
// Forex risk-off
{ id: 'usdjpy', label: 'USD/JPY', bloc: 'Forex' },
// Dérivés calculés
{ id: 'slope_10y3m', label: 'Slope 10Y-3M', bloc: 'Dérivés' },
{ id: 'gold_copper_ratio',label: 'Ratio Or/Cuivre', bloc: 'Dérivés' },
{ id: 'spx_vs_200d', label: 'SPX vs MA 200j', bloc: 'Dérivés' },
]
const MACRO_BLOC_ORDER = ['Liquidité','Crédit','Volatilité','Métaux','Énergie','Croissance','Secteurs','Global','Forex','Dérivés']
const TYPE_LABELS: Record<string, string> = {
news: 'News',
fundamental: 'Fondamental',
@@ -101,6 +149,58 @@ function InstrumentSelector({
}
function MacroGaugeSelector({
selected,
onChange,
}: {
selected: string[]
onChange: (v: string[]) => void
}) {
const toggle = (id: string) =>
onChange(selected.includes(id) ? selected.filter(x => x !== id) : [...selected, id])
const blocs = MACRO_BLOC_ORDER.filter(b => MACRO_GAUGE_LIST.some(g => g.bloc === b))
return (
<div className="space-y-3">
<div className="flex items-center justify-between">
<label className="text-xs text-slate-400">Compteurs macro surveillés</label>
<button
onClick={() => onChange(selected.length === MACRO_GAUGE_LIST.length ? [] : MACRO_GAUGE_LIST.map(g => g.id))}
className="text-xs text-rose-400 hover:text-rose-300"
>
{selected.length === MACRO_GAUGE_LIST.length ? 'Tout désélectionner' : 'Tout sélectionner'}
</button>
</div>
{blocs.map(bloc => {
const gauges = MACRO_GAUGE_LIST.filter(g => g.bloc === bloc)
return (
<div key={bloc}>
<div className="text-[10px] text-slate-600 uppercase tracking-wider mb-1">{bloc}</div>
<div className="flex flex-wrap gap-1.5">
{gauges.map(g => (
<button
key={g.id}
onClick={() => toggle(g.id)}
className={clsx(
'px-2 py-0.5 rounded text-xs border transition-colors',
selected.includes(g.id)
? 'bg-rose-900/40 border-rose-600/60 text-rose-300'
: 'bg-dark-800 border-slate-700/40 text-slate-500 hover:border-slate-600',
)}
>
{g.label}
</button>
))}
</div>
</div>
)
})}
</div>
)
}
function SignalToggle({
signal,
value,
@@ -405,9 +505,9 @@ function DeskEditor({
const set = (k: keyof AIDesk, v: any) => setD(prev => ({ ...prev, [k]: v }))
const dirty = JSON.stringify(d) !== JSON.stringify(desk)
// Ensure signals config is initialized from catalog for technical desks
// Ensure signals config is initialized from catalog for technical + sentiment desks
useEffect(() => {
if (d.type !== 'technical' || !catalog.length) return
if ((d.type !== 'technical' && d.type !== 'sentiment') || !catalog.length) return
const signals = d.config.signals ?? {}
let changed = false
const next = { ...signals }
@@ -477,11 +577,18 @@ function DeskEditor({
/>
</div>
{/* Instruments */}
<InstrumentSelector
selected={d.instruments}
onChange={v => set('instruments', v)}
/>
{/* Instruments / Gauges */}
{d.type === 'sentiment' ? (
<MacroGaugeSelector
selected={d.instruments}
onChange={v => set('instruments', v)}
/>
) : (
<InstrumentSelector
selected={d.instruments}
onChange={v => set('instruments', v)}
/>
)}
{/* Type-specific config */}
<div>