lse-data 3.0__tar.gz → 3.0.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.
Files changed (25) hide show
  1. {lse_data-3.0 → lse_data-3.0.1}/CHANGELOG.md +18 -0
  2. {lse_data-3.0 → lse_data-3.0.1}/PKG-INFO +22 -25
  3. {lse_data-3.0 → lse_data-3.0.1}/README.md +20 -23
  4. {lse_data-3.0 → lse_data-3.0.1}/docs/api.md +98 -78
  5. {lse_data-3.0 → lse_data-3.0.1}/examples/history_file.py +5 -5
  6. {lse_data-3.0 → lse_data-3.0.1}/lse/__init__.py +1 -1
  7. {lse_data-3.0 → lse_data-3.0.1}/lse/client.py +353 -145
  8. {lse_data-3.0 → lse_data-3.0.1}/lse/data3.py +61 -25
  9. {lse_data-3.0 → lse_data-3.0.1}/pyproject.toml +2 -2
  10. {lse_data-3.0 → lse_data-3.0.1}/.gitignore +0 -0
  11. {lse_data-3.0 → lse_data-3.0.1}/LICENSE +0 -0
  12. {lse_data-3.0 → lse_data-3.0.1}/examples/async_stream.py +0 -0
  13. {lse_data-3.0 → lse_data-3.0.1}/examples/basic_stream.py +0 -0
  14. {lse_data-3.0 → lse_data-3.0.1}/examples/callback_style.py +0 -0
  15. {lse_data-3.0 → lse_data-3.0.1}/examples/catalogue.py +0 -0
  16. {lse_data-3.0 → lse_data-3.0.1}/examples/disconnect_example.py +0 -0
  17. {lse_data-3.0 → lse_data-3.0.1}/examples/macro_series.py +0 -0
  18. {lse_data-3.0 → lse_data-3.0.1}/examples/options_data.py +0 -0
  19. {lse_data-3.0 → lse_data-3.0.1}/examples/options_stream.py +0 -0
  20. {lse_data-3.0 → lse_data-3.0.1}/examples/reference_rows.py +0 -0
  21. {lse_data-3.0 → lse_data-3.0.1}/examples/save_to_csv.py +0 -0
  22. {lse_data-3.0 → lse_data-3.0.1}/examples/unsubscribe_example.py +0 -0
  23. {lse_data-3.0 → lse_data-3.0.1}/lse/cli.py +0 -0
  24. {lse_data-3.0 → lse_data-3.0.1}/lse/py.typed +0 -0
  25. {lse_data-3.0 → lse_data-3.0.1}/lse/tui.py +0 -0
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## LSE Data 3.0.1
4
+
5
+ Fixes to the 3.0 client; the calls and their names are the same. `pip install -U lse-data`.
6
+
7
+ Fixed
8
+
9
+ - Direct `rows()` downloads, including `candles()`, verify the response SHA-256 and declared size before parsing. A damaged or shortened CSV is refused instead of becoming partial history.
10
+ - Long weekly and monthly `candles()` reads keep each candle whole. Internal date chunks previously split a week or month into two partial rows. Timestamp-bounded daily reads also stay whole across exchange session boundaries, and intraday chunks join a candle that crosses a session boundary. Requested start/end bounds and the row limit are preserved.
11
+ - A `429` could come back as nothing. With `retries=0`, a call that waited out a `429` returned `None` (`usage()` and every JSON call), or raised `no-response` (`rows()`, `candles()`, the reference readers) or `download-missing` (`export()`), although the server would have answered the next ask; a client that had waited once could do the same on a later call with the default retries. Now every call waits a `Retry-After` of two minutes or less once, the wait does not use up a retry, nothing is carried from one call to the next, and a refusal that stands is raised as `LSEError` with the server's own code and sentence (`calls-exhausted`, `connections-exhausted`, `rows-exhausted`, `allowance-exhausted`).
12
+ - Crypto streams again from the default client. Crypto moved to `wss://london-ws.londonstrategicedge.com` on 8 November 2026, and `stream(["BTC/USD", "AAPL"])` on the default address then gave one old price for BTC/USD and nothing after it. The client now opens the crypto socket beside the default one, with the same key, for the symbols the catalogue lists as crypto, and their ticks arrive in the same loop and callbacks; `subscribe()` and `unsubscribe()` while connected go to the right socket. A client made with `url=` uses that socket alone, as before. If the crypto socket refuses the key, the `error` event carries its sentence with that address and the other symbols keep streaming.
13
+ - A missing-data answer is recognised by its code, whatever its sentence: the server's sentence now begins "No stored data for this selection" and says why.
14
+
15
+ Changed
16
+
17
+ - `stream(..., start=...)` and `stream_async(..., start=...)`: replay was removed from the sockets on 25 October 2026, and a `start` left the loop waiting for ticks that never came. The server's refusal is now raised as `LSEError` (410, `REPLAY_REMOVED`) with its sentence. For history use `candles()`, `rows()` or `export()`.
18
+ - The README's examples run as written: the Level 2 and Level 3 examples ask for a minute by time, the years-of-candles example asks for five years (about half of the 2,500,000 rows one export may read), the options flow examples name their day.
19
+ - The guide (`docs/api.md`) describes the server as it answers now: files are sent at once and not kept (the prepared-job routes answer `404 export-unknown`), paid plans download at 250 Mbit/s, one export reads at most 2,500,000 rows (`413 export-too-wide`), the free plan reads 12,500,000 rows a day (`429 rows-exhausted`), and `/v3/usage` reports both under `rows`.
20
+
3
21
  ## LSE Data 3.0
4
22
 
