Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 16 additions & 16 deletions internal_dependency_matrix.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
"path": "pyproject.toml",
"package": "quant-platform-kit",
"source_repo": "QuantPlatformKit",
"ref": "651c9ac4f37ce6e7fe1bac84dc7646cd5abc9e6e"
"ref": "ff09c889ed21e2eb6fcb37f6cdaa159190ec82da"
},
{
"consumer_repo": "BinancePlatform",
Expand All @@ -27,14 +27,14 @@
"path": "uv.lock",
"package": "quant-platform-kit",
"source_repo": "QuantPlatformKit",
"ref": "651c9ac4f37ce6e7fe1bac84dc7646cd5abc9e6e"
"ref": "ff09c889ed21e2eb6fcb37f6cdaa159190ec82da"
},
{
"consumer_repo": "CharlesSchwabPlatform",
"path": "pyproject.toml",
"package": "quant-platform-kit",
"source_repo": "QuantPlatformKit",
"ref": "651c9ac4f37ce6e7fe1bac84dc7646cd5abc9e6e"
"ref": "ff09c889ed21e2eb6fcb37f6cdaa159190ec82da"
},
{
"consumer_repo": "CharlesSchwabPlatform",
Expand All @@ -48,7 +48,7 @@
"path": "uv.lock",
"package": "quant-platform-kit",
"source_repo": "QuantPlatformKit",
"ref": "651c9ac4f37ce6e7fe1bac84dc7646cd5abc9e6e"
"ref": "ff09c889ed21e2eb6fcb37f6cdaa159190ec82da"
},
{
"consumer_repo": "CharlesSchwabPlatform",
Expand Down Expand Up @@ -118,14 +118,14 @@
"path": "pyproject.toml",
"package": "quant-platform-kit",
"source_repo": "QuantPlatformKit",
"ref": "651c9ac4f37ce6e7fe1bac84dc7646cd5abc9e6e"
"ref": "ff09c889ed21e2eb6fcb37f6cdaa159190ec82da"
},
{
"consumer_repo": "CryptoStrategies",
"path": "uv.lock",
"package": "quant-platform-kit",
"source_repo": "QuantPlatformKit",
"ref": "651c9ac4f37ce6e7fe1bac84dc7646cd5abc9e6e"
"ref": "ff09c889ed21e2eb6fcb37f6cdaa159190ec82da"
},
{
"consumer_repo": "FirstradePlatform",
Expand Down Expand Up @@ -195,7 +195,7 @@
"path": "pyproject.toml",
"package": "quant-platform-kit",
"source_repo": "QuantPlatformKit",
"ref": "651c9ac4f37ce6e7fe1bac84dc7646cd5abc9e6e"
"ref": "ff09c889ed21e2eb6fcb37f6cdaa159190ec82da"
},
{
"consumer_repo": "InteractiveBrokersPlatform",
Expand All @@ -216,7 +216,7 @@
"path": "uv.lock",
"package": "quant-platform-kit",
"source_repo": "QuantPlatformKit",
"ref": "651c9ac4f37ce6e7fe1bac84dc7646cd5abc9e6e"
"ref": "ff09c889ed21e2eb6fcb37f6cdaa159190ec82da"
},
{
"consumer_repo": "InteractiveBrokersPlatform",
Expand All @@ -237,7 +237,7 @@
"path": "pyproject.toml",
"package": "quant-platform-kit",
"source_repo": "QuantPlatformKit",
"ref": "651c9ac4f37ce6e7fe1bac84dc7646cd5abc9e6e"
"ref": "ff09c889ed21e2eb6fcb37f6cdaa159190ec82da"
},
{
"consumer_repo": "LongBridgePlatform",
Expand All @@ -258,7 +258,7 @@
"path": "uv.lock",
"package": "quant-platform-kit",
"source_repo": "QuantPlatformKit",
"ref": "651c9ac4f37ce6e7fe1bac84dc7646cd5abc9e6e"
"ref": "ff09c889ed21e2eb6fcb37f6cdaa159190ec82da"
},
{
"consumer_repo": "LongBridgePlatform",
Expand Down Expand Up @@ -300,7 +300,7 @@
"path": "pyproject.toml",
"package": "quant-platform-kit",
"source_repo": "QuantPlatformKit",
"ref": "37c81901160c5b31127a27dba1c63944933fb6bf"
"ref": "ff09c889ed21e2eb6fcb37f6cdaa159190ec82da"
},
{
"consumer_repo": "UsEquitySnapshotPipelines",
Expand All @@ -314,14 +314,14 @@
"path": "pyproject.toml",
"package": "us-equity-strategies",
"source_repo": "UsEquityStrategies",
"ref": "17ddb86c72d44b2c7b78ba7a10d8f71b21180166"
"ref": "b0b590ce5ac0233a40b8fb957c249cf375a6ff21"
},
{
"consumer_repo": "UsEquitySnapshotPipelines",
"path": "uv.lock",
"package": "quant-platform-kit",
"source_repo": "QuantPlatformKit",
"ref": "37c81901160c5b31127a27dba1c63944933fb6bf"
"ref": "ff09c889ed21e2eb6fcb37f6cdaa159190ec82da"
},
{
"consumer_repo": "UsEquitySnapshotPipelines",
Expand All @@ -335,21 +335,21 @@
"path": "uv.lock",
"package": "us-equity-strategies",
"source_repo": "UsEquityStrategies",
"ref": "17ddb86c72d44b2c7b78ba7a10d8f71b21180166"
"ref": "b0b590ce5ac0233a40b8fb957c249cf375a6ff21"
},
{
"consumer_repo": "UsEquityStrategies",
"path": "pyproject.toml",
"package": "quant-platform-kit",
"source_repo": "QuantPlatformKit",
"ref": "651c9ac4f37ce6e7fe1bac84dc7646cd5abc9e6e"
"ref": "ff09c889ed21e2eb6fcb37f6cdaa159190ec82da"
},
{
"consumer_repo": "UsEquityStrategies",
"path": "uv.lock",
"package": "quant-platform-kit",
"source_repo": "QuantPlatformKit",
"ref": "651c9ac4f37ce6e7fe1bac84dc7646cd5abc9e6e"
"ref": "ff09c889ed21e2eb6fcb37f6cdaa159190ec82da"
}
]
}
73 changes: 69 additions & 4 deletions platform-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,19 +27,75 @@
"domains": {
"us_equity": {
"label_zh": "美股",
"label_en": "US Equity"
"label_en": "US Equity",
"scheduler_profile": "us_daily"
},
"hk_equity": {
"label_zh": "港股",
"label_en": "HK Equity"
"label_en": "HK Equity",
"scheduler_profile": "hk_daily"
},
"cn_equity": {
"label_zh": "A股",
"label_en": "CN A-share"
"label_en": "CN A-share",
"scheduler_profile": "cn_daily"
},
"crypto": {
"label_zh": "加密",
"label_en": "Crypto"
"label_en": "Crypto",
"scheduler_profile": "crypto_intraday"
}
},
"scheduling": {
"profiles": {
"us_daily": {
"timezone": "America/New_York",
"main_time": "45 15 * * *",
"probe_time": "35 9,15 * * *",
"precheck_time": "45 9 * * *"
},
"us_dca_month_end": {
"timezone": "America/New_York",
"main_time": "45 15 25-28 * *",
"probe_time": "35 9,15 25-28 * *",
"precheck_time": "45 9 25-28 * *"
},
"us_snapshot_month_start": {
"timezone": "America/New_York",
"main_time": "45 15 1-7 * *",
"probe_time": "35 9,15 1-7 * *",
"precheck_time": "45 9 1-7 * *"
},
"hk_daily": {
"timezone": "Asia/Hong_Kong",
"main_time": "45 15 * * *",
"probe_time": "35 9,15 * * *",
"precheck_time": "45 9 * * *"
},
"hk_snapshot_month_start": {
"timezone": "Asia/Hong_Kong",
"main_time": "45 15 1-7 * *",
"probe_time": "35 9,15 1-7 * *",
"precheck_time": "45 9 1-7 * *"
},
"cn_daily": {
"timezone": "Asia/Shanghai",
"main_time": "45 15 * * *",
"probe_time": "35 9,15 * * *",
"precheck_time": "45 9 * * *"
},
"cn_snapshot_month_start": {
"timezone": "Asia/Shanghai",
"main_time": "45 15 1-7 * *",
"probe_time": "35 9,15 1-7 * *",
"precheck_time": "45 9 1-7 * *"
},
"crypto_intraday": {
"timezone": "UTC",
"main_time": "0,30 * * * *",
"probe_time": "0 6,18 * * *",
"precheck_time": "55 5 * * *"
}
}
},
"platforms": {
Expand Down Expand Up @@ -353,6 +409,7 @@
"label": "纳指标普定投",
"label_en": "NASDAQ/S&P 500 DCA",
"domain": "us_equity",
"scheduler_profile": "us_dca_month_end",
"runtime_enabled": true,
"lifecycle_stage": "runtime_enabled",
"can_switch_live": true,
Expand All @@ -377,6 +434,10 @@
"label": "IBIT比特币定投",
"label_en": "IBIT Bitcoin DCA",
"domain": "us_equity",
"scheduler_profile": "us_dca_month_end",
"scheduler_profile_by_plugin": {
"ibit_zscore_exit": "us_daily"
},
"runtime_enabled": true,
"lifecycle_stage": "runtime_enabled",
"can_switch_live": true,
Expand Down Expand Up @@ -440,6 +501,7 @@
"label": "罗素Top50领涨",
"label_en": "Russell Top50 Leaders",
"domain": "us_equity",
"scheduler_profile": "us_snapshot_month_start",
"runtime_enabled": true,
"lifecycle_stage": "runtime_enabled",
"can_switch_live": true,
Expand Down Expand Up @@ -596,6 +658,7 @@
"label": "港股红利质量",
"label_en": "HK Dividend Quality",
"domain": "hk_equity",
"scheduler_profile": "hk_snapshot_month_start",
"runtime_enabled": true,
"lifecycle_stage": "runtime_enabled",
"can_switch_live": true,
Expand Down Expand Up @@ -728,6 +791,7 @@
"label": "A股红利质量",
"label_en": "CN Dividend Quality",
"domain": "cn_equity",
"scheduler_profile": "cn_snapshot_month_start",
"runtime_enabled": false,
"lifecycle_stage": "research_backtest_only",
"can_switch_live": false,
Expand All @@ -746,6 +810,7 @@
"label": "创业板成长质量快照",
"label_en": "CN ChiNext Growth Quality Snapshot",
"domain": "cn_equity",
"scheduler_profile": "cn_snapshot_month_start",
"runtime_enabled": false,
"lifecycle_stage": "research_backtest_only",
"can_switch_live": false,
Expand Down
70 changes: 70 additions & 0 deletions python/scripts/build_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
import subprocess
import sys
from pathlib import Path
from zoneinfo import ZoneInfo, ZoneInfoNotFoundError

ROOT = Path(__file__).resolve().parents[2]
CONFIG_PATH = ROOT / "platform-config.json"
Expand All @@ -36,6 +37,7 @@
"allowed_execution_modes",
"blocked_live_reason",
}
SCHEDULER_FIELDS = {"timezone", "main_time", "probe_time", "precheck_time"}


