feat: trade lifecycle management — close, archive, target/stop alerts

- DB: 9 new columns on trade_entry_prices (status, closed_at, close_reason,
  close_note, pnl_realized, close_price, target_pct, stop_loss_pct, signal_threshold)
  via ALTER TABLE migration; close_trade(), get_closed_trades(),
  update_trade_exit_params() helpers; exit_defaults config key
- Backend: PATCH /trades/{id}/close, PATCH /trades/{id}/exit-params,
  GET/PUT /exit-defaults, GET /closed-trades with win-rate/avg-PnL stats;
  trade-mtm now computes alert_type (target_reached|stop_loss) per trade
- Journal: new "Fermés" tab with closed trades table + stats banner (win rate,
  avg PnL, total PnL, best trade); open trades show Cible/Stop progress bar +
  🎯/🛑 alert badges + 1-click close modal (price, reason, note)
- Config: new "Paramètres de sortie" panel — target_pct, stop_loss_pct,
  signal_reversal_mode, signal_reversal_threshold with live sliders

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OpenSquared
2026-06-19 14:17:29 +02:00
parent 0ba73cae0b
commit ee69f3cbd9
5 changed files with 673 additions and 13 deletions

View File

@@ -404,10 +404,60 @@ export const useTradeMtm = (days = 30) =>
queryKey: ['journal-mtm', days],
queryFn: () => api.get(`/journal/trade-mtm?days=${days}`).then(r => r.data),
staleTime: 0,
refetchInterval: 5 * 60_000, // re-fetch live prices every 5 minutes
refetchInterval: 5 * 60_000,
refetchIntervalInBackground: false,
})
export const useClosedTrades = (days = 180) =>
useQuery({
queryKey: ['journal-closed', days],
queryFn: () => api.get(`/journal/closed-trades?days=${days}`).then(r => r.data),
staleTime: 30_000,
})
export const useExitDefaults = () =>
useQuery({
queryKey: ['journal-exit-defaults'],
queryFn: () => api.get('/journal/exit-defaults').then(r => r.data),
staleTime: 60_000,
})
export const useCloseTrade = () => {
const qc = useQueryClient()
return useMutation({
mutationFn: ({ id, body }: { id: number; body: {
close_price: number; pnl_realized?: number;
close_reason: string; close_note: string
}}) => api.patch(`/journal/trades/${id}/close`, body).then(r => r.data),
onSuccess: () => {
qc.invalidateQueries({ queryKey: ['journal-mtm'] })
qc.invalidateQueries({ queryKey: ['journal-closed'] })
qc.invalidateQueries({ queryKey: ['journal-summary'] })
},
})
}
export const useUpdateExitParams = () => {
const qc = useQueryClient()
return useMutation({
mutationFn: ({ id, body }: { id: number; body: {
target_pct?: number; stop_loss_pct?: number; signal_threshold?: number
}}) => api.patch(`/journal/trades/${id}/exit-params`, body).then(r => r.data),
onSuccess: () => qc.invalidateQueries({ queryKey: ['journal-mtm'] }),
})
}
export const useSaveExitDefaults = () => {
const qc = useQueryClient()
return useMutation({
mutationFn: (body: {
target_pct?: number; stop_loss_pct?: number;
signal_reversal_mode?: string; signal_reversal_threshold?: number
}) => api.put('/journal/exit-defaults', body).then(r => r.data),
onSuccess: () => qc.invalidateQueries({ queryKey: ['journal-exit-defaults'] }),
})
}
// ── Risk Profiles ─────────────────────────────────────────────────────────────
export const useRiskProfiles = () =>