simudyne-pulse 0.7.0.dev17__tar.gz → 0.7.0.dev19__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. {simudyne_pulse-0.7.0.dev17/src/simudyne_pulse.egg-info → simudyne_pulse-0.7.0.dev19}/PKG-INFO +1 -1
  2. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev19}/pyproject.toml +1 -1
  3. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev19}/src/simudyne/resources/api_keys.py +15 -0
  4. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev19}/src/simudyne/resources/data.py +6 -0
  5. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev19}/src/simudyne/resources/fix.py +4 -0
  6. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev19}/src/simudyne/resources/fm.py +11 -0
  7. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev19}/src/simudyne/resources/profile.py +17 -0
  8. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev19}/src/simudyne/resources/simulation.py +47 -0
  9. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev19}/src/simudyne/resources/simulator_gym.py +20 -0
  10. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev19}/src/simudyne/resources/validation.py +73 -5
  11. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev19/src/simudyne_pulse.egg-info}/PKG-INFO +1 -1
  12. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev19}/tests/test_validation.py +22 -1
  13. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev19}/LICENSE +0 -0
  14. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev19}/README.md +0 -0
  15. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev19}/setup.cfg +0 -0
  16. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev19}/src/simudyne/__init__.py +0 -0
  17. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev19}/src/simudyne/client.py +0 -0
  18. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev19}/src/simudyne/exceptions.py +0 -0
  19. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev19}/src/simudyne/resources/__init__.py +0 -0
  20. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev19}/src/simudyne_pulse.egg-info/SOURCES.txt +0 -0
  21. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev19}/src/simudyne_pulse.egg-info/dependency_links.txt +0 -0
  22. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev19}/src/simudyne_pulse.egg-info/requires.txt +0 -0
  23. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev19}/src/simudyne_pulse.egg-info/top_level.txt +0 -0
  24. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev19}/tests/test_new_resources.py +0 -0
  25. {simudyne_pulse-0.7.0.dev17 → simudyne_pulse-0.7.0.dev19}/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.dev17
3
+ Version: 0.7.0.dev19
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.7.0-dev.17"
7
+ version = "0.7.0-dev.19"
8
8
  description = "Python SDK for the Simudyne Pulse synthetic market data API"
9
9
  readme = "README.md"
10
10
  license = "MIT"
@@ -44,6 +44,11 @@ class ApiKeysResource:
44
44
  PulseAPIError
45
45
  If the account has reached its key limit, or ``name`` is rejected.
46
46
 
47
+ See Also
48
+ --------
49
+ list : Every active key on the account.
50
+ revoke : Deactivate a key.
51
+
47
52
  Examples
48
53
  --------
49
54
  >>> new_key = client.api_keys.create(name="Training script")
@@ -76,6 +81,11 @@ class ApiKeysResource:
76
81
  PulseAPIError
77
82
  If the calling key is invalid or has been revoked.
78
83
 
84
+ See Also
85
+ --------
86
+ create : Issue a new key.
87
+ revoke : Deactivate a key.
88
+
79
89
  Examples
80
90
  --------
81
91
  >>> for key in client.api_keys.list():
@@ -104,6 +114,11 @@ class ApiKeysResource:
104
114
  PulseAPIError
105
115
  If ``key_id`` does not exist or does not belong to your account.
106
116
 
117
+ See Also
118
+ --------
119
+ list : Find the key id to revoke.
120
+ create : Issue a replacement key.
121
+
107
122
  Examples
108
123
  --------
109
124
  >>> new_key = client.api_keys.create(name="Training script")
@@ -306,6 +306,12 @@ class DataResource:
306
306
  If the key is not demo tier (403), or there is no raw market data
307
307
  for the symbol on ``cal_date`` (400).
308
308
 
309
+ See Also
310
+ --------
311
+ available_data : Days that exist and could be calibrated.
312
+ calibrated_data : Days already calibrated, where this one lands.
313
+ simudyne.resources.simulation.SimulationResource.run : Simulate a calibrated day.
314
+
309
315
  Notes
