simudyne-pulse 0.7.0.dev6__tar.gz → 0.7.2__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.dev6/src/simudyne_pulse.egg-info → simudyne_pulse-0.7.2}/PKG-INFO +1 -1
- {simudyne_pulse-0.7.0.dev6 → simudyne_pulse-0.7.2}/pyproject.toml +1 -1
- {simudyne_pulse-0.7.0.dev6 → simudyne_pulse-0.7.2}/src/simudyne/client.py +0 -4
- {simudyne_pulse-0.7.0.dev6 → simudyne_pulse-0.7.2}/src/simudyne/resources/data.py +0 -4
- {simudyne_pulse-0.7.0.dev6 → simudyne_pulse-0.7.2}/src/simudyne/resources/simulation.py +3 -106
- simudyne_pulse-0.7.2/src/simudyne/resources/validation.py +299 -0
- {simudyne_pulse-0.7.0.dev6 → simudyne_pulse-0.7.2/src/simudyne_pulse.egg-info}/PKG-INFO +1 -1
- {simudyne_pulse-0.7.0.dev6 → simudyne_pulse-0.7.2}/src/simudyne_pulse.egg-info/SOURCES.txt +1 -5
- simudyne_pulse-0.7.0.dev6/src/simudyne/resources/fix.py +0 -32
- simudyne_pulse-0.7.0.dev6/src/simudyne/resources/fm.py +0 -227
- simudyne_pulse-0.7.0.dev6/src/simudyne/resources/validation.py +0 -822
- simudyne_pulse-0.7.0.dev6/tests/test_new_resources.py +0 -150
- simudyne_pulse-0.7.0.dev6/tests/test_validation.py +0 -268
- {simudyne_pulse-0.7.0.dev6 → simudyne_pulse-0.7.2}/LICENSE +0 -0
- {simudyne_pulse-0.7.0.dev6 → simudyne_pulse-0.7.2}/README.md +0 -0
- {simudyne_pulse-0.7.0.dev6 → simudyne_pulse-0.7.2}/setup.cfg +0 -0
- {simudyne_pulse-0.7.0.dev6 → simudyne_pulse-0.7.2}/src/simudyne/__init__.py +0 -0
- {simudyne_pulse-0.7.0.dev6 → simudyne_pulse-0.7.2}/src/simudyne/exceptions.py +0 -0
- {simudyne_pulse-0.7.0.dev6 → simudyne_pulse-0.7.2}/src/simudyne/resources/__init__.py +0 -0
- {simudyne_pulse-0.7.0.dev6 → simudyne_pulse-0.7.2}/src/simudyne/resources/api_keys.py +0 -0
- {simudyne_pulse-0.7.0.dev6 → simudyne_pulse-0.7.2}/src/simudyne/resources/historical.py +0 -0
- {simudyne_pulse-0.7.0.dev6 → simudyne_pulse-0.7.2}/src/simudyne/resources/profile.py +0 -0
- {simudyne_pulse-0.7.0.dev6 → simudyne_pulse-0.7.2}/src/simudyne/resources/simulator_gym.py +0 -0
- {simudyne_pulse-0.7.0.dev6 → simudyne_pulse-0.7.2}/src/simudyne_pulse.egg-info/dependency_links.txt +0 -0
- {simudyne_pulse-0.7.0.dev6 → simudyne_pulse-0.7.2}/src/simudyne_pulse.egg-info/requires.txt +0 -0
- {simudyne_pulse-0.7.0.dev6 → simudyne_pulse-0.7.2}/src/simudyne_pulse.egg-info/top_level.txt +0 -0
- {simudyne_pulse-0.7.0.dev6 → simudyne_pulse-0.7.2}/tests/test_simulator_gym.py +0 -0
|
@@ -36,8 +36,6 @@ class PulseABM:
|
|
|
36
36
|
from simudyne.resources.profile import ProfileResource
|
|
37
37
|
from simudyne.resources.api_keys import ApiKeysResource
|
|
38
38
|
from simudyne.resources.data import DataResource
|
|
39
|
-
from simudyne.resources.fix import FixResource
|
|
40
|
-
from simudyne.resources.fm import FmResource
|
|
41
39
|
from simudyne.resources.simulation import SimulationResource
|
|
42
40
|
from simudyne.resources.simulator_gym import SimulatorGymResource
|
|
43
41
|
from simudyne.resources.validation import ValidationResource
|
|
@@ -45,8 +43,6 @@ class PulseABM:
|
|
|
45
43
|
self.profile = ProfileResource(self)
|
|
46
44
|
self.api_keys = ApiKeysResource(self)
|
|
47
45
|
self.data = DataResource(self)
|
|
48
|
-
self.fix = FixResource(self)
|
|
49
|
-
self.fm = FmResource(self)
|
|
50
46
|
self.simulation = SimulationResource(self)
|
|
51
47
|
self.simulator_gym = SimulatorGymResource(self)
|
|
52
48
|
self.validation = ValidationResource(self)
|
|
@@ -11,7 +11,6 @@ class DataResource:
|
|
|
11
11
|
exchange: str | None = None,
|
|
12
12
|
provider: str | None = None,
|
|
13
13
|
date: str | None = None,
|
|
14
|
-
q: str | None = None,
|
|
15
14
|
limit: int | None = None,
|
|
16
15
|
offset: int | None = None,
|
|
17
16
|
):
|
|
@@ -32,8 +31,6 @@ class DataResource:
|
|
|
32
31
|
Calibration date "YYYY-MM-DD". Keeps only instruments
|
|
33
32
|
calibrated on that date, and narrows each instrument's
|
|
34
33
|
available_dates to it.
|
|
35
|
-
q : str, optional
|
|
36
|
-
Case-insensitive substring match over ticker and company name.
|
|
37
34
|
limit : int, optional
|
|
38
35
|
Max instruments to return.
|
|
39
36
|
offset : int, optional
|
|
@@ -49,7 +46,6 @@ class DataResource:
|
|
|
49
46
|
"exchange": exchange,
|
|
50
47
|
"provider": provider,
|
|
51
48
|
"date": date,
|
|
52
|
-
"q": q,
|
|
53
49
|
"limit": limit,
|
|
54
50
|
"offset": offset,
|
|
55
51
|
}.items() if v is not None
|
|
@@ -21,7 +21,6 @@ RESULTS_PATH = "/simulation/results"
|
|
|
21
21
|
CACHED_PATH = "/simulation/cached"
|
|
22
22
|
SAMPLE_PATH = "/simulation/sample"
|
|
23
23
|
CALIBRATE_PATH = "/calibrate"
|
|
24
|
-
LRM_PATH = "/simulation/lrm/run"
|
|
25
24
|
|
|
26
25
|
# Available market scenarios
|
|
27
26
|
SCENARIOS = {
|
|
@@ -319,17 +318,12 @@ class SimulationResource:
|
|
|
319
318
|
payload["batch_size"] = batch_size
|
|
320
319
|
return self._pro_request("POST", CALIBRATE_PATH, json=payload)
|
|
321
320
|
|
|
322
|
-
def get_jobs(self
|
|
323
|
-
"""Get simulation jobs submitted by the authenticated user
|
|
321
|
+
def get_jobs(self):
|
|
322
|
+
"""Get all simulation jobs submitted by the authenticated user.
|
|
324
323
|
|
|
325
324
|
Returns a list of jobs with their associated simulation IDs. Use this
|
|
326
325
|
to find job IDs for past runs or to see what simulations are pending.
|
|
327
326
|
|
|
328
|
-
Parameters
|
|
329
|
-
----------
|
|
330
|
-
limit : int, default 100
|
|
331
|
-
Max jobs to return (default 100, max 500)
|
|
332
|
-
|
|
333
327
|
Returns
|
|
334
328
|
-------
|
|
335
329
|
dict
|
|
@@ -339,7 +333,6 @@ class SimulationResource:
|
|
|
339
333
|
- sim_ids (list): List of simulation IDs in this job
|
|
340
334
|
- created_at (str): Timestamp when job was submitted
|
|
341
335
|
- total (int): Total number of jobs
|
|
342
|
-
- returned (int): Jobs in this page
|
|
343
336
|
|
|
344
337
|
Examples
|
|
345
338
|
--------
|
|
@@ -350,7 +343,7 @@ class SimulationResource:
|
|
|
350
343
|
... print(f"Job {job['job_id']}: {len(job['sim_ids'])} simulations")
|
|
351
344
|
... print(f" Created: {job['created_at']}")
|
|
352
345
|
"""
|
|
353
|
-
return self._pro_request("GET", JOBS_PATH
|
|
346
|
+
return self._pro_request("GET", JOBS_PATH)
|
|
354
347
|
|
|
355
348
|
def get_job_status(self, job_id: str):
|
|
356
349
|
"""Get the status of all simulations in a job.
|
|
@@ -479,102 +472,6 @@ class SimulationResource:
|
|
|
479
472
|
"""
|
|
480
473
|
return self._pro_request("GET", f"{JOBS_PATH}/{job_id}/results")
|
|
481
474
|
|
|
482
|
-
def get_job_logs(self, job_id: str) -> str:
|
|
483
|
-
"""Fetch the engine run log for one of your jobs, as plain text.
|
|
484
|
-
|
|
485
|
-
The worker writes a diagnostic log per job — this is the thing to read
|
|
486
|
-
when a run fails or finishes with nothing plottable, and the thing to
|
|
487
|
-
attach when sending a problem to support@simudyne.com.
|
|
488
|
-
|
|
489
|
-
Parameters
|
|
490
|
-
----------
|
|
491
|
-
job_id : str
|
|
492
|
-
The job ID from run() or get_jobs()
|
|
493
|
-
|
|
494
|
-
Returns
|
|
495
|
-
-------
|
|
496
|
-
str
|
|
497
|
-
The log text.
|
|
498
|
-
|
|
499
|
-
Raises
|
|
500
|
-
------
|
|
501
|
-
PulseAPIError
|
|
502
|
-
404 when the job does not exist, is not yours, or
|
|
503
|
-
wrote no log.
|
|
504
|
-
|
|
505
|
-
Examples
|
|
506
|
-
--------
|
|
507
|
-
>>> status = client.simulation.get_job_status(job_id)
|
|
508
|
-
>>> if status["has_errors"]:
|
|
509
|
-
... print(client.simulation.get_job_logs(job_id)[:2000])
|
|
510
|
-
"""
|
|
511
|
-
# Plain text, not JSON — go through the retrying transport directly.
|
|
512
|
-
url = f"{self._client.base_url}{JOBS_PATH}/{job_id}/logs"
|
|
513
|
-
response = self._client._request_with_retries("GET", url)
|
|
514
|
-
return response.text
|
|
515
|
-
|
|
516
|
-
def run_lrm(
|
|
517
|
-
self,
|
|
518
|
-
symbol: str,
|
|
519
|
-
cal_date: str,
|
|
520
|
-
provider: str,
|
|
521
|
-
exchange: str,
|
|
522
|
-
order_sizes: list,
|
|
523
|
-
n_runs: int = 50,
|
|
524
|
-
seed: int = 42,
|
|
525
|
-
horizon_mins: int = 60,
|
|
526
|
-
strategy: str = "vwap",
|
|
527
|
-
side: str = None,
|
|
528
|
-
):
|
|
529
|
-
"""Run a liquidity-risk grid: market impact across a ladder of order sizes.
|
|
530
|
-
|
|
531
|
-
One execution algo is built per entry in order_sizes, and all of them
|
|
532
|
-
share a single baseline, so the cost is ``n_runs * (1 + len(order_sizes))``
|
|
533
|
-
simulations rather than one baseline per size. Poll the returned job_id
|
|
534
|
-
through the usual job endpoints.
|
|
535
|
-
|
|
536
|
-
Parameters
|
|
537
|
-
----------
|
|
538
|
-
symbol : str
|
|
539
|
-
Trading symbol (e.g. "700")
|
|
540
|
-
cal_date : str
|
|
541
|
-
Calibration date in YYYY-MM-DD format
|
|
542
|
-
provider : str
|
|
543
|
-
Data provider (e.g. "omd", "bmll")
|
|
544
|
-
exchange : str
|
|
545
|
-
Exchange protocol (e.g. "hkex_securities")
|
|
546
|
-
order_sizes : list
|
|
547
|
-
Order sizes in LOTS — one algo per entry
|
|
548
|
-
n_runs : int, default 50
|
|
549
|
-
Monte Carlo runs per arm (default 50)
|
|
550
|
-
seed : int, default 42
|
|
551
|
-
Random seed (default 42)
|
|
552
|
-
horizon_mins : int, default 60
|
|
553
|
-
Execution horizon in minutes (default 60)
|
|
554
|
-
strategy : str, default 'vwap'
|
|
555
|
-
"vwap" or "twap" (default "vwap")
|
|
556
|
-
side : str, optional
|
|
557
|
-
"buy" or "sell"; defaults to the sign of each order size
|
|
558
|
-
|
|
559
|
-
Returns
|
|
560
|
-
-------
|
|
561
|
-
dict with job_id and the queued sim_ids
|
|
562
|
-
"""
|
|
563
|
-
payload = {
|
|
564
|
-
"symbol": symbol,
|
|
565
|
-
"cal_date": cal_date,
|
|
566
|
-
"provider": provider,
|
|
567
|
-
"exchange": exchange,
|
|
568
|
-
"order_sizes": order_sizes,
|
|
569
|
-
"n_runs": n_runs,
|
|
570
|
-
"seed": seed,
|
|
571
|
-
"horizon_mins": horizon_mins,
|
|
572
|
-
"strategy": strategy,
|
|
573
|
-
}
|
|
574
|
-
if side is not None:
|
|
575
|
-
payload["side"] = side
|
|
576
|
-
return self._pro_request("POST", LRM_PATH, json=payload)
|
|
577
|
-
|
|
578
475
|
def list_sim_files(self, sim_id: str):
|
|
579
476
|
"""List available files for a specific simulation.
|
|
580
477
|
|
|
@@ -0,0 +1,299 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Validation Resource for the Pulse SDK.
|
|
3
|
+
|
|
4
|
+
This module provides methods for validating simulation quality by comparing
|
|
5
|
+
simulated LOB data against historical data using distributional metrics,
|
|
6
|
+
impact response analysis, and FID scores.
|
|
7
|
+
|
|
8
|
+
Workflow:
|
|
9
|
+
1. Submit a validation job with run() -> returns job_id
|
|
10
|
+
2. Poll status with get_job(job_id) or use run_pipeline() for blocking
|
|
11
|
+
3. View results including distances and plots
|
|
12
|
+
4. List past jobs with list_jobs()
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import time
|
|
16
|
+
import base64
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
RUN_PATH = "/validation/run"
|
|
20
|
+
JOBS_PATH = "/validation/jobs"
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class ValidationResource:
|
|
24
|
+
def __init__(self, client):
|
|
25
|
+
self._client = client
|
|
26
|
+
|
|
27
|
+
def run(
|
|
28
|
+
self,
|
|
29
|
+
symbol: str,
|
|
30
|
+
date: str,
|
|
31
|
+
sim_ids: list[str],
|
|
32
|
+
ticksize: float = 1.0,
|
|
33
|
+
run_metrics: bool = True,
|
|
34
|
+
run_impact: bool = False,
|
|
35
|
+
run_fid: bool = False,
|
|
36
|
+
n_levels: int = 10,
|
|
37
|
+
rescale_volumes: bool = True,
|
|
38
|
+
lot_size: int = 1,
|
|
39
|
+
run_stylised_facts: bool | None = None,
|
|
40
|
+
) -> dict:
|
|
41
|
+
"""Submit a validation job.
|
|
42
|
+
|
|
43
|
+
Compares simulation output against historical market data using
|
|
44
|
+
distributional distance metrics (L1, Wasserstein), impact response
|
|
45
|
+
curves, and FID scores.
|
|
46
|
+
|
|
47
|
+
Historical data is fetched automatically from GCS based on symbol and date.
|
|
48
|
+
Simulation data is fetched from each sim_id's sim_data.parquet in GCS.
|
|
49
|
+
|
|
50
|
+
Parameters
|
|
51
|
+
----------
|
|
52
|
+
symbol : str
|
|
53
|
+
Trading symbol (e.g. "700.HK")
|
|
54
|
+
date : str
|
|
55
|
+
Calibration date in YYYY-MM-DD format (e.g. "2025-09-01")
|
|
56
|
+
sim_ids : list[str]
|
|
57
|
+
List of simulation IDs to validate (max 25)
|
|
58
|
+
ticksize : float, default 1.0
|
|
59
|
+
Tick size for the symbol
|
|
60
|
+
run_metrics : bool, default True
|
|
61
|
+
Compute L1/Wasserstein distributional distances
|
|
62
|
+
run_impact : bool, default False
|
|
63
|
+
Compute impact response curves
|
|
64
|
+
run_fid : bool, default False
|
|
65
|
+
Compute Frechet Inception Distance
|
|
66
|
+
run_stylised_facts : bool, optional
|
|
67
|
+
Compute stylised facts (autocorrelation, heavy
|
|
68
|
+
tails, volatility clustering). Left unset it is omitted from the
|
|
69
|
+
request, so the API applies your tier's default — demo accounts
|
|
70
|
+
get them, pro accounts do not.
|
|
71
|
+
n_levels : int, default 10
|
|
72
|
+
Number of L2 book levels to use
|
|
73
|
+
rescale_volumes : bool, default True
|
|
74
|
+
Multiply simulated L2 size columns by lot_size
|
|
75
|
+
lot_size : int, default 1
|
|
76
|
+
Lot size multiplier for volume rescaling
|
|
77
|
+
|
|
78
|
+
Returns
|
|
79
|
+
-------
|
|
80
|
+
dict with job_id, status, message
|
|
81
|
+
"""
|
|
82
|
+
# run_metrics/run_impact/run_fid keep sending their long-standing values
|
|
83
|
+
# so existing callers see no change. run_stylised_facts is omitted when
|
|
84
|
+
# unset, letting the API apply the tier default; sending False would opt
|
|
85
|
+
# demo accounts out of results that tier is meant to return.
|
|
86
|
+
config = {
|
|
87
|
+
"run_metrics": run_metrics,
|
|
88
|
+
"run_impact": run_impact,
|
|
89
|
+
"run_fid": run_fid,
|
|
90
|
+
"n_levels": n_levels,
|
|
91
|
+
"rescale_volumes": rescale_volumes,
|
|
92
|
+
"lot_size": lot_size,
|
|
93
|
+
}
|
|
94
|
+
if run_stylised_facts is not None:
|
|
95
|
+
config["run_stylised_facts"] = run_stylised_facts
|
|
96
|
+
|
|
97
|
+
payload = {
|
|
98
|
+
"symbol": symbol,
|
|
99
|
+
"date": date,
|
|
100
|
+
"sim_ids": sim_ids,
|
|
101
|
+
"ticksize": ticksize,
|
|
102
|
+
"config": config,
|
|
103
|
+
}
|
|
104
|
+
return self._client._request("POST", RUN_PATH, json=payload)
|
|
105
|
+
|
|
106
|
+
def get_job(self, job_id: str) -> dict:
|
|
107
|
+
"""Get validation job status and results.
|
|
108
|
+
|
|
109
|
+
Parameters
|
|
110
|
+
----------
|
|
111
|
+
job_id : str
|
|
112
|
+
The job ID returned by run()
|
|
113
|
+
|
|
114
|
+
Returns
|
|
115
|
+
-------
|
|
116
|
+
dict with
|
|
117
|
+
- status: "pending", "running", "completed", or "failed"
|
|
118
|
+
- distances: dict of {metric: {l1: [...], w: [...]}} (when completed)
|
|
119
|
+
- metadata: dict with run parameters, including which passes ran
|
|
120
|
+
- error: error message (when failed)
|
|
121
|
+
|
|
122
|
+
Demo-tier accounts additionally receive the numbers derived from the
|
|
123
|
+
historical data, which are withheld at the pro tier:
|
|
124
|
+
|
|
125
|
+
- distributions: per-metric historical vs simulated histograms
|
|
126
|
+
- impact_response: impact response curves as numbers, historical and
|
|
127
|
+
one block per sim run
|
|
128
|
+
- stylised_facts: historical and one block per sim run
|
|
129
|
+
- fid_scores: one score per sim run (None where not computable)
|
|
130
|
+
"""
|
|
131
|
+
return self._client._request("GET", f"{JOBS_PATH}/{job_id}")
|
|
132
|
+
|
|
133
|
+
def list_jobs(self, limit: int = 50) -> dict:
|
|
134
|
+
"""List validation jobs for the current user.
|
|
135
|
+
|
|
136
|
+
Parameters
|
|
137
|
+
----------
|
|
138
|
+
limit : int, default 50
|
|
139
|
+
Max number of jobs to return (default 50, max 200)
|
|
140
|
+
|
|
141
|
+
Returns
|
|
142
|
+
-------
|
|
143
|
+
dict with jobs list and total count
|
|
144
|
+
"""
|
|
145
|
+
return self._client._request("GET", JOBS_PATH, params={"limit": limit})
|
|
146
|
+
|
|
147
|
+
def run_pipeline(
|
|
148
|
+
self,
|
|
149
|
+
symbol: str,
|
|
150
|
+
date: str,
|
|
151
|
+
sim_ids: list[str],
|
|
152
|
+
ticksize: float = 1.0,
|
|
153
|
+
run_metrics: bool = True,
|
|
154
|
+
run_impact: bool = False,
|
|
155
|
+
run_fid: bool = False,
|
|
156
|
+
n_levels: int = 10,
|
|
157
|
+
rescale_volumes: bool = True,
|
|
158
|
+
lot_size: int = 1,
|
|
159
|
+
poll_interval: float = 3.0,
|
|
160
|
+
timeout: float = 600.0,
|
|
161
|
+
run_stylised_facts: bool | None = None,
|
|
162
|
+
) -> dict:
|
|
163
|
+
"""Submit a validation job and block until it completes.
|
|
164
|
+
|
|
165
|
+
Combines run() + polling get_job() into a single call.
|
|
166
|
+
Prints progress to stderr.
|
|
167
|
+
|
|
168
|
+
Parameters
|
|
169
|
+
----------
|
|
170
|
+
symbol : str
|
|
171
|
+
Trading symbol (e.g. "700.HK")
|
|
172
|
+
date : str
|
|
173
|
+
Calibration date in YYYY-MM-DD format
|
|
174
|
+
sim_ids : list[str]
|
|
175
|
+
List of simulation IDs to validate (max 25)
|
|
176
|
+
ticksize : float, default 1.0
|
|
177
|
+
Tick size for the symbol
|
|
178
|
+
run_metrics : bool, default True
|
|
179
|
+
Compute L1/Wasserstein distributional distances
|
|
180
|
+
run_impact : bool, default False
|
|
181
|
+
Compute impact response curves
|
|
182
|
+
run_fid : bool, default False
|
|
183
|
+
Compute Frechet Inception Distance
|
|
184
|
+
run_stylised_facts : bool, optional
|
|
185
|
+
Compute stylised facts (autocorrelation, heavy
|
|
186
|
+
tails, volatility clustering). Left unset it is omitted from the
|
|
187
|
+
request, so the API applies your tier's default — demo accounts
|
|
188
|
+
get them, pro accounts do not.
|
|
189
|
+
n_levels : int, default 10
|
|
190
|
+
Number of L2 book levels to use
|
|
191
|
+
rescale_volumes : bool, default True
|
|
192
|
+
Multiply simulated L2 size columns by lot_size
|
|
193
|
+
lot_size : int, default 1
|
|
194
|
+
Lot size multiplier for volume rescaling
|
|
195
|
+
poll_interval : float, default 3.0
|
|
196
|
+
Seconds between status checks (default 3)
|
|
197
|
+
timeout : float, default 600.0
|
|
198
|
+
Max seconds to wait (default 600)
|
|
199
|
+
|
|
200
|
+
Returns
|
|
201
|
+
-------
|
|
202
|
+
dict with full validation results (distances, plots, metadata)
|
|
203
|
+
|
|
204
|
+
Raises
|
|
205
|
+
------
|
|
206
|
+
RuntimeError
|
|
207
|
+
If the validation job fails
|
|
208
|
+
TimeoutError
|
|
209
|
+
If the job doesn't complete within timeout
|
|
210
|
+
"""
|
|
211
|
+
import sys
|
|
212
|
+
|
|
213
|
+
job = self.run(
|
|
214
|
+
symbol=symbol,
|
|
215
|
+
date=date,
|
|
216
|
+
sim_ids=sim_ids,
|
|
217
|
+
ticksize=ticksize,
|
|
218
|
+
run_metrics=run_metrics,
|
|
219
|
+
run_impact=run_impact,
|
|
220
|
+
run_fid=run_fid,
|
|
221
|
+
run_stylised_facts=run_stylised_facts,
|
|
222
|
+
n_levels=n_levels,
|
|
223
|
+
rescale_volumes=rescale_volumes,
|
|
224
|
+
lot_size=lot_size,
|
|
225
|
+
)
|
|
226
|
+
job_id = job["job_id"]
|
|
227
|
+
print(f"Validation job submitted: {job_id}", file=sys.stderr)
|
|
228
|
+
|
|
229
|
+
start = time.time()
|
|
230
|
+
while True:
|
|
231
|
+
result = self.get_job(job_id)
|
|
232
|
+
status = result["status"]
|
|
233
|
+
|
|
234
|
+
if status == "completed":
|
|
235
|
+
elapsed = time.time() - start
|
|
236
|
+
print(f"Completed in {elapsed:.1f}s", file=sys.stderr)
|
|
237
|
+
return result
|
|
238
|
+
elif status == "failed":
|
|
239
|
+
raise RuntimeError(f"Validation failed: {result.get('error')}")
|
|
240
|
+
|
|
241
|
+
if time.time() - start > timeout:
|
|
242
|
+
raise TimeoutError(f"Validation job {job_id} timed out after {timeout}s")
|
|
243
|
+
|
|
244
|
+
time.sleep(poll_interval)
|
|
245
|
+
|
|
246
|
+
def display_plots(self, result: dict) -> "PlotDisplay":
|
|
247
|
+
"""Return a PlotDisplay object for displaying validation plots.
|
|
248
|
+
|
|
249
|
+
Usage:
|
|
250
|
+
plots = client.validation.display_plots(result)
|
|
251
|
+
plots.distributions() # show distribution histograms
|
|
252
|
+
plots.distances() # show spider plots
|
|
253
|
+
plots.impact_response() # show impact response plots
|
|
254
|
+
|
|
255
|
+
Parameters
|
|
256
|
+
----------
|
|
257
|
+
result : dict
|
|
258
|
+
The result dict from run_pipeline() or get_job()
|
|
259
|
+
"""
|
|
260
|
+
return PlotDisplay(result)
|
|
261
|
+
|
|
262
|
+
|
|
263
|
+
class PlotDisplay:
|
|
264
|
+
"""Displays categorized validation plots inline in Jupyter notebooks."""
|
|
265
|
+
|
|
266
|
+
def __init__(self, result: dict):
|
|
267
|
+
plots = result.get("plots") or {}
|
|
268
|
+
self._distributions = plots.get("distributions", [])
|
|
269
|
+
self._distances = plots.get("distances", [])
|
|
270
|
+
self._impact_response = plots.get("impact_response", [])
|
|
271
|
+
|
|
272
|
+
def _show(self, plot_list, title):
|
|
273
|
+
from IPython.display import display, Image
|
|
274
|
+
|
|
275
|
+
if not plot_list:
|
|
276
|
+
print(f"No {title} plots available")
|
|
277
|
+
return
|
|
278
|
+
|
|
279
|
+
for plot in plot_list:
|
|
280
|
+
print(f"\n--- {plot['name']} ---")
|
|
281
|
+
display(Image(data=base64.b64decode(plot["content_base64"])))
|
|
282
|
+
|
|
283
|
+
def distributions(self):
|
|
284
|
+
"""Display distribution histogram plots."""
|
|
285
|
+
self._show(self._distributions, "distribution")
|
|
286
|
+
|
|
287
|
+
def distances(self):
|
|
288
|
+
"""Display spider plots (L1 and Wasserstein distances)."""
|
|
289
|
+
self._show(self._distances, "distance")
|
|
290
|
+
|
|
291
|
+
def impact_response(self):
|
|
292
|
+
"""Display impact response plots."""
|
|
293
|
+
self._show(self._impact_response, "impact response")
|
|
294
|
+
|
|
295
|
+
def all(self):
|
|
296
|
+
"""Display all plots."""
|
|
297
|
+
self.distances()
|
|
298
|
+
self.distributions()
|
|
299
|
+
self.impact_response()
|
|
@@ -7,8 +7,6 @@ src/simudyne/exceptions.py
|
|
|
7
7
|
src/simudyne/resources/__init__.py
|
|
8
8
|
src/simudyne/resources/api_keys.py
|
|
9
9
|
src/simudyne/resources/data.py
|
|
10
|
-
src/simudyne/resources/fix.py
|
|
11
|
-
src/simudyne/resources/fm.py
|
|
12
10
|
src/simudyne/resources/historical.py
|
|
13
11
|
src/simudyne/resources/profile.py
|
|
14
12
|
src/simudyne/resources/simulation.py
|
|
@@ -19,6 +17,4 @@ src/simudyne_pulse.egg-info/SOURCES.txt
|
|
|
19
17
|
src/simudyne_pulse.egg-info/dependency_links.txt
|
|
20
18
|
src/simudyne_pulse.egg-info/requires.txt
|
|
21
19
|
src/simudyne_pulse.egg-info/top_level.txt
|
|
22
|
-
tests/
|
|
23
|
-
tests/test_simulator_gym.py
|
|
24
|
-
tests/test_validation.py
|
|
20
|
+
tests/test_simulator_gym.py
|
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
"""
|
|
2
|
-
FIX Resource for the Pulse SDK.
|
|
3
|
-
|
|
4
|
-
Pulse simulations can be consumed over the FIX protocol by a market-data
|
|
5
|
-
session instead of the REST download endpoints. The FIX connection itself is
|
|
6
|
-
provisioned per organisation (see the FIX docs page); this resource covers
|
|
7
|
-
what the SDK can usefully do about it — reporting what your account has run
|
|
8
|
-
over FIX.
|
|
9
|
-
"""
|
|
10
|
-
|
|
11
|
-
USAGE_PATH = "/fix/usage"
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class FixResource:
|
|
15
|
-
def __init__(self, client):
|
|
16
|
-
self._client = client
|
|
17
|
-
|
|
18
|
-
def usage(self) -> dict:
|
|
19
|
-
"""FIX simulation statistics: what was run over FIX, and how often.
|
|
20
|
-
|
|
21
|
-
Returns
|
|
22
|
-
-------
|
|
23
|
-
dict with ``total_runs``, ``simulated_seconds``, and ``runs`` — one
|
|
24
|
-
entry per distinct configuration, each with its ``symbol``,
|
|
25
|
-
``cal_date``, ``scenario`` and ``runs`` count.
|
|
26
|
-
|
|
27
|
-
Examples
|
|
28
|
-
--------
|
|
29
|
-
>>> usage = client.fix.usage()
|
|
30
|
-
>>> print(f"{usage['total_runs']} FIX runs")
|
|
31
|
-
"""
|
|
32
|
-
return self._client._request("GET", USAGE_PATH)
|