simudyne-pulse 0.7.0.dev8__tar.gz → 0.7.0.dev10__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.
- {simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/PKG-INFO +30 -1
- {simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/README.md +29 -0
- {simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/pyproject.toml +1 -1
- {simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/src/simudyne/resources/validation.py +44 -10
- {simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/src/simudyne_pulse.egg-info/PKG-INFO +30 -1
- {simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/tests/test_validation.py +53 -0
- {simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/LICENSE +0 -0
- {simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/setup.cfg +0 -0
- {simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/src/simudyne/__init__.py +0 -0
- {simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/src/simudyne/client.py +0 -0
- {simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/src/simudyne/exceptions.py +0 -0
- {simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/src/simudyne/resources/__init__.py +0 -0
- {simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/src/simudyne/resources/api_keys.py +0 -0
- {simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/src/simudyne/resources/data.py +0 -0
- {simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/src/simudyne/resources/fix.py +0 -0
- {simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/src/simudyne/resources/fm.py +0 -0
- {simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/src/simudyne/resources/historical.py +0 -0
- {simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/src/simudyne/resources/profile.py +0 -0
- {simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/src/simudyne/resources/simulation.py +0 -0
- {simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/src/simudyne/resources/simulator_gym.py +0 -0
- {simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/src/simudyne_pulse.egg-info/SOURCES.txt +0 -0
- {simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/src/simudyne_pulse.egg-info/dependency_links.txt +0 -0
- {simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/src/simudyne_pulse.egg-info/requires.txt +0 -0
- {simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/src/simudyne_pulse.egg-info/top_level.txt +0 -0
- {simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/tests/test_new_resources.py +0 -0
- {simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/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.
|
|
3
|
+
Version: 0.7.0.dev10
|
|
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
|
|
@@ -59,6 +59,35 @@ pip install --pre simudyne-pulse
|
|
|
59
59
|
Only use dev builds for testing unreleased changes; they are not guaranteed
|
|
60
60
|
stable. Merge `dev` into `prod` to promote those changes to a stable release.
|
|
61
61
|
|
|
62
|
+
### Comparing named populations
|
|
63
|
+
|
|
64
|
+
Simulations are often not one population but several — a foundation model and
|
|
65
|
+
an ABM, or two versions of a model. Name them and the results compare them
|
|
66
|
+
instead of averaging them together:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
job = client.validation.run(
|
|
70
|
+
symbol="TSCO", date="2026-06-23", provider="bmll", exchange="lse",
|
|
71
|
+
sim_ids={"fm": fm_ids, "abm": abm_ids},
|
|
72
|
+
plots=["statistical.radar", "stylised_facts.overall",
|
|
73
|
+
"volume_correlation.overall"],
|
|
74
|
+
)
|
|
75
|
+
result = client.validation.get_job(job["job_id"], plot_dir="figs")
|
|
76
|
+
result["distances"]["fm"]["spread"]["l1"]
|
|
77
|
+
result["stylised_fact_verdicts"]["heavy_tails"]["simulated"]["abm"]
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`distances`, `distributions`, the verdicts and the FID/MIND scores come back
|
|
81
|
+
keyed by name; the radar draws a polygon per population, the verdict table a
|
|
82
|
+
column each, and volume correlation puts historical first then a heatmap per
|
|
83
|
+
population. `sim_files` groups the same way.
|
|
84
|
+
|
|
85
|
+
A plain list is one unnamed population and keeps the flat shape exactly as
|
|
86
|
+
before. The historical day is measured once however many groups there are.
|
|
87
|
+
|
|
88
|
+
`plot_all=True` draws everything the enabled areas can draw, not just the
|
|
89
|
+
summaries.
|
|
90
|
+
|
|
62
91
|
### Working from a checkout
|
|
63
92
|
|
|
64
93
|
To run the SDK from source — editing it, or using unreleased changes — install
|
|
@@ -33,6 +33,35 @@ pip install --pre simudyne-pulse
|
|
|
33
33
|
Only use dev builds for testing unreleased changes; they are not guaranteed
|
|
34
34
|
stable. Merge `dev` into `prod` to promote those changes to a stable release.
|
|
35
35
|
|
|
36
|
+
### Comparing named populations
|
|
37
|
+
|
|
38
|
+
Simulations are often not one population but several — a foundation model and
|
|
39
|
+
an ABM, or two versions of a model. Name them and the results compare them
|
|
40
|
+
instead of averaging them together:
|
|
41
|
+
|
|
42
|
+
```python
|
|
43
|
+
job = client.validation.run(
|
|
44
|
+
symbol="TSCO", date="2026-06-23", provider="bmll", exchange="lse",
|
|
45
|
+
sim_ids={"fm": fm_ids, "abm": abm_ids},
|
|
46
|
+
plots=["statistical.radar", "stylised_facts.overall",
|
|
47
|
+
"volume_correlation.overall"],
|
|
48
|
+
)
|
|
49
|
+
result = client.validation.get_job(job["job_id"], plot_dir="figs")
|
|
50
|
+
result["distances"]["fm"]["spread"]["l1"]
|
|
51
|
+
result["stylised_fact_verdicts"]["heavy_tails"]["simulated"]["abm"]
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
`distances`, `distributions`, the verdicts and the FID/MIND scores come back
|
|
55
|
+
keyed by name; the radar draws a polygon per population, the verdict table a
|
|
56
|
+
column each, and volume correlation puts historical first then a heatmap per
|
|
57
|
+
population. `sim_files` groups the same way.
|
|
58
|
+
|
|
59
|
+
A plain list is one unnamed population and keeps the flat shape exactly as
|
|
60
|
+
before. The historical day is measured once however many groups there are.
|
|
61
|
+
|
|
62
|
+
`plot_all=True` draws everything the enabled areas can draw, not just the
|
|
63
|
+
summaries.
|
|
64
|
+
|
|
36
65
|
### Working from a checkout
|
|
37
66
|
|
|
38
67
|
To run the SDK from source — editing it, or using unreleased changes — install
|
{simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/src/simudyne/resources/validation.py
RENAMED
|
@@ -64,7 +64,8 @@ _AREA_FLAGS = (
|
|
|
64
64
|
#: needing the message stream; ``sample_period`` and ``match_generated_sample``
|
|
65
65
|
#: set the grid the book is resampled onto; ``plots`` is False, True, or a list
|
|
66
66
|
#: of plot ids.
|
|
67
|
-
_EXTRA_FIELDS = ("lob", "sample_period", "match_generated_sample", "plots"
|
|
67
|
+
_EXTRA_FIELDS = ("lob", "sample_period", "match_generated_sample", "plots",
|
|
68
|
+
"plot_all")
|
|
68
69
|
|
|
69
70
|
|
|
70
71
|
def _frame_to_parquet(entry, index: int):
|
|
@@ -172,6 +173,7 @@ class ValidationResource:
|
|
|
172
173
|
match_generated_sample=None,
|
|
173
174
|
n_levels: int = 10,
|
|
174
175
|
plots=None,
|
|
176
|
+
plot_all: bool = False,
|
|
175
177
|
) -> dict:
|
|
176
178
|
"""Submit a validation job.
|
|
177
179
|
|
|
@@ -192,13 +194,17 @@ class ValidationResource:
|
|
|
192
194
|
``(symbol, provider, exchange)`` is the identity, not the symbol.
|
|
193
195
|
exchange : str
|
|
194
196
|
Exchange, e.g. ``"lse"`` or ``"hkex_securities"``.
|
|
195
|
-
sim_ids : list of str, optional
|
|
196
|
-
Platform simulation IDs, 1 to 25.
|
|
197
|
-
``
|
|
198
|
-
|
|
197
|
+
sim_ids : list of str or dict, optional
|
|
198
|
+
Platform simulation IDs, 1 to 25. A flat list is one unnamed
|
|
199
|
+
population. A mapping — ``{"fm": [...], "abm": [...]}`` — compares
|
|
200
|
+
the named populations: distances, distributions, verdicts and
|
|
201
|
+
FID/MIND scores come back keyed by name, and the summary figures
|
|
202
|
+
draw one series per name instead of averaging them together.
|
|
203
|
+
Exactly one of ``sim_ids`` or ``sim_files`` is required.
|
|
204
|
+
sim_files : list or dict, optional
|
|
199
205
|
Your own simulated runs, up to 25: paths, ``(filename, bytes)``
|
|
200
|
-
pairs, or polars / pandas DataFrames in pulse format.
|
|
201
|
-
historical side is fetched for you.
|
|
206
|
+
pairs, or polars / pandas DataFrames in pulse format. Grouped the
|
|
207
|
+
same way as ``sim_ids``. The historical side is fetched for you.
|
|
202
208
|
ticksize : float, default 1.0
|
|
203
209
|
Minimum price increment. Should match the instrument.
|
|
204
210
|
statistical, stylised_facts, impact, volume_correlation, fid, mind : bool, optional
|
|
@@ -221,6 +227,10 @@ class ValidationResource:
|
|
|
221
227
|
``None`` or ``False`` for none, ``True`` for every figure, or plot
|
|
222
228
|
ids such as ``["statistical.radar", "stylised_facts.overall"]``.
|
|
223
229
|
Rendered server-side; :meth:`get_job` writes them to disk.
|
|
230
|
+
plot_all : bool, default False
|
|
231
|
+
Draw everything the enabled areas can draw — every distribution and
|
|
232
|
+
every stylised fact, not just the summaries. An area switched off
|
|
233
|
+
still draws nothing, so this means "all of what ran".
|
|
224
234
|
|
|
225
235
|
Returns
|
|
226
236
|
-------
|
|
@@ -239,7 +249,12 @@ class ValidationResource:
|
|
|
239
249
|
raise ValueError("sim_ids must not be empty")
|
|
240
250
|
if sim_files is not None and not sim_files:
|
|
241
251
|
raise ValueError("sim_files must not be empty")
|
|
242
|
-
|
|
252
|
+
given = sim_ids if sim_ids is not None else sim_files
|
|
253
|
+
count = (
|
|
254
|
+
sum(len(v) for v in given.values())
|
|
255
|
+
if isinstance(given, dict)
|
|
256
|
+
else len(given)
|
|
257
|
+
)
|
|
243
258
|
if count > MAX_SIM_FILES:
|
|
244
259
|
raise ValueError(f"Maximum {MAX_SIM_FILES} simulations per validation job")
|
|
245
260
|
if ticksize <= 0:
|
|
@@ -259,6 +274,7 @@ class ValidationResource:
|
|
|
259
274
|
sample_period=sample_period,
|
|
260
275
|
match_generated_sample=match_generated_sample,
|
|
261
276
|
plots=plots,
|
|
277
|
+
plot_all=plot_all or None,
|
|
262
278
|
)
|
|
263
279
|
|
|
264
280
|
if sim_ids is not None:
|
|
@@ -270,7 +286,11 @@ class ValidationResource:
|
|
|
270
286
|
"date": date,
|
|
271
287
|
"provider": provider,
|
|
272
288
|
"exchange": exchange,
|
|
273
|
-
"sim_ids":
|
|
289
|
+
"sim_ids": (
|
|
290
|
+
{k: list(v) for k, v in sim_ids.items()}
|
|
291
|
+
if isinstance(sim_ids, dict)
|
|
292
|
+
else list(sim_ids)
|
|
293
|
+
),
|
|
274
294
|
"ticksize": ticksize,
|
|
275
295
|
"config": config,
|
|
276
296
|
},
|
|
@@ -278,10 +298,24 @@ class ValidationResource:
|
|
|
278
298
|
else:
|
|
279
299
|
# Three-tuple parts: the content type matters to the server's
|
|
280
300
|
# multipart parser, so it is sent explicitly.
|
|
301
|
+
# Grouped uploads travel flat with the grouping beside them, the
|
|
302
|
+
# same way sim_ids do — multipart has no nesting.
|
|
303
|
+
if isinstance(sim_files, dict):
|
|
304
|
+
flat, groups, offset = [], {}, 0
|
|
305
|
+
for name, runs in sim_files.items():
|
|
306
|
+
runs = list(runs)
|
|
307
|
+
groups[name] = list(range(offset, offset + len(runs)))
|
|
308
|
+
offset += len(runs)
|
|
309
|
+
flat.extend(runs)
|
|
310
|
+
else:
|
|
311
|
+
flat, groups = list(sim_files), None
|
|
312
|
+
|
|
281
313
|
files = [
|
|
282
314
|
("sim_files", (*_frame_to_parquet(entry, i), "application/octet-stream"))
|
|
283
|
-
for i, entry in enumerate(
|
|
315
|
+
for i, entry in enumerate(flat)
|
|
284
316
|
]
|
|
317
|
+
if groups:
|
|
318
|
+
config = {**config, "sim_groups": groups}
|
|
285
319
|
submitted = self._client._request(
|
|
286
320
|
"POST",
|
|
287
321
|
UPLOAD_PATH,
|
{simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/src/simudyne_pulse.egg-info/PKG-INFO
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: simudyne-pulse
|
|
3
|
-
Version: 0.7.0.
|
|
3
|
+
Version: 0.7.0.dev10
|
|
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
|
|
@@ -59,6 +59,35 @@ pip install --pre simudyne-pulse
|
|
|
59
59
|
Only use dev builds for testing unreleased changes; they are not guaranteed
|
|
60
60
|
stable. Merge `dev` into `prod` to promote those changes to a stable release.
|
|
61
61
|
|
|
62
|
+
### Comparing named populations
|
|
63
|
+
|
|
64
|
+
Simulations are often not one population but several — a foundation model and
|
|
65
|
+
an ABM, or two versions of a model. Name them and the results compare them
|
|
66
|
+
instead of averaging them together:
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
job = client.validation.run(
|
|
70
|
+
symbol="TSCO", date="2026-06-23", provider="bmll", exchange="lse",
|
|
71
|
+
sim_ids={"fm": fm_ids, "abm": abm_ids},
|
|
72
|
+
plots=["statistical.radar", "stylised_facts.overall",
|
|
73
|
+
"volume_correlation.overall"],
|
|
74
|
+
)
|
|
75
|
+
result = client.validation.get_job(job["job_id"], plot_dir="figs")
|
|
76
|
+
result["distances"]["fm"]["spread"]["l1"]
|
|
77
|
+
result["stylised_fact_verdicts"]["heavy_tails"]["simulated"]["abm"]
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
`distances`, `distributions`, the verdicts and the FID/MIND scores come back
|
|
81
|
+
keyed by name; the radar draws a polygon per population, the verdict table a
|
|
82
|
+
column each, and volume correlation puts historical first then a heatmap per
|
|
83
|
+
population. `sim_files` groups the same way.
|
|
84
|
+
|
|
85
|
+
A plain list is one unnamed population and keeps the flat shape exactly as
|
|
86
|
+
before. The historical day is measured once however many groups there are.
|
|
87
|
+
|
|
88
|
+
`plot_all=True` draws everything the enabled areas can draw, not just the
|
|
89
|
+
summaries.
|
|
90
|
+
|
|
62
91
|
### Working from a checkout
|
|
63
92
|
|
|
64
93
|
To run the SDK from source — editing it, or using unreleased changes — install
|
|
@@ -230,3 +230,56 @@ class TestSimulatedFrameShapes:
|
|
|
230
230
|
frames = [pd.DataFrame({"a": [1]}), pd.DataFrame({"a": [2]})]
|
|
231
231
|
names = [f[1][0] for f in self._files_sent(frames)]
|
|
232
232
|
assert names == ["sim_0.parquet", "sim_1.parquet"]
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
class TestNamedPopulations:
|
|
236
|
+
"""Runs can be named so the results compare populations, not an average."""
|
|
237
|
+
|
|
238
|
+
def test_grouped_sim_ids_go_as_a_mapping(self):
|
|
239
|
+
client = FakeClient([{"job_id": "v1"}])
|
|
240
|
+
ValidationResource(client).run(
|
|
241
|
+
sim_ids={"fm": ["a"], "abm": ["b", "c"]}, **IDENTITY
|
|
242
|
+
)
|
|
243
|
+
assert client.calls[0][2]["json"]["sim_ids"] == {
|
|
244
|
+
"fm": ["a"],
|
|
245
|
+
"abm": ["b", "c"],
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
def test_the_total_is_what_is_capped(self):
|
|
249
|
+
client = FakeClient()
|
|
250
|
+
with pytest.raises(ValueError, match="Maximum 25"):
|
|
251
|
+
ValidationResource(client).run(
|
|
252
|
+
sim_ids={"a": ["x"] * 13, "b": ["y"] * 13}, **IDENTITY
|
|
253
|
+
)
|
|
254
|
+
assert client.calls == []
|
|
255
|
+
|
|
256
|
+
def test_grouped_uploads_travel_flat_with_the_grouping_beside_them(self):
|
|
257
|
+
"""Multipart has no nesting, so the groups go in the config."""
|
|
258
|
+
client = FakeClient([{"job_id": "v1"}])
|
|
259
|
+
ValidationResource(client).run(
|
|
260
|
+
sim_files={"fm": [("a.parquet", b"x")],
|
|
261
|
+
"abm": [("b.parquet", b"y"), ("c.parquet", b"z")]},
|
|
262
|
+
**IDENTITY,
|
|
263
|
+
)
|
|
264
|
+
kwargs = client.calls[0][2]
|
|
265
|
+
assert len(kwargs["files"]) == 3
|
|
266
|
+
assert json.loads(kwargs["data"]["config"])["sim_groups"] == {
|
|
267
|
+
"fm": [0], "abm": [1, 2]
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
def test_an_ungrouped_upload_carries_no_grouping(self):
|
|
271
|
+
client = FakeClient([{"job_id": "v1"}])
|
|
272
|
+
ValidationResource(client).run(
|
|
273
|
+
sim_files=[("a.parquet", b"x")], **IDENTITY
|
|
274
|
+
)
|
|
275
|
+
assert "sim_groups" not in json.loads(client.calls[0][2]["data"]["config"])
|
|
276
|
+
|
|
277
|
+
|
|
278
|
+
class TestPlotAll:
|
|
279
|
+
def test_it_is_forwarded(self):
|
|
280
|
+
client, _ = _run([{"job_id": "v1"}], plot_all=True)
|
|
281
|
+
assert client.calls[0][2]["json"]["config"]["plot_all"] is True
|
|
282
|
+
|
|
283
|
+
def test_it_is_omitted_when_not_asked_for(self):
|
|
284
|
+
client, _ = _run([{"job_id": "v1"}])
|
|
285
|
+
assert "plot_all" not in client.calls[0][2]["json"]["config"]
|
|
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
|
{simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/src/simudyne/resources/historical.py
RENAMED
|
File without changes
|
|
File without changes
|
{simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/src/simudyne/resources/simulation.py
RENAMED
|
File without changes
|
{simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/src/simudyne/resources/simulator_gym.py
RENAMED
|
File without changes
|
{simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/src/simudyne_pulse.egg-info/SOURCES.txt
RENAMED
|
File without changes
|
|
File without changes
|
{simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/src/simudyne_pulse.egg-info/requires.txt
RENAMED
|
File without changes
|
{simudyne_pulse-0.7.0.dev8 → simudyne_pulse-0.7.0.dev10}/src/simudyne_pulse.egg-info/top_level.txt
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|