310
316
  -----
311
317
  Calibration is only needed for a symbol-day that is not already
@@ -58,6 +58,10 @@ class FixResource:
58
58
  If ``user_id`` is given without an admin key, or names an account
59
59
  that does not exist.
60
60
 
61
+ See Also
62
+ --------
63
+ simudyne.resources.fm.FmResource.live : Start a live FIX-compatible stream.
64
+
61
65
  Examples
62
66
  --------
63
67
  >>> usage = client.fix.usage()
@@ -82,6 +82,12 @@ class FmResource:
82
82
  PulseAPIError
83
83
  If the key is not pro tier — status 403.
84
84
 
85
+ See Also
86
+ --------
87
+ simudyne.resources.data.DataResource.available_data : Days a model will accept, via model_id.
88
+ simudyne.resources.simulation.SimulationResource.run : Run a model with engine="fm".
89
+ live : Stream a model live instead of in batch.
90
+
85
91
  Examples
86
92
  --------
87
93
  >>> for m in client.fm.models()["models"]:
@@ -132,6 +138,11 @@ class FmResource:
132
138
  If ``model_id`` is unknown (404), ``model_args`` holds an unknown
133
139
  or out-of-range key (400), or the key is not pro tier (403).
134
140
 
141
+ See Also
142
+ --------
143
+ models : Models and what each accepts.
144
+ simudyne.resources.fix.FixResource.usage : What a FIX session has run.
145
+
135
146
  Examples
136
147
  --------
137
148
  >>> session = client.fm.live(model_id="tradefm-hkex", horizon=5000)
@@ -48,6 +48,12 @@ class ProfileResource:
48
48
  PulseAPIError
49
49
  If the key is invalid, revoked or expired — status 401.
50
50
 
51
+ See Also
52
+ --------
53
+ usage : Requests this month, by endpoint.
54
+ downloads : The download quota and what it has been spent on.
55
+ simudyne.resources.api_keys.ApiKeysResource.list : The account's API keys.
56
+
51
57
  Examples
52
58
  --------
53
59
  >>> me = client.profile.get()
@@ -86,6 +92,11 @@ class ProfileResource:
86
92
  PulseAPIError
87
93
  If the key is invalid, revoked or expired — status 401.
88
94
 
95
+ See Also
96
+ --------
97
+ get : The account itself.
98
+ downloads : The download quota.
99
+
89
100
  Examples
90
101
  --------
91
102
  >>> usage = client.profile.usage()
@@ -125,6 +136,12 @@ class ProfileResource:
125
136
  downloaded_group_details : list of dict or None
126
137
  The same groups, each with a ``downloaded_at`` timestamp.
127
138
 
139
+ See Also
140
+ --------
141
+ usage : Requests this month, by endpoint.
142
+ simudyne.resources.simulation.SimulationResource.get_bulk_data : Download many runs at once.
143
+ simudyne.resources.simulation.SimulationResource.get_sim_data : Download one run's files.
144
+
128
145
  Notes
129
146
  -----
130
147
  A request that would exceed the allowance returns HTTP 429 without
@@ -415,6 +415,11 @@ class SimulationResource:
415
415
  PulseAPIError
416
416
  If the key is not pro tier (403), or ``limit`` is above 500 (400).
417
417
 
418
+ See Also
419
+ --------
420
+ get_job_status : Poll one job.
421
+ get_job_results : Read one job's results.
422
+
418
423
  Examples
419
424
  --------
420
425
  >>> result = client.simulation.get_jobs()
@@ -464,6 +469,12 @@ class SimulationResource:
464
469
  If ``job_id`` is unknown or belongs to another account (404), or
465
470
  the key is not pro tier (403).
466
471
 
472
+ See Also
473
+ --------
474
+ run : Submit a job.
475
+ get_job_results : Read results once the job completes.
476
+ get_job_logs : The run log, for a job that failed.
477
+
467
478
  Notes