5
23
  Every read of history now comes from `https://download.londonstrategicedge.com/v3`: eleven markets, over 76,000 instruments and 67 million US options contracts, resumable CSV and Parquet exports checked by SHA-256, macro and reference data, options chains and flow. The websocket, the key and the package are the same. [docs/api.md](https://github.com/londonstrategicedge/lse-data/blob/main/docs/api.md) describes every call.
@@ -1,7 +1,7 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lse-data
3
- Version: 3.0
4
- Summary: Python client for London Strategic Edge market data: live streaming, LSE Data 3.0 history as CSV and Parquet with resumable checked downloads, macro and reference data, options chains and flow
3
+ Version: 3.0.1
4
+ Summary: Python client for London Strategic Edge market data: live streaming, LSE Data 3.0 history as CSV and Parquet with checksum-verified downloads, macro and reference data, options chains and flow
5
5
  Project-URL: Homepage, https://londonstrategicedge.com
6
6
  Project-URL: Documentation, https://github.com/londonstrategicedge/lse-data/blob/main/docs/api.md
7
7
  Project-URL: Repository, https://github.com/londonstrategicedge/lse-data
@@ -85,7 +85,7 @@ latest = client.candles("BTCUSDT", "1d", limit=30, order="desc")
85
85
 
86
86
  # The last ten rows of anything, and what an export would weigh, before you ask for it
87
87
  peek = client.preview("us_stocks", "AAPL", "5m")
88
- size = client.estimate("us_stocks", "AAPL", "1m", "2016-01-04", "2026-10-21", format="parquet")
88
+ size = client.estimate("us_stocks", "AAPL", "1m", "2021-10-21", "2026-10-21", format="parquet")
89
89
 
90
90
  # Macro series and yields: (date, value) rows, any series in one call
91
91
  cpi = client.series("US.core_inflation_rate", start="2020-01-01")
@@ -117,19 +117,23 @@ Rows are held in memory, so keep them to days or weeks. Years of candles, ticks
117
117
 
118
118
  ## Files: CSV and Parquet
119
119
 
120
- `export()` saves a selection as a file and returns its path. Large exports are prepared on the server; downloads resume if the connection drops and are verified when they finish. The free plan includes 50 GB a month.
120
+ `export()` saves a selection as a file and returns its path. Every export is sent whole with its size and SHA-256 and verified when it finishes; a download that drops is requested again from the start. The free plan includes 50 GB a month.
121
121
 
122
122
  ```python
123
- path = client.export("us_stocks", "AAPL", "1m", "2016-01-04", "2026-10-21", format="parquet")
123
+ path = client.export("us_stocks", "AAPL", "1m", "2021-10-21", "2026-10-21", format="parquet") # five years of one-minute candles
124
124
  path = client.export("us_stocks", "AAPL", "tick", "2026-10-21", "2026-10-21", format="csv") # the trade slice
125
- path = client.export("nasdaq", "NVDA", "tick", "2026-10-14", "2026-10-14", data="book") # the Level 2 book
126
- path = client.export("cme", "ESZ6", "tick", "2026-10-13", "2026-10-13", data="orders") # Level 3 orders
125
+ path = client.export("nasdaq", "NVDA", "tick", "2026-10-14 14:30:00", "2026-10-14 14:31:00", data="book") # the Level 2 book, a minute of it
126
+ path = client.export("cme", "ESZ6", "tick", "2026-10-13 14:30:00", "2026-10-13 14:31:00", data="orders") # Level 3 orders, a minute of them
127
127
  path = client.macro_export("observation", "US.core_inflation_rate", "1957-02-01", "2026-07-01", format="csv")
128
128
  path = client.reference_export("company_profiles", "AAPL", format="csv")
129
129
 
130
130
  df = client.history("AAPL", timeframe="1d", start="2020-01-01", end="2026-10-01") # a DataFrame with the frames extra
131
131
  ```
132
132
 
133
+ One export reads at most 2,500,000 rows, on every plan; a wider one is refused with `413 export-too-wide`, which says how many rows it would have read. Five years of AAPL's one-minute candles are about 1.25 million rows. The book and the orders of a busy instrument run to millions of rows a day, so ask for them by time: two UTC timestamps, an hour or less at a time. `estimate()` with the same arguments says how many rows a selection holds before you download it.
134
+
135
+ The free plan also reads at most 12,500,000 rows a day (`429 rows-exhausted` until 00:00 UTC) and 12.5 GiB a week (`429 allowance-exhausted` until Monday 00:00 UTC), at 50 Mbit/s. A paid plan has neither limit and downloads at 250 Mbit/s. Every plan has one download open at a time; one more answers `429 connections-exhausted` and names the file to wait for. `client.usage()` shows your figures and what is used.
136
+
133
137
  `pip install 'lse-data[frames]'` adds DataFrames to `history()`, `dataset()` and `load(path)`.
134
138
 
135
139
  CME Globex and Nasdaq TotalView are paid feeds. Their rows need the feed's plan, and a free key gets an error naming the plan. Tick counts, dates, table sizes and estimates are open to every key.
@@ -140,13 +144,13 @@ The whole US options tape, every contract, with implied volatility and the Greek
140
144
 
141
145
  ```python
142
146
  chain = client.options("apple", type="call", max_dte=30)
143
- prints = client.options_flow("NVDA", min_premium=100_000)
144
- sweep = client.options_flow(min_premium=250_000, limit=200)
147
+ prints = client.options_flow("NVDA", min_premium=100_000, start="2026-11-09", end="2026-11-09")
148
+ sweep = client.options_flow(min_premium=250_000, start="2026-11-09", end="2026-11-09", limit=200)
145
149
  bars = client.option_candles("AAPL", strike=100, expiry="2026-11-20", type="call")
146
150
  names = client.options_underlyings()
147
151
  ```
148
152
 
149
- `options()` returns the chain, one row per contract with the last trade, volume, premium, implied volatility and Greeks. `options_flow()` returns prints with their premium and venue; leave out the underlying to scan every name. Its window is today by default, up to seven days with `start` and `end`. `option_candles()` returns candles for one contract. `subscribe_options(["AAPL"])` streams every AAPL contract live as `OptionTick` objects, which add `underlying`, `right`, `strike`, `expiry`, `dte`, `premium` and `notional` to a tick. With `from lse import tape`, `client.on("tick", tape())` prints them as a readable table.
153
+ `options()` returns the chain, one row per contract with the last trade, volume, premium, implied volatility and Greeks. `options_flow()` returns prints with their premium and venue; leave out the underlying to scan every name. `start` and `end` set the window, up to seven days without an underlying; without them it is today (UTC), and a session's prints are stored after it ends, so name the days you want. `option_candles()` returns candles for one contract. `subscribe_options(["AAPL"])` streams every AAPL contract live as `OptionTick` objects, which add `underlying`, `right`, `strike`, `expiry`, `dte`, `premium` and `notional` to a tick. With `from lse import tape`, `client.on("tick", tape())` prints them as a readable table.
150
154
 
151
155
  ## Find instruments
152
156
 
@@ -180,7 +184,9 @@ client.on("tick", lambda t: print(t.symbol, t.price))
180
184
  client.connect(["BTC/USD"])
181
185
  ```
182
186
 
183
- Events are `tick`, `connected`, `authenticated`, `disconnected`, `error`, `replay_started` and `replay_complete`.
187
+ Events are `tick`, `connected`, `authenticated`, `disconnected` and `error`.
188
+
189
+ Crypto streams from `wss://london-ws.londonstrategicedge.com`; the default socket carries the other symbols of these examples. The client opens the crypto socket beside the default one, with the same key, for the crypto symbols you ask for, so one loop or callback carries both; a plan's connections are counted on each socket. If the crypto socket refuses the key, the `error` event says so with that address and the other symbols keep streaming. `LSE(api_key, url=...)` uses the one socket you name and nothing else; the [WebSocket documentation](https://londonstrategicedge.com/websocket-documentation) lists the sockets and what each carries.
184
190
 
185
191
  Change subscriptions while connected:
186
192
 
@@ -190,17 +196,7 @@ client.unsubscribe(["BTC/USD"])
190
196
  client.subscribe_options(["AAPL"]) # every AAPL contract at once
191
197
  ```
192
198
 
193
- ### Replay then live
194
-
195
- Pass `start` to replay up to 24 hours of history before the live ticks, on the same connection.
196
-
197
- ```python
198
- from datetime import datetime, timedelta, timezone
199
-
200
- start = (datetime.now(timezone.utc) - timedelta(hours=1)).strftime("%Y-%m-%dT%H:%M:%S") # UTC
201
- for tick in client.stream(["BTC/USD"], start=start):
202
- print("replay" if tick.replay else "live", tick.symbol, tick.price)
203
- ```
199
+ The sockets carry live ticks only. Replay, `stream(..., start=...)`, was removed on 25 October 2026: the server refuses it and the client raises that refusal as `LSEError` (code `REPLAY_REMOVED`). For history use `candles()`, `rows()` or `export()`.
204
200
 
205
201
  ### Async
206
202
 
@@ -236,7 +232,7 @@ with LSE() as client:
236
232
  ...
237
233
  ```
238
234
 
239
- A tick has `symbol`, `price`, `bid`, `ask`, `volume`, `timestamp` (ISO 8601), `name` and `replay`; `tick.datetime` is the parsed timestamp.
235
+ A tick has `symbol`, `price`, `bid`, `ask`, `volume`, `timestamp` (ISO 8601) and `name`; `tick.datetime` is the parsed timestamp.
240
236
 
241
237
  ## Settings
242
238
 
@@ -247,9 +243,9 @@ Every setting of `LSE()` is optional:
247
243
  | `timeout` | `60` | seconds to wait for each HTTP call |
248
244
  | `retries` | `3` | retries of a call that got no answer, or a 429, 500, 502, 503 or 504 |
249
245
  | `poll_seconds` | `0.5` | how often a prepared export is polled |
250
- | `wait_for_limit` | `True` | past your plan's calls a minute, wait the server's `Retry-After` once (at most two minutes) and try again; `False` raises at once |
246
+ | `wait_for_limit` | `True` | on a `429`, wait the server's `Retry-After` once in a call, when it is two minutes or less, and ask again without using up a retry; `False` never waits |
251
247
  | `data_url` | `https://download.londonstrategicedge.com` | the LSE Data 3.0 server |
252
- | `url` | `wss://data-ws.londonstrategicedge.com` | the live WebSocket |
248
+ | `url` | `wss://data-ws.londonstrategicedge.com` | the live WebSocket; left at its default, crypto symbols are streamed from `wss://london-ws.londonstrategicedge.com` beside it |
253
249
 
254
250
  ```python
255
251
  client = LSE(api_key="your_key", timeout=120, wait_for_limit=False)
@@ -272,6 +268,7 @@ Scripts written for 0.14 keep working: the method names and row shapes are the s
272
268
  - Symbols follow the catalogue. The 0.x spellings still resolve: `EUR/USD` to `EUR_USD`, `BTC/USD` to `BTCUSDT` (crypto is quoted in Tether).
273
269
  - `series()` takes catalogue codes (`US10Y`, `US.core_inflation_rate`). `cpi_yoy` and `fdtr` still work, as `US.inflation_rate` and `US.interest_rate`. A name matching several series raises `LSEError` with code `series-ambiguous` and lists them.
274
270
  - `get()` read the retired tables. It now raises `LSEError` (410, `removed`) naming the methods to use.
271
+ - `stream(..., start=...)` replayed recent ticks before the live ones. Replay was removed on 25 October 2026; the call raises `LSEError` (410, `REPLAY_REMOVED`).
275
272
 
276
273
  [CHANGELOG.md](https://github.com/londonstrategicedge/lse-data/blob/main/CHANGELOG.md) lists every change and [docs/api.md](https://github.com/londonstrategicedge/lse-data/blob/main/docs/api.md) every call.
277
274
 
@@ -53,7 +53,7 @@ latest = client.candles("BTCUSDT", "1d", limit=30, order="desc")
53
53
 
54
54
  # The last ten rows of anything, and what an export would weigh, before you ask for it
55
55
  peek = client.preview("us_stocks", "AAPL", "5m")
56
- size = client.estimate("us_stocks", "AAPL", "1m", "2016-01-04", "2026-10-21", format="parquet")
56
+ size = client.estimate("us_stocks", "AAPL", "1m", "2021-10-21", "2026-10-21", format="parquet")
57
57
 
58
58
  # Macro series and yields: (date, value) rows, any series in one call
59
59
  cpi = client.series("US.core_inflation_rate", start="2020-01-01")
@@ -85,19 +85,23 @@ Rows are held in memory, so keep them to days or weeks. Years of candles, ticks
85
85
 
86
86
  ## Files: CSV and Parquet
87
87
 
88
- `export()` saves a selection as a file and returns its path. Large exports are prepared on the server; downloads resume if the connection drops and are verified when they finish. The free plan includes 50 GB a month.
88
+ `export()` saves a selection as a file and returns its path. Every export is sent whole with its size and SHA-256 and verified when it finishes; a download that drops is requested again from the start. The free plan includes 50 GB a month.
89
89
 
90
90
  ```python
91
- path = client.export("us_stocks", "AAPL", "1m", "2016-01-04", "2026-10-21", format="parquet")
91
+ path = client.export("us_stocks", "AAPL", "1m", "2021-10-21", "2026-10-21", format="parquet") # five years of one-minute candles
92
92
  path = client.export("us_stocks", "AAPL", "tick", "2026-10-21", "2026-10-21", format="csv") # the trade slice
93
- path = client.export("nasdaq", "NVDA", "tick", "2026-10-14", "2026-10-14", data="book") # the Level 2 book
94
- path = client.export("cme", "ESZ6", "tick", "2026-10-13", "2026-10-13", data="orders") # Level 3 orders
93
+ path = client.export("nasdaq", "NVDA", "tick", "2026-10-14 14:30:00", "2026-10-14 14:31:00", data="book") # the Level 2 book, a minute of it
94
+ path = client.export("cme", "ESZ6", "tick", "2026-10-13 14:30:00", "2026-10-13 14:31:00", data="orders") # Level 3 orders, a minute of them
95
95
  path = client.macro_export("observation", "US.core_inflation_rate", "1957-02-01", "2026-07-01", format="csv")
96
96
  path = client.reference_export("company_profiles", "AAPL", format="csv")
97
97
 
98
98
  df = client.history("AAPL", timeframe="1d", start="2020-01-01", end="2026-10-01") # a DataFrame with the frames extra
99
99
  ```
100
100
 
101
+ One export reads at most 2,500,000 rows, on every plan; a wider one is refused with `413 export-too-wide`, which says how many rows it would have read. Five years of AAPL's one-minute candles are about 1.25 million rows. The book and the orders of a busy instrument run to millions of rows a day, so ask for them by time: two UTC timestamps, an hour or less at a time. `estimate()` with the same arguments says how many rows a selection holds before you download it.
102
+
103
+ The free plan also reads at most 12,500,000 rows a day (`429 rows-exhausted` until 00:00 UTC) and 12.5 GiB a week (`429 allowance-exhausted` until Monday 00:00 UTC), at 50 Mbit/s. A paid plan has neither limit and downloads at 250 Mbit/s. Every plan has one download open at a time; one more answers `429 connections-exhausted` and names the file to wait for. `client.usage()` shows your figures and what is used.
104
+
101
105
  `pip install 'lse-data[frames]'` adds DataFrames to `history()`, `dataset()` and `load(path)`.
102
106
 
103
107
  CME Globex and Nasdaq TotalView are paid feeds. Their rows need the feed's plan, and a free key gets an error naming the plan. Tick counts, dates, table sizes and estimates are open to every key.
@@ -108,13 +112,13 @@ The whole US options tape, every contract, with implied volatility and the Greek
108
112
 
109
113
  ```python
110
114
  chain = client.options("apple", type="call", max_dte=30)
111
- prints = client.options_flow("NVDA", min_premium=100_000)
112
- sweep = client.options_flow(min_premium=250_000, limit=200)
115
+ prints = client.options_flow("NVDA", min_premium=100_000, start="2026-11-09", end="2026-11-09")
116
+ sweep = client.options_flow(min_premium=250_000, start="2026-11-09", end="2026-11-09", limit=200)
113
117
  bars = client.option_candles("AAPL", strike=100, expiry="2026-11-20", type="call")
114
118
  names = client.options_underlyings()
115
119
  ```
116
120
 
117
- `options()` returns the chain, one row per contract with the last trade, volume, premium, implied volatility and Greeks. `options_flow()` returns prints with their premium and venue; leave out the underlying to scan every name. Its window is today by default, up to seven days with `start` and `end`. `option_candles()` returns candles for one contract. `subscribe_options(["AAPL"])` streams every AAPL contract live as `OptionTick` objects, which add `underlying`, `right`, `strike`, `expiry`, `dte`, `premium` and `notional` to a tick. With `from lse import tape`, `client.on("tick", tape())` prints them as a readable table.
121
+ `options()` returns the chain, one row per contract with the last trade, volume, premium, implied volatility and Greeks. `options_flow()` returns prints with their premium and venue; leave out the underlying to scan every name. `start` and `end` set the window, up to seven days without an underlying; without them it is today (UTC), and a session's prints are stored after it ends, so name the days you want. `option_candles()` returns candles for one contract. `subscribe_options(["AAPL"])` streams every AAPL contract live as `OptionTick` objects, which add `underlying`, `right`, `strike`, `expiry`, `dte`, `premium` and `notional` to a tick. With `from lse import tape`, `client.on("tick", tape())` prints them as a readable table.
118
122
 
119
123
  ## Find instruments
120
124
 
@@ -148,7 +152,9 @@ client.on("tick", lambda t: print(t.symbol, t.price))
148
152
  client.connect(["BTC/USD"])
149
153
  ```
150
154
 
151
- Events are `tick`, `connected`, `authenticated`, `disconnected`, `error`, `replay_started` and `replay_complete`.
155
+ Events are `tick`, `connected`, `authenticated`, `disconnected` and `error`.
156
+
157
+ Crypto streams from `wss://london-ws.londonstrategicedge.com`; the default socket carries the other symbols of these examples. The client opens the crypto socket beside the default one, with the same key, for the crypto symbols you ask for, so one loop or callback carries both; a plan's connections are counted on each socket. If the crypto socket refuses the key, the `error` event says so with that address and the other symbols keep streaming. `LSE(api_key, url=...)` uses the one socket you name and nothing else; the [WebSocket documentation](https://londonstrategicedge.com/websocket-documentation) lists the sockets and what each carries.
152
158
 
153
159
  Change subscriptions while connected:
154
160
 
@@ -158,17 +164,7 @@ client.unsubscribe(["BTC/USD"])
158
164
  client.subscribe_options(["AAPL"]) # every AAPL contract at once
159
165
  ```
160
166
 
161
- ### Replay then live
162
-
163
- Pass `start` to replay up to 24 hours of history before the live ticks, on the same connection.
164
-
165
- ```python
166
- from datetime import datetime, timedelta, timezone
167
-
168
- start = (datetime.now(timezone.utc) - timedelta(hours=1)).strftime("%Y-%m-%dT%H:%M:%S") # UTC
169
- for tick in client.stream(["BTC/USD"], start=start):
170
- print("replay" if tick.replay else "live", tick.symbol, tick.price)
171
- ```
167
+ The sockets carry live ticks only. Replay, `stream(..., start=...)`, was removed on 25 October 2026: the server refuses it and the client raises that refusal as `LSEError` (code `REPLAY_REMOVED`). For history use `candles()`, `rows()` or `export()`.
172
168
 
173
169
  ### Async
174
170
 
@@ -204,7 +200,7 @@ with LSE() as client:
204
200
  ...
205
201
  ```
206
202
 
207
- A tick has `symbol`, `price`, `bid`, `ask`, `volume`, `timestamp` (ISO 8601), `name` and `replay`; `tick.datetime` is the parsed timestamp.
203
+ A tick has `symbol`, `price`, `bid`, `ask`, `volume`, `timestamp` (ISO 8601) and `name`; `tick.datetime` is the parsed timestamp.
208
204
 
209
205
  ## Settings
210
206
 
@@ -215,9 +211,9 @@ Every setting of `LSE()` is optional:
215
211
  | `timeout` | `60` | seconds to wait for each HTTP call |
216
212
  | `retries` | `3` | retries of a call that got no answer, or a 429, 500, 502, 503 or 504 |
217
213
  | `poll_seconds` | `0.5` | how often a prepared export is polled |
218
- | `wait_for_limit` | `True` | past your plan's calls a minute, wait the server's `Retry-After` once (at most two minutes) and try again; `False` raises at once |
214
+ | `wait_for_limit` | `True` | on a `429`, wait the server's `Retry-After` once in a call, when it is two minutes or less, and ask again without using up a retry; `False` never waits |
219
215
  | `data_url` | `https://download.londonstrategicedge.com` | the LSE Data 3.0 server |
220
- | `url` | `wss://data-ws.londonstrategicedge.com` | the live WebSocket |
216
+ | `url` | `wss://data-ws.londonstrategicedge.com` | the live WebSocket; left at its default, crypto symbols are streamed from `wss://london-ws.londonstrategicedge.com` beside it |
221
217
 
222
218
  ```python
223
219
  client = LSE(api_key="your_key", timeout=120, wait_for_limit=False)
@@ -240,6 +236,7 @@ Scripts written for 0.14 keep working: the method names and row shapes are the s
240
236
  - Symbols follow the catalogue. The 0.x spellings still resolve: `EUR/USD` to `EUR_USD`, `BTC/USD` to `BTCUSDT` (crypto is quoted in Tether).
241
237
  - `series()` takes catalogue codes (`US10Y`, `US.core_inflation_rate`). `cpi_yoy` and `fdtr` still work, as `US.inflation_rate` and `US.interest_rate`. A name matching several series raises `LSEError` with code `series-ambiguous` and lists them.
242
238
  - `get()` read the retired tables. It now raises `LSEError` (410, `removed`) naming the methods to use.
239
+ - `stream(..., start=...)` replayed recent ticks before the live ones. Replay was removed on 25 October 2026; the call raises `LSEError` (410, `REPLAY_REMOVED`).
243
240
 
244
241
  [CHANGELOG.md](https://github.com/londonstrategicedge/lse-data/blob/main/CHANGELOG.md) lists every change and [docs/api.md](https://github.com/londonstrategicedge/lse-data/blob/main/docs/api.md) every call.
245
242
 
@@ -69,22 +69,45 @@ and its HTTP status says the kind: 400 the request is wrong, 401 the key, 403 th
69
69
  The refusal codes are listed at the end. Browsers may call the server only from LSE's own website, so call it from
70
70
  your own code, not from a web page on another site.
71
71
 
72
- **Files.** An export answers either the bytes at once (200, `Content-Disposition` with the file name, the job id in
73
- `x-export-job`, `x-export-status-url`, and `x-lse3-adjusted` 1 or 0) or a prepared job (202, the job object) when the
74
- file could exceed 100 MB (`ceiling_bytes` in the estimate), the build has not finished within 10 seconds, or
75
- `prepare=1` was asked. A job lives 24 hours
76
- (`expires_at`, Unix seconds); `/v3/jobs/<id>` tells its state and `/v3/jobs/<id>/file` gives the file, whole or by
77
- `Range`, with an ETag of the SHA-256 and `x-checksum-sha256`. Only the key that made a job sees it: any other key, and
78
- a malformed id, is told `404 export-unknown`. A selection with no stored rows answers `404 data-unavailable` instead of
79
- an empty file. Parquet files type `size` as double, because split-adjusted volumes can be fractional.
72
+ **Files.** An export answers the bytes at once: `200` with `Content-Disposition` naming the file, `Content-Length`,
73
+ `x-checksum-sha256` (the file's SHA-256), `x-export-id` (the server's name for this export in its log), `x-export-rows`
74
+ (the rows it charged) and `x-lse3-adjusted` 1 or 0. The file is built whole before the first byte goes out, so the
75
+ size and checksum are exact, and nothing is kept once it has been sent (since 2026-11-06; before it a large or slow
76
+ export became a prepared job kept 24 hours). `prepare=1` is accepted and ignored. `/v3/jobs/<id>` and
77
+ `/v3/jobs/<id>/file`, the prepared-file routes of before, answer `404 export-unknown`: a download that was cut is
78
+ requested again from the start. A selection with no stored rows answers `404 data-unavailable` instead of an empty
79
+ file; its message begins "No stored data for this selection" and says why. Parquet files type `size` as double,
80
+ because split-adjusted volumes can be fractional.
80
81
 
81
82
  **One download at a time.** A key has one download open at a time, on every plan. While an export of this key is
82
- streaming, or a job of its is building, or a job's file is being fetched, one more `/v3/export`, `/v3/dataset/export`,
83
- macro or reference export, or `/v3/jobs/<id>/file` answers `429 connections-exhausted` (`429 exports-busy` while the
84
- first is still building), with the message "One download at a time. <file> is still coming." naming the file to wait
85
- for; the same request once the first is done goes through. The bytes are paced per key: 50 Mbit/s
86
- on the free plan, 500 Mbit/s on a paid plan. `GET /v3/usage` shows the pair as `rate_mbit` and `connections`, and `open`
87
- says whether a download is running now.
83
+ being built or sent, one more `/v3/export`, `/v3/dataset/export`, macro or reference export answers
84
+ `429 connections-exhausted` (`429 exports-busy` while the first is still building), with the message "One download at a
85
+ time. <file> is still coming." naming the file to wait for, and `Retry-After: 5`; the same request once the first is
86
+ done goes through. The bytes are paced per key: 50 Mbit/s on the free plan, 250 Mbit/s on a paid plan.
87
+ `GET /v3/usage` shows the pair as `rate_mbit` and `connections`, and `open` says whether a download is running now.
88
+
89
+ **Rows an export and rows a day.** Every export is measured in rows before it is built, and the plan sets two
90
+ figures, which `GET /v3/usage` reports under `rows`:
91
+
92
+ | Plan | Rows one export may read | Rows a day |
93
+ |---|---|---|
94
+ | Free | 2,500,000 | 12,500,000, reset at 00:00 UTC |
95
+ | Paid tiers | 2,500,000 | no daily limit |
96
+
97
+ An export past the first answers `413 export-too-wide` before anything is built, with both figures in the sentence;
98
+ so does one the server could not measure within 10 seconds. Past the rows a day it answers `429 rows-exhausted`, with
99
+ `Retry-After` and `retry_after` giving the seconds to 00:00 UTC. Nothing is charged for a selection with no rows or
100
+ for a refused export. Ask for a long history in pieces: `/v3/estimate` says how many rows a selection holds.
101
+
102
+ ```text
103
+ GET /v3/export?market=us_stocks&symbol=AAPL&timeframe=1m&from=2003-01-01&to=2026-10-24&format=csv
104
+ 413 {"code": "export-too-wide", "message": "This export would read 4,132,206 rows; your plan allows at most 2,500,000 rows an export. Choose shorter dates."}
105
+
106
+ 429 {"code": "rows-exhausted", "message": "Your plan allows 12,500,000 rows a day; 11,900,000 are used and this export would read 6,743. It resets at 00:00 UTC, in 5 h 12 min.", "retry_after": 18720}
107
+ ```
108
+
109
+ The free plan also has a weekly allowance of 12.5 GiB, counted on the bytes of its exports; past it every export
110
+ answers `429 allowance-exhausted` until Monday 00:00 UTC. A paid plan has none.
88
111
 
89
112
  **What an answer says about you.** No answer carries an email, a user id or a person's name. The one thing a caller
90
113
  learns about itself is in `/v3/usage`: the key's own name as it was typed on the Data page, its plan and its product.
@@ -110,17 +133,22 @@ GET /v3/health
110
133
  What this key may do and has used. No parameters. Answers `key` (the key's name as it was typed on the Data page),
111
134
  `plan`, `product`, `markets` (the enabled markets the key opens), `rate_mbit`, `connections` (downloads at once),
112
135
  `open` (downloads running now), `bytes_since_start`, `calls_per_minute`, `levels` and `history` per paid market (`l1`,
113
- `l2`, `l3` months, -1 the whole archive, and the tier's label), `weekly` (`allowance_bytes`, -1 for a paid plan;
114
- `used_bytes`; `week` as `2026-W43`) and `withheld` (null, or the sentence that explains why every download is refused).
136
+ `l2`, `l3` months, -1 the whole archive, and the tier's label), `rows` (`per_export`; `per_day`, -1 for a paid plan;
137
+ `used_today`; `day`; `resets_in_seconds`), `weekly` (`allowance_bytes`, -1 for a paid plan; `used_bytes`; `week` as
138
+ `2026-W46`) and `withheld` (null, or the sentence that explains why every download is refused).
115
139
 
116
140
  ```text
117
141
  GET /v3/usage (a free key)
118
142
  {"key": "Default", "plan": "free", "product": "global",
119
143
  "markets": ["us_stocks", "uk_stocks", "ca_stocks", "forex", "crypto", "futures", "indices", "commodities", "us_options"],
120
- "rate_mbit": 50.0, "connections": 1, "open": 0, "bytes_since_start": 0, "calls_per_minute": 60, "levels": {}, "history": {},
121
- "weekly": {"allowance_bytes": 13421772800, "used_bytes": 385, "week": "2026-W43"}, "withheld": null}
144
+ "rate_mbit": 50.0, "connections": 1, "open": 0, "bytes_since_start": 29818, "calls_per_minute": 60, "levels": {}, "history": {},
145
+ "rows": {"day": "2026-11-11", "per_day": 12500000, "per_export": 2500000, "resets_in_seconds": 61553, "used_today": 996029},
146
+ "weekly": {"allowance_bytes": 13421772800, "used_bytes": 14457484, "week": "2026-W46"}, "withheld": null}
122
147
  ```
123
148
 
149
+ A key of a paid tier reads `"rate_mbit": 250.0`, `"rows": {"per_day": -1, "per_export": 2500000, ...}` and
150
+ `"weekly": {"allowance_bytes": -1, ...}`.
151
+
124
152
  A key whose subscription ended reads `"plan": "cme:ended", "markets": [], "withheld": "your CME Group subscription has
