cnhkmcp 1.2.0__tar.gz → 1.2.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.
- {cnhkmcp-1.2.0/cnhkmcp.egg-info → cnhkmcp-1.2.2}/PKG-INFO +1 -1
- {cnhkmcp-1.2.0 → cnhkmcp-1.2.2}/cnhkmcp/__init__.py +1 -1
- {cnhkmcp-1.2.0 → cnhkmcp-1.2.2}/cnhkmcp/untracked/platform_functions.py +85 -47
- {cnhkmcp-1.2.0 → cnhkmcp-1.2.2/cnhkmcp.egg-info}/PKG-INFO +1 -1
- {cnhkmcp-1.2.0 → cnhkmcp-1.2.2}/setup.py +1 -1
- {cnhkmcp-1.2.0 → cnhkmcp-1.2.2}/LICENSE +0 -0
- {cnhkmcp-1.2.0 → cnhkmcp-1.2.2}/MANIFEST.in +0 -0
- {cnhkmcp-1.2.0 → cnhkmcp-1.2.2}/README.md +0 -0
- {cnhkmcp-1.2.0 → cnhkmcp-1.2.2}/cnhkmcp/untracked/forum_functions.py +0 -0
- {cnhkmcp-1.2.0 → cnhkmcp-1.2.2}/cnhkmcp/untracked/user_config.json +0 -0
- {cnhkmcp-1.2.0 → cnhkmcp-1.2.2}/cnhkmcp.egg-info/SOURCES.txt +0 -0
- {cnhkmcp-1.2.0 → cnhkmcp-1.2.2}/cnhkmcp.egg-info/dependency_links.txt +0 -0
- {cnhkmcp-1.2.0 → cnhkmcp-1.2.2}/cnhkmcp.egg-info/entry_points.txt +0 -0
- {cnhkmcp-1.2.0 → cnhkmcp-1.2.2}/cnhkmcp.egg-info/not-zip-safe +0 -0
- {cnhkmcp-1.2.0 → cnhkmcp-1.2.2}/cnhkmcp.egg-info/requires.txt +0 -0
- {cnhkmcp-1.2.0 → cnhkmcp-1.2.2}/cnhkmcp.egg-info/top_level.txt +0 -0
- {cnhkmcp-1.2.0 → cnhkmcp-1.2.2}/requirements.txt +0 -0
- {cnhkmcp-1.2.0 → cnhkmcp-1.2.2}/setup.cfg +0 -0
|
@@ -389,7 +389,7 @@ class BrainApiClient:
|
|
|
389
389
|
response = self.session.get(f"{self.base_url}/data-sets", params=params)
|
|
390
390
|
response.raise_for_status()
|
|
391
391
|
response = response.json()
|
|
392
|
-
response['extraNote'] = "if your returned result is 0, you may want to check your parameter by using
|
|
392
|
+
response['extraNote'] = "if your returned result is 0, you may want to check your parameter by using get_platform_setting_options tool to got correct parameter"
|
|
393
393
|
return response
|
|
394
394
|
except Exception as e:
|
|
395
395
|
self.log(f"Failed to get datasets: {str(e)}", "ERROR")
|
|
@@ -423,7 +423,7 @@ class BrainApiClient:
|
|
|
423
423
|
response = self.session.get(f"{self.base_url}/data-fields", params=params)
|
|
424
424
|
response.raise_for_status()
|
|
425
425
|
response = response.json()
|
|
426
|
-
response['extraNote'] = "if your returned result is 0, you may want to check your parameter by using
|
|
426
|
+
response['extraNote'] = "if your returned result is 0, you may want to check your parameter by using get_platform_setting_options tool to got correct parameter"
|
|
427
427
|
return response
|
|
428
428
|
except Exception as e:
|
|
429
429
|
self.log(f"Failed to get datafields: {str(e)}", "ERROR")
|
|
@@ -436,7 +436,15 @@ class BrainApiClient:
|
|
|
436
436
|
try:
|
|
437
437
|
response = self.session.get(f"{self.base_url}/alphas/{alpha_id}/recordsets/pnl")
|
|
438
438
|
response.raise_for_status()
|
|
439
|
-
return
|
|
439
|
+
# Some alphas may return 204 No Content or an empty body
|
|
440
|
+
text = (response.text or "").strip()
|
|
441
|
+
if not text:
|
|
442
|
+
return {}
|
|
443
|
+
try:
|
|
444
|
+
return response.json()
|
|
445
|
+
except Exception as parse_err:
|
|
446
|
+
self.log(f"PnL JSON parse failed for {alpha_id}: {parse_err}", "WARNING")
|
|
447
|
+
return {}
|
|
440
448
|
except Exception as e:
|
|
441
449
|
self.log(f"Failed to get alpha PnL: {str(e)}", "ERROR")
|
|
442
450
|
raise
|
|
@@ -786,9 +794,11 @@ class BrainApiClient:
|
|
|
786
794
|
await self.ensure_authenticated()
|
|
787
795
|
|
|
788
796
|
try:
|
|
789
|
-
response = self.session.get(f"{self.base_url}/alphas/{alpha_id}/
|
|
797
|
+
response = self.session.get(f"{self.base_url}/alphas/{alpha_id}/correlations/prod")
|
|
790
798
|
response.raise_for_status()
|
|
791
|
-
|
|
799
|
+
if response.text:
|
|
800
|
+
return response.json()
|
|
801
|
+
return {} # Return empty dict for empty response
|
|
792
802
|
except Exception as e:
|
|
793
803
|
self.log(f"Failed to get production correlation: {str(e)}", "ERROR")
|
|
794
804
|
raise
|
|
@@ -798,9 +808,11 @@ class BrainApiClient:
|
|
|
798
808
|
await self.ensure_authenticated()
|
|
799
809
|
|
|
800
810
|
try:
|
|
801
|
-
response = self.session.get(f"{self.base_url}/alphas/{alpha_id}/
|
|
811
|
+
response = self.session.get(f"{self.base_url}/alphas/{alpha_id}/correlations/self")
|
|
802
812
|
response.raise_for_status()
|
|
803
|
-
|
|
813
|
+
if response.text:
|
|
814
|
+
return response.json()
|
|
815
|
+
return {} # Return empty dict for empty response
|
|
804
816
|
except Exception as e:
|
|
805
817
|
self.log(f"Failed to get self correlation: {str(e)}", "ERROR")
|
|
806
818
|
raise
|
|
@@ -1050,7 +1062,7 @@ class BrainApiClient:
|
|
|
1050
1062
|
self.log(f"Failed to get competition agreement: {str(e)}", "ERROR")
|
|
1051
1063
|
raise
|
|
1052
1064
|
|
|
1053
|
-
async def
|
|
1065
|
+
async def get_platform_setting_options(self) -> Dict[str, Any]:
|
|
1054
1066
|
"""Get available instrument types, regions, delays, and universes."""
|
|
1055
1067
|
await self.ensure_authenticated()
|
|
1056
1068
|
|
|
@@ -1200,14 +1212,54 @@ brain_client = BrainApiClient()
|
|
|
1200
1212
|
# Configuration management
|
|
1201
1213
|
CONFIG_FILE = "user_config.json"
|
|
1202
1214
|
|
|
1215
|
+
def _resolve_config_path(for_write: bool = False) -> str:
|
|
1216
|
+
"""Resolve the absolute path to the config file.
|
|
1217
|
+
|
|
1218
|
+
Resolution order:
|
|
1219
|
+
1) BRAIN_CONFIG_PATH env var (explicit file path)
|
|
1220
|
+
2) File in the same directory as this Python module
|
|
1221
|
+
3) Current working directory (read fallback only)
|
|
1222
|
+
|
|
1223
|
+
When for_write is True, prefers module directory (or env path) and will
|
|
1224
|
+
return that even if it doesn't exist yet.
|
|
1225
|
+
"""
|
|
1226
|
+
try:
|
|
1227
|
+
# 1) Explicit override
|
|
1228
|
+
env_path = os.environ.get("BRAIN_CONFIG_PATH")
|
|
1229
|
+
if env_path:
|
|
1230
|
+
return os.path.abspath(env_path)
|
|
1231
|
+
|
|
1232
|
+
# 2) Same directory as this .py module
|
|
1233
|
+
module_dir = os.path.dirname(os.path.abspath(__file__))
|
|
1234
|
+
module_path = os.path.join(module_dir, CONFIG_FILE)
|
|
1235
|
+
if not for_write and os.path.exists(module_path):
|
|
1236
|
+
return module_path
|
|
1237
|
+
|
|
1238
|
+
# 3) Fallback to CWD for backward compatibility (read-only preference)
|
|
1239
|
+
cwd_path = os.path.abspath(CONFIG_FILE)
|
|
1240
|
+
if not for_write and os.path.exists(cwd_path):
|
|
1241
|
+
return cwd_path
|
|
1242
|
+
|
|
1243
|
+
# For writes (or when nothing exists), prefer the module directory
|
|
1244
|
+
return module_path
|
|
1245
|
+
except Exception as e:
|
|
1246
|
+
# As a last resort, use the bare filename in CWD
|
|
1247
|
+
logger.error(f"Failed to resolve config path, defaulting to CWD: {e}")
|
|
1248
|
+
return os.path.abspath(CONFIG_FILE)
|
|
1249
|
+
|
|
1203
1250
|
def load_config() -> Dict[str, Any]:
|
|
1204
|
-
"""Load configuration from file.
|
|
1205
|
-
|
|
1251
|
+
"""Load configuration from file with robust path resolution.
|
|
1252
|
+
|
|
1253
|
+
Looks for the config in this order: BRAIN_CONFIG_PATH -> module directory -> CWD.
|
|
1254
|
+
Returns an empty dict when not found or on error.
|
|
1255
|
+
"""
|
|
1256
|
+
path = _resolve_config_path(for_write=False)
|
|
1257
|
+
if os.path.exists(path):
|
|
1206
1258
|
try:
|
|
1207
|
-
with open(
|
|
1259
|
+
with open(path, 'r', encoding='utf-8') as f:
|
|
1208
1260
|
return json.load(f)
|
|
1209
1261
|
except Exception as e:
|
|
1210
|
-
logger.error(f"Failed to load config: {e}")
|
|
1262
|
+
logger.error(f"Failed to load config from '{path}': {e}")
|
|
1211
1263
|
return {}
|
|
1212
1264
|
|
|
1213
1265
|
def load_brain_credentials() -> Optional[tuple]:
|
|
@@ -1225,10 +1277,16 @@ def load_brain_credentials() -> Optional[tuple]:
|
|
|
1225
1277
|
return None
|
|
1226
1278
|
|
|
1227
1279
|
def save_config(config: Dict[str, Any]):
|
|
1228
|
-
"""Save configuration to file.
|
|
1280
|
+
"""Save configuration to file using the resolved config path.
|
|
1281
|
+
|
|
1282
|
+
Uses BRAIN_CONFIG_PATH if set; otherwise writes next to this module.
|
|
1283
|
+
Ensures the target directory exists.
|
|
1284
|
+
"""
|
|
1229
1285
|
try:
|
|
1230
|
-
|
|
1231
|
-
|
|
1286
|
+
path = _resolve_config_path(for_write=True)
|
|
1287
|
+
os.makedirs(os.path.dirname(path), exist_ok=True)
|
|
1288
|
+
with open(path, 'w', encoding='utf-8') as f:
|
|
1289
|
+
json.dump(config, f, indent=2, ensure_ascii=False)
|
|
1232
1290
|
except Exception as e:
|
|
1233
1291
|
logger.error(f"Failed to save config: {e}")
|
|
1234
1292
|
|
|
@@ -1636,35 +1694,7 @@ async def save_simulation_data(simulation_id: str, filename: str) -> Dict[str, A
|
|
|
1636
1694
|
except Exception as e:
|
|
1637
1695
|
return {"error": str(e)}
|
|
1638
1696
|
|
|
1639
|
-
|
|
1640
|
-
async def analyze_alpha_performance(alpha_id: str) -> Dict[str, Any]:
|
|
1641
|
-
"""
|
|
1642
|
-
📊 Comprehensive alpha performance analysis.
|
|
1643
|
-
|
|
1644
|
-
Args:
|
|
1645
|
-
alpha_id: The alpha ID to analyze
|
|
1646
|
-
|
|
1647
|
-
Returns:
|
|
1648
|
-
Comprehensive performance analysis
|
|
1649
|
-
"""
|
|
1650
|
-
try:
|
|
1651
|
-
# Get alpha details
|
|
1652
|
-
alpha_details = await brain_client.get_alpha_details(alpha_id)
|
|
1653
|
-
|
|
1654
|
-
# Get PnL data
|
|
1655
|
-
pnl_data = await brain_client.get_alpha_pnl(alpha_id)
|
|
1656
|
-
|
|
1657
|
-
# Get yearly stats if available
|
|
1658
|
-
yearly_stats = await brain_client.get_alpha_yearly_stats(alpha_id)
|
|
1659
|
-
|
|
1660
|
-
return {
|
|
1661
|
-
"alpha_details": alpha_details,
|
|
1662
|
-
"pnl_data": pnl_data,
|
|
1663
|
-
"yearly_stats": yearly_stats,
|
|
1664
|
-
"analysis_timestamp": datetime.now().isoformat()
|
|
1665
|
-
}
|
|
1666
|
-
except Exception as e:
|
|
1667
|
-
return {"error": str(e)}
|
|
1697
|
+
|
|
1668
1698
|
|
|
1669
1699
|
@mcp.tool()
|
|
1670
1700
|
async def get_operators() -> Dict[str, Any]:
|
|
@@ -1962,10 +1992,18 @@ async def get_competition_agreement(competition_id: str) -> Dict[str, Any]:
|
|
|
1962
1992
|
return {"error": str(e)}
|
|
1963
1993
|
|
|
1964
1994
|
@mcp.tool()
|
|
1965
|
-
async def
|
|
1966
|
-
"""
|
|
1995
|
+
async def get_platform_setting_options() -> Dict[str, Any]:
|
|
1996
|
+
"""Discover valid simulation setting options (instrument types, regions, delays, universes, neutralization).
|
|
1997
|
+
|
|
1998
|
+
Use this when a simulation request might contain an invalid/mismatched setting. If an AI or user supplies
|
|
1999
|
+
incorrect parameters (e.g., wrong region for an instrument type), call this tool to retrieve the authoritative
|
|
2000
|
+
option sets and correct the inputs before proceeding.
|
|
2001
|
+
|
|
2002
|
+
Returns:
|
|
2003
|
+
A structured list of valid combinations and choice lists to validate or fix simulation settings.
|
|
2004
|
+
"""
|
|
1967
2005
|
try:
|
|
1968
|
-
return await brain_client.
|
|
2006
|
+
return await brain_client.get_platform_setting_options()
|
|
1969
2007
|
except Exception as e:
|
|
1970
2008
|
return {"error": str(e)}
|
|
1971
2009
|
|
|
@@ -13,7 +13,7 @@ def read_requirements():
|
|
|
13
13
|
|
|
14
14
|
setup(
|
|
15
15
|
name="cnhkmcp",
|
|
16
|
-
version="1.2.
|
|
16
|
+
version="1.2.2",
|
|
17
17
|
author="CNHK",
|
|
18
18
|
author_email="cnhk@example.com",
|
|
19
19
|
description="A comprehensive Model Context Protocol (MCP) server for quantitative trading platform integration",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|