simudyne-pulse 0.9.0.dev1__tar.gz → 0.9.0.dev2__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 (24) hide show
  1. {simudyne_pulse-0.9.0.dev1/src/simudyne_pulse.egg-info → simudyne_pulse-0.9.0.dev2}/PKG-INFO +1 -1
  2. {simudyne_pulse-0.9.0.dev1 → simudyne_pulse-0.9.0.dev2}/pyproject.toml +1 -1
  3. {simudyne_pulse-0.9.0.dev1 → simudyne_pulse-0.9.0.dev2}/src/simudyne/resources/validation.py +40 -20
  4. {simudyne_pulse-0.9.0.dev1 → simudyne_pulse-0.9.0.dev2/src/simudyne_pulse.egg-info}/PKG-INFO +1 -1
  5. {simudyne_pulse-0.9.0.dev1 → simudyne_pulse-0.9.0.dev2}/tests/test_validation.py +30 -2
  6. {simudyne_pulse-0.9.0.dev1 → simudyne_pulse-0.9.0.dev2}/LICENSE +0 -0
  7. {simudyne_pulse-0.9.0.dev1 → simudyne_pulse-0.9.0.dev2}/README.md +0 -0
  8. {simudyne_pulse-0.9.0.dev1 → simudyne_pulse-0.9.0.dev2}/setup.cfg +0 -0
  9. {simudyne_pulse-0.9.0.dev1 → simudyne_pulse-0.9.0.dev2}/src/simudyne/__init__.py +0 -0
  10. {simudyne_pulse-0.9.0.dev1 → simudyne_pulse-0.9.0.dev2}/src/simudyne/client.py +0 -0
  11. {simudyne_pulse-0.9.0.dev1 → simudyne_pulse-0.9.0.dev2}/src/simudyne/exceptions.py +0 -0
  12. {simudyne_pulse-0.9.0.dev1 → simudyne_pulse-0.9.0.dev2}/src/simudyne/resources/__init__.py +0 -0
  13. {simudyne_pulse-0.9.0.dev1 → simudyne_pulse-0.9.0.dev2}/src/simudyne/resources/api_keys.py +0 -0
  14. {simudyne_pulse-0.9.0.dev1 → simudyne_pulse-0.9.0.dev2}/src/simudyne/resources/data.py +0 -0
  15. {simudyne_pulse-0.9.0.dev1 → simudyne_pulse-0.9.0.dev2}/src/simudyne/resources/fix.py +0 -0
  16. {simudyne_pulse-0.9.0.dev1 → simudyne_pulse-0.9.0.dev2}/src/simudyne/resources/fm.py +0 -0
  17. {simudyne_pulse-0.9.0.dev1 → simudyne_pulse-0.9.0.dev2}/src/simudyne/resources/profile.py +0 -0
  18. {simudyne_pulse-0.9.0.dev1 → simudyne_pulse-0.9.0.dev2}/src/simudyne/resources/simulation.py +0 -0
  19. {simudyne_pulse-0.9.0.dev1 → simudyne_pulse-0.9.0.dev2}/src/simudyne_pulse.egg-info/SOURCES.txt +0 -0
  20. {simudyne_pulse-0.9.0.dev1 → simudyne_pulse-0.9.0.dev2}/src/simudyne_pulse.egg-info/dependency_links.txt +0 -0
  21. {simudyne_pulse-0.9.0.dev1 → simudyne_pulse-0.9.0.dev2}/src/simudyne_pulse.egg-info/requires.txt +0 -0
  22. {simudyne_pulse-0.9.0.dev1 → simudyne_pulse-0.9.0.dev2}/src/simudyne_pulse.egg-info/top_level.txt +0 -0
  23. {simudyne_pulse-0.9.0.dev1 → simudyne_pulse-0.9.0.dev2}/tests/test_api_contract.py +0 -0
  24. {simudyne_pulse-0.9.0.dev1 → simudyne_pulse-0.9.0.dev2}/tests/test_new_resources.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simudyne-pulse
