Skip to content

Shadow bets per filter, second-opinion scorecard, maker fills from price history - #23

Merged
botta0oss merged 2 commits into
mainfrom
claude/lucid-lovelace-h1c8tm
Sep 26, 2026
Merged

botta0oss merged 2 commits into
mainfrom
claude/lucid-lovelace-h1c8tm

Conversation

@botta0oss

Copy link
Copy Markdown
Owner

This PR adds measurement for the filters from #20, #21 and #22, so each one can be judged on data. There are no new betting rules.

1. Shadow bets (backend/betting/shadow.py, new shadow_bets table)

An automatic bet blocked only by filters is still followed, without money, as if it had been bought at the ask, until the market resolves. Filters covered:

  • economic-assessment reasons: longshot, second_opinion, price_market, too_close, roi_too_low. The assessment is re-run with those reason codes ignored, using the new ignore parameter of evaluate;
  • objective_evidence: the signal was a buy with Jev's own evidence rating and became HOLD with the facts-based one;
  • clv_guard: the guard's pause, or a category the guard excluded. paper_exclusions.source is now guard for those, and existing guard exclusions are migrated.

A bet also blocked by something else (illiquid, margin after costs…) is not counted. Only one shadow bet per market and filter stays open at a time. Shadow bets are settled like real ones and deleted by a portfolio reset.

Where the results show up:

  • the portfolio card «What the filters blocked», per filter: blocked, open, won/lost, hypothetical result, per dollar, price move after the block;
  • GET /portfolio/shadow;
  • the Shadow sheet and per-filter summary rows in the Excel export.

2. Second-opinion scorecard (Calibration)

/predictions/calibration now includes second_opinion, computed on the latest forecast with a second opinion per resolved market:

  • Brier score of the second opinion, Jev and the price on the same markets;
  • gain_second with its 95% interval;
  • disagreements with a signal, and how many of them Jev got right;
  • blocked_result_per_share, the average result of the bets the disagreement blocked.

The Calibration page shows this in a new card.

3. Maker fills from the price history

Between two syncs, a pending limit order also fills when the CLOB's minute price history shows the side trading strictly below the limit while the order was waiting. The fill uses the time of that trade. Trading exactly at the limit does not count, because other orders may be ahead in the queue. With no history (no token, CLOB unreachable) the order falls back to the snapshot rule. This is controlled by MAKER_FILL_FROM_HISTORY, and fetch_price_history gains a fidelities argument.

Tests

  • tests/test_shadow.py: 8 new tests. Older tests run with MAKER_FILL_FROM_HISTORY=false. 234 passed.
  • Playwright smoke test of the portfolio, calibration and settings pages in IT and EN: no JS errors, no missing translations.
  • Docs updated in EN and IT (strategy, verification, configuration, API, development), plus .env.example.

🤖 Generated with Claude Code

https://claude.ai/code/session_01JLWZrfy12imc6dQRsEtFjj


Generated by Claude Code

…ice history

- Shadow bets: automatic bets blocked only by filters (long shots, second
  opinion, price markets, too close, return too low, objective evidence,
  closing-line guard) are followed without money until resolution; per filter
  the portfolio shows blocked, won/lost, hypothetical result and price move.
  GET /portfolio/shadow, Shadow sheet in the export. Guard exclusions are
  marked with source=guard
- Calibration: how the second opinion forecast against Jev and the price,
  and who was right when it disagreed with a signal
- Maker orders also fill when the CLOB's minute price history traded below
  the limit between two syncs, at the time of that trade
- Settings, docs (EN/IT), .env.example and tests

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLWZrfy12imc6dQRsEtFjj
…by text

The assertion searched the text "0.3" in the state sent to Jev, which also
matched timestamps such as 07:47:40.392993 depending on the clock: main
failed after the merge of #22. It now checks that no number in the state
equals the price and that no field is named after a price.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JLWZrfy12imc6dQRsEtFjj
@botta0oss
botta0oss merged commit be2950c into main Sep 26, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants