cnhkmcp 2.0.3__py3-none-any.whl → 2.1.0__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 (39) hide show
  1. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/README.md +38 -0
  2. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/config.json +6 -0
  3. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/get_knowledgeBase_tool/ace_lib.py +1510 -0
  4. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/get_knowledgeBase_tool/fetch_all_datasets.py +157 -0
  5. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/get_knowledgeBase_tool/fetch_all_documentation.py +132 -0
  6. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/get_knowledgeBase_tool/fetch_all_operators.py +99 -0
  7. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/get_knowledgeBase_tool/helpful_functions.py +180 -0
  8. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/icon.ico +0 -0
  9. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/icon.png +0 -0
  10. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/knowledge/test.txt +1 -0
  11. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/main.py +581 -0
  12. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/process_knowledge_base.py +280 -0
  13. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/rag_engine.py +265 -0
  14. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/requirements.txt +12 -0
  15. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/run.bat +3 -0
  16. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242/vector_db/chroma.sqlite3 +0 -0
  17. cnhkmcp/untracked/AI/321/206/320/261/320/234/321/211/320/255/320/262/321/206/320/237/320/242/321/204/342/225/227/342/225/242//321/211/320/266/320/246/321/206/320/274/320/261/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 +265 -0
  18. cnhkmcp/untracked/APP/Tranformer/Transformer.py +2804 -11
  19. cnhkmcp/untracked/APP/Tranformer/output/Alpha_candidates.json +1524 -889
  20. cnhkmcp/untracked/APP/Tranformer/output/Alpha_generated_expressions_error.json +884 -111
  21. cnhkmcp/untracked/APP/Tranformer/output/Alpha_generated_expressions_success.json +442 -168
  22. cnhkmcp/untracked/APP/Tranformer/template_summary.txt +2775 -1
  23. cnhkmcp/untracked/APP/ace.log +2 -0
  24. cnhkmcp/untracked/APP/give_me_idea/fetch_all_datasets.py +157 -0
  25. cnhkmcp/untracked/APP/give_me_idea/fetch_all_operators.py +99 -0
  26. cnhkmcp/untracked/APP/simulator/simulator_wqb.py +16 -16
  27. cnhkmcp/untracked/APP/static/brain.js +61 -0
  28. cnhkmcp/untracked/APP/static/script.js +140 -0
  29. cnhkmcp/untracked/APP/templates/index.html +25 -4
  30. 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 +70 -8
  31. {cnhkmcp-2.0.3.dist-info → cnhkmcp-2.1.0.dist-info}/METADATA +1 -1
  32. {cnhkmcp-2.0.3.dist-info → cnhkmcp-2.1.0.dist-info}/RECORD +36 -20
  33. cnhkmcp/untracked/APP/hkSimulator/ace.log +0 -0
  34. cnhkmcp/untracked/APP/hkSimulator/autosim_20251205_145240.log +0 -0
  35. cnhkmcp/untracked/APP/hkSimulator/autosim_20251215_030103.log +0 -0
  36. {cnhkmcp-2.0.3.dist-info → cnhkmcp-2.1.0.dist-info}/WHEEL +0 -0
  37. {cnhkmcp-2.0.3.dist-info → cnhkmcp-2.1.0.dist-info}/entry_points.txt +0 -0
  38. {cnhkmcp-2.0.3.dist-info → cnhkmcp-2.1.0.dist-info}/licenses/LICENSE +0 -0
  39. {cnhkmcp-2.0.3.dist-info → cnhkmcp-2.1.0.dist-info}/top_level.txt +0 -0
@@ -65,3 +65,5 @@ Incorrect email or password
65
65
  2025-12-18 02:52:11,310 - ace - WARNING - No fields found: region=CHN, delay=1, universe=TOP2000U, type=MATRIX, dataset.id=analyst45
66
66
  2025-12-18 13:17:02,856 - ace - WARNING - No fields found: region=USA, delay=1, universe=TOP3000, type=MATRIX, dataset.id=shortinterest3
67
67
  2025-12-18 13:24:08,699 - ace - WARNING - No fields found: region=USA, delay=1, universe=TOP3000, type=MATRIX, dataset.id=shortinterest3
68
+ 2025-12-24 02:44:57,254 - ace - ERROR - Simulation failed. {'id': '3fryaq7y750HbRmjNtbF3x8', 'type': 'REGULAR', 'status': 'ERROR', 'message': 'Your simulation has been running too long. If you are running simulations in batch, consider to reduce number of concurrent simulations or input data.'}
69
+ 2025-12-24 03:14:08,010 - ace - ERROR - Simulation failed. {'id': '1ZdRKEal75hUamZOV5ZRpTt', 'type': 'REGULAR', 'status': 'ERROR', 'message': 'Your simulation has been running too long. If you are running simulations in batch, consider to reduce number of concurrent simulations or input data.'}
@@ -0,0 +1,157 @@
1
+ import getpass
2
+ import json
3
+ import os
4
+ import sys
5
+ from typing import List
6
+
7
+ import pandas as pd
8
+
9
+ # Ensure we can import ace_lib from the project root
10
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
11
+ ROOT_DIR = os.path.dirname(SCRIPT_DIR)
12
+ if ROOT_DIR not in sys.path:
13
+ sys.path.append(ROOT_DIR)
14
+
15
+ import ace_lib # noqa: E402
16
+
17
+
18
+ def prompt_credentials() -> tuple[str, str]:
19
+ """Prompt user for platform credentials."""
20
+ email = input("Enter BRAIN Email: ").strip()
21
+ while not email:
22
+ email = input("Email is required. Enter BRAIN Email: ").strip()
23
+
24
+ password = getpass.getpass("Enter BRAIN Password: ").strip()
25
+ while not password:
26
+ password = getpass.getpass("Password is required. Enter BRAIN Password: ").strip()
27
+
28
+ return email, password
29
+
30
+
31
+ def fetch_all_combinations(session: ace_lib.SingleSession) -> pd.DataFrame:
32
+ """Return all valid instrument/region/delay/universe combos from platform settings."""
33
+ options_df = ace_lib.get_instrument_type_region_delay(session)
34
+ if options_df is None or options_df.empty:
35
+ raise RuntimeError("No simulation options fetched; cannot enumerate datasets.")
36
+ return options_df
37
+
38
+
39
+ def fetch_datasets_for_combo(
40
+ session: ace_lib.SingleSession,
41
+ instrument_type: str,
42
+ region: str,
43
+ delay: int,
44
+ universe: str,
45
+ ) -> pd.DataFrame:
46
+ """Fetch datasets for one combination (theme ALL to include both theme true/false)."""
47
+ df = ace_lib.get_datasets(
48
+ session,
49
+ instrument_type=instrument_type,
50
+ region=region,
51
+ delay=delay,
52
+ universe=universe,
53
+ theme="ALL",
54
+ )
55
+ if df is None:
56
+ return pd.DataFrame()
57
+
58
+ df = df.copy()
59
+ df["param_instrument_type"] = instrument_type
60
+ df["param_region"] = region
61
+ df["param_delay"] = delay
62
+ df["param_universe"] = universe
63
+ df["combo_key"] = df.apply(
64
+ lambda row: f"{instrument_type}-{region}-D{delay}-{universe}",
65
+ axis=1,
66
+ )
67
+ return df
68
+
69
+
70
+ def merge_and_deduplicate(datasets: List[pd.DataFrame]) -> pd.DataFrame:
71
+ """Merge fetched datasets and deduplicate by dataset id, keeping all combo metadata."""
72
+ combined = pd.concat([df for df in datasets if not df.empty], ignore_index=True)
73
+ if combined.empty:
74
+ return combined
75
+
76
+ # Aggregate availability combos per dataset id
77
+ availability = (
78
+ combined.groupby("id")["combo_key"]
79
+ .agg(lambda x: " | ".join(sorted(set(x))))
80
+ .rename("available_in")
81
+ .reset_index()
82
+ )
83
+
84
+ # Drop duplicate rows by dataset id, keep first occurrence of other columns
85
+ unique_df = combined.drop_duplicates(subset=["id"]).copy()
86
+ unique_df = unique_df.merge(availability, on="id", how="left")
87
+
88
+ # Sort for readability
89
+ sort_cols = [col for col in ["category", "subcategory", "id"] if col in unique_df.columns]
90
+ if sort_cols:
91
+ # Ensure sort keys are hashable/strings to avoid unhashable dict errors
92
+ for col in sort_cols:
93
+ unique_df[col] = unique_df[col].apply(
94
+ lambda v: v
95
+ if pd.isna(v) or isinstance(v, (int, float, str, bool))
96
+ else json.dumps(v, ensure_ascii=False, sort_keys=True)
97
+ )
98
+ unique_df = unique_df.sort_values(sort_cols).reset_index(drop=True)
99
+
100
+ return unique_df
101
+
102
+
103
+ def main():
104
+ print("=== Fetch All BRAIN Datasets (all regions/universes/delays) ===")
105
+
106
+ email, password = prompt_credentials()
107
+
108
+ # Monkey-patch ace_lib credential retrieval so start_session uses provided credentials
109
+ ace_lib.get_credentials = lambda: (email, password)
110
+
111
+ print("Logging in...")
112
+ try:
113
+ session = ace_lib.start_session()
114
+ print("Login successful.")
115
+ except Exception as exc:
116
+ print(f"Login failed: {exc}")
117
+ return
118
+
119
+ print("Fetching valid instrument/region/delay/universe combinations from platform settings...")
120
+ try:
121
+ options_df = fetch_all_combinations(session)
122
+ except Exception as exc:
123
+ print(f"Failed to fetch simulation options: {exc}")
124
+ return
125
+
126
+ all_datasets: List[pd.DataFrame] = []
127
+ total_combos = 0
128
+
129
+ for _, row in options_df.iterrows():
130
+ instrument_type = row.get("InstrumentType")
131
+ region = row.get("Region")
132
+ delay = row.get("Delay")
133
+ universes = row.get("Universe") or []
134
+
135
+ for universe in universes:
136
+ total_combos += 1
137
+ print(f"[{total_combos}] Fetching datasets for {instrument_type} / {region} / D{delay} / {universe}...")
138
+ try:
139
+ df = fetch_datasets_for_combo(session, instrument_type, region, delay, universe)
140
+ print(f" -> Retrieved {len(df)} rows")
141
+ all_datasets.append(df)
142
+ except Exception as exc:
143
+ print(f" -> Failed for {instrument_type}-{region}-D{delay}-{universe}: {exc}")
144
+
145
+ result_df = merge_and_deduplicate(all_datasets)
146
+
147
+ if result_df.empty:
148
+ print("No datasets fetched; nothing to save.")
149
+ return
150
+
151
+ output_path = os.path.join(SCRIPT_DIR, "all_datasets_full.csv")
152
+ result_df.to_csv(output_path, index=False)
153
+ print(f"Saved {len(result_df)} unique datasets to {output_path}")
154
+
155
+
156
+ if __name__ == "__main__":
157
+ main()
@@ -0,0 +1,99 @@
1
+ import getpass
2
+ import os
3
+ import sys
4
+ from typing import List
5
+
6
+ import pandas as pd
7
+
8
+ # Make ace_lib importable
9
+ SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__))
10
+ ROOT_DIR = os.path.dirname(SCRIPT_DIR)
11
+ if ROOT_DIR not in sys.path:
12
+ sys.path.append(ROOT_DIR)
13
+
14
+ import ace_lib # noqa: E402
15
+
16
+
17
+ def prompt_credentials() -> tuple[str, str]:
18
+ email = input("Enter BRAIN Email: ").strip()
19
+ while not email:
20
+ email = input("Email is required. Enter BRAIN Email: ").strip()
21
+
22
+ password = getpass.getpass("Enter BRAIN Password: ").strip()
23
+ while not password:
24
+ password = getpass.getpass("Password is required. Enter BRAIN Password: ").strip()
25
+
26
+ return email, password
27
+
28
+
29
+ def fetch_operators(session: ace_lib.SingleSession) -> pd.DataFrame:
30
+ df = ace_lib.get_operators(session)
31
+ if df is None or df.empty:
32
+ return pd.DataFrame()
33
+
34
+ df = df.copy()
35
+
36
+ # Choose an identifier column robustly
37
+ id_col = "id" if "id" in df.columns else None
38
+ if id_col is None:
39
+ if "name" in df.columns:
40
+ id_col = "name"
41
+ else:
42
+ id_col = "_row_id"
43
+ df[id_col] = df.index
44
+
45
+ # Re-aggregate scopes so each operator id is unique
46
+ if "scope" in df.columns:
47
+ scope_map = (
48
+ df.groupby(id_col)["scope"]
49
+ .agg(lambda x: sorted(set([item for item in x if pd.notna(item)])))
50
+ .rename("scopes")
51
+ .reset_index()
52
+ )
53
+ else:
54
+ scope_map = pd.DataFrame({id_col: df[id_col].unique(), "scopes": [[] for _ in range(df[id_col].nunique())]})
55
+
56
+ unique_df = df.drop(columns=["scope"], errors="ignore").drop_duplicates(subset=[id_col]).merge(
57
+ scope_map, on=id_col, how="left"
58
+ )
59
+
60
+ # Sort for readability
61
+ sort_cols: List[str] = [col for col in ["category", "subcategory", "name", id_col] if col in unique_df.columns]
62
+ if sort_cols:
63
+ unique_df = unique_df.sort_values(sort_cols).reset_index(drop=True)
64
+
65
+ return unique_df
66
+
67
+
68
+ def main():
69
+ print("=== Fetch All BRAIN Operators ===")
70
+
71
+ email, password = prompt_credentials()
72
+ ace_lib.get_credentials = lambda: (email, password)
73
+
74
+ print("Logging in...")
75
+ try:
76
+ session = ace_lib.start_session()
77
+ print("Login successful.")
78
+ except Exception as exc:
79
+ print(f"Login failed: {exc}")
80
+ return
81
+
82
+ print("Fetching operators...")
83
+ try:
84
+ operators_df = fetch_operators(session)
85
+ except Exception as exc:
86
+ print(f"Failed to fetch operators: {exc}")
87
+ return
88
+
89
+ if operators_df.empty:
90
+ print("No operators returned; nothing to save.")
91
+ return
92
+
93
+ output_path = os.path.join(SCRIPT_DIR, "all_operators.csv")
94
+ operators_df.to_csv(output_path, index=False)
95
+ print(f"Saved {len(operators_df)} operators to {output_path}")
96
+
97
+
98
+ if __name__ == "__main__":
99
+ main()
@@ -466,15 +466,15 @@ async def automated_main(json_file_content, username, password, start_position=0
466
466
  print(f"成功完成 {len(resps)} 个回测")
467
467
 
468
468
  print("\nAlpha IDs:")
469
- for i, resp in enumerate(resps):
470
- try:
471
- alpha_id = resp.json()['alpha']
472
- alpha_ids.append(alpha_id)
473
- successful_count += 1
474
- print(f" {i+1:4d}. {alpha_id}")
475
- except Exception as e:
476
- failed_count += 1
477
- print(f" {i+1:4d}. 错误: {e}")
469
+ # for i, resp in enumerate(resps):
470
+ # try:
471
+ # alpha_id = resp.json()['alpha']
472
+ # alpha_ids.append(alpha_id)
473
+ # successful_count += 1
474
+ # print(f" {i+1:4d}. {alpha_id}")
475
+ # except Exception as e:
476
+ # failed_count += 1
477
+ # print(f" {i+1:4d}. 错误: {e}")
478
478
 
479
479
  print("\n✅ 处理完成!")
480
480
 
@@ -597,13 +597,13 @@ async def main():
597
597
  else:
598
598
  print(f"成功完成 {len(resps)} 个回测")
599
599
 
600
- print("\nAlpha IDs:")
601
- for i, resp in enumerate(resps):
602
- try:
603
- alpha_id = resp.json()['alpha']
604
- print(f" {i+1:4d}. {alpha_id}")
605
- except Exception as e:
606
- print(f" {i+1:4d}. 错误: {e}")
600
+ # print("\nAlpha IDs:")
601
+ # for i, resp in enumerate(resps):
602
+ # try:
603
+ # alpha_id = resp.json()['alpha']
604
+ # print(f" {i+1:4d}. {alpha_id}")
605
+ # except Exception as e:
606
+ # print(f" {i+1:4d}. 错误: {e}")
607
607
 
608
608
  except KeyboardInterrupt:
609
609
  print("\n\n⚠️ 回测被用户中断")
@@ -304,6 +304,65 @@ async function getUserOperators() {
304
304
  }
305
305
 
306
306
  // Get data fields via proxy server
307
+ async function getDatasets(region = 'USA', delay = 1, universe = 'TOP3000') {
308
+ if (!brainSession || !brainSessionId) {
309
+ throw new Error('Not authenticated with BRAIN');
310
+ }
311
+
312
+ try {
313
+ const params = new URLSearchParams({
314
+ region: region,
315
+ delay: delay.toString(),
316
+ universe: universe
317
+ });
318
+
319
+ const response = await fetch(`${PROXY_BASE}/api/datasets?${params}`, {
320
+ method: 'GET',
321
+ headers: {
322
+ 'Session-ID': brainSessionId
323
+ }
324
+ });
325
+
326
+ if (!response.ok) {
327
+ const errorData = await response.json();
328
+ throw new Error(errorData.error || 'Failed to fetch datasets');
329
+ }
330
+
331
+ const data = await response.json();
332
+ return data.results || [];
333
+
334
+ } catch (error) {
335
+ console.error('Failed to fetch datasets:', error);
336
+ throw error;
337
+ }
338
+ }
339
+
340
+ async function getSimulationOptions() {
341
+ if (!brainSession || !brainSessionId) {
342
+ throw new Error('Not authenticated with BRAIN');
343
+ }
344
+
345
+ try {
346
+ const response = await fetch(`${PROXY_BASE}/api/simulation-options`, {
347
+ method: 'GET',
348
+ headers: {
349
+ 'Session-ID': brainSessionId
350
+ }
351
+ });
352
+
353
+ if (!response.ok) {
354
+ const errorData = await response.json();
355
+ throw new Error(errorData.error || 'Failed to fetch simulation options');
356
+ }
357
+
358
+ return await response.json();
359
+
360
+ } catch (error) {
361
+ console.error('Failed to fetch simulation options:', error);
362
+ throw error;
363
+ }
364
+ }
365
+
307
366
  async function getDataFields(region = 'USA', delay = 1, universe = 'TOP3000', datasetId = 'fundamental6') {
308
367
  if (!brainSession || !brainSessionId) {
309
368
  throw new Error('Not authenticated with BRAIN');
@@ -514,6 +573,8 @@ window.brainAPI = {
514
573
  isConnectedToBrain,
515
574
  getAllOperators,
516
575
  getAllDataFields,
576
+ getDatasets,
577
+ getSimulationOptions,
517
578
  getDataFields,
518
579
  getOperatorsByCategory,
519
580
  searchOperators,
@@ -2265,6 +2265,99 @@ function openBrainDataFieldsModal() {
2265
2265
  setupDataFieldsModalEventListeners();
2266
2266
 
2267
2267
  modal.style.display = 'block';
2268
+
2269
+ // Auto-load datasets if connected
2270
+ if (window.brainAPI && window.brainAPI.isConnectedToBrain()) {
2271
+ loadDatasets();
2272
+ loadSimulationOptions();
2273
+ }
2274
+ }
2275
+
2276
+ let brainSimulationOptions = null;
2277
+
2278
+ async function loadSimulationOptions() {
2279
+ if (brainSimulationOptions) return; // Already loaded
2280
+
2281
+ try {
2282
+ if (!window.brainAPI || !window.brainAPI.isConnectedToBrain()) {
2283
+ return;
2284
+ }
2285
+
2286
+ brainSimulationOptions = await window.brainAPI.getSimulationOptions();
2287
+
2288
+ // Populate Region Select
2289
+ const regionSelect = document.getElementById('regionSelect');
2290
+ const instrumentType = 'EQUITY'; // Default
2291
+
2292
+ if (brainSimulationOptions && brainSimulationOptions[instrumentType]) {
2293
+ const regions = Object.keys(brainSimulationOptions[instrumentType]);
2294
+
2295
+ regionSelect.innerHTML = '<option value="">Select...</option>';
2296
+ regions.forEach(r => {
2297
+ const option = document.createElement('option');
2298
+ option.value = r;
2299
+ option.textContent = r;
2300
+ regionSelect.appendChild(option);
2301
+ });
2302
+
2303
+ regionSelect.style.display = 'block';
2304
+
2305
+ // Trigger updates if values are already set
2306
+ updateDelayOptions();
2307
+ }
2308
+
2309
+ } catch (error) {
2310
+ console.error('Failed to load simulation options:', error);
2311
+ }
2312
+ }
2313
+
2314
+ function updateDelayOptions() {
2315
+ const region = document.getElementById('regionInput').value;
2316
+ const delaySelect = document.getElementById('delaySelect');
2317
+ const instrumentType = 'EQUITY';
2318
+
2319
+ if (!brainSimulationOptions || !brainSimulationOptions[instrumentType] || !brainSimulationOptions[instrumentType][region]) {
2320
+ delaySelect.style.display = 'none';
2321
+ return;
2322
+ }
2323
+
2324
+ // The structure is brainSimulationOptions[instrumentType][region] = { delays: [], universes: [], ... }
2325
+ const delays = brainSimulationOptions[instrumentType][region].delays || [];
2326
+
2327
+ delaySelect.innerHTML = '<option value="">Select...</option>';
2328
+ delays.forEach(d => {
2329
+ const option = document.createElement('option');
2330
+ option.value = d;
2331
+ option.textContent = d;
2332
+ delaySelect.appendChild(option);
2333
+ });
2334
+
2335
+ delaySelect.style.display = 'block';
2336
+ updateUniverseOptions();
2337
+ }
2338
+
2339
+ function updateUniverseOptions() {
2340
+ const region = document.getElementById('regionInput').value;
2341
+ const universeSelect = document.getElementById('universeSelect');
2342
+ const instrumentType = 'EQUITY';
2343
+
2344
+ if (!brainSimulationOptions || !brainSimulationOptions[instrumentType] || !brainSimulationOptions[instrumentType][region]) {
2345
+ universeSelect.style.display = 'none';
2346
+ return;
2347
+ }
2348
+
2349
+ // Universe depends on Region, not Delay in this data structure
2350
+ const universes = brainSimulationOptions[instrumentType][region].universes || [];
2351
+
2352
+ universeSelect.innerHTML = '<option value="">Select...</option>';
2353
+ universes.forEach(u => {
2354
+ const option = document.createElement('option');
2355
+ option.value = u;
2356
+ option.textContent = u;
2357
+ universeSelect.appendChild(option);
2358
+ });
2359
+
2360
+ universeSelect.style.display = 'block';
2268
2361
  }
2269
2362
 
2270
2363
  function closeBrainDataFieldsModal() {
@@ -2274,6 +2367,49 @@ function closeBrainDataFieldsModal() {
2274
2367
  updateSelectedDataFieldsDisplay();
2275
2368
  }
2276
2369
 
2370
+ async function loadDatasets() {
2371
+ const region = document.getElementById('regionInput').value;
2372
+ const delay = document.getElementById('delayInput').value;
2373
+ const universe = document.getElementById('universeInput').value;
2374
+ const select = document.getElementById('datasetSelect');
2375
+ const btn = document.getElementById('loadDatasetsBtn');
2376
+
2377
+ try {
2378
+ const originalText = btn.textContent;
2379
+ btn.disabled = true;
2380
+ btn.textContent = 'Loading...';
2381
+
2382
+ if (!window.brainAPI || !window.brainAPI.isConnectedToBrain()) {
2383
+ throw new Error('Not connected to BRAIN');
2384
+ }
2385
+
2386
+ const datasets = await window.brainAPI.getDatasets(region, parseInt(delay), universe);
2387
+
2388
+ // Clear and populate select
2389
+ select.innerHTML = '<option value="">Select dataset...</option>';
2390
+
2391
+ // Sort datasets by ID
2392
+ datasets.sort((a, b) => a.id.localeCompare(b.id));
2393
+
2394
+ datasets.forEach(ds => {
2395
+ const option = document.createElement('option');
2396
+ option.value = ds.id;
2397
+ option.textContent = `${ds.id} - ${ds.description || ds.name || ''}`;
2398
+ option.title = ds.description || ds.name || '';
2399
+ select.appendChild(option);
2400
+ });
2401
+
2402
+ select.style.display = 'block';
2403
+ btn.textContent = 'Reload List';
2404
+
2405
+ } catch (error) {
2406
+ alert(`Failed to load datasets: ${error.message}`);
2407
+ btn.textContent = 'Load List';
2408
+ } finally {
2409
+ btn.disabled = false;
2410
+ }
2411
+ }
2412
+
2277
2413
  async function loadDataFields() {
2278
2414
  const region = document.getElementById('regionInput').value;
2279
2415
  const delay = document.getElementById('delayInput').value;
@@ -2566,6 +2702,10 @@ function setupDataFieldsModalEventListeners() {
2566
2702
 
2567
2703
  loadBtn.onclick = loadDataFields;
2568
2704
 
2705
+ // Input listeners for cascading options
2706
+ document.getElementById('regionInput').addEventListener('input', updateDelayOptions);
2707
+ document.getElementById('delayInput').addEventListener('input', updateUniverseOptions);
2708
+
2569
2709
  // Filter checkbox listeners
2570
2710
  highCoverageFilter.onchange = () => populateDataFieldsList();
2571
2711
  popularFilter.onchange = () => populateDataFieldsList();
@@ -330,19 +330,40 @@
330
330
  <div class="params-row">
331
331
  <div class="param-group">
332
332
  <label for="regionInput">Region:</label>
333
- <input type="text" id="regionInput" class="form-input" placeholder="e.g., USA" value="USA">
333
+ <div style="display: flex; gap: 5px;">
334
+ <input type="text" id="regionInput" class="form-input" placeholder="e.g., USA" value="USA" style="flex: 1;">
335
+ <select id="regionSelect" class="form-input" style="flex: 1; display: none;" onchange="if(this.value) { document.getElementById('regionInput').value = this.value; updateDelayOptions(); }">
336
+ <option value="">Select...</option>
337
+ </select>
338
+ </div>
334
339
  </div>
335
340
  <div class="param-group">
336
341
  <label for="delayInput">Delay:</label>
337
- <input type="text" id="delayInput" class="form-input" placeholder="e.g., 1" value="1">
342
+ <div style="display: flex; gap: 5px;">
343
+ <input type="text" id="delayInput" class="form-input" placeholder="e.g., 1" value="1" style="flex: 1;">
344
+ <select id="delaySelect" class="form-input" style="flex: 1; display: none;" onchange="if(this.value) { document.getElementById('delayInput').value = this.value; updateUniverseOptions(); }">
345
+ <option value="">Select...</option>
346
+ </select>
347
+ </div>
338
348
  </div>
339
349
  <div class="param-group">
340
350
  <label for="universeInput">Universe:</label>
341
- <input type="text" id="universeInput" class="form-input" placeholder="e.g., TOP3000" value="TOP3000">
351
+ <div style="display: flex; gap: 5px;">
352
+ <input type="text" id="universeInput" class="form-input" placeholder="e.g., TOP3000" value="TOP3000" style="flex: 1;">
353
+ <select id="universeSelect" class="form-input" style="flex: 1; display: none;" onchange="if(this.value) document.getElementById('universeInput').value = this.value">
354
+ <option value="">Select...</option>
355
+ </select>
356
+ </div>
342
357
  </div>
343
358
  <div class="param-group">
344
359
  <label for="datasetInput">Dataset ID:</label>
345
- <input type="text" id="datasetInput" class="form-input" placeholder="e.g., fundamental6" value="fundamental6">
360
+ <div style="display: flex; gap: 5px;">
361
+ <input type="text" id="datasetInput" class="form-input" placeholder="e.g., fundamental6" value="fundamental6" style="flex: 1;">
362
+ <select id="datasetSelect" class="form-input" style="flex: 1; display: none;" onchange="if(this.value) document.getElementById('datasetInput').value = this.value">
363
+ <option value="">Select dataset...</option>
364
+ </select>
365
+ <button id="loadDatasetsBtn" class="btn btn-small btn-secondary" onclick="loadDatasets()" title="Load Datasets from BRAIN">Load List</button>
366
+ </div>
346
367
  </div>
347
368
  <div class="param-group">
348
369
  <button id="loadDataFieldsBtn" class="btn btn-secondary">Load Data Fields</button>