feat: instrument analysis
This commit is contained in:
@@ -1066,14 +1066,20 @@ function ExplanationScore({
|
||||
// ── Main page ─────────────────────────────────────────────────────────────────
|
||||
|
||||
const PERIODS = [
|
||||
{ key: '3mo', label: '3M' }, { key: '6mo', label: '6M' },
|
||||
{ key: '1y', label: '1Y' }, { key: '2y', label: '2Y' }, { key: '5y', label: '5Y' },
|
||||
{ key: '5d', label: '5D' },
|
||||
{ key: '1mo', label: '1M' },
|
||||
{ key: '3mo', label: '3M' },
|
||||
{ key: '6mo', label: '6M' },
|
||||
{ key: '1y', label: '1Y' },
|
||||
{ key: '2y', label: '2Y' },
|
||||
{ key: '5y', label: '5Y' },
|
||||
]
|
||||
|
||||
export default function InstrumentDashboard() {
|
||||
const { id = 'SPY' } = useParams<{ id: string }>()
|
||||
const navigate = useNavigate()
|
||||
const [period, setPeriod] = useState('1y')
|
||||
const [chartStyle, setChartStyle] = useState<'candles' | 'line'>('candles')
|
||||
const [instruments, setInstruments] = useState<InstrumentConfig[]>([])
|
||||
const [snapshot, setSnapshot] = useState<Snapshot | null>(null)
|
||||
const [narrative, setNarrative] = useState('')
|
||||
@@ -1265,6 +1271,20 @@ export default function InstrumentDashboard() {
|
||||
)}
|
||||
|
||||
<div className="ml-auto flex items-center gap-2">
|
||||
{/* Chart style toggle */}
|
||||
<div className="flex items-center bg-dark-800 border border-slate-700/40 rounded-xl p-1">
|
||||
<button
|
||||
onClick={() => setChartStyle('candles')}
|
||||
title="Chandeliers"
|
||||
className={clsx('p-1.5 rounded-lg transition-colors', chartStyle === 'candles' ? 'bg-blue-700/50 text-blue-300' : 'text-slate-500 hover:text-white hover:bg-dark-700')}
|
||||
><BarChart2 className="w-3.5 h-3.5" /></button>
|
||||
<button
|
||||
onClick={() => setChartStyle('line')}
|
||||
title="Courbe lisse"
|
||||
className={clsx('p-1.5 rounded-lg transition-colors', chartStyle === 'line' ? 'bg-blue-700/50 text-blue-300' : 'text-slate-500 hover:text-white hover:bg-dark-700')}
|
||||
><TrendingUp className="w-3.5 h-3.5" /></button>
|
||||
</div>
|
||||
{/* Period selector */}
|
||||
<div className="flex items-center gap-1 bg-dark-800 border border-slate-700/40 rounded-xl p-1">
|
||||
{PERIODS.map(p => (
|
||||
<button key={p.key} onClick={() => setPeriod(p.key)}
|
||||
@@ -1300,6 +1320,7 @@ export default function InstrumentDashboard() {
|
||||
indicators={snapshot.indicators}
|
||||
events={[]}
|
||||
height={420}
|
||||
chartType={chartStyle}
|
||||
onDateHover={handleDateHover}
|
||||
/>
|
||||
|
||||
|
||||
@@ -998,6 +998,7 @@ export default function MarketEvents() {
|
||||
|
||||
// ── Filter state ─────────────────────────────────────────────────────────
|
||||
const [search, setSearch] = useState('')
|
||||
const [category, setCategory] = useState('')
|
||||
const [level, setLevel] = useState('')
|
||||
const [minScore, setMinScore] = useState(0)
|
||||
const [evaluated, setEvaluated] = useState<'' | 'true' | 'false'>('')
|
||||
@@ -1049,6 +1050,7 @@ export default function MarketEvents() {
|
||||
setLoading(true)
|
||||
const params = new URLSearchParams()
|
||||
if (search) params.set('search', search)
|
||||
if (category) params.set('category', category)
|
||||
if (level) params.set('level', level)
|
||||
if (minScore) params.set('min_score', String(minScore))
|
||||
if (dateFrom) params.set('date_from', dateFrom)
|
||||
@@ -1075,7 +1077,7 @@ export default function MarketEvents() {
|
||||
if (e.name === 'AbortError') return // superseded by a newer load
|
||||
}
|
||||
setLoading(false)
|
||||
}, [search, level, minScore, dateFrom, dateTo, genFrom, genTo, evaluated,
|
||||
}, [search, category, level, minScore, dateFrom, dateTo, genFrom, genTo, evaluated,
|
||||
instTicker, instMinScore, instDir, sortBy, sortDir])
|
||||
|
||||
useEffect(() => {
|
||||
@@ -1163,6 +1165,33 @@ export default function MarketEvents() {
|
||||
|
||||
</div>
|
||||
|
||||
{/* ── Quick category filter ── */}
|
||||
<div className="px-3 py-1.5 border-b border-slate-800/40 flex items-center gap-1 flex-wrap">
|
||||
{([
|
||||
{ key: '', label: 'Tout', dot: 'bg-slate-500' },
|
||||
{ key: 'event_calendar', label: '★ Cal.', dot: 'bg-amber-500' },
|
||||
{ key: 'geopolitical', label: '★ Géo.', dot: 'bg-red-500' },
|
||||
{ key: 'fundamental', label: '★ Fund.', dot: 'bg-emerald-500' },
|
||||
{ key: 'report', label: '★ Rep.', dot: 'bg-blue-500' },
|
||||
{ key: 'sentiment', label: '★ Sent.', dot: 'bg-violet-500' },
|
||||
{ key: 'technical', label: '★ Tech.', dot: 'bg-cyan-500' },
|
||||
] as const).map(({ key, label, dot }) => (
|
||||
<button
|
||||
key={key}
|
||||
onClick={() => setCategory(key)}
|
||||
className={clsx(
|
||||
'flex items-center gap-1 px-2 py-0.5 rounded text-[10px] border transition-colors',
|
||||
category === key
|
||||
? 'bg-slate-700/80 border-slate-500/60 text-slate-200'
|
||||
: 'border-slate-800/60 text-slate-600 hover:border-slate-600 hover:text-slate-400'
|
||||
)}
|
||||
>
|
||||
<span className={clsx('w-1.5 h-1.5 rounded-full shrink-0', dot)} />
|
||||
{label}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* ── Sort + more filters toggle ── */}
|
||||
<div className="px-3 py-1.5 border-b border-slate-800/40 flex items-center gap-2">
|
||||
<span className="text-xs text-slate-600">Tri:</span>
|
||||
@@ -1183,7 +1212,7 @@ export default function MarketEvents() {
|
||||
? 'bg-slate-700 border-slate-600 text-slate-300'
|
||||
: 'border-slate-800 text-slate-600 hover:border-slate-600')}>
|
||||
<SlidersHorizontal size={10} />
|
||||
Filtres {(level || minScore > 0 || evaluated || instTicker) ? '●' : ''}
|
||||
Filtres {(category || level || minScore > 0 || evaluated || instTicker) ? '●' : ''}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user