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
parent
922a5df88d
commit
ce33deb6b9
|
|
@ -106,6 +106,7 @@ Deterministic or local first-pass targets:
|
||||||
- `tests/test_watchlists.py`
|
- `tests/test_watchlists.py`
|
||||||
- `tests/test_storage_audit.py`
|
- `tests/test_storage_audit.py`
|
||||||
- `tests/test_backfill_audit_snippet.py`
|
- `tests/test_backfill_audit_snippet.py`
|
||||||
|
- `tests/test_ib_history.py`
|
||||||
- `tests/test_accounting.py::test_account_file_default_empty`
|
- `tests/test_accounting.py::test_account_file_default_empty`
|
||||||
- `tests/test_services.py::test_runtime_boot`
|
- `tests/test_services.py::test_runtime_boot`
|
||||||
- `tests/test_services.py::test_datad_spawn`
|
- `tests/test_services.py::test_datad_spawn`
|
||||||
|
|
@ -188,6 +189,7 @@ tests/
|
||||||
test_dpi_font.py Qt DPI/font behavior
|
test_dpi_font.py Qt DPI/font behavior
|
||||||
test_ems.py actor, EMS, and paper-position behavior
|
test_ems.py actor, EMS, and paper-position behavior
|
||||||
test_feeds.py live Binance/Kraken feeds and shared memory
|
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_questrade.py obsolete credentialed tests; skipped
|
||||||
test_services.py pikerd/datad/feed/EMS actor lifecycle
|
test_services.py pikerd/datad/feed/EMS actor lifecycle
|
||||||
test_storage_audit.py read-only NativeDB audit and JSON CLI
|
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/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 |
|
| `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 |
|
| `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/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/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 |
|
| `piker/ui/_style.py`, `piker/ui/qt.py` | `tests/test_dpi_font.py` | GUI/config-isolated opt-in |
|
||||||
|
|
|
||||||
|
|
@ -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.
|
||||||
|
|
@ -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
|
||||||
|
|
@ -29,7 +29,10 @@ from dataclasses import (
|
||||||
asdict,
|
asdict,
|
||||||
astuple,
|
astuple,
|
||||||
)
|
)
|
||||||
from datetime import datetime
|
from datetime import (
|
||||||
|
UTC,
|
||||||
|
datetime,
|
||||||
|
)
|
||||||
from functools import (
|
from functools import (
|
||||||
partial,
|
partial,
|
||||||
)
|
)
|
||||||
|
|
@ -395,15 +398,13 @@ class Client:
|
||||||
default_dt_duration
|
default_dt_duration
|
||||||
)
|
)
|
||||||
|
|
||||||
# TODO: maybe remove all this?
|
|
||||||
global _enters
|
global _enters
|
||||||
if end_dt is None:
|
if end_dt is None:
|
||||||
end_dt: str = ''
|
end_date_time: str = ''
|
||||||
|
|
||||||
else:
|
else:
|
||||||
est_end_dt = end_dt.in_tz('EST')
|
end_date_time = end_dt.astimezone(UTC).strftime(
|
||||||
if est_end_dt != end_dt:
|
'%Y%m%d-%H:%M:%S'
|
||||||
breakpoint()
|
)
|
||||||
|
|
||||||
_enters += 1
|
_enters += 1
|
||||||
|
|
||||||
|
|
@ -412,7 +413,7 @@ class Client:
|
||||||
|
|
||||||
kwargs: dict[str, Any] = dict(
|
kwargs: dict[str, Any] = dict(
|
||||||
contract=contract,
|
contract=contract,
|
||||||
endDateTime=end_dt,
|
endDateTime=end_date_time,
|
||||||
formatDate=2,
|
formatDate=2,
|
||||||
|
|
||||||
# OHLC sampling values:
|
# OHLC sampling values:
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
Loading…
Reference in New Issue