cnhkmcp 1.3.6__py3-none-any.whl → 1.3.8__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.
Files changed (57) hide show
  1. cnhkmcp/__init__.py +1 -1
  2. cnhkmcp/untracked/APP/.gitignore +32 -0
  3. cnhkmcp/untracked/APP/MODULAR_STRUCTURE.md +123 -0
  4. cnhkmcp/untracked/APP/README.md +309 -0
  5. cnhkmcp/untracked/APP/__pycache__/app.cpython-313.pyc +0 -0
  6. cnhkmcp/untracked/APP/blueprints/__init__.py +5 -0
  7. cnhkmcp/untracked/APP/blueprints/__pycache__/__init__.cpython-313.pyc +0 -0
  8. cnhkmcp/untracked/APP/blueprints/__pycache__/feature_engineering.cpython-313.pyc +0 -0
  9. cnhkmcp/untracked/APP/blueprints/__pycache__/idea_house.cpython-313.pyc +0 -0
  10. cnhkmcp/untracked/APP/blueprints/__pycache__/inspiration_house.cpython-313.pyc +0 -0
  11. cnhkmcp/untracked/APP/blueprints/__pycache__/paper_analysis.cpython-313.pyc +0 -0
  12. cnhkmcp/untracked/APP/blueprints/__pycache__/simulator.cpython-313.pyc +0 -0
  13. cnhkmcp/untracked/APP/blueprints/__pycache__/unified_tools.cpython-313.pyc +0 -0
  14. cnhkmcp/untracked/APP/blueprints/__pycache__/wqb_simulator.cpython-313.pyc +0 -0
  15. cnhkmcp/untracked/APP/blueprints/feature_engineering.py +347 -0
  16. cnhkmcp/untracked/APP/blueprints/idea_house.py +221 -0
  17. cnhkmcp/untracked/APP/blueprints/inspiration_house.py +432 -0
  18. cnhkmcp/untracked/APP/blueprints/paper_analysis.py +570 -0
  19. cnhkmcp/untracked/APP/custom_templates/templates.json +4582 -0
  20. cnhkmcp/untracked/APP/hkSimulator/ace_lib.py +1476 -0
  21. cnhkmcp/untracked/APP/hkSimulator/autosimulator.py +378 -0
  22. cnhkmcp/untracked/APP/hkSimulator/helpful_functions.py +180 -0
  23. cnhkmcp/untracked/APP/mirror_config.txt +20 -0
  24. cnhkmcp/untracked/APP/operaters.csv +129 -0
  25. cnhkmcp/untracked/APP/requirements.txt +44 -0
  26. cnhkmcp/untracked/APP/run_app.bat +28 -0
  27. cnhkmcp/untracked/APP/run_app.sh +34 -0
  28. cnhkmcp/untracked/APP/setup_tsinghua.bat +39 -0
  29. cnhkmcp/untracked/APP/setup_tsinghua.sh +43 -0
  30. cnhkmcp/untracked/APP/simulator/__pycache__/simulator_wqb.cpython-313.pyc +0 -0
  31. cnhkmcp/untracked/APP/simulator/alpha_submitter.py +366 -0
  32. cnhkmcp/untracked/APP/simulator/simulator_wqb.py +602 -0
  33. cnhkmcp/untracked/APP/ssrn-3332513.pdf +109188 -19
  34. cnhkmcp/untracked/APP/static/brain.js +478 -0
  35. cnhkmcp/untracked/APP/static/decoder.js +1275 -0
  36. cnhkmcp/untracked/APP/static/feature_engineering.js +1729 -0
  37. cnhkmcp/untracked/APP/static/idea_house.js +937 -0
  38. cnhkmcp/untracked/APP/static/inspiration_house.js +868 -0
  39. cnhkmcp/untracked/APP/static/paper_analysis.js +390 -0
  40. cnhkmcp/untracked/APP/static/script.js +2577 -0
  41. cnhkmcp/untracked/APP/static/simulator.js +597 -0
  42. cnhkmcp/untracked/APP/static/styles.css +3099 -0
  43. cnhkmcp/untracked/APP/templates/feature_engineering.html +959 -0
  44. cnhkmcp/untracked/APP/templates/idea_house.html +563 -0
  45. cnhkmcp/untracked/APP/templates/index.html +769 -0
  46. cnhkmcp/untracked/APP/templates/inspiration_house.html +860 -0
  47. cnhkmcp/untracked/APP/templates/paper_analysis.html +90 -0
  48. cnhkmcp/untracked/APP/templates/simulator.html +342 -0
  49. cnhkmcp/untracked/APP//321/210/342/224/220/320/240/321/210/320/261/320/234/321/206/320/231/320/243/321/205/342/225/235/320/220/321/206/320/230/320/241.py +1489 -0
  50. {cnhkmcp-1.3.6.dist-info → cnhkmcp-1.3.8.dist-info}/METADATA +1 -1
  51. cnhkmcp-1.3.8.dist-info/RECORD +67 -0
  52. cnhkmcp/untracked/APP.zip +0 -0
  53. cnhkmcp-1.3.6.dist-info/RECORD +0 -20
  54. {cnhkmcp-1.3.6.dist-info → cnhkmcp-1.3.8.dist-info}/WHEEL +0 -0
  55. {cnhkmcp-1.3.6.dist-info → cnhkmcp-1.3.8.dist-info}/entry_points.txt +0 -0
  56. {cnhkmcp-1.3.6.dist-info → cnhkmcp-1.3.8.dist-info}/licenses/LICENSE +0 -0
  57. {cnhkmcp-1.3.6.dist-info → cnhkmcp-1.3.8.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,378 @@
1
+ import sys
2
+ import subprocess
3
+ # --- Dependency Check & Auto-Install ---
4
+ required_imports = [
5
+ ("getpass", None),
6
+ ("json", None),
7
+ ("logging", None),
8
+ ("os", None),
9
+ ("threading", None),
10
+ ("time", None),
11
+ ("functools", None),
12
+ ("multiprocessing", None),
13
+ ("pathlib", None),
14
+ ("typing", None),
15
+ ("urllib.parse", None),
16
+ ("pandas", "pandas"),
17
+ ("requests", "requests"),
18
+ ("tqdm", "tqdm"),
19
+ ("pandas.io.formats.style", "pandas"),
20
+ ]
21
+ for mod, pipname in required_imports:
22
+ try:
23
+ if "." in mod:
24
+ __import__(mod.split(".")[0])
25
+ else:
26
+ __import__(mod)
27
+ except ImportError:
28
+ if pipname:
29
+ print(f"Installing missing package: {pipname}")
30
+ subprocess.check_call([sys.executable, "-m", "pip", "install", pipname])
31
+ else:
32
+ print(f"Module {mod} is a built-in or not installable via pip.")
33
+ # --- Script Description ---
34
+ """
35
+ Autosimulator for WorldQuant BRAIN platform
36
+ - Timestamped logger
37
+ - Authentication with biometric check
38
+ - User-specified alpha JSON input
39
+ - Single/multi simulation mode
40
+ - Simulation worker: sends jobs, retries, saves locations
41
+ - Result worker: fetches results, saves to JSON
42
+ """
43
+ import os
44
+ import sys
45
+ import time
46
+ import json
47
+ import threading
48
+ import logging
49
+ from datetime import datetime
50
+ from pathlib import Path
51
+ import msvcrt
52
+ import requests
53
+ from ace_lib import (
54
+ check_session_and_relogin,
55
+ simulate_single_alpha,
56
+ simulate_multi_alpha,
57
+ )
58
+
59
+ # --- Logger Setup ---
60
+ def setup_logger():
61
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
62
+ log_filename = f'autosim_{timestamp}.log'
63
+ logger = logging.getLogger(f'autosim_{timestamp}')
64
+ logger.setLevel(logging.DEBUG)
65
+ formatter = logging.Formatter('%(asctime)s - %(levelname)s - %(message)s')
66
+ fh = logging.FileHandler(log_filename)
67
+ fh.setFormatter(formatter)
68
+ logger.addHandler(fh)
69
+ ch = logging.StreamHandler()
70
+ ch.setFormatter(formatter)
71
+ logger.addHandler(ch)
72
+ return logger, log_filename
73
+
74
+ logger, log_filename = setup_logger()
75
+
76
+ # --- Authentication ---
77
+ def check_session_timeout(s):
78
+ """
79
+ Check if the current session has timed out.
80
+
81
+ Args:
82
+ s (SingleSession): The current session object.
83
+
84
+ Returns:
85
+ int: The number of seconds until the session expires, or 0 if the session has expired or an error occurred.
86
+ """
87
+ brain_api_url = os.environ.get("BRAIN_API_URL", "https://api.worldquantbrain.com")
88
+ authentication_url = brain_api_url + "/authentication"
89
+ try:
90
+ result = s.get(authentication_url).json()["token"]["expiry"]
91
+ logger.debug(f"Session (ID: {id(s)}) timeout check result: {result}")
92
+ return result
93
+ except Exception:
94
+ logger.error(f"Session timeout check failed for session (ID: {id(s)})")
95
+ return 0
96
+ def get_credentials():
97
+ email = input("Email: ").strip()
98
+ print("Password: ", end='', flush=True)
99
+ password = ''
100
+ while True:
101
+ ch = msvcrt.getch()
102
+ if ch in (b'\r', b'\n'):
103
+ print()
104
+ break
105
+ elif ch == b'\x08': # Backspace
106
+ if len(password) > 0:
107
+ password = password[:-1]
108
+ print('\b \b', end='', flush=True)
109
+ elif ch == b'\x03': # Ctrl+C
110
+ raise KeyboardInterrupt
111
+ else:
112
+ password += ch.decode('utf-8', errors='ignore')
113
+ print('*', end='', flush=True)
114
+ return (email, password)
115
+
116
+ def authenticate():
117
+ from ace_lib import SingleSession
118
+ session = SingleSession()
119
+ session.auth = get_credentials()
120
+ brain_api_url = os.environ.get("BRAIN_API_URL", "https://api.worldquantbrain.com")
121
+ r = session.post(brain_api_url + "/authentication")
122
+ logger.debug(f"New session created (ID: {id(session)}) with authentication response: {r.status_code}, {r.json()}")
123
+ if r.status_code == requests.status_codes.codes.unauthorized:
124
+ if r.headers.get("WWW-Authenticate") == "persona":
125
+ print(
126
+ "Complete biometrics authentication and press any key to continue: \n"
127
+ + r.url + "/persona?inquiry=" + r.headers.get("Location", "")
128
+ + "\n"
129
+ )
130
+ input()
131
+ session.post(r.headers.get("Location", r.url))
132
+ while True:
133
+ if session.post(r.headers.get("Location", r.url)).status_code != 201:
134
+ input(
135
+ "Biometrics authentication is not complete. Please try again and press any key when completed \n"
136
+ )
137
+ else:
138
+ break
139
+ else:
140
+ logger.error("\nIncorrect email or password\n")
141
+ return authenticate()
142
+ return session
143
+
144
+ # --- User Input ---
145
+ MASTER_LOG_PATH = "autosim_master_log.json"
146
+
147
+ def update_master_log(input_json_path, latest_index):
148
+ """
149
+ Update the master log file with the latest successful index for the given input file name.
150
+ """
151
+ import os, json
152
+ file_name = os.path.basename(input_json_path)
153
+ log_data = {}
154
+ # Read existing log if present
155
+ if os.path.exists(MASTER_LOG_PATH):
156
+ try:
157
+ with open(MASTER_LOG_PATH, "r", encoding="utf-8") as f:
158
+ log_data = json.load(f)
159
+ except Exception:
160
+ log_data = {}
161
+ # Update with latest index
162
+ log_data[file_name] = latest_index
163
+ # Atomic write
164
+ tmp_path = MASTER_LOG_PATH + ".tmp"
165
+ with open(tmp_path, "w", encoding="utf-8") as f:
166
+ json.dump(log_data, f, indent=2)
167
+ os.replace(tmp_path, MASTER_LOG_PATH)
168
+ def get_user_json():
169
+ import re
170
+ while True:
171
+ raw_path = input('Enter path to alpha JSON file: ').strip()
172
+ json_path = re.sub(r'^["\']+|["\']+$', '', raw_path.strip())
173
+ if os.path.exists(json_path):
174
+ try:
175
+ with open(json_path, 'r') as f:
176
+ alpha_list = json.load(f)
177
+ # Check master log for previous progress
178
+ file_name = os.path.basename(json_path)
179
+ start_index = 0
180
+ if os.path.exists(MASTER_LOG_PATH):
181
+ try:
182
+ with open(MASTER_LOG_PATH, 'r', encoding='utf-8') as logf:
183
+ log_data = json.load(logf)
184
+ if file_name in log_data:
185
+ last_index = log_data[file_name]
186
+ print(f'Last time you simulated to position {last_index}.')
187
+ resp = input(f'Do you want to start from {last_index + 1}? (Y/n) Or enter another starting index: ').strip()
188
+ if resp.lower() in ['', 'y', 'yes']:
189
+ start_index = last_index + 1
190
+ elif resp.isdigit():
191
+ start_index = int(resp)
192
+ else:
193
+ print('Invalid input, starting from 0.')
194
+ start_index = 0
195
+ except Exception:
196
+ pass
197
+ # Slice alpha_list to start from chosen index
198
+ class AlphaList(list):
199
+ pass
200
+ alpha_list = AlphaList(alpha_list[start_index:])
201
+ alpha_list._start_index = start_index
202
+ return alpha_list, json_path
203
+ except Exception as e:
204
+ logger.error(f'Error reading JSON file: {e}')
205
+ else:
206
+ logger.error(f'JSON file not found: {json_path}')
207
+ print('Please enter a valid path to your alpha JSON file.')
208
+
209
+ def get_simulation_mode():
210
+ mode = input('Select simulation mode (single/multi): ').strip().lower()
211
+ if mode not in ['single', 'multi']:
212
+ logger.error('Invalid mode. Choose "single" or "multi".')
213
+ sys.exit(1)
214
+ batch_size = None
215
+ if mode == 'multi':
216
+ while True:
217
+ try:
218
+ batch_size = int(input('Enter number of elements per multi-simulation batch (2-10): ').strip())
219
+ if 2 <= batch_size <= 10:
220
+ break
221
+ else:
222
+ print('Batch size must be between 2 and 10.')
223
+ except Exception:
224
+ print('Please enter a valid integer between 2 and 10.')
225
+ return mode, batch_size
226
+
227
+ def get_retry_timeout():
228
+ try:
229
+ timeout = int(input('Enter retry timeout in seconds (default 60): ').strip())
230
+ if timeout < 1:
231
+ timeout = 60
232
+ except Exception:
233
+ timeout = 60
234
+ return timeout
235
+
236
+ # --- Simulation Worker ---
237
+ def simulation_worker(session, alpha_list, mode, json_path, location_path, retry_timeout, batch_size=None):
238
+ locations = {}
239
+ # Initialize sent_count from user starting index (passed via alpha_list attribute if set)
240
+ file_name = os.path.basename(json_path)
241
+ sent_count = getattr(alpha_list, '_start_index', 0)
242
+ while alpha_list:
243
+ # Check session timeout before proceeding
244
+ if check_session_timeout(session) == 0:
245
+ logger.error('Session expired. Stopping simulation worker.')
246
+ break
247
+ session = check_session_and_relogin(session)
248
+ # Prepare batch but do NOT pop yet
249
+ if mode == 'single':
250
+ batch = [alpha_list[0]]
251
+ else:
252
+ size = batch_size if batch_size else min(10, max(2, len(alpha_list)))
253
+ batch = [alpha_list[i] for i in range(min(size, len(alpha_list)))]
254
+ try:
255
+ from ace_lib import start_simulation
256
+ location = None
257
+ while location is None:
258
+ # Check session timeout before each send
259
+ if check_session_timeout(session) == 0:
260
+ logger.error('Session expired. Stopping simulation worker.')
261
+ return
262
+ if mode == 'single':
263
+ response = start_simulation(session, batch[0])
264
+ location = response.headers.get('Location')
265
+ else:
266
+ response = start_simulation(session, batch)
267
+ location = response.headers.get('Location')
268
+ if location is None:
269
+ logger.info(f'Simulation sent, location(s) saved: None')
270
+ logger.info(f'No location received, waiting {retry_timeout} seconds and retrying...')
271
+ time.sleep(retry_timeout)
272
+ # Only pop/remove after location is valid
273
+ if mode == 'single':
274
+ alpha_list.pop(0)
275
+ sent_count += 1
276
+ update_master_log(json_path, sent_count - 1)
277
+ else:
278
+ for _ in range(len(batch)):
279
+ alpha_list.pop(0)
280
+ sent_count += len(batch)
281
+ update_master_log(json_path, sent_count - 1)
282
+ locations[str(time.time())] = location
283
+ with open(location_path, 'w') as f:
284
+ json.dump(locations, f, indent=2)
285
+ # Do NOT overwrite the input JSON file
286
+ logger.info(f'Simulation sent, location(s) saved: {location}')
287
+ except Exception as e:
288
+ logger.error(f'Simulation error: {e}. Retrying in {retry_timeout} seconds.')
289
+ time.sleep(retry_timeout)
290
+
291
+ # --- Result Worker ---
292
+ def result_worker(session, location_path, result_path, poll_interval=30):
293
+ results = {}
294
+ from time import sleep
295
+ while True:
296
+ # Check session timeout before proceeding
297
+ if check_session_timeout(session) == 0:
298
+ logger.error('Session expired. Stopping result worker.')
299
+ break
300
+ session = check_session_and_relogin(session)
301
+ if not os.path.exists(location_path):
302
+ time.sleep(poll_interval)
303
+ continue
304
+ with open(location_path, 'r') as f:
305
+ locations = json.load(f)
306
+ for loc_key, loc_val in locations.items():
307
+ if loc_key in results:
308
+ continue
309
+ if not loc_val or not isinstance(loc_val, str) or not loc_val.startswith('http'):
310
+ logger.error(f'Invalid or missing location for key {loc_key}: {loc_val}')
311
+ continue
312
+ try:
313
+ # Check session timeout before each result fetch
314
+ if check_session_timeout(session) == 0:
315
+ logger.error('Session expired. Stopping result worker.')
316
+ return
317
+ simulation_progress_url = loc_val
318
+ while True:
319
+ simulation_progress = session.get(simulation_progress_url)
320
+ retry_after = simulation_progress.headers.get("Retry-After", 0)
321
+ if float(retry_after) == 0:
322
+ break
323
+ logger.info(f"Sleeping for {retry_after} seconds for location {simulation_progress_url}")
324
+ sleep(float(retry_after))
325
+ sim_json = simulation_progress.json()
326
+ # Multi-simulation: check for children
327
+ if "children" in sim_json and sim_json.get("status") == "COMPLETE":
328
+ child_results = {}
329
+ for child_id in sim_json["children"]:
330
+ child_url = f"https://api.worldquantbrain.com/simulations/{child_id}"
331
+ child_resp = session.get(child_url)
332
+ child_json = child_resp.json()
333
+ alpha_id = child_json.get("alpha")
334
+ if not alpha_id:
335
+ logger.error(f"No alpha_id found for child {child_id}")
336
+ child_results[child_id] = {"error": "No alpha_id found"}
337
+ else:
338
+ alpha = session.get(f"https://api.worldquantbrain.com/alphas/{alpha_id}")
339
+ child_results[child_id] = alpha.json()
340
+ results[loc_key] = {"multi_children": child_results}
341
+ logger.info(f"Multi-simulation results fetched for location {loc_val}")
342
+ else:
343
+ # Single simulation
344
+ alpha_id = sim_json.get("alpha")
345
+ if not alpha_id:
346
+ logger.error(f"No alpha_id found for location {simulation_progress_url}")
347
+ results[loc_key] = {"error": "No alpha_id found"}
348
+ else:
349
+ alpha = session.get(f"https://api.worldquantbrain.com/alphas/{alpha_id}")
350
+ results[loc_key] = alpha.json()
351
+ logger.info(f"Result fetched for location {loc_val}")
352
+ with open(result_path, 'w') as f:
353
+ json.dump(results, f, indent=2)
354
+ except Exception as e:
355
+ logger.error(f'Error fetching result for {loc_val}: {e}')
356
+ time.sleep(poll_interval)
357
+
358
+ # --- Main ---
359
+ def main():
360
+ session = authenticate()
361
+ alpha_list, json_path = get_user_json()
362
+ mode, batch_size = get_simulation_mode()
363
+ retry_timeout = get_retry_timeout()
364
+ timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
365
+ location_path = f'autosim_locations_{timestamp}.json'
366
+ result_path = f'autosim_results_{timestamp}.json'
367
+ # Start workers
368
+ sim_thread = threading.Thread(target=simulation_worker, args=(session, alpha_list, mode, json_path, location_path, retry_timeout, batch_size))
369
+ res_thread = threading.Thread(target=result_worker, args=(session, location_path, result_path))
370
+ sim_thread.start()
371
+ res_thread.start()
372
+ sim_thread.join()
373
+ # Result worker runs until all locations processed
374
+ logger.info('Simulation worker finished. Waiting for results...')
375
+ res_thread.join()
376
+
377
+ if __name__ == '__main__':
378
+ main()
@@ -0,0 +1,180 @@
1
+ import json
2
+ import os
3
+ from typing import Union
4
+
5
+ import pandas as pd
6
+ from pandas.io.formats.style import Styler
7
+
8
+ brain_api_url = os.environ.get("BRAIN_API_URL", "https://api.worldquantbrain.com")
9
+ brain_url = os.environ.get("BRAIN_URL", "https://platform.worldquantbrain.com")
10
+
11
+
12
+ def make_clickable_alpha_id(alpha_id: str) -> str:
13
+ """
14
+ Create a clickable HTML link for an alpha ID.
15
+
16
+ Args:
17
+ alpha_id (str): The ID of the alpha.
18
+
19
+ Returns:
20
+ str: An HTML string containing a clickable link to the alpha's page on the platform.
21
+ """
22
+
23
+ url = brain_url + "/alpha/"
24
+ return f'<a href="{url}{alpha_id}">{alpha_id}</a>'
25
+
26
+
27
+ def prettify_result(
28
+ result: list, detailed_tests_view: bool = False, clickable_alpha_id: bool = False
29
+ ) -> Union[pd.DataFrame, Styler]:
30
+ """
31
+ Combine and format simulation results into a single DataFrame for analysis.
32
+
33
+ Args:
34
+ result (list): A list of dictionaries containing simulation results.
35
+ detailed_tests_view (bool, optional): If True, include detailed test results. Defaults to False.
36
+ clickable_alpha_id (bool, optional): If True, make alpha IDs clickable. Defaults to False.
37
+
38
+ Returns:
39
+ pandas.DataFrame or pandas.io.formats.style.Styler: A DataFrame containing formatted results,
40
+ optionally with clickable alpha IDs.
41
+ """
42
+ list_of_is_stats = [result[x]["is_stats"] for x in range(len(result)) if result[x]["is_stats"] is not None]
43
+ is_stats_df = pd.concat(list_of_is_stats).reset_index(drop=True)
44
+ is_stats_df = is_stats_df.sort_values("fitness", ascending=False)
45
+
46
+ expressions = {
47
+ result[x]["alpha_id"]: (
48
+ {
49
+ "selection": result[x]["simulate_data"]["selection"],
50
+ "combo": result[x]["simulate_data"]["combo"],
51
+ }
52
+ if result[x]["simulate_data"]["type"] == "SUPER"
53
+ else result[x]["simulate_data"]["regular"]
54
+ )
55
+ for x in range(len(result))
56
+ if result[x]["is_stats"] is not None
57
+ }
58
+ expression_df = pd.DataFrame(list(expressions.items()), columns=["alpha_id", "expression"])
59
+
60
+ list_of_is_tests = [result[x]["is_tests"] for x in range(len(result)) if result[x]["is_tests"] is not None]
61
+ is_tests_df = pd.concat(list_of_is_tests, sort=True).reset_index(drop=True)
62
+ is_tests_df = is_tests_df[is_tests_df["result"] != "WARNING"]
63
+ if detailed_tests_view:
64
+ cols = ["limit", "result", "value"]
65
+ is_tests_df["details"] = is_tests_df[cols].to_dict(orient="records")
66
+ is_tests_df = is_tests_df.pivot(index="alpha_id", columns="name", values="details").reset_index()
67
+ else:
68
+ is_tests_df = is_tests_df.pivot(index="alpha_id", columns="name", values="result").reset_index()
69
+
70
+ alpha_stats = pd.merge(is_stats_df, expression_df, on="alpha_id")
71
+ alpha_stats = pd.merge(alpha_stats, is_tests_df, on="alpha_id")
72
+ alpha_stats = alpha_stats.drop(columns=alpha_stats.columns[(alpha_stats == "PENDING").any()])
73
+ alpha_stats.columns = alpha_stats.columns.str.replace("(?<=[a-z])(?=[A-Z])", "_", regex=True).str.lower()
74
+ if clickable_alpha_id:
75
+ return alpha_stats.style.format({"alpha_id": lambda x: make_clickable_alpha_id(str(x))})
76
+ return alpha_stats
77
+
78
+
79
+ def concat_pnl(result: list) -> pd.DataFrame:
80
+ """
81
+ Combine PnL results from multiple alphas into a single DataFrame.
82
+
83
+ Args:
84
+ result (list): A list of dictionaries containing simulation results with PnL data.
85
+
86
+ Returns:
87
+ pandas.DataFrame: A DataFrame containing combined PnL data for all alphas.
88
+ """
89
+ list_of_pnls = [result[x]["pnl"] for x in range(len(result)) if result[x]["pnl"] is not None]
90
+ pnls_df = pd.concat(list_of_pnls).reset_index()
91
+
92
+ return pnls_df
93
+
94
+
95
+ def concat_is_tests(result: list) -> pd.DataFrame:
96
+ """
97
+ Combine in-sample test results from multiple alphas into a single DataFrame.
98
+
99
+ Args:
100
+ result (list): A list of dictionaries containing simulation results with in-sample test data.
101
+
102
+ Returns:
103
+ pandas.DataFrame: A DataFrame containing combined in-sample test results for all alphas.
104
+ """
105
+ is_tests_list = [result[x]["is_tests"] for x in range(len(result)) if result[x]["is_tests"] is not None]
106
+ is_tests_df = pd.concat(is_tests_list, sort=True).reset_index(drop=True)
107
+ return is_tests_df
108
+
109
+
110
+ def save_simulation_result(result: dict) -> None:
111
+ """
112
+ Save the simulation result to a JSON file in the 'simulation_results' folder.
113
+
114
+ Args:
115
+ result (dict): A dictionary containing the simulation result for an alpha.
116
+ """
117
+
118
+ alpha_id = result["id"]
119
+ region = result["settings"]["region"]
120
+ folder_path = "simulation_results/"
121
+ file_path = os.path.join(folder_path, f"{alpha_id}_{region}")
122
+
123
+ os.makedirs(folder_path, exist_ok=True)
124
+
125
+ with open(file_path, "w") as file:
126
+ json.dump(result, file)
127
+
128
+
129
+ def save_pnl(pnl_df: pd.DataFrame, alpha_id: str, region: str) -> None:
130
+ """
131
+ Save the PnL data for an alpha to a CSV file in the 'alphas_pnl' folder.
132
+
133
+ Args:
134
+ pnl_df (pandas.DataFrame): The DataFrame containing PnL data.
135
+ alpha_id (str): The ID of the alpha.
136
+ region (str): The region for which the PnL data was generated.
137
+ """
138
+
139
+ folder_path = "alphas_pnl/"
140
+ file_path = os.path.join(folder_path, f"{alpha_id}_{region}.csv")
141
+ os.makedirs(folder_path, exist_ok=True)
142
+
143
+ pnl_df.to_csv(file_path)
144
+
145
+
146
+ def save_yearly_stats(yearly_stats: pd.DataFrame, alpha_id: str, region: str):
147
+ """
148
+ Save the yearly statistics for an alpha to a CSV file in the 'yearly_stats' folder.
149
+
150
+ Args:
151
+ yearly_stats (pandas.DataFrame): The DataFrame containing yearly statistics.
152
+ alpha_id (str): The ID of the alpha.
153
+ region (str): The region for which the statistics were generated.
154
+ """
155
+
156
+ folder_path = "yearly_stats/"
157
+ file_path = os.path.join(folder_path, f"{alpha_id}_{region}.csv")
158
+ os.makedirs(folder_path, exist_ok=True)
159
+
160
+ yearly_stats.to_csv(file_path, index=False)
161
+
162
+
163
+ def expand_dict_columns(data: pd.DataFrame) -> pd.DataFrame:
164
+ """
165
+ Expand dictionary columns in a DataFrame into separate columns.
166
+
167
+ Args:
168
+ data (pandas.DataFrame): The input DataFrame with dictionary columns.
169
+
170
+ Returns:
171
+ pandas.DataFrame: A new DataFrame with expanded columns.
172
+ """
173
+ dict_columns = list(filter(lambda x: isinstance(data[x].iloc[0], dict), data.columns))
174
+ new_columns = pd.concat(
175
+ [data[col].apply(pd.Series).rename(columns=lambda x: f"{col}_{x}") for col in dict_columns],
176
+ axis=1,
177
+ )
178
+
179
+ data = pd.concat([data, new_columns], axis=1)
180
+ return data
@@ -0,0 +1,20 @@
1
+ # PyPI Mirror Configuration
2
+ # 配置 PyPI 镜像源
3
+
4
+ # Default: Tsinghua University (China)
5
+ # 默认:清华大学镜像源(中国)
6
+ https://pypi.tuna.tsinghua.edu.cn/simple
7
+
8
+ # Alternative mirrors / 其他可选镜像源:
9
+ #
10
+ # Aliyun (阿里云):
11
+ # https://mirrors.aliyun.com/pypi/simple/
12
+ #
13
+ # Douban (豆瓣):
14
+ # https://pypi.douban.com/simple/
15
+ #
16
+ # USTC (中国科学技术大学):
17
+ # https://pypi.mirrors.ustc.edu.cn/simple/
18
+ #
19
+ # Official PyPI (官方源):
20
+ # https://pypi.org/simple