simudyne-pulse 0.7.0.dev15__tar.gz → 0.7.0.dev17__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 (27) hide show
  1. {simudyne_pulse-0.7.0.dev15/src/simudyne_pulse.egg-info → simudyne_pulse-0.7.0.dev17}/PKG-INFO +28 -1
  2. {simudyne_pulse-0.7.0.dev15 → simudyne_pulse-0.7.0.dev17}/README.md +27 -0
  3. {simudyne_pulse-0.7.0.dev15 → simudyne_pulse-0.7.0.dev17}/pyproject.toml +1 -1
  4. {simudyne_pulse-0.7.0.dev15 → simudyne_pulse-0.7.0.dev17}/src/simudyne/client.py +2 -1
  5. simudyne_pulse-0.7.0.dev17/src/simudyne/resources/data.py +348 -0
  6. simudyne_pulse-0.7.0.dev17/src/simudyne/resources/fm.py +149 -0
  7. {simudyne_pulse-0.7.0.dev15 → simudyne_pulse-0.7.0.dev17}/src/simudyne/resources/simulation.py +190 -209
  8. {simudyne_pulse-0.7.0.dev15 → simudyne_pulse-0.7.0.dev17}/src/simudyne/resources/validation.py +114 -53
  9. {simudyne_pulse-0.7.0.dev15 → simudyne_pulse-0.7.0.dev17/src/simudyne_pulse.egg-info}/PKG-INFO +28 -1
  10. {simudyne_pulse-0.7.0.dev15 → simudyne_pulse-0.7.0.dev17}/tests/test_new_resources.py +88 -24
  11. {simudyne_pulse-0.7.0.dev15 → simudyne_pulse-0.7.0.dev17}/tests/test_validation.py +55 -2
  12. simudyne_pulse-0.7.0.dev15/src/simudyne/resources/data.py +0 -109
  13. simudyne_pulse-0.7.0.dev15/src/simudyne/resources/fm.py +0 -477
  14. {simudyne_pulse-0.7.0.dev15 → simudyne_pulse-0.7.0.dev17}/LICENSE +0 -0
  15. {simudyne_pulse-0.7.0.dev15 → simudyne_pulse-0.7.0.dev17}/setup.cfg +0 -0
  16. {simudyne_pulse-0.7.0.dev15 → simudyne_pulse-0.7.0.dev17}/src/simudyne/__init__.py +0 -0
  17. {simudyne_pulse-0.7.0.dev15 → simudyne_pulse-0.7.0.dev17}/src/simudyne/exceptions.py +0 -0
  18. {simudyne_pulse-0.7.0.dev15 → simudyne_pulse-0.7.0.dev17}/src/simudyne/resources/__init__.py +0 -0
  19. {simudyne_pulse-0.7.0.dev15 → simudyne_pulse-0.7.0.dev17}/src/simudyne/resources/api_keys.py +0 -0
  20. {simudyne_pulse-0.7.0.dev15 → simudyne_pulse-0.7.0.dev17}/src/simudyne/resources/fix.py +0 -0
  21. {simudyne_pulse-0.7.0.dev15 → simudyne_pulse-0.7.0.dev17}/src/simudyne/resources/profile.py +0 -0
  22. {simudyne_pulse-0.7.0.dev15 → simudyne_pulse-0.7.0.dev17}/src/simudyne/resources/simulator_gym.py +0 -0
  23. {simudyne_pulse-0.7.0.dev15 → simudyne_pulse-0.7.0.dev17}/src/simudyne_pulse.egg-info/SOURCES.txt +0 -0
  24. {simudyne_pulse-0.7.0.dev15 → simudyne_pulse-0.7.0.dev17}/src/simudyne_pulse.egg-info/dependency_links.txt +0 -0
  25. {simudyne_pulse-0.7.0.dev15 → simudyne_pulse-0.7.0.dev17}/src/simudyne_pulse.egg-info/requires.txt +0 -0
  26. {simudyne_pulse-0.7.0.dev15 → simudyne_pulse-0.7.0.dev17}/src/simudyne_pulse.egg-info/top_level.txt +0 -0
  27. {simudyne_pulse-0.7.0.dev15 → simudyne_pulse-0.7.0.dev17}/tests/test_simulator_gym.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simudyne-pulse