3
- Version: 0.9.0.dev1
3
+ Version: 0.9.0.dev2
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
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "simudyne-pulse"
7
- version = "0.9.0-dev.1"
7
+ version = "0.9.0-dev.2"
8
8
  description = "Python SDK for the Simudyne Pulse synthetic market data API"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -70,10 +70,13 @@ _AREA_FLAGS = (
70
70
  )
71
71
 
72
72
  #: ``lob`` marks the frames as L2 snapshots, which switches off anything
73
- #: needing the message stream; ``sample_period`` and ``match_generated_sample``
74
- #: set the grid the book is resampled onto; ``plots`` is False, True, or a list
75
- #: of plot ids.
76
- _EXTRA_FIELDS = ("lob", "sample_period", "match_generated_sample", "plots")
73
+ #: needing the message stream; ``resample_method`` and ``sessions`` say how the
74
+ #: historical day is put onto the generated frames' grid; ``plots`` is False,
75
+ #: True, or a list of plot ids.
76
+ _EXTRA_FIELDS = ("lob", "resample_method", "sessions", "plots")
77
+
78
+ #: How the historical day can be put onto the generated grid in lob mode.
79
+ RESAMPLE_METHODS = ("last", "pulseflow")
77
80
 
78
81
 
79
82
  def _frame_to_parquet(entry, index: int):
@@ -257,8 +260,8 @@ class ValidationResource:
257
260
  fid=None,
258
261
  mind=None,
259
262
  lob=None,
260
- sample_period=None,
261
- match_generated_sample=None,
263
+ resample_method=None,
264
+ sessions=None,
262
265
  n_levels: int = 10,
263
266
  plots=None,
264
267
  ) -> dict:
@@ -318,15 +321,22 @@ class ValidationResource:
318
321
  lob : bool, optional
319
322
  The frames are L2 snapshots. Anything needing the message stream
320
323
  is skipped, with the reason reported in
321
- ``metadata["areas_skipped"]``.
322
- sample_period : str, optional
323
- The grid the book is resampled onto in lob mode, e.g. ``"1s"``.
324
- Omitted, the cadence is read off your generated frames; if those
325
- are event-level there is no grid to match and nothing is
326
- resampled.
327
- match_generated_sample : bool, optional
328
- Match the generated frames' grid even when ``sample_period`` is
329
- given.
324
+ ``metadata["areas_skipped"]``. The historical day is resampled
325
+ onto your generated frames' own grid, read off them (e.g. one
326
+ second); event-level frames are left as they are.
327
+ resample_method : {"last", "pulseflow"}, optional
328
+ How the historical day is put onto that grid in lob mode.
329
+ ``"last"`` (the default when unset) keeps the last event in each
330
+ interval. ``"pulseflow"`` reproduces PulseFLOW's training-data
331
+ preprocessing exactly — the first event per interval on a fixed
332
+ session grid, forward-filled to the session edges, sizes clipped
333
+ at each session's 99th percentile — so a PulseFLOW run is scored
334
+ against the data it was trained on. Needs ``sessions``.
335
+ sessions : list of [start_h, start_m, end_h, end_m], optional
336
+ The model's trading sessions, for ``resample_method="pulseflow"``
337
+ only; the end is exclusive. ``[[8, 0, 16, 30]]`` for LSE
338
+ continuous trading, ``[[9, 30, 12, 0], [13, 0, 16, 0]]`` for
339
+ HKEX.
330
340
  n_levels : int, default 10
331
341
  Book levels to measure over.
332
342
  plots : bool or list of str, optional
@@ -408,14 +418,16 @@ class ValidationResource:
408
418
  ... plots=["statistical.radar", "stylised_facts.overall"],
409
419
  ... )
410
420
 
411
- L2 snapshots resampled onto a one-second grid:
421
+ A PulseFLOW run on its one-second grid, scored against the historical
422
+ day downsampled exactly as PulseFLOW's training data was:
412
423
 
413
424
  >>> job = client.validation.run(
414
425
  ... symbol="BARC", date="2025-09-02",
415
426
  ... provider="bmll", exchange="lse",
416
- ... sim_files=["book_snapshots.parquet"],
427
+ ... sim_files=["pulseflow_sample.parquet"],
417
428
  ... lob=True,
418
- ... sample_period="1s",
429
+ ... resample_method="pulseflow",
430
+ ... sessions=[[8, 0, 16, 30]],
419
431
  ... )
