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:
@@ -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."""
|
||||
|
||||
@@ -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()
|
||||
|
||||
Reference in New Issue
Block a user