3
- Version: 0.7.0.dev15
3
+ Version: 0.7.0.dev17
4
4
  Summary: Python SDK for the Simudyne Pulse synthetic market data API
5
5
  Author-email: Simudyne <support@simudyne.com>
6
6
  License-Expression: MIT
@@ -85,6 +85,33 @@ population. `sim_files` groups the same way.
85
85
  A plain list is one unnamed population and keeps the flat shape exactly as
86
86
  before. The historical day is measured once however many groups there are.
87
87
 
88
+ ### Your own runs beside platform runs
89
+
90
+ `sim_ids` and `sim_files` can be given together — your own model against
91
+ platform simulations, on one set of figures, 25 runs in total across the two:
92
+
93
+ ```python
94
+ job = client.validation.run(
95
+ symbol="TSCO", date="2026-06-23", provider="bmll", exchange="lse",
96
+ sim_files={"fm": ["my_run.parquet"]},
97
+ sim_ids={"abm": abm_ids},
98
+ plots=["statistical.radar", "stylised_facts.overall"],
99
+ )
100
+ ```
101
+
102
+ With both present each source is a population of its own, so a bare list gets
103
+ a name rather than merging into whatever else is there:
104
+
105
+ | passed | populations |
106
+ | --- | --- |
107
+ | files mapping + ids list | its names, plus `platform` |
108
+ | files list + ids mapping | `uploaded`, plus its names |
109
+ | both lists | `uploaded` and `platform` |
110
+ | both mappings | their own names |
111
+ | either one alone | exactly as before |
112
+
113
+ The uploads are measured first and the platform runs follow.
114
+
88
115
  `plots` is the only plot switch: unset draws nothing, `True` draws every
89
116
  figure the enabled areas can draw, and a list draws just those ids. An area
90
117
  switched off draws nothing either way.
@@ -59,6 +59,33 @@ population. `sim_files` groups the same way.
59
59
  A plain list is one unnamed population and keeps the flat shape exactly as
60
60
  before. The historical day is measured once however many groups there are.
61
61
 
62
+ ### Your own runs beside platform runs
63
+
64
+ `sim_ids` and `sim_files` can be given together — your own model against
65
+ platform simulations, on one set of figures, 25 runs in total across the two:
66
+
67
+ ```python
68
+ job = client.validation.run(
69
+ symbol="TSCO", date="2026-06-23", provider="bmll", exchange="lse",
70
+ sim_files={"fm": ["my_run.parquet"]},
71
+ sim_ids={"abm": abm_ids},
72
+ plots=["statistical.radar", "stylised_facts.overall"],
73
+ )
74
+ ```
75
+
76
+ With both present each source is a population of its own, so a bare list gets
77
+ a name rather than merging into whatever else is there:
78
+
79
+ | passed | populations |
80
+ | --- | --- |
81
+ | files mapping + ids list | its names, plus `platform` |
82
+ | files list + ids mapping | `uploaded`, plus its names |
83
+ | both lists | `uploaded` and `platform` |
84
+ | both mappings | their own names |
85
+ | either one alone | exactly as before |
86
+
87
+ The uploads are measured first and the platform runs follow.
88
+
62
89
  `plots` is the only plot switch: unset draws nothing, `True` draws every
63
90
  figure the enabled areas can draw, and a list draws just those ids. An area
64
91
  switched off draws nothing either way.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "simudyne-pulse"
7
- version = "0.7.0-dev.15"
7
+ version = "0.7.0-dev.17"
8
8
  description = "Python SDK for the Simudyne Pulse synthetic market data API"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -45,7 +45,8 @@ class PulseABM:
45
45
  api_keys : ApiKeysResource
46
46
  Create, list and revoke API keys.
47
47
  data : DataResource