420
432
  """
421
433
  if sim_ids is None and sim_files is None:
@@ -433,6 +445,14 @@ class ValidationResource:
433
445
  )
434
446
  if n_levels < 1:
435
447
  raise ValueError("n_levels must be at least 1")
448
+ if resample_method is not None and resample_method not in RESAMPLE_METHODS:
449
+ raise ValueError(f"resample_method must be one of {RESAMPLE_METHODS}")
450
+ if resample_method == "pulseflow" and not sessions:
451
+ raise ValueError(
452
+ "resample_method='pulseflow' needs sessions, e.g. [[8, 0, 16, 30]]"
453
+ )
454
+ if sessions and resample_method != "pulseflow":
455
+ raise ValueError("sessions only applies with resample_method='pulseflow'")
436
456
 
437
457
  config = _build_config(
438
458
  n_levels,
@@ -443,8 +463,8 @@ class ValidationResource:
443
463
  fid=fid,
444
464
  mind=mind,
445
465
  lob=lob,
446
- sample_period=sample_period,
447
- match_generated_sample=match_generated_sample,
466
+ resample_method=resample_method,
467
+ sessions=sessions,
448
468
  plots=plots,
449
469
  )
450
470
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simudyne-pulse
3
- Version: 0.9.0.dev1
3
+ Version: 0.9.0.dev2
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
@@ -106,14 +106,42 @@ class TestMetricFlags:
106
106
  "flag,value",
107
107
  [("statistical", False), ("stylised_facts", True), ("impact", False),
108
108
  ("volume_correlation", True), ("fid", False), ("mind", True),
109
- ("lob", True), ("sample_period", "100ms"),
110
- ("match_generated_sample", True),
109
+ ("lob", True), ("resample_method", "last"),
111
110
  ("plots", ["statistical.radar"])],
112
111
  )
113
112
  def test_explicit_options_are_forwarded(self, flag, value):
114
113
  client, _ = _run([{"job_id": "v1"}], **{flag: value})
115
114
  assert client.calls[0][2]["json"]["config"][flag] == value
116
115
 
116
+ def test_pulseflow_resampling_is_forwarded_with_its_sessions(self):
117
+ client, _ = _run([{"job_id": "v1"}], lob=True, resample_method="pulseflow",
118
+ sessions=[[8, 0, 16, 30]])
119
+ config = client.calls[0][2]["json"]["config"]
120
+ assert config["resample_method"] == "pulseflow"
121
+ assert config["sessions"] == [[8, 0, 16, 30]]
122
+
123
+ @pytest.mark.parametrize(
124
+ "kwargs,match",
125
+ [({"resample_method": "mean"}, "resample_method must be one of"),
126
+ ({"resample_method": "pulseflow"}, "needs sessions"),
127
+ ({"sessions": [[8, 0, 16, 30]]}, "only applies"),
128
+ ({"resample_method": "last", "sessions": [[8, 0, 16, 30]]}, "only applies")],
129
+ )
130
+ def test_bad_resampling_is_refused_before_any_request(self, kwargs, match):
131
+ client = FakeClient()
132
+ with pytest.raises(ValueError, match=match):
133
+ ValidationResource(client).run(sim_ids=SIM_IDS, **IDENTITY, **kwargs)
134
+ assert client.calls == []
135
+
136
+ def test_the_old_grid_options_are_gone(self):
137
+ """Replaced by resample_method: in lob mode the grid is read off the
138
+ generated frames."""
139
+ for dead in ("sample_period", "match_generated_sample"):
140
+ with pytest.raises(TypeError):
141
+ ValidationResource(FakeClient()).run(
142
+ sim_ids=SIM_IDS, **IDENTITY, **{dead: "1s"}
143
+ )
144
+
117
145
  def test_the_legacy_aliases_are_gone(self):
118
146
  """run_* and l2_only/plot_data were two names for one thing."""
119
147
  for dead in ("run_metrics", "run_impact", "run_stylised_facts",