125
153
  ended; renew it to download CME Group data"`.
126
154
 
@@ -428,7 +456,7 @@ Trades, quotes, orders and book exist on the paid feeds only; on the included ma
428
456
  `400 data-unavailable` ("This market offers timeframes, including tick"). Forex and commodities are priced from quotes,
429
457
  not trades, so they have candles but no trade slice: `tick` on them answers `404 data-unavailable`. Candles are `time, open, high, low, close, volume, trades`; a daily, weekly or
430
458
  monthly candle's `time` is the start of its first minute with a trade, in UTC (AAPL's 1 October 2026 candle reads
431
- `2026-10-01 04:02:00`). Trades are `time, price, size, kind`; quotes `time, bid_price, ask_price, bid_size, ask_size`;
459
+ `2026-10-01 04:02:00`), and it carries one more column, `date`: the session's date, or the first day of its week or month. Trades are `time, price, size, kind`; quotes `time, bid_price, ask_price, bid_size, ask_size`;
432
460
  orders `time, action, side, order_id, price, size`. The book is 67 columns: `time`, `sequence`, `action`, `side`, `price`, `size`, `depth`, then
433
461
  ten bid and ten ask levels of price, size and orders. Greeks are one row per traded minute with `price_at`, `iv`,
434
462
  `delta`, `gamma`, `theta`, `vega`, `option_price`, `price_source`, `underlying_price`, `years_to_expiry`, `rate` and