468
479
  -----
469
480
  ``is_complete`` and ``has_errors`` are independent: a job where some
@@ -663,6 +674,10 @@ class SimulationResource:
663
674
  404 when the job does not exist, is not yours, or
664
675
  wrote no log.
665
676
 
677
+ See Also
678
+ --------
679
+ get_job_status : Where the job has got to.
680
+
666
681
  Examples
667
682
  --------
668
683
  >>> status = client.simulation.get_job_status(job_id)
@@ -731,6 +746,12 @@ class SimulationResource:
731
746
  ``strategy`` is not "vwap" or "twap", or the symbol has no
732
747
  calibration for ``cal_date`` (400).
733
748
 
749
+ See Also
750
+ --------
751
+ run : A single run, with any execution algos.
752
+ get_job_status : Poll the submitted job.
753
+ get_job_results : Read per-size results once complete.
754
+
734
755
  Notes
735
756
  -----
736
757
  ``order_sizes`` is in **lots**, not shares — unlike ``order_size`` on
@@ -814,6 +835,12 @@ class SimulationResource:
814
835
  If ``sim_id`` is unknown or belongs to another account — status
815
836
  404.
816
837
 
838
+ See Also
839
+ --------
840
+ get_sim_data : Download one of the listed files.
841
+ get_sim_params : The parameters the run used.
842
+ get_sim_metrics : The run's summary metrics.
843
+
817
844
  Examples
818
845
  --------
819
846
  >>> files = client.simulation.list_sim_files(sim_id)
@@ -853,6 +880,11 @@ class SimulationResource:
853
880
  If ``sim_id`` is unknown or belongs to another account — status
854
881
  404.
855
882
 
883
+ See Also
884
+ --------
885
+ get_sim_metrics : The run's summary metrics.
886
+ list_sim_files : Every file the run produced.
887
+
856
888
  Examples
857
889
  --------
858
890
  >>> params = client.simulation.get_sim_params(sim_id)
@@ -887,6 +919,11 @@ class SimulationResource:
887
919
  If ``sim_id`` is unknown, belongs to another account, or the
888
920
  simulation has not finished and so wrote no metrics — status 404.
889
921
 
922
+ See Also
923
+ --------
924
+ get_sim_params : The parameters the run used.
925
+ list_sim_files : Every file the run produced.
926
+
890
927
  Examples
891
928
  --------
892
929
  >>> metrics = client.simulation.get_sim_metrics(sim_id)
@@ -1014,6 +1051,11 @@ class SimulationResource:
1014
1051
  PulseAPIError
1015
1052
  If the key is invalid, revoked or expired — status 401.
1016
1053
 
1054
+ See Also
1055
+ --------
1056
+ get_sim_data : Download a cached run's files.
1057
+ get_sample_data : A small sample without spending download quota.
1058
+
1017
1059
  Notes
1018
1060
  -----
1019
1061
  Available on every tier, and the way onto the platform without running
@@ -1082,6 +1124,11 @@ class SimulationResource:
1082
1124
  If no sample dataset has been published for this deployment — status
1083
1125
  404.
1084
1126
 
1127
+ See Also
1128
+ --------
1129
+ list_cached : The cached library the sample comes from.
1130
+ get_sim_data : The full files for one run.
1131
+
1085
1132
  Notes
1086
1133
  -----
1087
1134
  Available on every tier and charged against nothing, so it is the
@@ -69,6 +69,11 @@ class GymSession:
69
69
  ``RuntimeError`` rather than ``PulseAPIError`` — it speaks
70
70
  WebSocket, not HTTP.
71
71
 
72
+ See Also
73
+ --------
74
+ GymSession.step : Advance the episode.
75
+ GymSession.close : End the session.
76
+
72
77
  Examples
73
78
  --------
74
79
  >>> obs = env.reset(seed=42)
