feat: expandable inline rows in Journal + journal/maturity params in Config

- JournalDeBord: trade rows now expand inline (full-width) instead of
  PostmortemPanel appearing below the whole table. Click anywhere on a
  row to toggle. Period selector extended to 15/30/60/90j.
- Config: added Rétention Journal (30/60/90/180j) and Seuil Maturité
  (20/30/35/50%) controls, wired to the Appliquer button.
- Backend: journal_retention_days and maturity_threshold_pct read from
  config table; seeded at startup with defaults 90d / 35%. get_status()
  now returns both values so Config page can initialise correctly.
- cycle.py: CycleConfigRequest accepts and validates both new params.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OpenSquared
2026-06-18 10:08:16 +02:00
parent 8446876eb0
commit abee090881
5 changed files with 94 additions and 25 deletions

View File

@@ -359,6 +359,8 @@ export default function Config() {
const [cycleSimilarity, setCycleSimilarity] = useState(0.30)
const [minEv, setMinEv] = useState(0.0)
const [minScore, setMinScore] = useState(0)
const [retentionDays, setRetentionDays] = useState(90)
const [maturityThreshold, setMaturityThreshold] = useState(35)
useEffect(() => {
if (cs) {
setCycleEnabled(cs.enabled ?? false)
@@ -366,6 +368,8 @@ export default function Config() {
setCycleSimilarity(cs.similarity_threshold ?? 0.30)
setMinEv(cs.min_ev_threshold ?? 0.0)
setMinScore(cs.min_score_threshold ?? 0)
setRetentionDays(cs.journal_retention_days ?? 90)
setMaturityThreshold(cs.maturity_threshold_pct ?? 35)
}
}, [cs])
@@ -676,6 +680,41 @@ export default function Config() {
Toutes les N heures : suggère de nouveaux patterns, filtre les doublons, score tout, log les prix et génère un commentaire IA sur les performances.
</p>
<div className="grid grid-cols-2 gap-4 mb-4">
<div>
<label className="text-xs text-slate-500 mb-2 block">
Rétention Journal ({retentionDays}j trades effacés après N jours)
</label>
<div className="flex gap-1">
{[30, 60, 90, 180].map(d => (
<button key={d} onClick={() => setRetentionDays(d)}
className={clsx('flex-1 py-2 rounded text-sm transition-colors', {
'bg-blue-600 text-white': retentionDays === d,
'bg-dark-700 text-slate-400 hover:text-slate-200': retentionDays !== d,
})}>
{d}j
</button>
))}
</div>
</div>
<div>
<label className="text-xs text-slate-500 mb-2 block">
Seuil de maturité ({maturityThreshold}% trades en-dessous exclus des stats)
</label>
<div className="flex gap-1">
{[20, 30, 35, 50].map(p => (
<button key={p} onClick={() => setMaturityThreshold(p)}
className={clsx('flex-1 py-2 rounded text-sm transition-colors', {
'bg-blue-600 text-white': maturityThreshold === p,
'bg-dark-700 text-slate-400 hover:text-slate-200': maturityThreshold !== p,
})}>
{p}%
</button>
))}
</div>
</div>
</div>
<div className="grid grid-cols-3 gap-4 mb-4">
<div>
<label className="text-xs text-slate-500 mb-2 block">Activer l'auto-cycle</label>
@@ -743,7 +782,7 @@ export default function Config() {
<div className="flex items-center gap-3">
<button
onClick={() => updateCycleConfig(
{ enabled: cycleEnabled, interval_hours: cycleHours, similarity_threshold: cycleSimilarity, min_ev_threshold: minEv, min_score_threshold: minScore },
{ enabled: cycleEnabled, interval_hours: cycleHours, similarity_threshold: cycleSimilarity, min_ev_threshold: minEv, min_score_threshold: minScore, journal_retention_days: retentionDays, maturity_threshold_pct: maturityThreshold },
{ onSuccess: () => { refetchCycle(); setSavedMsg('Auto-cycle configuré'); setTimeout(() => setSavedMsg(''), 2000) } }
)}
disabled={savingCycle}

View File

@@ -1,4 +1,4 @@
import { useState, useEffect, useRef } from 'react'
import { useState, useEffect, useRef, Fragment } from 'react'
import { BookOpen, TrendingUp, TrendingDown, Activity, AlertTriangle, RefreshCw, Zap, CheckCircle, XCircle, Brain, Trash2, Search, X, ChevronDown, ChevronUp } from 'lucide-react'
import clsx from 'clsx'
import { useJournalSummary, useMacroHistory, useGeoHistory, useTradeMtm, useCycleHistory, useCycleStatus, useTriggerCycle, useTradePostmortem, useAnalyzePostmortem, useIvForTrade, useKellySizing, api } from '../hooks/useApi'
@@ -443,8 +443,13 @@ function TradeMtmSection({ days }: { days: number }) {
{trades.map((t: any) => {
const evNet = t.ev_net ?? null
const tradeScore = t.trade_score ?? null
const isExpanded = selectedTradeId === t.id
return (
<tr key={t.id} className="hover:bg-dark-700/30 transition-colors">
<Fragment key={t.id}>
<tr
className={clsx('hover:bg-dark-700/30 transition-colors cursor-pointer', isExpanded && 'bg-dark-700/40 border-b-0')}
onClick={() => setSelectedTradeId(isExpanded ? null : t.id)}
>
<td className="px-3 py-2 text-slate-300 max-w-[110px] truncate">{t.pattern_name || t.pattern_id}</td>
<td className="px-3 py-2">
{t.matched_profile ? (
@@ -532,21 +537,29 @@ function TradeMtmSection({ days }: { days: number }) {
<td className="px-3 py-2 text-right">
<PnlBadge pnl={t.pnl_pct} />
</td>
<td className="px-2 py-2">
<td className="px-2 py-2" onClick={e => e.stopPropagation()}>
<button
onClick={() => setSelectedTradeId(selectedTradeId === t.id ? null : t.id)}
onClick={() => setSelectedTradeId(isExpanded ? null : t.id)}
className={clsx(
'p-1 rounded transition-colors',
selectedTradeId === t.id
isExpanded
? 'text-blue-400 bg-blue-900/30'
: 'text-slate-600 hover:text-blue-400 hover:bg-blue-900/20'
)}
title="Post-mortem IA"
>
<Search className="w-3.5 h-3.5" />
{isExpanded ? <ChevronUp className="w-3.5 h-3.5" /> : <Search className="w-3.5 h-3.5" />}
</button>
</td>
</tr>
{isExpanded && (
<tr className="bg-dark-900/60">
<td colSpan={16} className="px-0 py-0 border-b border-blue-700/20">
<PostmortemPanel tradeId={t.id} onClose={() => setSelectedTradeId(null)} />
</td>
</tr>
)}
</Fragment>
)
})}
</tbody>
@@ -554,12 +567,6 @@ function TradeMtmSection({ days }: { days: number }) {
</div>
)}
{selectedTradeId !== null && (
<PostmortemPanel
tradeId={selectedTradeId}
onClose={() => setSelectedTradeId(null)}
/>
)}
</div>
)
}
@@ -848,7 +855,7 @@ export default function JournalDeBord() {
{/* Period selector */}
<div className="flex gap-1 bg-dark-700 p-1 rounded text-xs">
{[7, 15, 30].map(d => (
{[15, 30, 60, 90].map(d => (
<button key={d} onClick={() => setDays(d)}
className={clsx('px-2.5 py-1 rounded transition-colors', {
'bg-blue-600 text-white': days === d,