@@ -450,9 +478,10 @@ GET /v3/preview?market=us_stocks&symbol=AAPL&timeframe=5m&data=candles
450
478
 
451
479
  #### GET /v3/estimate
452
480
 
453
- The rows and bytes an export would have. Answers `rows`, `estimated_bytes` (the expected size of the file, usually a
454
- little above it), `ceiling_bytes` (the most the file can weigh: above 100 MB the export is prepared as a job), `mode`
455
- (`stream` or `file`), `filename`, `availability` and `message`. Without `from` and `to` it describes the same
481
+ The rows and bytes an export would have. Answers `rows`, `basis` (what was counted), `estimated_bytes` (the expected
482
+ size of the file, usually a little above it), `ceiling_bytes` (the most the file can weigh), `mode` (`stream` or
483
+ `file`, a field kept from the prepared jobs of before: every export is now sent at once), `filename`, `availability`
484
+ and `message`. `rows` is the figure the rows limits are judged on. Without `from` and `to` it describes the same
456
485
  newest-ten-rows window the preview shows, with `latest_latest` in the file name.
457
486
 
458
487
  A selection of a paid market the key may not download (no plan, a level the tier does not sell, dates before the plan's
@@ -471,67 +500,51 @@ GET /v3/estimate?market=nasdaq&symbol=NVDA&timeframe=tick&data=orders&from=2026-
471
500
 
472
501
  ```text
473
502
  GET /v3/estimate?market=us_stocks&symbol=AAPL&timeframe=5m&from=2026-10-08&to=2026-10-14&format=csv&adjusted=1&data=candles
474
- {"rows": 1021, "estimated_bytes": 89896, "ceiling_bytes": 261568, "mode": "stream", "filename": "AAPL_us_stocks_5m_2026-10-08_2026-10-14.csv", "message": null, ...}
503
+ {"rows": 1041, "estimated_bytes": 91336, "ceiling_bytes": 265408, "mode": "stream", "filename": "AAPL_us_stocks_5m_2026-10-08_2026-10-14.csv", "message": null, ...}
475
504
 
476
505
  GET /v3/estimate?market=us_stocks&symbol=AAPL&timeframe=5m&from=2026-10-17&to=2026-10-18 (a weekend)
477
- {"rows": 0, "estimated_bytes": 0, "ceiling_bytes": 0, "mode": "stream", "message": "...", ...}
506
+ {"rows": 0, "estimated_bytes": 0, "ceiling_bytes": 0, "mode": "stream",
507
+ "message": "No stored data for this selection: no candles of AAPL (pair 2000000040) are stored in the dates asked (no trading then, or dates not loaded).", ...}
478
508
  ```
479
509
 
480
510
  #### GET /v3/export, and /v3/candles
481
511
 
482
- The file. `/v3/candles` is a second name for the same call. `prepare=1` asks for a job even when the file is small.
483
- A stream (200) carries the bytes with `Content-Disposition`, `x-export-job`, `x-export-status-url` and
484
- `x-lse3-adjusted`; when the build finished before the first byte went out it also carries `Content-Length` and
485
- `x-checksum-sha256`. A job (202) is the job object. A selection with no rows answers `404 data-unavailable`. The
486
- weekly allowance of a free key is counted against exports and job files; past it every export answers
487
- `429 allowance-exhausted` with the figures: "Your plan's weekly allowance of 12.5 GiB is used up (12.54 GiB this week);
488
- it resets Monday 00:00 UTC. A paid plan has no weekly allowance."
512
+ The file, by the file rules above. `/v3/candles` is a second name for the same call; `prepare=1` is accepted and
513
+ ignored. The answer (200) carries the bytes with `Content-Disposition`, `Content-Length`, `x-checksum-sha256`,
514
+ `x-export-id`, `x-export-rows` and `x-lse3-adjusted`. A selection with no rows answers `404 data-unavailable`; an
515
+ export past the rows one export may read `413 export-too-wide`; past the free plan's rows a day `429 rows-exhausted`;
516
+ past its weekly allowance `429 allowance-exhausted` with the figures: "Your plan's weekly allowance of 12.5 GiB is
517
+ used up (12.54 GiB this week); it resets Monday 00:00 UTC. A paid plan has no weekly allowance." A daily, weekly or
518
+ monthly candle carries its session's `date` as a last column.
489
519
 
490
520
  ```text
491
521
  GET /v3/export?market=us_stocks&symbol=AAPL&timeframe=1d&from=2026-10-01&to=2026-10-14&format=csv&adjusted=1&data=candles
492
- 200 content-type: text/csv; charset=utf-8
522
+ 200 content-type: text/csv; charset=utf-8 content-length: 851
493
523
  content-disposition: attachment; filename="AAPL_us_stocks_1d_2026-10-01_2026-10-14.csv"
494
- x-export-job: dd0524aed651425ba2a89572dfdaa0d1 x-export-status-url: /v3/jobs/dd0524aed651425ba2a89572dfdaa0d1 x-lse3-adjusted: 1
495
- "time","open","high","low","close","volume","trades"
496
- "2026-10-01 04:02:00",334.18,334.83,325.82,330.79,15666143,265910
497
- "2026-10-02 04:00:00",331.14,334.54,330.17,333.53,15026716,228816
498
- ... 10 rows, 715 bytes
524
+ x-checksum-sha256: 05e6d3366eb6c948eb4dbc2460cb7b06c256d7992ec667dd0c88e8578055e812
525
+ x-export-id: 81ec941184f44427ade9e06cf33820bf x-export-rows: 10 x-lse3-adjusted: 1
526
+ "time","open","high","low","close","volume","trades","date"
527
+ "2026-10-01 04:02:00",334.18,334.83,325.82,330.79,15666143,265910,"2026-10-01"
528
+ "2026-10-02 04:00:00",331.14,334.54,330.17,333.53,15026716,228816,"2026-10-02"
529
+ ... 10 rows, 851 bytes
499
530
 
500
- GET /v3/export?...&format=csv&prepare=1
501
- 202 {"job_id": "c5bb197f076943f591cba223141d4510", "status": "building", "bytes": 0, "checksum": null, "filename": "AAPL_us_stocks_1d_2026-10-01_2026-10-14.csv",
502
- "expires_at": 1792808165, "category": "market", "status_url": "/v3/jobs/c5bb197f076943f591cba223141d4510", "download_url": "/v3/jobs/c5bb197f076943f591cba223141d4510/file",
503
- "error": null, "progress": null, "phase": null}
531
+ GET /v3/export?market=us_stocks&symbol=AAPL&timeframe=5m&from=2026-10-17&to=2026-10-18&format=csv (a weekend)
532
+ 404 {"code": "data-unavailable", "message": "No stored data for this selection: no candles of AAPL (pair 2000000040) are stored in the dates asked (no trading then, or dates not loaded)."}
504
533
 
505
534
  GET /v3/export?market=us_stocks&symbol=AAA&timeframe=1d&from=2026-10-01&to=2026-10-14
506
535
  404 {"code": "data-unavailable", "message": "..."}
507
536
  ```
508
537
 
509
- With `format=parquet` the same selection is a 3,685-byte Parquet file (`application/vnd.apache.parquet`).
538
+ With `format=parquet` the same selection is a 4,108-byte Parquet file (`application/vnd.apache.parquet`).
510
539
 
511
- #### GET /v3/jobs/{id}
540
+ #### GET /v3/jobs/{id}, and /v3/jobs/{id}/file
512
541
 
513
- A job's state: `job_id`, `status` (`building`, `ready`, `failed`, `expired`), `bytes`, `checksum` (SHA-256 of the
514
- file), `filename`, `expires_at`, `category` (`market` or `macro`), `status_url`, `download_url`, `error`, `progress`
515
- and `phase`. The job must be this key's; the market of a market job must still be in the key's plan.
542
+ The prepared-file routes of before 2026-11-06, when a large export became a job that was polled and then fetched.
543
+ Exports are no longer kept after they are sent, so every id answers the same:
516
544
 
