quantpad-data 0.4.0__tar.gz → 0.4.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {quantpad_data-0.4.0 → quantpad_data-0.4.2}/PKG-INFO +1 -1
- {quantpad_data-0.4.0 → quantpad_data-0.4.2}/pyproject.toml +1 -1
- {quantpad_data-0.4.0 → quantpad_data-0.4.2}/quantpad_data/client.py +1 -1
- {quantpad_data-0.4.0 → quantpad_data-0.4.2}/quantpad_data/v2.py +30 -1
- {quantpad_data-0.4.0 → quantpad_data-0.4.2}/tests/test_v2.py +85 -0
- {quantpad_data-0.4.0 → quantpad_data-0.4.2}/.gitignore +0 -0
- {quantpad_data-0.4.0 → quantpad_data-0.4.2}/README.md +0 -0
- {quantpad_data-0.4.0 → quantpad_data-0.4.2}/quantpad_data/errors.py +0 -0
- {quantpad_data-0.4.0 → quantpad_data-0.4.2}/quantpad_data/lean.py +0 -0
- {quantpad_data-0.4.0 → quantpad_data-0.4.2}/quantpad_data/memory.py +0 -0
- {quantpad_data-0.4.0 → quantpad_data-0.4.2}/quantpad_data/py.typed +0 -0
- {quantpad_data-0.4.0 → quantpad_data-0.4.2}/tests/test_client.py +0 -0
- {quantpad_data-0.4.0 → quantpad_data-0.4.2}/tests/test_lean.py +0 -0
- {quantpad_data-0.4.0 → quantpad_data-0.4.2}/tests/test_message_encoding.py +0 -0
- {quantpad_data-0.4.0 → quantpad_data-0.4.2}/tests/test_v2_gate.py +0 -0
- {quantpad_data-0.4.0 → quantpad_data-0.4.2}/tests/test_version.py +0 -0
|
@@ -841,7 +841,36 @@ def _merged_manifest(payloads: Sequence[dict]) -> dict:
|
|
|
841
841
|
for entry in entries:
|
|
842
842
|
if entry not in bucket:
|
|
843
843
|
bucket.append(entry)
|
|
844
|
-
|
|
844
|
+
# ``symbols`` and ``not_found`` sit BESIDE ``resolved`` in the same
|
|
845
|
+
# echo and vary for the same reason, so taking them from the first
|
|
846
|
+
# piece would be this function's own defect one field over.
|
|
847
|
+
#
|
|
848
|
+
# ``not_found`` is what NO piece found rather than the union: a
|
|
849
|
+
# symbol the second piece resolved was served, and listing it as
|
|
850
|
+
# not found would report an answer the caller is holding as absent.
|
|
851
|
+
symbols = {s for r in resolutions for s in (r.get("symbols") or ())}
|
|
852
|
+
# ``found`` is what RESOLVED and nothing else. Reading ``symbols``
|
|
853
|
+
# into it would be safe against today's server, where the field is
|
|
854
|
+
# derived from the same intervals as ``resolved`` -- but its NAME says
|
|
855
|
+
# "what was asked for", and if it ever carries a requested handle that
|
|
856
|
+
# resolved nowhere, folding it in here deletes that handle from
|
|
857
|
+
# ``not_found`` and a complete MISS reads as served. On every read,
|
|
858
|
+
# not only a split. The whole point of this function is to stop a
|
|
859
|
+
# label claiming more than was served, so it must not depend on two
|
|
860
|
+
# fields of one echo agreeing.
|
|
861
|
+
found = set(instruments)
|
|
862
|
+
merged["resolved"] = {
|
|
863
|
+
**resolutions[0],
|
|
864
|
+
"resolved": instruments,
|
|
865
|
+
"symbols": sorted(symbols | found),
|
|
866
|
+
"not_found": [
|
|
867
|
+
symbol
|
|
868
|
+
for symbol in dict.fromkeys(
|
|
869
|
+
s for r in resolutions for s in (r.get("not_found") or ())
|
|
870
|
+
)
|
|
871
|
+
if symbol not in found
|
|
872
|
+
],
|
|
873
|
+
}
|
|
845
874
|
|
|
846
875
|
return {"metadata": merged}
|
|
847
876
|
|
|
@@ -596,12 +596,28 @@ class TestAWindowTooColdToFetchInOneGo:
|
|
|
596
596
|
"days": {"cached": 9, "missing": 1},
|
|
597
597
|
"clamped": ["start moved to the plan floor"],
|
|
598
598
|
"warnings": ["the first half says so"],
|
|
599
|
+
"resolved": {
|
|
600
|
+
"dataset": "XNAS.ITCH", "stype_in": "raw_symbol",
|
|
601
|
+
"symbols": ["NVDA"],
|
|
602
|
+
"resolved": {"NVDA": [{"start": "2026-08-20",
|
|
603
|
+
"end": "2026-09-03",
|
|
604
|
+
"value": "42"}]},
|
|
605
|
+
"not_found": ["CROX"],
|
|
606
|
+
},
|
|
599
607
|
}),
|
|
600
608
|
piece("https://r2.test/b.parquet", "2026-09-04", {
|
|
601
609
|
"served_start": "2026-09-03", "served_end": "2026-09-17",
|
|
602
610
|
"days": {"cached": 5, "missing": 0},
|
|
603
611
|
"clamped": [],
|
|
604
612
|
"warnings": ["and the second says otherwise"],
|
|
613
|
+
"resolved": {
|
|
614
|
+
"dataset": "XNAS.ITCH", "stype_in": "raw_symbol",
|
|
615
|
+
"symbols": ["CROX"],
|
|
616
|
+
"resolved": {"CROX": [{"start": "2026-09-03",
|
|
617
|
+
"end": "2026-09-17",
|
|
618
|
+
"value": "77"}]},
|
|
619
|
+
"not_found": [],
|
|
620
|
+
},
|
|
605
621
|
}),
|
|
606
622
|
])
|
|
607
623
|
monkeypatch.setattr(
|
|
@@ -628,6 +644,75 @@ class TestAWindowTooColdToFetchInOneGo:
|
|
|
628
644
|
assert attrs["warning"] == (
|
|
629
645
|
"the first half says so | and the second says otherwise"
|
|
630
646
|
)
|
|
647
|
+
# A chain resolves per day, so the pieces of one window can address
|
|
648
|
+
# different instruments. Both were served, so both are named.
|
|
649
|
+
assert attrs["resolved"] == "42,77"
|
|
650
|
+
resolution = attrs["resolution"]
|
|
651
|
+
assert resolution["resolved"] == {
|
|
652
|
+
"NVDA": [{"start": "2026-08-20", "end": "2026-09-03",
|
|
653
|
+
"value": "42"}],
|
|
654
|
+
"CROX": [{"start": "2026-09-03", "end": "2026-09-17",
|
|
655
|
+
"value": "77"}],
|
|
656
|
+
}
|
|
657
|
+
# The fields BESIDE resolved move with it, which is the half a
|
|
658
|
+
# merge of resolved alone gets wrong.
|
|
659
|
+
assert resolution["symbols"] == ["CROX", "NVDA"]
|
|
660
|
+
# CROX is absent from the first piece and served by the second, so
|
|
661
|
+
# reporting the first piece's not_found would call an instrument
|
|
662
|
+
# the caller is holding missing.
|
|
663
|
+
assert resolution["not_found"] == []
|
|
664
|
+
|
|
665
|
+
def test_a_symbol_no_piece_resolved_stays_not_found(self, monkeypatch):
|
|
666
|
+
"""The other direction, and the one that fails dangerously.
|
|
667
|
+
|
|
668
|
+
``not_found`` is emptied by what RESOLVED, so it must not be emptied
|
|
669
|
+
by what was ASKED FOR. Against today's server the two cannot
|
|
670
|
+
disagree -- ``symbols`` is derived from the same intervals as
|
|
671
|
+
``resolved`` -- but the field is *named* for the request, and if it
|
|
672
|
+
ever becomes that, folding it into the found set would delete a
|
|
673
|
+
genuine miss and a symbol nobody served would read as served.
|
|
674
|
+
"""
|
|
675
|
+
def piece(url, day, resolved):
|
|
676
|
+
return json_response({
|
|
677
|
+
"format": "parquet",
|
|
678
|
+
"files": [{"day": day, "instrument_id": 42, "symbol": "NVDA",
|
|
679
|
+
"key": "k", "size_bytes": 10, "url": url}],
|
|
680
|
+
"gaps": [],
|
|
681
|
+
"metadata": {"dataset": "XNAS.ITCH", "schema": "mbp-10",
|
|
682
|
+
"served_start": "2026-08-20",
|
|
683
|
+
"served_end": "2026-09-17",
|
|
684
|
+
"resolved": resolved},
|
|
685
|
+
})
|
|
686
|
+
|
|
687
|
+
# WHAT WAS ASKED FOR, which is the reading the field's name invites:
|
|
688
|
+
# both symbols in every piece, only one of them ever resolving.
|
|
689
|
+
api, _ = client_with([
|
|
690
|
+
self._refusal(),
|
|
691
|
+
piece("https://r2.test/a.parquet", "2026-08-21", {
|
|
692
|
+
"symbols": ["NVDA", "NOSUCH"],
|
|
693
|
+
"resolved": {"NVDA": [{"start": "2026-08-20",
|
|
694
|
+
"end": "2026-09-03", "value": "42"}]},
|
|
695
|
+
"not_found": ["NOSUCH"],
|
|
696
|
+
}),
|
|
697
|
+
piece("https://r2.test/b.parquet", "2026-09-04", {
|
|
698
|
+
"symbols": ["NVDA", "NOSUCH"],
|
|
699
|
+
"resolved": {"NVDA": [{"start": "2026-09-03",
|
|
700
|
+
"end": "2026-09-17", "value": "42"}]},
|
|
701
|
+
"not_found": ["NOSUCH"],
|
|
702
|
+
}),
|
|
703
|
+
])
|
|
704
|
+
monkeypatch.setattr(
|
|
705
|
+
"quantpad_data.v2._read_parquet_url",
|
|
706
|
+
lambda url, columns: pd.DataFrame({"close": [100.0]}),
|
|
707
|
+
)
|
|
708
|
+
|
|
709
|
+
frame = api.timeseries.get_range(
|
|
710
|
+
dataset="XNAS.ITCH", symbols=["NVDA", "NOSUCH"], schema="mbp-10",
|
|
711
|
+
start="2026-08-20", end="2026-09-17", mode="bulk",
|
|
712
|
+
)
|
|
713
|
+
resolution = frame.attrs["quantpad"]["resolution"]
|
|
714
|
+
assert resolution["not_found"] == ["NOSUCH"]
|
|
715
|
+
assert "NOSUCH" not in resolution["resolved"]
|
|
631
716
|
|
|
632
717
|
def test_an_unsplit_bulk_read_is_labelled_exactly_as_before(
|
|
633
718
|
self, monkeypatch
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|