simudyne-pulse 0.6.0.dev1__py3-none-any.whl
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/__init__.py +3 -0
- simudyne/client.py +121 -0
- simudyne/exceptions.py +7 -0
- simudyne/resources/__init__.py +0 -0
- simudyne/resources/api_keys.py +12 -0
- simudyne/resources/data.py +9 -0
- simudyne/resources/historical.py +1 -0
- simudyne/resources/profile.py +25 -0
- simudyne/resources/simulation.py +710 -0
- simudyne/resources/simulator_gym.py +118 -0
- simudyne/resources/validation.py +234 -0
- simudyne_pulse-0.6.0.dev1.dist-info/METADATA +147 -0
- simudyne_pulse-0.6.0.dev1.dist-info/RECORD +16 -0
- simudyne_pulse-0.6.0.dev1.dist-info/WHEEL +5 -0
- simudyne_pulse-0.6.0.dev1.dist-info/licenses/LICENSE +21 -0
- simudyne_pulse-0.6.0.dev1.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
import websocket
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class GymSession:
|
|
7
|
+
"""A live connection to a Pulse simulator-gym environment session."""
|
|
8
|
+
|
|
9
|
+
def __init__(self, ws: websocket.WebSocket, session_id: str):
|
|
10
|
+
self._ws = ws
|
|
11
|
+
self.session_id = session_id
|
|
12
|
+
|
|
13
|
+
def reset(self, seed: int = None) -> dict:
|
|
14
|
+
"""Reset the environment and return the initial observation.
|
|
15
|
+
|
|
16
|
+
Args:
|
|
17
|
+
seed: Optional random seed for reproducibility.
|
|
18
|
+
|
|
19
|
+
Returns:
|
|
20
|
+
dict with keys: type, obs (47 floats), reward, done, info
|
|
21
|
+
"""
|
|
22
|
+
self._ws.send(json.dumps({"type": "reset", "seed": seed}))
|
|
23
|
+
msg = json.loads(self._ws.recv())
|
|
24
|
+
if msg["type"] == "error":
|
|
25
|
+
raise RuntimeError(f"Simulator error: {msg['message']}")
|
|
26
|
+
return msg
|
|
27
|
+
|
|
28
|
+
def step(self, action: int) -> dict:
|
|
29
|
+
"""Take one step in the environment.
|
|
30
|
+
|
|
31
|
+
Args:
|
|
32
|
+
action: Integer action in range 0-13.
|
|
33
|
+
|
|
34
|
+
Returns:
|
|
35
|
+
dict with keys: type, obs (47 floats), reward, done, info
|
|
36
|
+
"""
|
|
37
|
+
self._ws.send(json.dumps({"type": "step", "action": action}))
|
|
38
|
+
msg = json.loads(self._ws.recv())
|
|
39
|
+
if msg["type"] == "error":
|
|
40
|
+
raise RuntimeError(f"Simulator error: {msg['message']}")
|
|
41
|
+
return msg
|
|
42
|
+
|
|
43
|
+
def close(self):
|
|
44
|
+
"""Close the session and the underlying WebSocket connection."""
|
|
45
|
+
try:
|
|
46
|
+
self._ws.send(json.dumps({"type": "close"}))
|
|
47
|
+
self._ws.recv()
|
|
48
|
+
finally:
|
|
49
|
+
self._ws.close()
|
|
50
|
+
|
|
51
|
+
def __enter__(self):
|
|
52
|
+
return self
|
|
53
|
+
|
|
54
|
+
def __exit__(self, *args):
|
|
55
|
+
self.close()
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
class SimulatorGymResource:
|
|
59
|
+
def __init__(self, client):
|
|
60
|
+
self._client = client
|
|
61
|
+
|
|
62
|
+
def _ws_base_url(self) -> str:
|
|
63
|
+
url = self._client.base_url
|
|
64
|
+
if url.startswith("https://"):
|
|
65
|
+
return "wss://" + url[len("https://"):]
|
|
66
|
+
if url.startswith("http://"):
|
|
67
|
+
return "ws://" + url[len("http://"):]
|
|
68
|
+
return url
|
|
69
|
+
|
|
70
|
+
def connect(self, symbol: str, cal_date: str, exchange: str) -> GymSession:
|
|
71
|
+
"""Open a new simulator-gym session.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
symbol: Trading symbol, e.g. "700.HK"
|
|
75
|
+
cal_date: Calibration date, e.g. "2025-09-02"
|
|
76
|
+
exchange: Exchange identifier, e.g. "HKEX.Securities"
|
|
77
|
+
|
|
78
|
+
Returns:
|
|
79
|
+
GymSession — use as a context manager:
|
|
80
|
+
|
|
81
|
+
with client.simulator_gym.connect("700.HK", "2025-09-02", "HKEX.Securities") as env:
|
|
82
|
+
obs = env.reset(seed=42)
|
|
83
|
+
result = env.step(0)
|
|
84
|
+
"""
|
|
85
|
+
ws_url = f"{self._ws_base_url()}/ws/simulator-gym"
|
|
86
|
+
try:
|
|
87
|
+
ws = websocket.create_connection(
|
|
88
|
+
ws_url,
|
|
89
|
+
header={"X-API-Key": self._client.api_key},
|
|
90
|
+
)
|
|
91
|
+
except websocket.WebSocketBadStatusException as e:
|
|
92
|
+
if "403" in str(e):
|
|
93
|
+
raise RuntimeError(
|
|
94
|
+
"Authentication failed: invalid or expired API key. "
|
|
95
|
+
"Check your API key with client.api_keys.list()."
|
|
96
|
+
) from e
|
|
97
|
+
raise RuntimeError(f"Failed to connect to simulator-gym: {e}") from e
|
|
98
|
+
except Exception as e:
|
|
99
|
+
raise RuntimeError(f"Failed to connect to simulator-gym: {e}") from e
|
|
100
|
+
|
|
101
|
+
ws.send(json.dumps({
|
|
102
|
+
"type": "create",
|
|
103
|
+
"config": {
|
|
104
|
+
"symbol": symbol,
|
|
105
|
+
"cal_date": cal_date,
|
|
106
|
+
"exchange": exchange,
|
|
107
|
+
}
|
|
108
|
+
}))
|
|
109
|
+
|
|
110
|
+
msg = json.loads(ws.recv())
|
|
111
|
+
if msg["type"] == "error":
|
|
112
|
+
ws.close()
|
|
113
|
+
raise RuntimeError(f"Failed to create session: {msg['message']}")
|
|
114
|
+
if msg["type"] != "created":
|
|
115
|
+
ws.close()
|
|
116
|
+
raise RuntimeError(f"Unexpected response type '{msg['type']}' during session creation")
|
|
117
|
+
|
|
118
|
+
return GymSession(ws, session_id=msg["session_id"])
|
|
@@ -0,0 +1,234 @@
|
|
|
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
|
+
) -> dict:
|
|
40
|
+
"""Submit a validation job.
|
|
41
|
+
|
|
42
|
+
Compares simulation output against historical market data using
|
|
43
|
+
distributional distance metrics (L1, Wasserstein), impact response
|
|
44
|
+
curves, and FID scores.
|
|
45
|
+
|
|
46
|
+
Historical data is fetched automatically from GCS based on symbol and date.
|
|
47
|
+
Simulation data is fetched from each sim_id's sim_data.parquet in GCS.
|
|
48
|
+
|
|
49
|
+
Args:
|
|
50
|
+
symbol: Trading symbol (e.g. "700.HK")
|
|
51
|
+
date: Calibration date in YYYY-MM-DD format (e.g. "2025-09-01")
|
|
52
|
+
sim_ids: List of simulation IDs to validate (max 25)
|
|
53
|
+
ticksize: Tick size for the symbol
|
|
54
|
+
run_metrics: Compute L1/Wasserstein distributional distances
|
|
55
|
+
run_impact: Compute impact response curves
|
|
56
|
+
run_fid: Compute Frechet Inception Distance
|
|
57
|
+
n_levels: Number of L2 book levels to use
|
|
58
|
+
rescale_volumes: Multiply simulated L2 size columns by lot_size
|
|
59
|
+
lot_size: Lot size multiplier for volume rescaling
|
|
60
|
+
|
|
61
|
+
Returns:
|
|
62
|
+
dict with job_id, status, message
|
|
63
|
+
"""
|
|
64
|
+
payload = {
|
|
65
|
+
"symbol": symbol,
|
|
66
|
+
"date": date,
|
|
67
|
+
"sim_ids": sim_ids,
|
|
68
|
+
"ticksize": ticksize,
|
|
69
|
+
"config": {
|
|
70
|
+
"run_metrics": run_metrics,
|
|
71
|
+
"run_impact": run_impact,
|
|
72
|
+
"run_fid": run_fid,
|
|
73
|
+
"n_levels": n_levels,
|
|
74
|
+
"rescale_volumes": rescale_volumes,
|
|
75
|
+
"lot_size": lot_size,
|
|
76
|
+
},
|
|
77
|
+
}
|
|
78
|
+
return self._client._request("POST", RUN_PATH, json=payload)
|
|
79
|
+
|
|
80
|
+
def get_job(self, job_id: str) -> dict:
|
|
81
|
+
"""Get validation job status and results.
|
|
82
|
+
|
|
83
|
+
Args:
|
|
84
|
+
job_id: The job ID returned by run()
|
|
85
|
+
|
|
86
|
+
Returns:
|
|
87
|
+
dict with:
|
|
88
|
+
- status: "pending", "running", "completed", or "failed"
|
|
89
|
+
- distances: dict of {metric: {l1: [...], w: [...]}} (when completed)
|
|
90
|
+
- fid_scores: list of floats (when completed and run_fid=True)
|
|
91
|
+
- plots: list of {name, content_base64} (when completed)
|
|
92
|
+
- metadata: dict with run parameters
|
|
93
|
+
- error: error message (when failed)
|
|
94
|
+
"""
|
|
95
|
+
return self._client._request("GET", f"{JOBS_PATH}/{job_id}")
|
|
96
|
+
|
|
97
|
+
def list_jobs(self, limit: int = 50) -> dict:
|
|
98
|
+
"""List validation jobs for the current user.
|
|
99
|
+
|
|
100
|
+
Args:
|
|
101
|
+
limit: Max number of jobs to return (default 50, max 200)
|
|
102
|
+
|
|
103
|
+
Returns:
|
|
104
|
+
dict with jobs list and total count
|
|
105
|
+
"""
|
|
106
|
+
return self._client._request("GET", JOBS_PATH, params={"limit": limit})
|
|
107
|
+
|
|
108
|
+
def run_pipeline(
|
|
109
|
+
self,
|
|
110
|
+
symbol: str,
|
|
111
|
+
date: str,
|
|
112
|
+
sim_ids: list[str],
|
|
113
|
+
ticksize: float = 1.0,
|
|
114
|
+
run_metrics: bool = True,
|
|
115
|
+
run_impact: bool = False,
|
|
116
|
+
run_fid: bool = False,
|
|
117
|
+
n_levels: int = 10,
|
|
118
|
+
rescale_volumes: bool = True,
|
|
119
|
+
lot_size: int = 1,
|
|
120
|
+
poll_interval: float = 3.0,
|
|
121
|
+
timeout: float = 600.0,
|
|
122
|
+
) -> dict:
|
|
123
|
+
"""Submit a validation job and block until it completes.
|
|
124
|
+
|
|
125
|
+
Combines run() + polling get_job() into a single call.
|
|
126
|
+
Prints progress to stderr.
|
|
127
|
+
|
|
128
|
+
Args:
|
|
129
|
+
symbol: Trading symbol (e.g. "700.HK")
|
|
130
|
+
date: Calibration date in YYYY-MM-DD format
|
|
131
|
+
sim_ids: List of simulation IDs to validate (max 25)
|
|
132
|
+
ticksize: Tick size for the symbol
|
|
133
|
+
run_metrics: Compute L1/Wasserstein distributional distances
|
|
134
|
+
run_impact: Compute impact response curves
|
|
135
|
+
run_fid: Compute Frechet Inception Distance
|
|
136
|
+
n_levels: Number of L2 book levels to use
|
|
137
|
+
rescale_volumes: Multiply simulated L2 size columns by lot_size
|
|
138
|
+
lot_size: Lot size multiplier for volume rescaling
|
|
139
|
+
poll_interval: Seconds between status checks (default 3)
|
|
140
|
+
timeout: Max seconds to wait (default 600)
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
dict with full validation results (distances, plots, metadata)
|
|
144
|
+
|
|
145
|
+
Raises:
|
|
146
|
+
RuntimeError: If the validation job fails
|
|
147
|
+
TimeoutError: If the job doesn't complete within timeout
|
|
148
|
+
"""
|
|
149
|
+
import sys
|
|
150
|
+
|
|
151
|
+
job = self.run(
|
|
152
|
+
symbol=symbol,
|
|
153
|
+
date=date,
|
|
154
|
+
sim_ids=sim_ids,
|
|
155
|
+
ticksize=ticksize,
|
|
156
|
+
run_metrics=run_metrics,
|
|
157
|
+
run_impact=run_impact,
|
|
158
|
+
run_fid=run_fid,
|
|
159
|
+
n_levels=n_levels,
|
|
160
|
+
rescale_volumes=rescale_volumes,
|
|
161
|
+
lot_size=lot_size,
|
|
162
|
+
)
|
|
163
|
+
job_id = job["job_id"]
|
|
164
|
+
print(f"Validation job submitted: {job_id}", file=sys.stderr)
|
|
165
|
+
|
|
166
|
+
start = time.time()
|
|
167
|
+
while True:
|
|
168
|
+
result = self.get_job(job_id)
|
|
169
|
+
status = result["status"]
|
|
170
|
+
|
|
171
|
+
if status == "completed":
|
|
172
|
+
elapsed = time.time() - start
|
|
173
|
+
print(f"Completed in {elapsed:.1f}s", file=sys.stderr)
|
|
174
|
+
return result
|
|
175
|
+
elif status == "failed":
|
|
176
|
+
raise RuntimeError(f"Validation failed: {result.get('error')}")
|
|
177
|
+
|
|
178
|
+
if time.time() - start > timeout:
|
|
179
|
+
raise TimeoutError(f"Validation job {job_id} timed out after {timeout}s")
|
|
180
|
+
|
|
181
|
+
time.sleep(poll_interval)
|
|
182
|
+
|
|
183
|
+
def display_plots(self, result: dict) -> "PlotDisplay":
|
|
184
|
+
"""Return a PlotDisplay object for displaying validation plots.
|
|
185
|
+
|
|
186
|
+
Usage:
|
|
187
|
+
plots = client.validation.display_plots(result)
|
|
188
|
+
plots.distributions() # show distribution histograms
|
|
189
|
+
plots.distances() # show spider plots
|
|
190
|
+
plots.impact_response() # show impact response plots
|
|
191
|
+
|
|
192
|
+
Args:
|
|
193
|
+
result: The result dict from run_pipeline() or get_job()
|
|
194
|
+
"""
|
|
195
|
+
return PlotDisplay(result)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
class PlotDisplay:
|
|
199
|
+
"""Displays categorized validation plots inline in Jupyter notebooks."""
|
|
200
|
+
|
|
201
|
+
def __init__(self, result: dict):
|
|
202
|
+
plots = result.get("plots") or {}
|
|
203
|
+
self._distributions = plots.get("distributions", [])
|
|
204
|
+
self._distances = plots.get("distances", [])
|
|
205
|
+
self._impact_response = plots.get("impact_response", [])
|
|
206
|
+
|
|
207
|
+
def _show(self, plot_list, title):
|
|
208
|
+
from IPython.display import display, Image
|
|
209
|
+
|
|
210
|
+
if not plot_list:
|
|
211
|
+
print(f"No {title} plots available")
|
|
212
|
+
return
|
|
213
|
+
|
|
214
|
+
for plot in plot_list:
|
|
215
|
+
print(f"\n--- {plot['name']} ---")
|
|
216
|
+
display(Image(data=base64.b64decode(plot["content_base64"])))
|
|
217
|
+
|
|
218
|
+
def distributions(self):
|
|
219
|
+
"""Display distribution histogram plots."""
|
|
220
|
+
self._show(self._distributions, "distribution")
|
|
221
|
+
|
|
222
|
+
def distances(self):
|
|
223
|
+
"""Display spider plots (L1 and Wasserstein distances)."""
|
|
224
|
+
self._show(self._distances, "distance")
|
|
225
|
+
|
|
226
|
+
def impact_response(self):
|
|
227
|
+
"""Display impact response plots."""
|
|
228
|
+
self._show(self._impact_response, "impact response")
|
|
229
|
+
|
|
230
|
+
def all(self):
|
|
231
|
+
"""Display all plots."""
|
|
232
|
+
self.distances()
|
|
233
|
+
self.distributions()
|
|
234
|
+
self.impact_response()
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: simudyne-pulse
|
|
3
|
+
Version: 0.6.0.dev1
|
|
4
|
+
Summary: Python SDK for the Simudyne Pulse synthetic market data API
|
|
5
|
+
Author-email: Simudyne <support@simudyne.com>
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Homepage, https://pulse.simudyne.com
|
|
8
|
+
Project-URL: Documentation, https://pulse.simudyne.com/docs
|
|
9
|
+
Project-URL: Repository, https://github.com/simudyne/pulse-api
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
15
|
+
Classifier: Operating System :: OS Independent
|
|
16
|
+
Requires-Python: >=3.10
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
License-File: LICENSE
|
|
19
|
+
Requires-Dist: requests>=2.28.0
|
|
20
|
+
Requires-Dist: polars>=0.20.0
|
|
21
|
+
Requires-Dist: tqdm>=4.60.0
|
|
22
|
+
Requires-Dist: websocket-client>=1.0.0
|
|
23
|
+
Provides-Extra: test
|
|
24
|
+
Requires-Dist: pytest>=8.0.0; extra == "test"
|
|
25
|
+
Dynamic: license-file
|
|
26
|
+
|
|
27
|
+
# Simudyne Pulse Python SDK
|
|
28
|
+
|
|
29
|
+
Python client for the [Pulse](https://pulse.simudyne.com) synthetic market data API. Returns data as [Polars](https://pola.rs/) DataFrames.
|
|
30
|
+
|
|
31
|
+
## Installation
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pip install simudyne-pulse
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
The distribution is named `simudyne-pulse`; the import name is `simudyne`:
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
from simudyne import PulseABM
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Requires Python 3.10+.
|
|
44
|
+
|
|
45
|
+
### Development builds
|
|
46
|
+
|
|
47
|
+
The `dev` branch is a prerelease channel. Pushes to it publish prerelease
|
|
48
|
+
versions (e.g. `0.6.0.dev1`) that are separate from the stable versions cut on
|
|
49
|
+
`prod`. `pip install simudyne-pulse` always resolves to the latest **stable**
|
|
50
|
+
release and ignores prereleases, so dev builds can never affect a normal
|
|
51
|
+
install.
|
|
52
|
+
|
|
53
|
+
To install the latest dev build, opt in with `--pre`:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install --pre simudyne-pulse
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
Only use dev builds for testing unreleased changes; they are not guaranteed
|
|
60
|
+
stable. Merge `dev` into `main` to promote those changes to a stable release.
|
|
61
|
+
|
|
62
|
+
## Quick start
|
|
63
|
+
|
|
64
|
+
```python
|
|
65
|
+
from simudyne import PulseABM
|
|
66
|
+
|
|
67
|
+
client = PulseABM(api_key="pk_live_...")
|
|
68
|
+
|
|
69
|
+
# List available exchanges, symbols, and dates
|
|
70
|
+
symbols = client.data.get_symbols(year=2024)
|
|
71
|
+
print(symbols)
|
|
72
|
+
|
|
73
|
+
# Fetch L2 order book data
|
|
74
|
+
df = client.data.get_L2("HKEX", "HSIJ4", "2024-04-02T09:15:00", "2024-04-02T09:16:00")
|
|
75
|
+
print(df.head())
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## API reference
|
|
79
|
+
|
|
80
|
+
### `PulseABM(api_key, base_url=None)`
|
|
81
|
+
|
|
82
|
+
| Parameter | Env variable | Default |
|
|
83
|
+
|-----------|-------------|---------|
|
|
84
|
+
| `api_key` | `SIMUDYNE_API_KEY` | required |
|
|
85
|
+
| `base_url` | `SIMUDYNE_BASE_URL` | Pulse API |
|
|
86
|
+
|
|
87
|
+
### `client.data`
|
|
88
|
+
|
|
89
|
+
All data methods return Polars DataFrames. Large result sets are automatically paginated.
|
|
90
|
+
|
|
91
|
+
```python
|
|
92
|
+
# Available exchanges, symbols, and dates
|
|
93
|
+
client.data.get_symbols(year=2024)
|
|
94
|
+
|
|
95
|
+
# L1: top of book (best bid/ask)
|
|
96
|
+
client.data.get_L1("HKEX", "HSIJ4", "2024-04-02T09:15:00", "2024-04-02T10:00:00")
|
|
97
|
+
|
|
98
|
+
# L2: full order book (all levels)
|
|
99
|
+
client.data.get_L2("HKEX", "HSIJ4", "2024-04-02T09:15:00", "2024-04-02T09:16:00")
|
|
100
|
+
|
|
101
|
+
# Orders: individual order events
|
|
102
|
+
client.data.get_orders("HKEX", "HSIJ4", "2024-04-02T09:15:00", "2024-04-02T10:00:00")
|
|
103
|
+
|
|
104
|
+
# Trades: executed trades
|
|
105
|
+
client.data.get_trades("HKEX", "HSIJ4", "2024-04-02T09:15:00", "2024-04-02T10:00:00")
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
**Parameters** (same for all data methods):
|
|
109
|
+
|
|
110
|
+
| Parameter | Type | Description |
|
|
111
|
+
|-----------|------|-------------|
|
|
112
|
+
| `exchange` | str | Exchange code (e.g. `HKEX`) |
|
|
113
|
+
| `sym` | str | Symbol name (e.g. `HSIJ4`) |
|
|
114
|
+
| `datetime_start` | str | Start time, ISO 8601 (e.g. `2024-04-02T09:15:00`) |
|
|
115
|
+
| `datetime_end` | str | End time, ISO 8601 |
|
|
116
|
+
|
|
117
|
+
### `client.profile`
|
|
118
|
+
|
|
119
|
+
```python
|
|
120
|
+
client.profile.get() # Account info
|
|
121
|
+
client.profile.usage() # API usage stats
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### `client.api_keys`
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
client.api_keys.list() # List active keys
|
|
128
|
+
client.api_keys.create(name="research") # Create a new key
|
|
129
|
+
client.api_keys.revoke(key_id="key_...") # Revoke a key
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
## Configuration
|
|
133
|
+
|
|
134
|
+
You can set your API key as an environment variable instead of passing it directly:
|
|
135
|
+
|
|
136
|
+
```bash
|
|
137
|
+
export SIMUDYNE_API_KEY=pk_live_...
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
```python
|
|
141
|
+
from simudyne import PulseABM
|
|
142
|
+
client = PulseABM() # picks up SIMUDYNE_API_KEY automatically
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
## License
|
|
146
|
+
|
|
147
|
+
MIT
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
simudyne/__init__.py,sha256=m4FqChvvnm2ESEG1imAqGj7u-ksFxY6gPgRlyHFlL1c,92
|
|
2
|
+
simudyne/client.py,sha256=7EHrhp1sdJVPn1EEmuqQnN-i82__gZ9CZ64cs5Cp_3U,4606
|
|
3
|
+
simudyne/exceptions.py,sha256=ZfMiN4TTIMvqQ4EVfJ_4aNe3OvtZlzpWSyvfkuE5neo,270
|
|
4
|
+
simudyne/resources/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
simudyne/resources/api_keys.py,sha256=Pu2srG6ymsf0Ax1VbeA3X0Ofs0pq8j6b42iQ81T0iUE,393
|
|
6
|
+
simudyne/resources/data.py,sha256=_KH40tvLuMESJuH4t7ign7FKr2WcuObLRAF2bPpZbgg,241
|
|
7
|
+
simudyne/resources/historical.py,sha256=-PkcCd7KRZyO2uG3GMpWgZjwmnmZL42-_4I_mJSbrww,76
|
|
8
|
+
simudyne/resources/profile.py,sha256=XYMdRoF1s2NXvHdIBay0M3gkeuKf7P7YSK1XffoE1JM,1028
|
|
9
|
+
simudyne/resources/simulation.py,sha256=xRc9PBmrh4OE5qh8pzs9WebOw2Utfx8uaMYOJ-IeNIg,28000
|
|
10
|
+
simudyne/resources/simulator_gym.py,sha256=MWo7xv5fVFYpTAKU3sJVyHoDZvA8P0dDgSxgCindCMk,3873
|
|
11
|
+
simudyne/resources/validation.py,sha256=V8-IFEKa8s0mlqHzT1kQ1j_OKKLFcM9RGx53p7a65uQ,8025
|
|
12
|
+
simudyne_pulse-0.6.0.dev1.dist-info/licenses/LICENSE,sha256=H_hgosvz8TIG6NAgsaluNzCBOe3lHZddEWwyJM9PTWs,1069
|
|
13
|
+
simudyne_pulse-0.6.0.dev1.dist-info/METADATA,sha256=fCIMn2TD-pvUseI2f3kII3qBnJLRxtppUiGRSFTt344,4141
|
|
14
|
+
simudyne_pulse-0.6.0.dev1.dist-info/WHEEL,sha256=K260EYznzXsJYBQGqmI8VTxEdiZYNvDZwW9cBh9-_MA,91
|
|
15
|
+
simudyne_pulse-0.6.0.dev1.dist-info/top_level.txt,sha256=PT1iWu7CFbICTaYJ7vZSKdup-YPFtHV5j2y5LPe6UP0,9
|
|
16
|
+
simudyne_pulse-0.6.0.dev1.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 Simudyne Ltd
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
simudyne
|