517
545
  ```text
518
546
  GET /v3/jobs/21a88b355dc84b6982d4da81328764dc
519
- {"job_id": "21a88b355dc84b6982d4da81328764dc", "status": "ready", "bytes": 14043, "checksum": "ce570f7a54a10bdfc1c79dd11fba922355fffe78120cd3332522feca7a6872d5",
520
- "filename": "AAPL_us_stocks_5m_2026-10-08_2026-10-08.csv", "expires_at": 1792808061, "category": "market", ...}
521
-
522
- GET /v3/jobs/abc, or another key's id 404 {"code": "export-unknown", "message": "Export is not available"}
523
- ```
524
-
525
- #### GET /v3/jobs/{id}/file
526
-
527
- The file of a ready job, whole (200) or by `Range` (206), with `Content-Length`, `Accept-Ranges: bytes`, `ETag` (the
528
- checksum in quotes), `x-checksum-sha256`, and `Content-Range` on a range. `If-Range` with a different ETag gives the
529
- whole file (200). A range past the end answers 416 with `Content-Range: bytes */<length>` and no body. While the job
530
- is still building the answer is 202 with the job; a failed build `409 export-failed`.
531
-
532
- ```text
533
- GET /v3/jobs/d4194e84ef3c4364ad18525e9d802e51/file Range: bytes=0-99
534
- 206 content-range: bytes 0-99/715 etag: "8eaf9f0d83ae7ca3ddd09e272d285fbc893949ea5a04c87aa02b6d0b1d84ffc0" accept-ranges: bytes
547
+ 404 {"code": "export-unknown", "message": "Exports are no longer kept after they are sent. Request the export again."}
535
548
  ```
