Resting limit orders, sortable drill-down table, and LLM-tuned Dark Horse limits - #1
Conversation
…nce insights Click any wallet row to open a detail panel showing: - a short description of the wallet's current strategy (from the strategy module docstring; permanent wallets get bespoke blurbs), - a performance snapshot (equity, realized/unrealized P&L, fees, holdings, avg cost, trade/buy/sell counts, and sell win rate), - open orders (empty in the paper harness, which fills or rejects every intent on its candle, with an explanatory note), and - full order history: placed/filled timestamps, side, type, requested vs filled qty, price, notional, fee, realized P&L, status, and reason. The dev harness now records every execution result per wallet (filled and rejected) with candle-close timestamps, and the read model exposes them via the existing /orders and /fills endpoints plus insights + description on the wallet detail. Rows are keyboard-accessible buttons; Escape / backdrop close the panel. All rendering uses the existing safe DOM primitives (no innerHTML).
…vserver port Two dev-harness follow-ups: - build_market now accepts end_ms (the last candle's close time). build_view passes 'now' aligned to a 5-minute boundary, so synthetic (offline) order history reads with real recent timestamps instead of the 1970 epoch. The price walk and balances are unchanged — only the timeline shifts. _candle keeps its legacy epoch-relative default so existing tests are unaffected. - main() now checks the port before the market replay and exits 1 with a clear message if it is already bound, instead of losing the bind race to a stale process that keeps serving old in-memory code (the trap behind the earlier 'Dark Horse frozen' report).
The 12 builtin strategies used `len(candles)` as an absolute candle index for their time-based gates (entry cooldown in base, grid re-centre in s01). That conflates "how much history was handed to me" with "how much time has elapsed". When the caller feeds a bounded trailing window — the dev harness passes only the last 150 candles — that length flatlines once the window fills, so every `index - last >= N` gate reads "no time has passed" and the strategies stop entering after ~12 h. Observed as: all wallet trades bunched on the first day of the live window and then near-silence. Replace the window length with `bar_ordinal`, a monotonic clock derived from the newest candle's open time and the candle spacing, so it keeps climbing across the whole run regardless of how much history is retained. Gates compare relative differences, so the change is behaviour-preserving for the existing tests; it only unfreezes the clock under a bounded window. Live replay trades now spread across the whole window (per-day builtin fills 17->157/19/4/7 before, 64/286/236/266 after) with the harness window unchanged.
…-tuned Dark Horse limits Wallet table: - every column header sorts asc/desc (numeric-aware), with aria-sort - four new columns: Unrealized P&L, Fees, Open orders, Completed orders Resting limit orders (open orders are now real, not structurally zero): - new RestingBook: a per-wallet ladder of LIMIT orders that carries across candles, fills when a candle trades through the price, expires on a TTL, and fills at most once per wallet per candle (respects the audited A01). The audited ExecutionService is unchanged — the book only decides what to submit. - strategies rest limits WHERE NATURAL (builtins stay fixed): mean-reversion entries rest bids below and take-profit exits rest asks above; breakout/trend entries and all stops stay MARKET. base.py buy/sell helpers gained limit_bps. - the devserver replay expires/fills/rests each tick and surfaces the resting set into open_orders_by_wallet, lighting up the column and the drill-down. Dark Horse: - Dark Horse (4h committee) stays MARKET — a conviction accumulator acts now. - Darkhorse - Daily gains guardrailed entry_limit_bps/exit_limit_bps tunables. LLM daily re-tune loop (opt-in --llm-adapt, off by default): - new harness_adaptation.py wires the existing lesson + adaptation pipeline into the replay: per simulated day it builds DailyFacts, generates lessons via an LLM analyst, and adapts Darkhorse - Daily's params (incl. limit offsets) via an LLM proposer, applying the guardrailed result before the next day. - degrades to a no-op when the local model is down; deterministic when off. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 44012754f5
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| book.rest(RestingOrder( | ||
| order_id=iid, wallet_id=wallet.wallet_id, | ||
| strategy_version_id=version_id, side=side, | ||
| limit_price=limit_price, quantity=qty, reason_code=reason, | ||
| placed_open_ms=snapshot.open_time_ms)) |
There was a problem hiding this comment.
Prevent repeated entry signals from overcommitting cash
When an entry predicate remains true across multiple candles, such as a prolonged Bollinger deep-z signal, the wallet remains non-holding until a limit fills, so every tick reaches this call and adds another order without consulting book.has_order() or reserving cash. With the default one-candle cooldown, the ladder can therefore contain several orders each sized from the same original balance; two 40% orders can later fill on separate candles and deploy about 80% instead of the intended entry allocation, while further orders become ledger rejects. Deduplicate or replace pending entries, or reserve their notional when sizing new orders.
Useful? React with 👍 / 👎.
| return [self.sell_all_intent(context, "profit_target", | ||
| limit_bps=self.exit_limit_bps)] |
There was a problem hiding this comment.
Preserve strategy state until a limit exit actually fills
When this profit target emits a resting limit, BuiltinStrategy.on_market_snapshot immediately clears entry_price as though the sell had filled. If the ask is not reached and expires after its TTL, this strategy still owns BTC but entry_price remains None, so the profit-target condition can never submit another exit while the EMA trend remains bullish. Limit-order placement therefore needs pending-order state distinct from fill-driven position state, or the entry state must only be cleared after execution.
Useful? React with 👍 / 👎.
| new_params = retuner.end_day(current_day, wallet_by_id, | ||
| prev.mark_price, version_by_id, | ||
| day_fills) | ||
| _apply_daily_params(permanents, new_params) |
There was a problem hiding this comment.
Activate the new strategy version after daily adaptation
When --llm-adapt produces a changed adaptation, DailyReTuner.end_day advances retuner.daily_version_id, but this boundary applies only its parameters. The permanent slot and version_by_id remain on dark-horse-daily-v1, so subsequent committee intents and fills are attributed to the old strategy version and the next day's lesson also reports the old version despite trading with new parameters. Propagate the returned version to the active slot and version map together with the parameters.
Useful? React with 👍 / 👎.
| pr.accumulate_fraction = params["accumulate_fraction"] | ||
| pr.reduce_fraction = params["reduce_fraction"] | ||
| pr.cadence_seconds = int(params["signal_cadence_hours"] * 3600) | ||
| pr.entry_limit_bps = params["entry_limit_bps"] | ||
| pr.exit_limit_bps = params["exit_limit_bps"] |
There was a problem hiding this comment.
Apply the tuned confidence thresholds to committee decisions
The proposer is allowed to adjust accumulate_confidence and reduce_confidence, but this application step ignores both values and _permanent_committee_intent continues calling synthesize, which uses the hard-coded ACCUMULATE_CONFIDENCE and REDUCE_CONFIDENCE constants. Consequently, successful adaptations can record new confidence values and versions while producing no behavioral change; wire these thresholds into the committee decision or remove them from the harness's advertised tunable surface.
Useful? React with 👍 / 👎.
| ts = _iso(snapshot.close_time_ms) | ||
| placed_at = _iso(placed_ms + FIVE_MIN_MS) if placed_ms is not None else ts |
There was a problem hiding this comment.
Derive resting-order placement times from the candle interval
In live mode the CLI accepts intervals other than 5m, but a resting order's placement timestamp is reconstructed by adding the fixed FIVE_MIN_MS to its candle open. With --live --interval 1h, for example, every resting order is displayed as placed 55 minutes before the strategy actually evaluated the closed candle; the expired-order record has the same assumption. Store the placement close time directly or derive it from the snapshot's actual close time.
Useful? React with 👍 / 👎.
Bundles this session's dashboard + strategy work (squash-merging into one commit).
Wallet drill-down & table
Resting limit orders (open orders are now real)
RestingBook: a per-wallet ladder of LIMIT orders that carries across candles, fills when a candle trades through, expires on a TTL, and fills at most once per wallet per candle (respects the audited A01 one-fill rule).ExecutionServiceis untouched.Dark Horse
entry_limit_bps/exit_limit_bpstunables.LLM daily re-tune loop (opt-in
--llm-adapt, off by default)harness_adaptation.pywires the existing lesson + adaptation pipeline into the replay: per simulated day it buildsDailyFacts, generates lessons via an LLM analyst, and adapts Darkhorse - Daily's params (incl. limit offsets) via an LLM proposer, applying the guardrailed result before the next day. Degrades to a no-op when the model is down; deterministic when off.Also included
Tests
Full suite green: 899 passing (order-book, strategy-limit-emission, harness-adaptation, degrade-path, sorting/columns, and prior fixes).
🤖 Generated with Claude Code