feat: instrument analysis
This commit is contained in:
@@ -41,6 +41,30 @@ const SOURCE_LABELS: Record<string, string> = {
|
||||
reports: 'Reports',
|
||||
}
|
||||
|
||||
interface DeskRef {
|
||||
id: number
|
||||
name: string
|
||||
type: string
|
||||
active: boolean
|
||||
}
|
||||
|
||||
const DESK_TYPE_TO_SOURCE: Record<string, string> = {
|
||||
news: 'news',
|
||||
eco: 'eco',
|
||||
fundamental: 'news',
|
||||
technical: 'technical',
|
||||
report: 'reports',
|
||||
}
|
||||
|
||||
const DESK_TYPE_COLORS: Record<string, string> = {
|
||||
news: 'bg-blue-900/30 border-blue-600/50 text-blue-300',
|
||||
fundamental: 'bg-amber-900/30 border-amber-600/50 text-amber-300',
|
||||
technical: 'bg-cyan-900/30 border-cyan-600/50 text-cyan-300',
|
||||
eco: 'bg-emerald-900/30 border-emerald-600/50 text-emerald-300',
|
||||
report: 'bg-purple-900/30 border-purple-600/50 text-purple-300',
|
||||
sentiment: 'bg-rose-900/30 border-rose-600/50 text-rose-300',
|
||||
}
|
||||
|
||||
const CAT_COLORS: Record<string, string> = {
|
||||
event_calendar: 'text-amber-400',
|
||||
geopolitical: 'text-red-400',
|
||||
@@ -120,7 +144,6 @@ function BootstrapAction({ action }: { action: ActionDef }) {
|
||||
// ── Check Market Events config panel ────────────────────────────────────────
|
||||
|
||||
interface CheckEventsConfig {
|
||||
sources: string[]
|
||||
news_impact_min: number
|
||||
eco_z_threshold: number
|
||||
technical_lookback_days: number
|
||||
@@ -148,7 +171,6 @@ function presetDates(hours: number): { date_from: string; date_to: string } {
|
||||
|
||||
function makeDefaultConfig(): CheckEventsConfig {
|
||||
return {
|
||||
sources: ['news', 'eco', 'technical', 'reports'],
|
||||
news_impact_min: 0.55,
|
||||
eco_z_threshold: 1.5,
|
||||
technical_lookback_days: 7,
|
||||
@@ -160,30 +182,40 @@ function makeDefaultConfig(): CheckEventsConfig {
|
||||
|
||||
function CheckEventsPanel() {
|
||||
const [config, setConfig] = useState<CheckEventsConfig>(makeDefaultConfig)
|
||||
const [desks, setDesks] = useState<DeskRef[]>([])
|
||||
const [running, setRunning] = useState(false)
|
||||
const [result, setResult] = useState<ActionResult | null>(null)
|
||||
const [error, setError] = useState<string | null>(null)
|
||||
const [expanded, setExpanded] = useState(false)
|
||||
const [showConfig, setShowConfig] = useState(false)
|
||||
|
||||
const toggleSource = (src: string) => {
|
||||
setConfig(c => ({
|
||||
...c,
|
||||
sources: c.sources.includes(src)
|
||||
? c.sources.filter(s => s !== src)
|
||||
: [...c.sources, src],
|
||||
}))
|
||||
useEffect(() => {
|
||||
fetch(`${API}/api/ai-desks`)
|
||||
.then(r => r.json())
|
||||
.then(d => setDesks(d))
|
||||
.catch(() => {})
|
||||
}, [])
|
||||
|
||||
const toggleDesk = async (desk: DeskRef) => {
|
||||
const next = !desk.active
|
||||
setDesks(prev => prev.map(d => d.id === desk.id ? { ...d, active: next } : d))
|
||||
await fetch(`${API}/api/ai-desks/${desk.id}/toggle`, { method: 'PATCH' }).catch(() => {})
|
||||
}
|
||||
|
||||
const run = async () => {
|
||||
setRunning(true)
|
||||
setResult(null)
|
||||
setError(null)
|
||||
const sources = [...new Set(
|
||||
desks
|
||||
.filter(d => d.active && DESK_TYPE_TO_SOURCE[d.type])
|
||||
.map(d => DESK_TYPE_TO_SOURCE[d.type])
|
||||
)]
|
||||
try {
|
||||
const res = await fetch(`${API}/api/actions/check-market-events`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify(config),
|
||||
body: JSON.stringify({ ...config, sources }),
|
||||
})
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({ detail: res.statusText }))
|
||||
@@ -211,7 +243,7 @@ function CheckEventsPanel() {
|
||||
<span className="w-2 h-2 rounded-full bg-emerald-400" />
|
||||
<span className="text-slate-100 font-semibold text-sm">Check New Market Events</span>
|
||||
<span className="text-xs text-slate-500 ml-1">
|
||||
Scans news · FRED · MA crossovers · rapports institutionnels
|
||||
Scans news · éco. calendar · MA crossovers · rapports institutionnels
|
||||
</span>
|
||||
</div>
|
||||
<button
|
||||
@@ -221,7 +253,7 @@ function CheckEventsPanel() {
|
||||
{showConfig ? 'Masquer config' : 'Config'}
|
||||
</button>
|
||||
<button
|
||||
disabled={running || config.sources.length === 0}
|
||||
disabled={running}
|
||||
onClick={run}
|
||||
className="flex items-center gap-2 bg-emerald-600 hover:bg-emerald-500 disabled:bg-slate-700
|
||||
disabled:text-slate-500 text-white text-sm font-medium rounded-lg px-4 py-2 transition-colors"
|
||||
@@ -266,21 +298,25 @@ function CheckEventsPanel() {
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Sources */}
|
||||
{/* Desks IA */}
|
||||
<div>
|
||||
<div className="text-slate-400 mb-2 text-xs uppercase tracking-wide">Sources actives</div>
|
||||
<div className="text-slate-400 mb-2 text-xs uppercase tracking-wide">Desks IA</div>
|
||||
<div className="flex flex-wrap gap-2">
|
||||
{['news', 'eco', 'technical', 'reports'].map(s => (
|
||||
<button key={s} onClick={() => toggleSource(s)}
|
||||
className={`px-3 py-1 rounded-full text-xs border transition-colors ${
|
||||
config.sources.includes(s)
|
||||
? 'bg-emerald-600/30 border-emerald-500/50 text-emerald-300'
|
||||
: 'border-slate-700 text-slate-500 hover:border-slate-500'
|
||||
{desks.map(desk => (
|
||||
<button key={desk.id} onClick={() => toggleDesk(desk)}
|
||||
className={`flex items-center gap-1.5 px-3 py-1 rounded border text-xs transition-colors ${
|
||||
desk.active
|
||||
? (DESK_TYPE_COLORS[desk.type] ?? 'bg-slate-700/40 border-slate-600 text-slate-300')
|
||||
: 'border-slate-700 text-slate-600 hover:border-slate-500 hover:text-slate-400'
|
||||
}`}
|
||||
>
|
||||
{SOURCE_LABELS[s]}
|
||||
<span className={`w-1.5 h-1.5 rounded-full flex-shrink-0 ${desk.active ? 'bg-current' : 'bg-slate-700'}`} />
|
||||
{desk.name}
|
||||
</button>
|
||||
))}
|
||||
{desks.length === 0 && (
|
||||
<span className="text-xs text-slate-600 italic">Aucun desk configuré</span>
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user