536
549
 
537
550
  #### Older calls
@@ -571,7 +584,7 @@ does not answer within 10 s `503 dataset-unavailable`.
571
584
  GET /v3/dataset/export?market=us_stocks&symbol=AAPL&timeframe=5m&from=2026-10-08&to=2026-10-13&indicators=rsi:14,sma:20
572
585
  &companion_market=us_stocks&companion_symbol=MSFT&companion_features=close,return,corr&corr_window=20
573
586
  &hour_start=10&hour_end=15&tz=America/New_York&day_of_week=monday,tuesday,wednesday,thursday,friday&format=csv
574
- 200 content-length: 28080 x-checksum-sha256: 37e9ef05a409385e911fc67e79b1f972be72f313f571bd6c7da5c39b4e38141d
587
+ 200 content-length: 28112 x-checksum-sha256: 18e8d02d2d469c31f5fbd88a482a01d090b6547265a66b0aaf95856d1da3ab35
575
588
  content-disposition: attachment; filename="AAPL_us_stocks_5m_2026-10-08_2026-10-13_dataset.csv"
576
589
  time,open,high,low,close,volume,trades,rsi_14,sma_20,msft_close,msft_return,corr_msft
577
590
  2026-10-08 14:00:00,337.27,337.66,337.06,337.325,186128.0,3492,,,531.145,,
