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`))
backfiller_deep_fixes
Gud Boi 2026-07-28 23:44:15 -04:00
parent 922a5df88d
commit ce33deb6b9
5 changed files with 158 additions and 8 deletions

View File

@ -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 |

View File

@ -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.

View File

@ -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

View File

@ -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:

View File

@ -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