Repository navigation
feat: stop preview in the arm modal (POST /entry/preview dry run) - #243
Merged
Merged
Conversation
Three of October's stops were ATR fallbacks or single-level stops at 0.15% to 0.26% of price, stopped by noise within minutes; the operator only learned the stop method after the fill. PositionManager::preview_entry runs the immediate-arm path without side effects: the month's capital base as arm_handler loads it, a transient Armed position shaped like arm_position_with_policy builds, the detector's immediate-signal analysis (new DetectorTask:: preview_immediate_signal, no info log, no event), the futures balance and trading rules, and decide_entry_with_rules on a cloned engine. It returns reference price, technical stop with distance, method and confidence, size, notional, planned risk, executable trigger and span, the domain rejection the arm would hit, and advisory warnings. Nothing is saved and no order is placed; a unit test asserts that. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Call POST /entry/preview for the current symbol and side and show the result in the card's monospace style: reference, stop with distance and percentage, method and confidence, size and notional, planned risk against capital, executable trigger and span. Warnings turn the button into ARM ANYWAY; a domain rejection disables it with the reason. The preview is keyed to the inputs, so changing symbol or side invalidates it. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…ail-closed on the rules cache Review findings (Codex, P1/P2) on POST /entry/preview: - The handler held `position_manager.read()` across two candle fetches, a balance call and an exchangeInfo lookup, so a slow exchange delayed every `write()` holder (arm, close, panic) behind it. The preview is now two steps: `prepare_entry_preview` captures capital, risk config, the transient position, the detector and the exchange/engine handles under a short guard; `EntryPreviewJob::run` does the I/O after the guard is dropped, under a 15 s timeout (504 on expiry). A process-wide semaphore serializes previews (429 when one is running), so previews cannot fan out exchange calls faster than a real arm. - The adapter's trading-rules cache refused new symbols once its 64 slots were full, which previews could fill and then a real arm on an uncached symbol would fail. It now evicts the least recently fetched entry instead. - Availability failures (no or too few candles, trading rules unavailable) are reported as `unavailable`, distinct from `rejection`, so the UI never blocks an arm on missing data; only an explicit domain rejection blocks. A missing futures balance is surfaced as a warning. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…p five significant digits `unavailable` is rendered as a NO DATA line and an explanatory note while ARM stays enabled (ARM ANYWAY); only `rejection` blocks. Prices and spans print with at least five significant digits and two decimals (83,454.50; 0.12340 vs 0.12240), monetary totals stay at two decimals. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…es cache or its mutex Second review round (Codex): - The preview still fetched trading rules through the adapter's cached path, which holds the adapter mutex across the exchangeInfo HTTP call (the same mutex the market-exit path takes) and populates/evicts the cache real arms depend on. New `ExchangePort::trading_rules_preview` (default: the normal lookup) is overridden by the Binance adapter with a peek-or-fetch: a fresh cached entry is read under a momentary lock, otherwise exchangeInfo is fetched without writing the cache. The preview job uses it. - Cache eviction was by oldest fetch, so a burst of distinct symbols could evict an actively traded symbol whose timestamp never refreshed on hits. Entries now track last use and eviction is LRU; test asserts the active symbol survives. - Identical previews (same symbol and side) within 5 s are answered from the last result, so a double click or a second tab costs no exchange traffic. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
… preview Below one millionth fixed decimals stop being readable, so values switch to scientific notation with four significant digits; between a millionth and a thousand the decimals grow up to ten. Fixture: reference 0.00000123, stop 0.000001228, span 2e-9. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…instead of memo Third review round (Codex): - A symbol living on its stale trading-rules snapshot after failed refreshes was never marked as used, so it could become the LRU victim while actively traded. `touch_stale_trading_rules` updates `last_used_at` (keeping `fetched_at`, so the next lookup still retries) and the eviction test covers the stale path. - The 5 s per-key memo could serve a preview computed before a capital recalibration or the UTC month rollover, including an obsolete rejection that disables ARM, and it throttled only identical inputs. Replaced by a sliding budget of 6 previews per 60 s across all inputs (429 beyond), with no memoisation: every served preview reloads capital and candles. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
# Conflicts: # CHANGELOG.md
# Conflicts: # CHANGELOG.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
POST /entry/previewreuses the production detector and engine end to end (same capital base, sameTechnicalStopConfig, same invalidation guard, same sizing) without persisting anything or placing orders, and the arm modal gets a STOP PREVIEW panel: technical stop, distance, method and confidence, guard level, size, planned risk and executable trigger/span. Advisory warnings (ATR fallback, medium confidence, distance under 0.5%) turn ARM into ARM ANYWAY; only an explicit domain rejection blocks; an unavailable preview (no candles, metadata down) never blocks.Changes
robsond/src/position_manager.rs:prepare_entry_preview(symbol, side) -> EntryPreviewJob(captured under a short manager read guard; the only I/O under it is the localmonthly_stateread) andEntryPreviewJob::run(candles, balance, metadata, engine decision, after the guard is released).EntryPreview { reference_price, capital_base, analysis, sizing, rejection, unavailable, warnings }. Availability failures (no candles,Insufficient candle data, trading rules unavailable) go tounavailable; engineDomainErrorgoes torejection.robsond/src/detector.rs:preview_immediate_signal(100 × 15m candles, anchor on last close,create_signal), no events, no tasks.robsond/src/api.rs:preview_entry_handleron the authenticated router only; sliding budget of 6 previews / 60 s across all inputs (429), one preview at a time (429 on overlap), 15 s timeout on the I/O (504). No memoisation.robson-exec/src/ports.rs:ExchangePort::trading_rules_preview(default = normal lookup).robsond/src/binance_exchange.rs: override that reads a fresh cached entry under a momentary lock or fetches exchangeInfo without writing the cache; the production cache is now LRU (last_used_at, updated on fresh and stale hits) and evicts instead of failing closed at 64 symbols.robsonApi.previewEntry,EntryPreviewtypes/normalizer,presentation/preview.ts(previewSeverity,previewLines,fmtNumwith ≥5 significant digits and scientific notation below 1e-6), STOP PREVIEW panel inArmModal.sveltekeyed tosymbol|sideso a stale preview never applies to different inputs.Tests
cargo test -p robson-exec -p robsond --features robsond/postgres— 366 robsond + exec tests passed (container, rust 1.92); doc-tests pass; clippy-D correctness -D suspiciousclean;cargo +nightly-2026-08-22 fmt --checkcleancd frontend && npx vitest run— 138 passed;svelte-check0 errorsposition_manager.rs::preview_entry_sizes_without_persisting_anything,preview_availability_failures_are_not_rejectionsbinance_exchange.rs::trading_rules_cache_evicts_the_least_recently_used_entry_when_full(active symbol survives; stale fallback in use survives)api.rs::preview_budget_admits_at_most_max_per_windowfrontend/tests/unit/preview.test.ts(severity, lines, unavailable never blocks, small and sub-micro price formatting, normalizer)Migrations
Backward Compatibility
Security & Data
Checklist
docs/DEVELOPER.md)🤖 Generated with Claude Code