def load_config() -> dict:
Expand All @@ -45,16 +47,84 @@ def load_config() -> dict:

def validate(config: dict) -> list[str]:
errors: list[str] = []
scheduling = config.get("scheduling")
scheduler_profiles = scheduling.get("profiles") if isinstance(scheduling, dict) else None
if not isinstance(scheduler_profiles, dict) or not scheduler_profiles:
errors.append("scheduling.profiles must be a non-empty object")
scheduler_profiles = {}
for profile, scheduler in scheduler_profiles.items():
if not isinstance(scheduler, dict):
errors.append(f"scheduler profile {profile}: must be an object")
continue
if set(scheduler) != SCHEDULER_FIELDS:
errors.append(
f"scheduler profile {profile}: fields must be {sorted(SCHEDULER_FIELDS)}"
)
continue
timezone = scheduler.get("timezone")
try:
ZoneInfo(str(timezone or ""))
except (ZoneInfoNotFoundError, ValueError):
errors.append(f"scheduler profile {profile}: invalid timezone {timezone!r}")
Comment thread
Pigbibi marked this conversation as resolved.
for field in SCHEDULER_FIELDS - {"timezone"}:
value = scheduler.get(field)
if not isinstance(value, str) or len(value.split()) not in {2, 5}:
errors.append(
f"scheduler profile {profile}: {field} must have 2 time fields or 5 cron fields"
)
for pid, pdata in config.get("platforms", {}).items():
if "capabilities" not in pdata:
errors.append(f"platform {pid}: missing capabilities")
if "default_account" not in pdata:
errors.append(f"platform {pid}: missing default_account")
if "supported_domains" not in pdata:
errors.append(f"platform {pid}: missing supported_domains")
domains = config.get("domains", {})
for domain, domain_data in domains.items():
if not isinstance(domain_data, dict):
errors.append(f"domain {domain}: must be an object")
continue
scheduler_profile = domain_data.get("scheduler_profile")
if not isinstance(scheduler_profile, str) or scheduler_profile not in scheduler_profiles:
errors.append(
f"domain {domain}: unknown scheduler_profile {scheduler_profile!r}"
)
for sid, sdata in config.get("strategies", {}).items():
if "domain" not in sdata:
errors.append(f"strategy {sid}: missing domain")
continue
domain_ref = sdata["domain"]
if not isinstance(domain_ref, str):
errors.append(f"strategy {sid}: domain must be a string")
domain_data = {}
elif domain_ref not in domains:
errors.append(f"strategy {sid}: unknown domain {domain_ref!r}")
domain_data = {}
else:
domain_data = domains[domain_ref]
if not isinstance(domain_data, dict):
domain_data = {}
strategy_scheduler_profile = sdata.get("scheduler_profile")
if strategy_scheduler_profile is not None and not isinstance(strategy_scheduler_profile, str):
errors.append(
f"strategy {sid}: scheduler_profile must be a string"
)
scheduler_profile = None
else:
scheduler_profile = strategy_scheduler_profile or domain_data.get("scheduler_profile")
if not isinstance(scheduler_profile, str) or scheduler_profile not in scheduler_profiles:
errors.append(
f"strategy {sid}: unknown scheduler_profile {scheduler_profile!r}"
)
plugin_overrides = sdata.get("scheduler_profile_by_plugin", {})
if not isinstance(plugin_overrides, dict):
errors.append(f"strategy {sid}: scheduler_profile_by_plugin must be an object")
else:
for plugin, override in plugin_overrides.items():
if not isinstance(override, str) or override not in scheduler_profiles:
errors.append(
f"strategy {sid}: plugin {plugin} references unknown scheduler_profile {override!r}"
)
return errors


Expand Down
Loading
Loading