@@ -582,7 +595,7 @@ time,open,high,low,close,volume,trades,rsi_14,sma_20,msft_close,msft_return,corr
582
595
 
583
596
  A key of a CME Group or Nasdaq TotalView tier opens its market's candles, `trades` and `quotes` (Level 1, from Lite),
584
597
  `book` (Level 2) and `orders` (Level 3) as the tier sells them, within the tier's history months per level, lifts the free plan's weekly allowance
585
- and paces downloads at 500 Mbit/s instead of 50; one download at a time holds on every plan. Without the tier, its rows
598
+ and its rows a day, and paces downloads at 250 Mbit/s instead of 50; one download at a time and 2,500,000 rows an export hold on every plan. Without the tier, its rows
586
599
  answer `403 plan-required` naming the cheapest tier; its figures (counts, dates, sizes and estimates) answer every key. The pricing page shows an
587
600
  illustrative sample of each feed's data and schema for anyone. The refusals a paid key meets are
588
601
  `403 history-limit` (dates before the window, the body naming `earliest` and the next tier), `403 level-required` (a
@@ -655,7 +668,7 @@ named in the text above (`required_plan`, `plan`, `level`, `levels`, `earliest`,
655
668
  | 400 | `underlying-required` | contract filters on a sweep |
656
669
  | 400 | `window-bad` | to not after from |
657
670
  | 400 | `window-too-wide` | a sweep over more than 7 days |
658
- | 400, 404 | `data-unavailable` | 400: trades, quotes, orders or book on an included market, or greeks outside us_options; 404: tick on a quotes-only market, a pair with no data yet, or a selection with no stored rows |
671
+ | 400, 404 | `data-unavailable` | 400: trades, quotes, orders or book on an included market, or greeks outside us_options; 404: tick on a quotes-only market, a pair with no data yet, or a selection with no stored rows (the message begins "No stored data for this selection" and says why) |
659
672
  | 401 | `key-missing` | no key in X-API-Key or ?key= |
660
673
  | 401 | `key-unknown` | a key the server does not know |
661
674
  | 403 | `account-required` | a key that belongs to no account |
@@ -667,7 +680,7 @@ named in the text above (`required_plan`, `plan`, `level`, `levels`, `earliest`,
667
680
  | 403 | `plan-required` | a preview or an export of a paid market the key's plan does not open (its candles, trades, quotes, orders or book); required_plan names the cheapest tier |
668
681
  | 404 | `dataset-unknown` | a dataset id the reference catalogue does not list |
669
682
  | 404 | `date-not-trading` | cme or futures with no session in the dates asked |
670
- | 404 | `export-unknown` | an id this key did not make, or a malformed id |
683
+ | 404 | `export-unknown` | /v3/jobs: exports are no longer kept after they are sent |
671
684
  | 404 | `file-unknown` | a kind and feed that name no market |
672
685
  | 404 | `level-not-served` | orders or book on a market whose levels do not include L3 or L2 |
673
686
  | 404 | `market-not-enabled` | a market switched off or coming soon; the message carries its label |
@@ -681,14 +694,15 @@ named in the text above (`required_plan`, `plan`, `level`, `levels`, `earliest`,
681
694
  | 404 | `theme-unknown` | theme not light or dark |
682
695
  | 404 | `underlying-unknown` | an underlying the market does not list |
683
696
  | 404, 503 | `dataset-unavailable` | 404: a reference dataset not available yet; 503: the dataset builder did not answer |
684
- | 409, 502 | `export-failed` | the build failed (502 while streaming; 409 on a job's file) |
685
- | 410 | `export-expired` | past its 24 hours |
697
+ | 502 | `export-failed` | the build failed |
686
698
  | 410 | `export-required` | /v3/files and /v3/file, once the key passes the plan checks |
699
+ | 413 | `export-too-wide` | an export past the rows one export may read (2,500,000 on every plan), or one the server could not measure within 10 seconds |
687
700
  | 413 | `series-window-too-wide` | more than 100,000 rows |
688
- | 429 | `allowance-exhausted` | a free key past its weekly bytes |
701
+ | 429 | `allowance-exhausted` | a free key past its weekly bytes (12.5 GiB); it resets Monday 00:00 UTC |
689
702
  | 429 | `calls-exhausted` | past the plan's calls a minute; Retry-After says when |
690
- | 429 | `connections-exhausted` | one more download while this key's one download is still open: a stream, or a job's file being fetched |
691
- | 429, 503 | `exports-busy` | 503: every builder of the server is busy; 429: this key's one download is still building |
703
+ | 429 | `connections-exhausted` | one more download while this key's one download is still open; Retry-After 5 |
704
+ | 429 | `rows-exhausted` | past the plan's rows a day (12,500,000 on the free plan); Retry-After and retry_after give the seconds to 00:00 UTC |
705
+ | 429, 503 | `exports-busy` | 429: this key's one download is still building (Retry-After 5); 503: the server's queue of waiting builds is full, or the export could not be measured just now (Retry-After 30) |
692
706
  | 502 | `dataset-failed` | the dataset build failed |
693
707
  | 502 | `query-invalid` | the data store refused the query as malformed |
694
708
  | 502, 503 | `catalogue-invalid` | the catalogue answered badly (502), or could not be loaded (503) |
@@ -698,7 +712,6 @@ named in the text above (`required_plan`, `plan`, `level`, `levels`, `earliest`,
698
712
  | 503 | `catalogue-busy` | too many catalogue reads at once |
699
713
  | 503 | `catalogue-unavailable` | the catalogue did not answer |
700
714
  | 503 | `estimate-unavailable` | the figures for a selection the key may not download could not be read |
701
- | 503 | `export-unavailable` | the job could not be written or its file not opened |
702
715
  | 503 | `overview-unavailable` | the overview figures could not be read |
703
716
  | 503 | `query-busy` | too many reads at once |
704
717
  | 504 | `macro-timeout` | past 25 seconds |
@@ -717,7 +730,8 @@ named in the text above (`required_plan`, `plan`, `level`, `levels`, `earliest`,
717
730
  | `factors(market, symbol)` | `/v3/factors` |
718
731
  | `overview(market, symbol)` | `/v3/overview` |
719
732
  | `preview()`, `estimate()` | `/v3/preview`, `/v3/estimate` |
720
- | `export()`, `history()`, `job()` | `/v3/export` and the job calls |
733
+ | `export()`, `history()` | `/v3/export` |
734
+ | `job()` | `/v3/jobs/{id}`, which no longer keeps anything |
721
735
  | `rows()`, `candles()`, `option_candles()` | `/v3/export` as CSV, held in memory |
722
736
  | `dataset_export()` | `/v3/dataset/export` |
723
737
  | `options_chain()`, `options()` | `/v3/options/chain` |
@@ -730,8 +744,14 @@ named in the text above (`required_plan`, `plan`, `level`, `levels`, `earliest`,
730
744
 
731
745
  `catalog()` covers every market, Nasdaq TotalView included, and lists the two contract markets by underlying.
732
746
 
733
- The client keeps the key in the header, follows no redirect, accepts a job's `download_url` only on the server's own
734
- origin, resumes an interrupted file with `Range` and `If-Range` from its `.part` file, and checks the size and the
735
- SHA-256 before it places the destination. A refusal raises `LSEError` with `status`, `code`, `message` and
736
- `required_plan`; an export that outlasts its `timeout` raises it with code `timeout`, and the same call again
737
- continues it.
747
+ The client keeps the key in the header, follows no redirect, writes a download to a `.part` file beside the
748
+ destination, asks again from the start when a transfer is cut, and checks the SHA-256 before it places the
749
+ destination. A refusal raises `LSEError` with `status`, `code`, `message` and `required_plan`; an export that outlasts
750
+ its `timeout` raises it with code `timeout`. On a `429` that carries a `Retry-After` of two minutes or less
751
+ (`calls-exhausted`, `connections-exhausted`, `exports-busy`) the client waits that long once in a call and asks again,
752
+ unless it was made with `wait_for_limit=False`; a refusal that stands is raised with the server's code and sentence.
753
+
754
+ The same client streams live prices with the same key, `client.stream([...])`: the
755
+ [WebSocket documentation](https://londonstrategicedge.com/websocket-documentation) describes the sockets. Crypto
756
+ streams from `wss://london-ws.londonstrategicedge.com`, which a client on the default address opens beside the default
757
+ socket for the crypto symbols it is asked for.
@@ -1,9 +1,9 @@
1
- """Pull history as a file: CSV or Parquet, resumable, checked by SHA-256.
1
+ """Pull history as a file: CSV or Parquet, checked by SHA-256.
2
2
 
3
- A small export streams at once; a large one is prepared on the server and
4
- polled. If the connection drops, run the same call again and it resumes from
5
- the bytes already received. The file is placed only after its size and
6
- checksum match what the server states.
3
+ The server sends every export whole, with its size and SHA-256. If the
4
+ connection drops the client asks for it again, and the file is placed only
5
+ after its checksum matches what the server states. One export reads at most
6
+ 2,500,000 rows; estimate() says how many a selection holds.
7
7
 
8
8
  Usage:
9
9
  LSE_API_KEY=lse_live_... python history_file.py