From ce33deb6b9f3c002a16aa3b7bb2bdadf56b3cd1c Mon Sep 17 00:00:00 2001 From: goodboy Date: Tue, 28 Jul 2026 23:44:15 -0400 Subject: [PATCH] Use IB UTC syntax for history `endDateTime` Serialize aware reverse-fill boundaries as `YYYYMMDD-HH:MM:SS` before handing them to `ib_async`. This avoids Gateway error 10314 from its trailing-`UTC` format and drops the unfinished EST comparison breakpoint from `Client.bars()`. Prompt-IO: ai/prompt-io/opencode/20260729T034106Z_922a5df8_prompt_io.md (this patch was generated in some part by `opencode` using `gpt-5.6-sol` (`openai`)) --- .../run-tests/test-harness-reference.md | 3 + .../20260729T034106Z_922a5df8_prompt_io.md | 40 +++++++++ ...20260729T034106Z_922a5df8_prompt_io.raw.md | 25 ++++++ piker/brokers/ib/api.py | 17 ++-- tests/test_ib_history.py | 81 +++++++++++++++++++ 5 files changed, 158 insertions(+), 8 deletions(-) create mode 100644 ai/prompt-io/opencode/20260729T034106Z_922a5df8_prompt_io.md create mode 100644 ai/prompt-io/opencode/20260729T034106Z_922a5df8_prompt_io.raw.md create mode 100644 tests/test_ib_history.py diff --git a/.claude/skills/run-tests/test-harness-reference.md b/.claude/skills/run-tests/test-harness-reference.md index 412a08db..1c951ec0 100644 --- a/.claude/skills/run-tests/test-harness-reference.md +++ b/.claude/skills/run-tests/test-harness-reference.md @@ -106,6 +106,7 @@ Deterministic or local first-pass targets: - `tests/test_watchlists.py` - `tests/test_storage_audit.py` - `tests/test_backfill_audit_snippet.py` +- `tests/test_ib_history.py` - `tests/test_accounting.py::test_account_file_default_empty` - `tests/test_services.py::test_runtime_boot` - `tests/test_services.py::test_datad_spawn` @@ -188,6 +189,7 @@ tests/ test_dpi_font.py Qt DPI/font behavior test_ems.py actor, EMS, and paper-position behavior test_feeds.py live Binance/Kraken feeds and shared memory + test_ib_history.py deterministic IB history request formatting test_questrade.py obsolete credentialed tests; skipped test_services.py pikerd/datad/feed/EMS actor lifecycle test_storage_audit.py read-only NativeDB audit and JSON CLI @@ -203,6 +205,7 @@ tests/ | `piker/watchlists/` | `tests/test_watchlists.py` | CLI suite is skipped | | `piker/storage/_audit.py`, `piker/storage/cli.py` | `tests/test_storage_audit.py` | direct Typer app, no actor | | `snippets/nativedb_backfill_audit.xsh` | `tests/test_backfill_audit_snippet.py` | disposable paths only | +| `piker/brokers/ib/api.py` history formatting | `tests/test_ib_history.py` | fake client, no network | | `piker/config.py` | `test_account_file_default_empty` | root-network test has a known mismatch | | `piker/accounting/` | targeted accounting node | some cases use live/configured state | | `piker/ui/_style.py`, `piker/ui/qt.py` | `tests/test_dpi_font.py` | GUI/config-isolated opt-in | diff --git a/ai/prompt-io/opencode/20260729T034106Z_922a5df8_prompt_io.md b/ai/prompt-io/opencode/20260729T034106Z_922a5df8_prompt_io.md new file mode 100644 index 00000000..2e53ef8f --- /dev/null +++ b/ai/prompt-io/opencode/20260729T034106Z_922a5df8_prompt_io.md @@ -0,0 +1,40 @@ +--- +model: gpt-5.6-sol +provider: openai +service: opencode +session: 24fb9765-a550-4570-8350-f0fc9b7e17db +timestamp: 2026-07-29T03:41:06Z +git_ref: 922a5df8 +scope: code +substantive: true +raw_file: 20260729T034106Z_922a5df8_prompt_io.raw.md +--- + +## Prompt + +Diagnose the live IB reverse-fill failure where +`reqHistoricalDataAsync()` raised API error 10314 because its end date, +time, or timezone was invalid. + +## Response summary + +Traced the failure to `Client.bars()`, where an unfinished EST conversion +left the original datetime for `ib_async` to serialize as +`YYYYMMDD HH:MM:SS UTC`. Converted aware boundaries explicitly to UTC and +passed IB's accepted `YYYYMMDD-HH:MM:SS` string while retaining a blank +latest-request boundary. Added exact request-argument regressions. + +## Files changed + +- `piker/brokers/ib/api.py` - explicit UTC-dash history boundary format +- `tests/test_ib_history.py` - IB request-format regressions +- `.claude/skills/run-tests/test-harness-reference.md` + - deterministic IB history test mapping +- `ai/prompt-io/opencode/20260729T034106Z_922a5df8_prompt_io.raw.md` + - unedited response record +- `ai/prompt-io/opencode/20260729T034106Z_922a5df8_prompt_io.md` + - provenance metadata and response summary + +## Human edits + +None - generated changes have not been edited by the human. diff --git a/ai/prompt-io/opencode/20260729T034106Z_922a5df8_prompt_io.raw.md b/ai/prompt-io/opencode/20260729T034106Z_922a5df8_prompt_io.raw.md new file mode 100644 index 00000000..70df78ad --- /dev/null +++ b/ai/prompt-io/opencode/20260729T034106Z_922a5df8_prompt_io.raw.md @@ -0,0 +1,25 @@ +--- +model: gpt-5.6-sol +provider: openai +service: opencode +timestamp: 2026-07-29T03:41:06Z +git_ref: 922a5df8 +diff_cmd: git diff HEAD~1..HEAD +--- + +> `git diff HEAD~1..HEAD -- piker/brokers/ib/api.py tests/test_ib_history.py .claude/skills/run-tests/test-harness-reference.md` + +Fixed IB reverse-history requests to serialize aware end boundaries with +IB's explicit UTC-dash syntax, `YYYYMMDD-HH:MM:SS`. This bypasses +`ib_async`'s trailing-`UTC` representation, which the live Gateway rejected +with API error 10314, and removes the unfinished EST comparison breakpoint. + +The regression captures the exact `reqHistoricalDataAsync()` argument for +latest, stdlib-aware, Pendulum UTC, and America/New_York inputs. + +Verification generated with the patch: + +- IB history request regressions: 4 passed +- focused Ruff: passed +- `git diff --check`: passed +- adversarial review: no findings diff --git a/piker/brokers/ib/api.py b/piker/brokers/ib/api.py index 44d35609..496c9d18 100644 --- a/piker/brokers/ib/api.py +++ b/piker/brokers/ib/api.py @@ -29,7 +29,10 @@ from dataclasses import ( asdict, astuple, ) -from datetime import datetime +from datetime import ( + UTC, + datetime, +) from functools import ( partial, ) @@ -395,15 +398,13 @@ class Client: default_dt_duration ) - # TODO: maybe remove all this? global _enters if end_dt is None: - end_dt: str = '' - + end_date_time: str = '' else: - est_end_dt = end_dt.in_tz('EST') - if est_end_dt != end_dt: - breakpoint() + end_date_time = end_dt.astimezone(UTC).strftime( + '%Y%m%d-%H:%M:%S' + ) _enters += 1 @@ -412,7 +413,7 @@ class Client: kwargs: dict[str, Any] = dict( contract=contract, - endDateTime=end_dt, + endDateTime=end_date_time, formatDate=2, # OHLC sampling values: diff --git a/tests/test_ib_history.py b/tests/test_ib_history.py new file mode 100644 index 00000000..ec3ac694 --- /dev/null +++ b/tests/test_ib_history.py @@ -0,0 +1,81 @@ +''' +IB historical-data request regressions. + +''' +from datetime import ( + UTC, + datetime, +) +from types import SimpleNamespace + +from pendulum import datetime as pdatetime +import pytest +import trio + +from piker.brokers.ib.api import Client + + +@pytest.mark.parametrize( + ('end_dt', 'expected'), + [ + (None, ''), + ( + datetime(2026, 7, 28, 23, 30, tzinfo=UTC), + '20260728-23:30:00', + ), + ( + pdatetime(2026, 7, 28, 23, 30, tz='UTC'), + '20260728-23:30:00', + ), + ( + pdatetime( + 2026, + 7, + 28, + 23, + 30, + tz='America/New_York', + ), + '20260729-03:30:00', + ), + ], +) +def test_history_end_datetime_uses_ib_utc_format( + end_dt: datetime|None, + expected: str, +) -> None: + ''' + Reverse history boundaries must use IB's explicit UTC syntax. + + ``ib_async`` adds a trailing ``UTC`` zone to aware datetimes. + The live IB gateway rejected that form with error 10314 while + filling an MNQ gap. Invoke ``Client.bars()`` with stdlib and + Pendulum boundaries, then capture the request kwargs. + Prove each becomes IB's UTC-dash form while latest requests stay + blank. + + ''' + request: dict = {} + + class FakeIB: + async def reqHistoricalDataAsync(self, **kwargs) -> list: + request.update(kwargs) + return [] + + client = SimpleNamespace(ib=FakeIB()) + + async def find_contracts(fqme: str) -> list: + return [SimpleNamespace()] + + client.find_contracts = find_contracts + + async def main() -> None: + await Client.bars( + client, + fqme='mnq.cme.20260918', + sample_period_s=60, + end_dt=end_dt, + ) + + trio.run(main) + assert request['endDateTime'] == expected