quantpad-data 0.2.0__tar.gz → 0.2.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.
- quantpad_data-0.2.1/.gitignore +16 -0
- {quantpad_data-0.2.0 → quantpad_data-0.2.1}/PKG-INFO +1 -1
- {quantpad_data-0.2.0 → quantpad_data-0.2.1}/pyproject.toml +1 -1
- {quantpad_data-0.2.0 → quantpad_data-0.2.1}/quantpad_data/client.py +29 -3
- {quantpad_data-0.2.0 → quantpad_data-0.2.1}/tests/test_client.py +38 -0
- quantpad_data-0.2.0/.gitignore +0 -11
- {quantpad_data-0.2.0 → quantpad_data-0.2.1}/README.md +0 -0
- {quantpad_data-0.2.0 → quantpad_data-0.2.1}/quantpad_data/__init__.py +0 -0
- {quantpad_data-0.2.0 → quantpad_data-0.2.1}/quantpad_data/errors.py +0 -0
- {quantpad_data-0.2.0 → quantpad_data-0.2.1}/quantpad_data/lean.py +0 -0
- {quantpad_data-0.2.0 → quantpad_data-0.2.1}/quantpad_data/py.typed +0 -0
- {quantpad_data-0.2.0 → quantpad_data-0.2.1}/quantpad_data/v2.py +0 -0
- {quantpad_data-0.2.0 → quantpad_data-0.2.1}/tests/test_lean.py +0 -0
- {quantpad_data-0.2.0 → quantpad_data-0.2.1}/tests/test_v2.py +0 -0
- {quantpad_data-0.2.0 → quantpad_data-0.2.1}/tests/test_version.py +0 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.py[cod]
|
|
3
|
+
*$py.class
|
|
4
|
+
.env
|
|
5
|
+
.env.local
|
|
6
|
+
.venv/
|
|
7
|
+
venv/
|
|
8
|
+
*.egg-info/
|
|
9
|
+
.mypy_cache/
|
|
10
|
+
.ruff_cache/
|
|
11
|
+
.pytest_cache/
|
|
12
|
+
|
|
13
|
+
# Throwaway investigation probes. Named so they sort together and are
|
|
14
|
+
# obviously not part of the service; there are usually a lot of them
|
|
15
|
+
# mid-investigation and none of them should ever be committed.
|
|
16
|
+
_qp_*
|
|
@@ -24,7 +24,7 @@ from .errors import (
|
|
|
24
24
|
ValidationError,
|
|
25
25
|
)
|
|
26
26
|
|
|
27
|
-
VERSION = "0.2.
|
|
27
|
+
VERSION = "0.2.1"
|
|
28
28
|
USER_AGENT = f"quantpad-data-python/{VERSION}"
|
|
29
29
|
TICK_SCHEMAS = (
|
|
30
30
|
"trades", "mbp-1", "mbp-10", "cmbp-1", "tcbbo", "cbbo-1s",
|
|
@@ -32,6 +32,28 @@ TICK_SCHEMAS = (
|
|
|
32
32
|
)
|
|
33
33
|
ROLL_ADJUST_MODES = ("none", "back", "ratio")
|
|
34
34
|
|
|
35
|
+
#: ``(connect, read)`` for ``/v1/bars``, matching what ``/v1/ticks`` has
|
|
36
|
+
#: always used.
|
|
37
|
+
#:
|
|
38
|
+
#: The default 120s read budget is wrong for this route and was wrong in
|
|
39
|
+
#: the direction that hurts most. ``/v1/bars`` does not stream: the
|
|
40
|
+
#: server materialises the whole answer before the first byte, so the
|
|
41
|
+
#: read timeout is a cap on total server time rather than a stall
|
|
42
|
+
#: detector. Measured over three hours of production traffic, 24.6% of
|
|
43
|
+
#: bars requests took longer than 120s -- 37% of those from the notebook
|
|
44
|
+
#: sandbox, and 75% of every ``30s`` request -- against 2 of 1,924 tick
|
|
45
|
+
#: requests exceeding the 1800s ticks already allowed.
|
|
46
|
+
#:
|
|
47
|
+
#: Retrying made it worse rather than better. The server finishes an
|
|
48
|
+
#: abandoned request (it notices the client left on 1% of them), so a
|
|
49
|
+
#: timeout frees nothing and the three retries turn one slow call into
|
|
50
|
+
#: four concurrent copies of it, each holding one of the eight stream
|
|
51
|
+
#: slots a key is allowed. That is a self-inflicted 429.
|
|
52
|
+
#:
|
|
53
|
+
#: This is a floor under the symptom, not a fix: the tail beyond 1800s is
|
|
54
|
+
#: server latency and belongs in the engine, not in a client's patience.
|
|
55
|
+
BARS_TIMEOUT = (30, 1800)
|
|
56
|
+
|
|
35
57
|
|
|
36
58
|
class Client:
|
|
37
59
|
def __init__(
|
|
@@ -164,14 +186,18 @@ class Client:
|
|
|
164
186
|
"format": "arrow",
|
|
165
187
|
"roll_adjust": self._roll_adjust(symbol, roll_adjust),
|
|
166
188
|
}
|
|
167
|
-
response = self._request(
|
|
189
|
+
response = self._request(
|
|
190
|
+
"GET", "/v1/bars", params=params, timeout=BARS_TIMEOUT, stream=True
|
|
191
|
+
)
|
|
168
192
|
try:
|
|
169
193
|
table = ipc.open_stream(response.content).read_all()
|
|
170
194
|
frame = table.to_pandas()
|
|
171
195
|
except Exception:
|
|
172
196
|
response.close()
|
|
173
197
|
params["format"] = "csv"
|
|
174
|
-
fallback = self._request(
|
|
198
|
+
fallback = self._request(
|
|
199
|
+
"GET", "/v1/bars", params=params, timeout=BARS_TIMEOUT, stream=True
|
|
200
|
+
)
|
|
175
201
|
try:
|
|
176
202
|
frame = pd.read_csv(io.StringIO(fallback.text))
|
|
177
203
|
finally:
|
|
@@ -66,6 +66,44 @@ def test_bars_uses_api_key_and_preserves_aliases():
|
|
|
66
66
|
assert session.calls[0][2]["params"]["roll_adjust"] == "back"
|
|
67
67
|
|
|
68
68
|
|
|
69
|
+
def test_bars_gets_the_same_read_budget_as_ticks():
|
|
70
|
+
"""The default 120s was a cap on total server time, not a stall
|
|
71
|
+
detector: ``/v1/bars`` materialises the whole answer before the first
|
|
72
|
+
byte, and 24.6% of production bars requests take longer than that.
|
|
73
|
+
Timing out did not even save the work -- the server finishes the
|
|
74
|
+
request regardless, so the three retries just ran four copies of it.
|
|
75
|
+
"""
|
|
76
|
+
from quantpad_data.client import BARS_TIMEOUT
|
|
77
|
+
|
|
78
|
+
table = pa.table({"t": [1_700_000_000_000], "c": [1.5]})
|
|
79
|
+
session = FakeSession([response(body=arrow_bytes(table))])
|
|
80
|
+
client = Client(api_key="secret", session=session, max_retries=0)
|
|
81
|
+
|
|
82
|
+
client.get_bars("GC.FUT", "30s", 1, 2)
|
|
83
|
+
|
|
84
|
+
connect, read = session.calls[0][2]["timeout"]
|
|
85
|
+
assert (connect, read) == BARS_TIMEOUT
|
|
86
|
+
assert read >= 1800, "a one-year 30s request does not fit in 120s"
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_the_csv_fallback_keeps_the_long_read_budget():
|
|
90
|
+
"""It re-requests the same window, so inheriting the 120s default
|
|
91
|
+
would time out exactly where the Arrow attempt just did."""
|
|
92
|
+
from quantpad_data.client import BARS_TIMEOUT
|
|
93
|
+
|
|
94
|
+
table = pa.table({"t": [1_700_000_000_000], "c": [1.5]})
|
|
95
|
+
session = FakeSession(
|
|
96
|
+
[response(body=b"not arrow"), response(body=b"t,c\n1700000000000,1.5\n")]
|
|
97
|
+
)
|
|
98
|
+
client = Client(api_key="secret", session=session, max_retries=0)
|
|
99
|
+
|
|
100
|
+
client.get_bars("GC.FUT", "30s", 1, 2)
|
|
101
|
+
|
|
102
|
+
assert len(session.calls) == 2
|
|
103
|
+
assert session.calls[1][2]["timeout"] == BARS_TIMEOUT
|
|
104
|
+
assert session.calls[1][2]["params"]["format"] == "csv"
|
|
105
|
+
|
|
106
|
+
|
|
69
107
|
def test_rate_limit_exposes_retry_after():
|
|
70
108
|
session = FakeSession(
|
|
71
109
|
[response(429, b'{"detail":"rate limit"}', {"Retry-After": "7"})]
|
quantpad_data-0.2.0/.gitignore
DELETED
|
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
|