@@ -111,6 +116,11 @@ class GymSession:
111
116
  error. Note this resource raises ``RuntimeError`` rather than
112
117
  ``PulseAPIError`` — it speaks WebSocket, not HTTP.
113
118
 
119
+ See Also
120
+ --------
121
+ GymSession.reset : Start a new episode.
122
+ GymSession.close : End the session.
123
+
114
124
  Examples
115
125
  --------
116
126
  >>> result = env.step(0)
@@ -141,6 +151,10 @@ class GymSession:
141
151
 
142
152
  Called automatically when the session is used as a context manager.
143
153
 
154
+ See Also
155
+ --------
156
+ SimulatorGymResource.connect : Open a new session.
157
+
144
158
  Examples
145
159
  --------
146
160
  >>> env = client.simulator_gym.connect(
@@ -220,6 +234,12 @@ class SimulatorGymResource:
220
234
  often. This resource raises ``RuntimeError`` rather than
221
235
  ``PulseAPIError`` because it speaks WebSocket, not HTTP.
222
236
 
237
+ See Also
238
+ --------
239
+ GymSession.reset : Start an episode on the session.
240
+ GymSession.step : Advance it.
241
+ GymSession.close : End it.
242
+
223
243
  Notes
224
244
  -----
225
245
  ``exchange`` takes its display form here — ``"HKEX.Securities"`` — not
@@ -368,6 +368,12 @@ class ValidationResource:
368
368
  ``sim_id`` is unknown, or the tier does not allow a requested
369
369
  area.
370
370
 
371
+ See Also
372
+ --------
373
+ get_job_status : Poll the job without fetching its result.
374
+ get_job : The result once the job completes.
375
+ list_jobs : Earlier validation jobs.
376
+
371
377
  Examples
372
378
  --------
373
379
  Platform simulations against the real day:
@@ -534,6 +540,11 @@ class ValidationResource:
534
540
  If ``job_id`` is unknown or belongs to another account — status
535
541
  404.
536
542
 
543
+ See Also
544
+ --------
545
+ run : Submit a validation job.
546
+ get_job_status : Poll without fetching the result.
547
+
537
548
  Examples
538
549
  --------
539
550
  >>> result = client.validation.get_job(job_id)
@@ -561,25 +572,73 @@ class ValidationResource:
561
572
  job["plot_paths"] = _save_plots(job, plot_dir)
562
573
  return job
563
574
 
564
- def list_jobs(self, limit: int = 50) -> dict:
575
+ def get_job_status(self, job_id: str) -> dict:
576
+ """Where a validation job has got to, without its result.
577
+
578
+ The full result is large and tier-filtered, so a poll loop should ask
579
+ this instead of :meth:`get_job` and fetch the result once, when
580
+ ``is_complete`` turns true.
581
+
582
+ Parameters
583
+ ----------
584
+ job_id : str
585
+ A validation job handle from :meth:`run`.
586
+
587
+ Returns
588
+ -------
589
+ dict
590
+ ``{job_id, status, message, is_complete, created_at, updated_at}``.
591
+ ``status`` is one of pending / running / completed / failed, and
592
+ only the last two are terminal — which is what ``is_complete``
593
+ says, so a caller need not keep that list itself.
594
+
595
+ Raises
596
+ ------
597
+ PulseAPIError
598
+ If the job does not exist or belongs to another key — status 404.
599
+
600
+ See Also
601
+ --------
602
+ get_job : The result once the job completes.
603
+ run : Submit a validation job.
604
+
605
+ Examples
606
+ --------
607
+ >>> import time
608
+ >>> while not client.validation.get_job_status(job_id)["is_complete"]:
609
+ ... time.sleep(30)
610
+ >>> result = client.validation.get_job(job_id, plot_dir="figures/")
611
+ """
612
+ return self._client._request("GET", f"{JOBS_PATH}/{job_id}/status")
613
+
614
+ def list_jobs(self, limit: int = 50, offset: int = 0) -> dict:
565
615
  """List your validation jobs, newest first.
566
616
 
567
617
  Parameters
568
618
  ----------
569
619
  limit : int, default 50
570
- Maximum number of jobs to return.
620
+ Maximum number of jobs to return. Capped at 200 by the API.
621
+ offset : int, default 0
622
+ Jobs to skip before this page, for paging through more than
623
+ ``limit`` of them.
571
624
 
572
625
  Returns
573
626
  -------
574
627
  dict
575
- Contains ``jobs``, a list of job summaries newest first, and
576
- ``total``, the count before ``limit`` was applied.
628
+ ``{total, limit, offset, jobs}``. ``jobs`` is the page, newest
629
+ first; ``total`` is your full job count rather than the page size,
630
+ so a caller can tell whether more pages exist.
577
631
 
578
632
  Raises
579
633
  ------
580
634
  PulseAPIError
581
635
  If the key is invalid, revoked or expired — status 401.
582
636
 
637
+ See Also
638
+ --------
639
+ get_job : One job's result.
640
+ run : Submit a validation job.
641
+
583
642
  Examples
584
643
  --------
585
644
  >>> listing = client.validation.list_jobs(limit=10)
@@ -591,5 +650,14 @@ class ValidationResource:
591
650
  >>> done = [j for j in listing["jobs"] if j["status"] == "completed"]
592
651
  >>> newest = done[0]
593
652
  >>> result = client.validation.get_job(newest["job_id"])
653
+
654
+ Page through the rest:
655
+
656
+ >>> seen = len(listing["jobs"])
657
+ >>> while seen < listing["total"]:
658
+ ... page = client.validation.list_jobs(limit=10, offset=seen)
659
+ ... seen += len(page["jobs"])
594
660
  """
595
- return self._client._request("GET", JOBS_PATH, params={"limit": limit})
661
+ return self._client._request(
662
+ "GET", JOBS_PATH, params={"limit": limit, "offset": offset}
663
+ )
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: simudyne-pulse
3
- Version: 0.7.0.dev17
3
+ Version: 0.7.0.dev19
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
@@ -202,7 +202,7 @@ class TestOneEntryPoint:
202
202
  res.get_job("v1")
203
203
  res.list_jobs(limit=5)
204
204
  assert client.calls[0][:2] == ("GET", "/validation/jobs/v1")
205
- assert client.calls[1][2]["params"] == {"limit": 5}
205
+ assert client.calls[1][2]["params"] == {"limit": 5, "offset": 0}
206
206
 
207
207
 
208
208
  class TestSimulatedFrameShapes:
@@ -406,3 +406,24 @@ class TestBothSourcesAtOnce:
406
406
  sim_ids=["s"] * 13,
407
407
  **IDENTITY,
408
408
  )
409
+
410
+
411
+ class TestPagingAndStatus:
412
+ """Matching pulse-api-pod 1.69.0: a paged list and a lightweight poll."""
413
+
414
+ def test_list_jobs_pages(self):
415
+ client = FakeClient([{"total": 60, "limit": 10, "offset": 10, "jobs": []}])
416
+ ValidationResource(client).list_jobs(limit=10, offset=10)
417
+ assert client.calls[0][2]["params"] == {"limit": 10, "offset": 10}
418
+
419
+ def test_list_jobs_starts_at_the_beginning(self):
420
+ client = FakeClient([{"total": 0, "limit": 50, "offset": 0, "jobs": []}])
421
+ ValidationResource(client).list_jobs()
422
+ assert client.calls[0][2]["params"] == {"limit": 50, "offset": 0}
423
+
424
+ def test_status_asks_the_cheap_endpoint(self):
425
+ """Not get_job: the full result is large and tier-filtered."""
426
+ client = FakeClient([{"job_id": "v1", "status": "running", "is_complete": False}])
427
+ out = ValidationResource(client).get_job_status("v1")
428
+ assert client.calls[0][1] == "/validation/jobs/v1/status"
429
+ assert out["is_complete"] is False