quantpad-data 0.4.0__tar.gz → 0.4.1__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.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: quantpad-data
3
- Version: 0.4.0
3
+ Version: 0.4.1
4
4
  Summary: Official Python client for the QuantPad market-data API
5
5
  Project-URL: Documentation, https://api.quantpad.ai/external/docs
6
6
  Author: QuantPad
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "quantpad-data"
7
- version = "0.4.0"
7
+ version = "0.4.1"
8
8
  description = "Official Python client for the QuantPad market-data API"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -90,7 +90,7 @@ V2_NOT_AVAILABLE = (
90
90
  "QuantPad support about /v2 access."
91
91
  )
92
92
 
93
- VERSION = "0.4.0"
93
+ VERSION = "0.4.1"
94
94
  USER_AGENT = f"quantpad-data-python/{VERSION}"
95
95
  TICK_SCHEMAS = (
96
96
  "trades", "mbp-1", "mbp-10", "cmbp-1", "tcbbo", "cbbo-1s",
@@ -841,7 +841,27 @@ 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
- merged["resolved"] = {**resolutions[0], "resolved": instruments}
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 = symbols | set(instruments)
853
+ merged["resolved"] = {
854
+ **resolutions[0],
855
+ "resolved": instruments,
856
+ "symbols": sorted(found),
857
+ "not_found": [
858
+ symbol
859
+ for symbol in dict.fromkeys(
860
+ s for r in resolutions for s in (r.get("not_found") or ())
861
+ )
862
+ if symbol not in found
863
+ ],
864
+ }
845
865
 
846
866
  return {"metadata": merged}
847
867
 
@@ -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,23 @@ 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"] == []
631
664
 
632
665
  def test_an_unsplit_bulk_read_is_labelled_exactly_as_before(
633
666
  self, monkeypatch
File without changes
File without changes