feat: cockpit command center + skipped trades journal

Dashboard: insert 2 rows of 4 mini-cards between top row and trade ideas
- Row 1: PnL Simulé, Risque Simulé, Dernier Cycle, Régime Macro
- Row 2: Super Contexte, Signaux Géo, Meilleur Pattern, Patterns Actifs
- All cards link to underlying pages via react-router Link

Journal: add 'Non loggés' tab exposing trades suggested by cycle
but skipped because no risk profile was matched
- New skipped_trades table (auto-created on backend restart)
- log_trade_entries() persists each skip with score/gain/asset_class
- GET /api/journal/skipped-trades + useSkippedTrades hook

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
OpenSquared
2026-06-19 19:01:58 +02:00
parent 8f15dff721
commit 08651551db
5 changed files with 383 additions and 4 deletions

View File

@@ -6,7 +6,7 @@ from services.database import (
get_macro_regime_history, get_geo_alert_history, get_trade_entry_prices,
get_closed_trades, close_trade, update_trade_exit_params,
get_trade_entry_by_id, get_config, set_config, reset_journal_history,
_fetch_live_prices, _trade_maturity,
_fetch_live_prices, _trade_maturity, get_skipped_trades,
)
import json
@@ -253,6 +253,13 @@ def trade_check(body: TradeCheckRequest):
return check_new_trade(body.underlying, body.strategy, body.asset_class)
@router.get("/skipped-trades")
def skipped_trades_endpoint(days: int = 30):
"""Trades suggested by cycle that didn't pass any risk profile threshold."""
trades = get_skipped_trades(days)
return _sanitize({"trades": trades, "days": days, "count": len(trades)})
@router.delete("/reset")
def reset_journal():
"""Truncate all journal history (trades, macro, geo, cycles). Irreversible."""

View File

@@ -379,6 +379,25 @@ def init_db():
details TEXT
)""")
c.execute("""CREATE TABLE IF NOT EXISTS skipped_trades (
id INTEGER PRIMARY KEY AUTOINCREMENT,
run_id TEXT,
pattern_id TEXT,
pattern_name TEXT,
underlying TEXT,
strategy TEXT,
score INTEGER DEFAULT 0,
expected_move_pct REAL,
skip_reason TEXT DEFAULT 'no_profile',
skip_detail TEXT,
asset_class TEXT,
created_at TEXT DEFAULT (strftime('%Y-%m-%dT%H:%M:%S', 'now'))
)""")
try:
c.execute("CREATE INDEX IF NOT EXISTS idx_skipped_date ON skipped_trades(created_at DESC)")
except Exception:
pass
try:
c.execute("CREATE INDEX IF NOT EXISTS idx_kb_category ON knowledge_base(category, status)")
c.execute("CREATE INDEX IF NOT EXISTS idx_rs_version ON reasoning_state(version DESC)")
@@ -1014,6 +1033,17 @@ def log_trade_entries(run_id: str, scored_patterns: List[Dict[str, Any]], quotes
if matched is None:
skipped_no_profile += 1
_log.debug(f"[TradeLog] SKIP {underlying} score={eff_score} gain={exp_move:.0f}% — no profile match")
_trade_ac = trade.get("asset_class") or sp.get("asset_class") or _orig.get("asset_class") or ""
try:
log_skipped_trade(
run_id=run_id, pattern_id=pid, pattern_name=pattern_name,
underlying=underlying, strategy=strategy, score=eff_score,
expected_move_pct=exp_move,
skip_detail=f"profiles checked: {len(profiles)}, best: score>={eff_score} gain>={exp_move:.0f}%",
asset_class=_trade_ac,
)
except Exception:
pass
continue
ev_gross, ev_net, trade_score = _compute_trade_score(eff_score, exp_move)
@@ -1205,6 +1235,35 @@ def get_closed_trades(days: int = 180) -> List[Dict[str, Any]]:
return [dict(r) for r in rows]
def get_skipped_trades(days: int = 30) -> List[Dict[str, Any]]:
conn = get_conn()
rows = conn.execute(
"""SELECT * FROM skipped_trades
WHERE created_at >= date('now', ?)
ORDER BY created_at DESC, score DESC""",
(f"-{days} days",)
).fetchall()
conn.close()
return [dict(r) for r in rows]
def log_skipped_trade(run_id: str, pattern_id: str, pattern_name: str,
underlying: str, strategy: str, score: int,
expected_move_pct: float, skip_reason: str = "no_profile",
skip_detail: str = "", asset_class: str = "") -> None:
conn = get_conn()
conn.execute(
"""INSERT INTO skipped_trades
(run_id, pattern_id, pattern_name, underlying, strategy, score,
expected_move_pct, skip_reason, skip_detail, asset_class)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
(run_id, pattern_id, pattern_name, underlying, strategy, score,
expected_move_pct, skip_reason, skip_detail, asset_class)
)
conn.commit()
conn.close()
def close_trade(trade_id: int, close_price: float, pnl_realized: float,
close_reason: str, close_note: str = "") -> bool:
conn = get_conn()