48
- The catalog of calibrated symbols available to simulate.
48
+ The market data catalogue: what exists, what is calibrated,
49
+ and calibrating a day.
49
50
  simulation : SimulationResource
50
51
  Submit agent-based simulations and retrieve their results.
51
52
  validation : ValidationResource
@@ -0,0 +1,348 @@
1
+ """Market data: what exists, what the agent-based model can simulate, and the
2
+ one action that moves a symbol-day from the first to the second.
3
+
4
+ Reached as ``client.data``. The three calls are one story:
5
+
6
+ available_data() every symbol-day in the registry
7
+ calibrate() calibrate one of them
8
+ calibrated_data() the ones the ABM can now simulate
9
+
10
+ A foundation-model run is prompted from anything in ``available_data``; an
11
+ agent-based run needs the day to appear in ``calibrated_data`` first.
12
+ """
13
+
14
+ AVAILABLE_DATA_PATH = "/data/available-data"
15
+ CALIBRATED_DATA_PATH = "/data/calibrated-data"
16
+ CALIBRATE_PATH = "/data/calibrate"
17
+
18
+
19
+ class DataResource:
20
+ """Browse the market data catalogue and calibrate days in it.
21
+
22
+ Reached as ``client.data``. Every simulation is pinned to a symbol-day, so
23
+ this is the resource that tells you which ``provider`` / ``exchange`` /
24
+ ``symbol`` / ``cal_date`` combinations exist and which are ready to run.
25
+
26
+ Examples
27
+ --------
28
+ >>> catalog = client.data.calibrated_data(exchange="hkex_securities")
29
+ >>> for instrument in catalog["symbols"][:3]:
30
+ ... print(instrument["symbol"], instrument["available_dates"][:2])
31
+ """
32
+
33
+ def __init__(self, client):
34
+ self._client = client
35
+
36
+ def available_data(
37
+ self,
38
+ model_id: str | None = None,
39
+ symbol: str | None = None,
40
+ q: str | None = None,
41
+ provider: str | None = None,
42
+ exchange: str | None = None,
43
+ date: str | None = None,
44
+ include_calibration_state: bool = False,
45
+ limit: int | None = None,
46
+ offset: int | None = None,
47
+ ):
48
+ """Every symbol-day in the data registry: what EXISTS.
49
+
50
+ The raw-data catalogue, so it answers both "what can a foundation model
51
+ be prompted with?" and "what could I calibrate?". Its counterpart is
52
+ :meth:`calibrated_data`, which lists only the days the agent-based
53
+ pipeline has already processed.
54
+
55
+ Every filter is applied in the database. The catalogue holds the whole
56
+ vendor universe (~15k symbols) and is never returned whole, so page
57
+ through it with ``limit`` and ``offset`` rather than asking for all of
58
+ it.
59
+
60
+ Parameters
61
+ ----------
62
+ model_id : str, optional
63
+ A model from :meth:`~simudyne.resources.fm.FmResource.models`.
64
+ Keeps only the markets that model's ``supported_data`` declares, so
65
+ what comes back is exactly what it will accept at
66
+ :meth:`~simudyne.resources.simulation.SimulationResource.run` with
67
+ ``engine="fm"``. An unknown id raises.
68
+ symbol : str, optional
69
+ Exact symbol, e.g. "700" or "TSCO".
70
+ q : str, optional
71
+ Case-insensitive substring match over the symbol.
72
+ provider : str, optional
73
+ Data provider, e.g. "omd" or "bmll". Narrows further within
74
+ ``model_id``'s markets when both are given.
75
+ exchange : str, optional
76
+ Exchange protocol, e.g. "lse" or "hkex_securities".
77
+ date : str, optional
78
+ Trading date "YYYY-MM-DD". Keeps only symbols with data on that
79
+ day, and returns only that day.
80
+ include_calibration_state : bool, default False
81
+ Also return ``calibrated_dates`` and ``calibrated_count`` per
82
+ symbol — which of its days the agent-based model has already
83
+ calibrated. Costs a second query, so it is off by default; it means
84
+ nothing to a foundation model.
85
+ limit : int, optional
86
+ Max symbols to return (1-200, default 50).
87
+ offset : int, optional
88
+ Symbols to skip, for paging alongside ``limit``.
89
+
90
+ Returns
91
+ -------
92
+ dict
93
+ ``{total, limit, offset, symbols}``, paged by symbol identity
94
+ rather than by row so a symbol's dates are never truncated.
95
+ ``total`` is the match count before paging. Each symbol has:
96
+
97
+ - symbol (str): the ticker, e.g. "700"
98
+ - provider (str), exchange (str): the market it came from
99
+ - dates (list of str): trading days present, "YYYY-MM-DD"
100
+ - calibrated_dates (list of str), calibrated_count (int): only
101
+ with ``include_calibration_state``
102
+
103
+ ``dates`` is always a list of strings, with or without calibration
104
+ state, so a caller never branches on the shape of the response.
105
+
106
+ Raises
107
+ ------
108
+ PulseAPIError
109
+ If the key is not pro or demo tier (403), ``model_id`` names a
110
+ model that is not registered (404), or ``date`` is not
111
+ ``YYYY-MM-DD`` (422).
112
+
113
+ See Also
114
+ --------
115
+ calibrated_data : The subset the agent-based model can simulate.
116
+ calibrate : Move one symbol-day from here to there.
117
+
118
+ Examples
119
+ --------
120
+ Search the registry:
121
+
122
+ >>> page = client.data.available_data(q="tsc", exchange="lse")
123
+ >>> page["total"]
124
+ 3
125
+
126
+ What one model can be prompted with, which is the usual pre-run call:
127
+
128
+ >>> page = client.data.available_data(model_id="pulse-lob-1", limit=20)
129
+ >>> first = page["symbols"][0]
130
+ >>> first["symbol"], first["dates"][:2]
131
+ ('AZN', ['2026-06-22', '2026-06-23'])
132
+
133
+ Which of a symbol's days are already calibrated:
134
+
135
+ >>> page = client.data.available_data(
136
+ ... symbol="700", include_calibration_state=True,
137
+ ... )
138
+ >>> entry = page["symbols"][0]
139
+ >>> len(entry["dates"]), entry["calibrated_count"]
140
+ (60, 4)
141
+ """
142
+ params = {
143
+ k: v for k, v in {
144
+ "model_id": model_id,
145
+ "symbol": symbol,
146
+ "q": q,
147
+ "provider": provider,
148
+ "exchange": exchange,
149
+ "date": date,
150
+ "include_calibration_state": include_calibration_state or None,
151
+ "limit": limit,
152
+ "offset": offset,
153
+ }.items() if v is not None
154
+ }
155
+ return self._client._request("GET", AVAILABLE_DATA_PATH, params=params or None)
156
+
157
+ def calibrated_data(
158
+ self,
159
+ symbol: str | None = None,
160
+ exchange: str | None = None,
161
+ provider: str | None = None,
162
+ date: str | None = None,
163
+ q: str | None = None,
164
+ limit: int | None = None,
165
+ offset: int | None = None,
166
+ ):
167
+ """Symbol-days the agent-based model can simulate: what is CALIBRATED.
168
+
169
+ Every entry is a valid ``symbol`` / ``provider`` / ``exchange`` /
170
+ ``cal_date`` for
171
+ :meth:`~simudyne.resources.simulation.SimulationResource.run` with
172
+ ``engine="abm"``. A day that exists but has never been calibrated
173
+ appears in :meth:`available_data` and not here; :meth:`calibrate` is
174
+ what moves it across.
175
+
176
+ Parameters
177
+ ----------
178
+ symbol : str, optional
179
+ Exact symbol, e.g. "700.HK".
180
+ exchange : str, optional
181
+ Exchange protocol, e.g. "hkex_securities".
182
+ provider : str, optional
183
+ Data provider, e.g. "omd" or "bmll".
184
+ date : str, optional
185
+ Calibration date "YYYY-MM-DD". Keeps only instruments calibrated
186
+ on that date, and narrows each one's ``available_dates`` to it.
187
+ q : str, optional
188
+ Case-insensitive substring match over ticker and company name.
189
+ limit : int, optional
190
+ Max instruments to return. Omitted, the whole calibrated catalogue
191
+ comes back — it is far smaller than the registry.
192
+ offset : int, optional
193
+ Instruments to skip, for paging alongside ``limit``.
194
+
195
+ Returns
196
+ -------
197
+ dict
198
+ ``{total, limit, offset, symbols}``, the same envelope as
199
+ :meth:`available_data`, paged by instrument. Each entry has:
200
+
201
+ - symbol (str): the ticker, e.g. "700.HK"
202
+ - company_name (str or None): the issuer's name where the vendor
203
+ reports one
204
+ - provider (str), exchange (str): the market it came from
205
+ - instrument_type, currency, tick_size, lot_size
206
+ - available_dates (list of dict): one per calibrated day, each with
207
+ ``date``, ``status``, ``stage``, ``number_of_msgs``,
208
+ ``volume_traded`` and ``reference_price``. Any ``date`` here is a
209
+ valid ``cal_date`` to run against
210
+ - avg_number_of_msgs, avg_volume_traded: averaged over the dates
211
+ returned, so a ``date`` filter narrows them too
212
+
213
+ Raises
214
+ ------
215
+ PulseAPIError
216
+ If a filter is malformed — most often ``date`` not in
217
+ ``YYYY-MM-DD`` form (422).
218
+
219
+ See Also
220
+ --------
221
+ available_data : Everything that exists, calibrated or not.
222
+
223
+ Examples
224
+ --------
225
+ The whole calibrated catalogue:
226
+
227
+ >>> everything = client.data.calibrated_data()
228
+ >>> everything["total"]
229
+ 412
230
+
231
+ One instrument, to see which dates it can be run for:
232
+
233
+ >>> hits = client.data.calibrated_data(symbol="700.HK")
234
+ >>> [d["date"] for d in hits["symbols"][0]["available_dates"]]
235
+ ['2025-09-01', '2025-09-02', '2025-09-03']
236
+
237
+ Everything calibrated on one date, the usual way to pick a date that is
238
+ valid across several instruments:
239
+
240
+ >>> on_date = client.data.calibrated_data(date="2025-09-01")
241
+
242
+ Free-text search over ticker and company name, paged:
243
+
244
+ >>> page = client.data.calibrated_data(q="tencent", limit=10)
245
+ """
246
+ params = {
247
+ k: v for k, v in {
248
+ "symbol": symbol,
249
+ "exchange": exchange,
250
+ "provider": provider,
251
+ "date": date,
252
+ "q": q,
253
+ "limit": limit,
254
+ "offset": offset,
255
+ }.items() if v is not None
256
+ }
257
+ return self._client._request("GET", CALIBRATED_DATA_PATH, params=params or None)
258
+
259
+ def calibrate(
260
+ self,
261
+ symbol: str,
262
+ cal_date: str,
263
+ provider: str,
264
+ exchange: str,
265
+ simulations: int = None,
266
+ batch_size: int = None,
267
+ optimize_adj_params: bool = True,
268
+ ):
269
+ """Fit the agent-based model to one symbol-day.
270
+
271
+ Calibration is a data operation: its input is a day from
272
+ :meth:`available_data` and its output is an entry in
273
+ :meth:`calibrated_data`. It runs asynchronously — the call returns once
274
+ the job is queued, and the day appears in :meth:`calibrated_data` when
275
+ the pipeline finishes.
276
+
277
+ A symbol is identified by four fields: ``provider``, ``exchange``,
278
+ ``symbol`` and ``cal_date``.
279
+
280
+ Parameters
281
+ ----------
282
+ symbol : str
283
+ Trading symbol, e.g. "9999.HK".
284
+ cal_date : str
285
+ Trading day in YYYY-MM-DD format.
286
+ provider : str
287
+ Data provider the symbol is sourced from: "omd" or "bmll".
288
+ exchange : str
289
+ Exchange protocol name, e.g. "hkex_securities".
290
+ simulations : int, optional
291
+ Number of simulations to run during calibration.
292
+ batch_size : int, optional
293
+ Batch size for calibration runs.
294
+ optimize_adj_params : bool, default True
295
+ Whether to optimise adjustment parameters.
296
+
297
+ Returns
298
+ -------
299
+ dict
300
+ Calibration job submission result, containing the job identifier
301
+ and its accepted status.
302
+
303
+ Raises
304
+ ------
305
+ PulseAPIError
306
+ If the key is not demo tier (403), or there is no raw market data
307
+ for the symbol on ``cal_date`` (400).
308
+
309
+ Notes
310
+ -----
311
+ Calibration is only needed for a symbol-day that is not already
312
+ calibrated, and it is far slower than running against an existing
313
+ calibration. Check :meth:`calibrated_data` first — most instruments are
314
+ calibrated already.
315
+
316
+ Examples
317
+ --------
318
+ >>> job = client.data.calibrate(
319
+ ... symbol="9999.HK",
320
+ ... cal_date="2025-09-01",
321
+ ... provider="omd",
322
+ ... exchange="hkex_securities",
323
+ ... )
324
+
325
+ With an explicit budget, and adjustment-parameter optimisation off:
326
+
327
+ >>> job = client.data.calibrate(
328
+ ... symbol="9999.HK",
329
+ ... cal_date="2025-09-01",
330
+ ... provider="omd",
331
+ ... exchange="hkex_securities",
332
+ ... simulations=500,
333
+ ... batch_size=50,
334
+ ... optimize_adj_params=False,
335
+ ... )
336
+ """
337
+ payload: dict = {
338
+ "symbol": symbol,
339
+ "cal_date": cal_date,
340
+ "provider": provider,
341
+ "exchange": exchange,
342
+ "optimize_adj_params": optimize_adj_params,
343
+ }
344
+ if simulations is not None:
345
+ payload["simulations"] = simulations
346
+ if batch_size is not None:
347
+ payload["batch_size"] = batch_size
348
+ return self._client._request("POST", CALIBRATE_PATH, json=payload)
@@ -0,0 +1,149 @@
1
+ """The foundation-model registry: what can run here, and what each accepts.
2
+
3
+ RUNNING a foundation model is not here. ``client.simulation.run(engine="fm")``
4
+ submits every simulation, agent-based or foundation-model, and
5
+ ``client.simulation`` polls and reads them both — an FM sim carries the same
6
+ canonical ``sim_id``, differing only in its ``gen_method`` field
7
+ (``FM-{model_id}_v{version}`` rather than ``ABM_v{version}``).
8
+
9
+ What remains here is the registry, which is a catalogue of engines rather than
10
+ a simulation, and the live streaming session, which has no job and no sim_id
11
+ and so shares no lifecycle with the rest.
12
+
13
+ Workflow:
14
+ 1. ``models()`` — what can run here, and what each model accepts
15
+ 2. ``client.data.available_data(model_id=...)`` — what it can be prompted
16
+ with
17
+ 3. ``client.simulation.run(engine="fm", model_id=...)`` — submit; returns a
18
+ job_id and its sim_ids up front
19
+ 4. ``client.simulation.get_job_status(job_id)`` until it is complete
20
+ 5. ``client.simulation.get_sim_data(sim_id)`` — read the generated book
21
+ """
22
+
23
+ MODELS_PATH = "/fm/models"
24
+ LIVE_PATH = "/fm/live"
25
+
26
+
27
+ class FmResource:
28
+ """The foundation models registered in this environment.
29
+
30
+ Reached as ``client.fm``. Use it to discover what can run and what each
31
+ model accepts, then submit through ``client.simulation.run(engine="fm")``
32
+ like any other simulation.
33
+
34
+ Every call on this resource requires a pro-tier key.
35
+
36
+ Examples
37
+ --------
38
+ The full workflow, from picking a model to reading the generated book —
39
+ note that only the first step is on this resource:
40
+
41
+ >>> import time
42
+ >>> model = client.fm.models()["models"][0]
43
+ >>> job = client.simulation.run(
44
+ ... engine="fm",
45
+ ... model_id=model["model_id"],
46
+ ... symbol="700",
47
+ ... cal_date="2025-09-02",
48
+ ... provider="bmll",
49
+ ... exchange="hkex_securities",
50
+ ... duration_minutes=30,
51
+ ... )
52
+ >>> while not client.simulation.get_job_status(job["job_id"])["is_complete"]:
53
+ ... time.sleep(30)
54
+ >>> book = client.simulation.get_sim_data(job["sim_ids"][0])
55
+ """
56
+
57
+ def __init__(self, client):
58
+ self._client = client
59
+
60
+ def models(self):
61
+ """Foundation models active in this environment.
62
+
63
+ Requires a pro-tier key.
64
+
65
+ Returns
66
+ -------
67
+ dict
68
+ Contains ``models``, a list of model dicts, each with:
69
+
70
+ - model_id (str): the id to pass to :meth:`run`
71
+ - version (str): the model build version
72
+ - production_name (str): branding name, empty for dev builds
73
+ - supported_data (list of dict): ``{provider, exchange}`` markets
74
+ the model accepts a prompt from; empty means any
75
+ - min_prompt_rows (int or None): context rows it needs to start
76
+ - model_args (dict): the knobs it accepts, each
77
+ ``{type, default, min, max, description}``
78
+ - resources (dict): ``{cpu, memory, gpu}`` it is scheduled with
79
+
80
+ Raises
81
+ ------
82
+ PulseAPIError
83
+ If the key is not pro tier — status 403.
84
+
85
+ Examples
86
+ --------
87
+ >>> for m in client.fm.models()["models"]:
88
+ ... print(m["model_id"], m["version"], sorted(m["model_args"]))
89
+
90
+ Inspect one model's knobs before overriding them:
91
+
92
+ >>> models = client.fm.models()["models"]
93
+ >>> first = models[0]
94
+ >>> knobs = first["model_args"]
95
+ >>> knobs["temperature"]
96
+ {'type': 'float', 'default': 1.0, 'min': 0.1, 'max': 2.0, ...}
97
+ """
98
+ return self._client._request("GET", MODELS_PATH)
99
+
100
+ def live(
101
+ self,
102
+ model_id: str,
103
+ horizon: int = None,
104
+ seed: int = 42,
105
+ model_args: dict = None,
106
+ ):
107
+ """Start a live streaming session instead of a batch job.
108
+
109
+ Returns the token a live chart client uses to connect. Requires a
110
+ pro-tier key.
111
+
112
+ Parameters
113
+ ----------
114
+ model_id : str
115
+ A model from models().
116
+ horizon : int, optional
117
+ Frames to generate.
118
+ seed : int, default 42
119
+ Master random seed.
120
+ model_args : dict, optional
121
+ Overrides for the model's declared knobs.
122
+
123
+ Returns
124
+ -------
125
+ dict
126
+ Contains ``token`` — the connection token a live chart client
127
+ presents — plus ``job_id`` and ``model_id``.
128
+
129
+ Raises
130
+ ------
131
+ PulseAPIError
132
+ If ``model_id`` is unknown (404), ``model_args`` holds an unknown
133
+ or out-of-range key (400), or the key is not pro tier (403).
134
+
135
+ Examples
136
+ --------
137
+ >>> session = client.fm.live(model_id="tradefm-hkex", horizon=5000)
138
+ >>> print(session["token"])
139
+
140
+ A live session is not a batch job: nothing is queued, no ``sim_id`` is
141
+ minted, and there is no status to poll. The token is the whole
142
+ interface.
143
+ """
144
+ payload = {"model_id": model_id, "seed": seed}
145
+ if horizon is not None:
146
+ payload["horizon"] = horizon
147
+ if model_args:
148
+ payload["model_args"] = model_args
149
+ return self._client._request("POST", LIVE_PATH, json=payload)