Two defects made the KIS connector look like an empty, healthy account on
a live Korean brokerage account.
`get_open_orders` asked `inquire-daily-ccld` for EXCG_ID_DVSN_CD=KRX only.
Since Nextrade (NXT) opened as a second venue, brokers route most retail
orders through SOR, and the endpoint's venue buckets are DISJOINT rather
than nested: a live account returned 6 rows for KRX and 33 for SOR with
ZERO overlap. Every SOR-routed fill was invisible, so `open_orders` and
`executions` came back empty while the account had traded that morning.
Ask each venue in `_EXCHANGE_IDS_INQUIRY` and merge on (ord_dt, odno). A
venue the broker rejects is reported in `venue_errors` instead of hiding
the others' fills.
KIS issues ONE token per app_key, so a token minted for the other
environment — or by any other process sharing the key — silently
invalidates the cached one long before its reported `expires_at`. The
cache still looked valid, so every call failed with "기간이 만료된
token 입니다" until the file aged out. Detect that message, drop the
cache, and re-issue once.
Order placement still goes to KRX only; this changes the read path.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Signed-off-by: jacob-clobot <jacob@clobot.co.kr>
Adversarial review follow-ups on #1725:
- The bulk wiring duplicated the loader import block in 17 files
(identical adjacent 'from backtest.loaders...' lines). Remove the
copies; ruff F811 never flagged them because both were used.
- _CONTRACT_REASONS was defined but never consulted, so a typo'd
reason string would pass silently. bad() now asserts membership.
- Wire the shared contract into the 5 remaining per-loader suites
(okx, wallex, zerodha, ccxt-perpetual, longbridge) so the claim
'every per-loader suite' holds. okx and wallex are canary symbols
in loader_health — exactly the drift-prone loaders to pin.
Full unit suite: 1813 passed, 71 skipped (4 OAuth/MCP env failures
and 6 akshare forex failures reproduce on clean upstream/main).
Signed-off-by: Jackzigen <Jackzigen@users.noreply.github.com>
Each per-loader test file pinned its canonical fixture with bespoke
assertions; the only shared schema check lived in the weekly health
lane. A data-source drift that renames/reorders columns or lets
non-finite values through now only breaks whichever bespoke assertions
happen to cover it, and can slip past review unnoticed.
Extract the canonical-frame checks from backtest/loader_health.py into
tests/loader_contract.py (one call, same failure vocabulary) and wire
it into every per-loader suite's canonical-frame test (24 files).
This is the first step of the #1723 roadmap: with every loader suite
asserting the same shared contract, a loader that emits a
non-conforming frame fails its own suite immediately instead of
waiting for the weekly health lane.
Closes#1723 (step 1/3)
Signed-off-by: Jackzigen <Jackzigen@users.noreply.github.com>
- Remove stale memory_summary= kwarg from test_system_prompt_format_succeeds
(str.format tolerates excess kwargs, so it silently passed while masking
a potential reintroduction of the field).
- Pin that the <agent-state> envelope stays a sibling prefix outside
<user-message> tags when loop.py goal-context wrapping is active.
Co-authored-by: Claude <noreply@anthropic.com>
WorkspaceMemory.to_summary() renders run_dir (fresh timestamp_uuid per
run) and tool counters (bumped every tool call). Rendering it into the
system prompt (## State section) meant the prompt bytes changed on every
turn and every tool call, invalidating the provider prefix cache for
everything after that point — including the cache_control block the
Anthropic adapter marks on the system message.
Move the volatile summary into the first user message wrapped in an
<agent-state> envelope (same convention as <recalled-memories>), drop
the ## State section from the template, and pin the new property with
tests: system prompt is byte-identical when only run_dir/counters
differ, and no counter or run-dir value reaches the system prompt.
Fixes#1707
Co-authored-by: Claude <noreply@anthropic.com>
Wait for actual session terminal events, cancel in-flight analysis on runner stop, and finish scheduler teardown across startup, concurrent stops and API shutdown. Correct heartbeat status units and remove stopped sentinels.
Isolate broker job stores with legacy migration and complete atomic writes under concurrency. Skip malformed emergency-close symbols and overflowing quantities, and make capped grounding feedback explicit about omitted findings.
Validation: 16,817 backend tests passed (190 skipped), 38 frontend status tests passed, and changed Python syntax/lint passed. Update seven README News entries.
MARKET_SPECS only modelled full-day holidays, so on NYSE early-close
days (day after Thanksgiving, weekday Christmas Eve) the market-hours
gate stayed open until 16:00 ET and the runner ticked a closed market
all afternoon: reconcile, agent invoke, audit.
_MarketSpec now carries an early_closes date -> bell mapping and
market_is_open_at resolves the close per date. The static table covers
the same 2026/2027 window as the holiday set.
Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>
`_drive_runner` awaited `LiveRunner.run_loop()` to completion and then
returned. `run_loop` is fire-and-forget: it resolves the jobs, calls
`Scheduler.start()` — which only spawns the loop task — and returns. The
task wrapping the driver is the only handle `POST /live/runner/stop`
holds, and its `add_done_callback` unregisters the task the moment the
driver returns. The result is that a runner whose driver returned keeps
firing `run_once()` on its cadence while `stop` answers
`was_running: false`, so the loop is never torn down and cannot be
stopped through the API.
Park the driver on the scheduler's loop task instead, so the driver's
lifetime is the scheduler's lifetime. Cancelling the driver then
actually stops trading. Teardown goes through `Scheduler.stop()` in a
`finally`, tolerant of both sync and async `stop` implementations.
Both helper reads are `getattr`-defensive: neither `LiveRunner` nor
`Scheduler` publishes an accessor for the loop task, so private access
is the only option without widening the change. A runner whose `run_loop`
declined to start — no scheduler, or a missing or expired mandate —
leaves `_task` unset and returns immediately as before.
Adds five tests covering: the driver staying registered while the
scheduler runs, `stop` reporting `was_running: true` and actually
stopping the scheduler, stop idempotency after a real stop, async
`run_loop` teardown, and the preserved declined-start behaviour.
Signed-off-by: Romzhou <1962128849@qq.com>
A mixed rejection used to name only the failing figures. With 65 issues
on the table and nothing saying which values the gate had already
passed, the safest-looking play for the model was to rewrite whole
numeric sections as qualitative prose. zeus229's production run on #1622
lost a valid Risk X-Ray block and a portfolio-vs-benchmarks table that
way: the corrected draft passed, minus most of its numbers.
The gate now carries the measured figures that checked clean on the
ValidationResult, and the correction prompt tells the model to keep
them exactly as written: "only the figures listed above need work". A
spelling that fails anywhere in the draft is endorsed nowhere, and bare
integers stay off the list since an unchecked pass would vouch for
nothing.
Tests: three new pins in test_grounding_release.py, red before the fix
and green after (the keep list names the clean figures in document
order and none of the rejected ones; no section renders when nothing
passed; the list caps at 24 values and counts the rest). The 667
grounding tests and the full agent suite pass.
Ref #1622
Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>