Skip to content

Commit 7b30259

Browse files
Pigbibiclaude
andcommitted
feat: Phase 2 — index.html config driven by platform-config.json
- build_platform_config.py now injects <script id="platform-config"> into index.html with generated config globals - index.html inline script uses window.__PLATFORM_CONFIG__ || {fallback} pattern — generated values take priority, hardcoded is fallback - 4 key blocks migrated: platformConfig, defaultAccountOptions, incomeLayer/optionOverlay defaults, dcaProfileDefaults - All 11 hardcoded blocks still exist as fallback for safety config flow: platform-config.json → build → injected <script> + config.js Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 2a8a669 commit 7b30259

4 files changed

Lines changed: 121 additions & 290 deletions

File tree

scripts/build_platform_config.py

Lines changed: 103 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -229,17 +229,119 @@ def build_strategy_profiles(config: dict) -> str:
229229
return payload
230230

231231

232+
def inject_index_html(config: dict) -> None:
233+
"""Inject generated config block into index.html."""
234+
index_path = ROOT / "web" / "strategy-switch-console" / "index.html"
235+
html = index_path.read_text(encoding="utf-8")
236+
237+
platforms = config["platforms"]
238+
strategies = config["strategies"]
239+
240+
# Generate injection block
241+
pc = {}
242+
dao = {}
243+
for pid, pdata in platforms.items():
244+
caps = pdata["capabilities"]
245+
depl = pdata["deployment"]
246+
pc[pid] = {
247+
"dry_run_only": depl.get("dry_run_only", False),
248+
"margin_policy": caps.get("margin_policy", False),
249+
"reserved_cash": caps.get("reserved_cash", False),
250+
"income_layer": caps.get("income_layer", False),
251+
"option_overlay": caps.get("option_overlay", False),
252+
"dca": caps.get("dca", False),
253+
"execution_mode": depl.get("default_execution_mode", "live"),
254+
"service_name": depl.get("service_name", ""),
255+
"default_execution_mode": depl.get("default_execution_mode", "live"),
256+
}
257+
acct = pdata.get("default_account", {})
258+
entry = {
259+
"key": acct.get("key", pid),
260+
"label": acct.get("label", pdata.get("label", pid)),
261+
"target_name": acct.get("target_name", acct.get("key", pid)),
262+
"supported_domains": acct.get("supported_domains", pdata.get("supported_domains", [])),
263+
"cash_currency": acct.get("cash_currency", "USD"),
264+
}
265+
for fld in ("default_strategy_profile", "service_name", "account_scope",
266+
"deployment_selector", "account_selector", "default_execution_mode",
267+
"min_reserved_cash_usd", "reserved_cash_ratio",
268+
"cash_only_execution_mode", "dca_mode", "dca_base_investment_usd"):
269+
val = acct.get(fld)
270+
if val: entry[fld] = val
271+
if "service_name" not in entry:
272+
entry["service_name"] = depl.get("service_name", "")
273+
if "default_execution_mode" not in entry:
274+
entry["default_execution_mode"] = depl.get("default_execution_mode", "live")
275+
dao[pid] = [entry]
276+
277+
dca = {}
278+
inc_layer = {}
279+
opt_overlay = {}
280+
for sid, sdata in strategies.items():
281+
feat = sdata.get("features", {})
282+
dd = sdata.get("dca_defaults")
283+
if dd:
284+
dca[sid] = {"defaultMode": dd.get("default_mode", "fixed"),
285+
"defaultBaseInvestmentUsd": str(dd.get("default_base_investment_usd", "1000"))}
286+
if feat.get("income_layer"):
287+
idl = sdata.get("income_layer_defaults", {})
288+
inc_layer[sid] = {"startUsd": int(idl.get("start_usd", 0)),
289+
"maxRatio": str(idl.get("max_ratio", "")),
290+
"allocations": idl.get("allocations", {})}
291+
if feat.get("option_overlay"):
292+
odl = sdata.get("option_overlay_defaults", {})
293+
families = []
294+
if odl.get("growth_enabled"):
295+
families.append({"family": "growth", "recipe": odl["growth_recipe"],
296+
"startUsd": odl["growth_start_usd"],
297+
"ratio": str(odl.get("nav_budget_ratio", "")), "ratioKind": "budget"})
298+
if odl.get("income_enabled"):
299+
families.append({"family": "income", "recipe": odl["income_recipe"],
300+
"startUsd": odl["income_start_usd"],
301+
"ratio": str(odl.get("nav_risk_ratio", "")), "ratioKind": "risk"})
302+
opt_overlay[sid] = {"liveGate": odl.get("live_gate", ""),
303+
"liveStatus": odl.get("live_status", ""),
304+
"families": families}
305+
306+
block_lines = [
307+
"<!-- Generated by build_platform_config.py from platform-config.json -->",
308+
'<script id="platform-config">',
309+
f"window.__PLATFORM_CONFIG__ = {json.dumps(pc, ensure_ascii=False)};",
310+
f"window.__DEFAULT_ACCOUNT_OPTIONS__ = {json.dumps(dao, ensure_ascii=False)};",
311+
f"window.__DCA_PROFILE_DEFAULTS__ = {json.dumps(dca, ensure_ascii=False)};",
312+
f"window.__INCOME_LAYER_DEFAULTS__ = {json.dumps(inc_layer, ensure_ascii=False)};",
313+
f"window.__OPTION_OVERLAY_DEFAULTS__ = {json.dumps(opt_overlay, ensure_ascii=False)};",
314+
"</script>",
315+
]
316+
block = "\n".join(block_lines)
317+
318+
# Replace existing block or inject before </head>
319+
existing_start = html.find('<script id="platform-config">')
320+
if existing_start >= 0:
321+
existing_end = html.find('</script>', existing_start) + 9
322+
html = html[:existing_start] + block + html[existing_end:]
323+
else:
324+
head_end = html.find('</head>')
325+
html = html[:head_end] + "\n" + block + "\n" + html[head_end:]
326+
327+
index_path.write_text(html, encoding="utf-8")
328+
print(f"Injected config into: {index_path}")
329+
330+
232331
def main() -> int:
233332
config = json.loads(SOURCE.read_text(encoding="utf-8"))
234333

235-
# Generate config.js
334+
# Generate config.js (ES module for worker.js)
236335
module = build_config_module(config)
237336
TARGET.write_text(module, encoding="utf-8")
238337

239338
# Generate strategy_profiles_asset.js
240339
profiles = build_strategy_profiles(config)
241340
STRATEGY_TARGET.write_text(profiles, encoding="utf-8")
242341

342+
# Inject config block into index.html
343+
inject_index_html(config)
344+
243345
print(f"Generated: {TARGET}")
244346
print(f"Generated: {STRATEGY_TARGET}")
245347
return 0

web/strategy-switch-console/index.html

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,6 +1317,16 @@
13171317
}
13181318
}
13191319
</style>
1320+
1321+
<!-- Generated by build_platform_config.py -->
1322+
<!-- Generated by build_platform_config.py from platform-config.json -->
1323+
<script id="platform-config">
1324+
window.__PLATFORM_CONFIG__ = {"longbridge": {"dry_run_only": false, "margin_policy": true, "reserved_cash": true, "income_layer": true, "option_overlay": true, "dca": true, "execution_mode": "live", "service_name": "", "default_execution_mode": "live"}, "ibkr": {"dry_run_only": false, "margin_policy": true, "reserved_cash": true, "income_layer": true, "option_overlay": true, "dca": false, "execution_mode": "live", "service_name": "", "default_execution_mode": "live"}, "schwab": {"dry_run_only": false, "margin_policy": true, "reserved_cash": true, "income_layer": true, "option_overlay": true, "dca": true, "execution_mode": "live", "service_name": "charles-schwab-quant-service", "default_execution_mode": "live"}, "firstrade": {"dry_run_only": false, "margin_policy": true, "reserved_cash": true, "income_layer": true, "option_overlay": true, "dca": true, "execution_mode": "live", "service_name": "firstrade-quant-service", "default_execution_mode": "live"}, "qmt": {"dry_run_only": true, "margin_policy": false, "reserved_cash": false, "income_layer": false, "option_overlay": false, "dca": false, "execution_mode": "paper", "service_name": "qmt-quant-service", "default_execution_mode": "paper"}, "binance": {"dry_run_only": false, "margin_policy": false, "reserved_cash": false, "income_layer": false, "option_overlay": false, "dca": true, "execution_mode": "live", "service_name": "", "default_execution_mode": "live"}};
1325+
window.__DEFAULT_ACCOUNT_OPTIONS__ = {"longbridge": [{"key": "preview", "label": "LongBridge", "target_name": "preview", "supported_domains": ["us_equity", "hk_equity"], "cash_currency": "USD", "default_strategy_profile": "tqqq_growth_income", "default_execution_mode": "live", "reserved_cash_ratio": "0.03", "service_name": ""}], "ibkr": [{"key": "preview", "label": "IBKR", "target_name": "preview", "supported_domains": ["us_equity", "hk_equity"], "cash_currency": "USD", "default_strategy_profile": "soxl_soxx_trend_income", "default_execution_mode": "live", "reserved_cash_ratio": "0.03", "service_name": ""}], "schwab": [{"key": "preview", "label": "Schwab", "target_name": "preview", "supported_domains": ["us_equity"], "cash_currency": "USD", "default_strategy_profile": "soxl_soxx_trend_income", "default_execution_mode": "live", "min_reserved_cash_usd": "150", "reserved_cash_ratio": "0.03", "service_name": "charles-schwab-quant-service"}], "firstrade": [{"key": "preview", "label": "Firstrade", "target_name": "preview", "supported_domains": ["us_equity"], "cash_currency": "USD", "default_strategy_profile": "ibit_smart_dca", "default_execution_mode": "live", "reserved_cash_ratio": "0.03", "service_name": "firstrade-quant-service"}], "qmt": [{"key": "default", "label": "QMT", "target_name": "default", "supported_domains": ["cn_equity"], "cash_currency": "CNY", "default_strategy_profile": "cn_industry_etf_rotation", "service_name": "qmt-quant-service", "default_execution_mode": "paper"}], "binance": [{"key": "default", "label": "Binance", "target_name": "default", "supported_domains": ["crypto"], "cash_currency": "USD", "default_strategy_profile": "crypto_live_pool_rotation", "service_name": "", "default_execution_mode": "live"}]};
1326+
window.__DCA_PROFILE_DEFAULTS__ = {"nasdaq_sp500_smart_dca": {"defaultMode": "fixed", "defaultBaseInvestmentUsd": "1000"}, "ibit_smart_dca": {"defaultMode": "fixed", "defaultBaseInvestmentUsd": "1000"}, "crypto_btc_dca": {"defaultMode": "fixed", "defaultBaseInvestmentUsd": "100"}};
1327+
window.__INCOME_LAYER_DEFAULTS__ = {"tqqq_growth_income": {"startUsd": 250000, "maxRatio": "0.55", "allocations": {"SCHD": 0.3, "DGRO": 0.2, "SGOV": 0.4, "SPYI": 0.08, "QQQI": 0.02}}, "soxl_soxx_trend_income": {"startUsd": 150000, "maxRatio": "0.95", "allocations": {"SCHD": 0.15, "DGRO": 0.1, "SGOV": 0.7, "SPYI": 0.04, "QQQI": 0.01}}, "global_etf_rotation": {"startUsd": 500000, "maxRatio": "0.15", "allocations": {"SCHD": 0.4, "DGRO": 0.25, "SGOV": 0.3, "SPYI": 0.05}}, "russell_top50_leader_rotation": {"startUsd": 300000, "maxRatio": "0.25", "allocations": {"SCHD": 0.45, "DGRO": 0.3, "SGOV": 0.25}}, "us_equity_combo": {"startUsd": 300000, "maxRatio": "0.25", "allocations": {"SCHD": 0.25, "DGRO": 0.25, "SGOV": 0.2, "SPYI": 0.15, "QQQI": 0.15}}};
1328+
window.__OPTION_OVERLAY_DEFAULTS__ = {"tqqq_growth_income": {"liveGate": "promotion_required", "liveStatus": "research_only", "families": [{"family": "growth", "recipe": "tqqq_leaps_growth_v1", "startUsd": "250000", "ratio": "0.03", "ratioKind": "budget"}]}, "soxl_soxx_trend_income": {"liveGate": "promotion_required", "liveStatus": "research_only", "families": [{"family": "income", "recipe": "soxx_put_credit_spread_income_v1", "startUsd": "150000", "ratio": "0.01", "ratioKind": "risk"}]}, "global_etf_rotation": {"liveGate": "promotion_required", "liveStatus": "research_only", "families": [{"family": "growth", "recipe": "spy_leaps_growth_v1", "startUsd": "500000", "ratio": "0.015", "ratioKind": "budget"}]}, "russell_top50_leader_rotation": {"liveGate": "promotion_required", "liveStatus": "research_only", "families": [{"family": "growth", "recipe": "spy_leaps_growth_v1", "startUsd": "300000", "ratio": "0.015", "ratioKind": "budget"}]}, "us_equity_combo": {"liveGate": "promotion_required", "liveStatus": "research_only", "families": [{"family": "growth", "recipe": "spy_leaps_growth_v1", "startUsd": "300000", "ratio": "0.015", "ratioKind": "budget"}]}};
1329+
</script>
13201330
</head>
13211331
<body class="app-loading">
13221332
<header class="topbar">
@@ -1525,7 +1535,7 @@ <h2 data-i18n="summary">切换摘要</h2>
15251535
// Alias for backward compatibility
15261536
const defaultRepositories = platformRepositories;
15271537

1528-
const defaultAccountOptions = {
1538+
const defaultAccountOptions = window.__DEFAULT_ACCOUNT_OPTIONS__ || {
15291539
binance: [{"key": "default", "label": "Binance", "target_name": "default", "cash_currency": "USD", "default_strategy_profile": "crypto_equity_combo", "supported_domains": ["crypto"]}],
15301540
firstrade: [{"key": "preview", "label": "Firstrade", "target_name": "preview", "supported_domains": ["us_equity"], "cash_currency": "USD", "default_execution_mode": "live", "service_name": "firstrade-quant-service"}],
15311541
ibkr: [{"key": "preview", "label": "IBKR", "target_name": "preview", "supported_domains": ["us_equity", "hk_equity"], "cash_currency": "USD", "default_execution_mode": "live"}],
@@ -1541,7 +1551,7 @@ <h2 data-i18n="summary">切换摘要</h2>
15411551
us_equity: { zh: "美股", en: "US Equity" },
15421552
};
15431553

1544-
const platformConfig = {
1554+
const platformConfig = window.__PLATFORM_CONFIG__ || {
15451555
binance: {
15461556
dry_run_only: false,
15471557
margin_policy: false,
@@ -1655,7 +1665,7 @@ <h2 data-i18n="summary">切换摘要</h2>
16551665
const incomeLayerEnabledVariable = "INCOME_LAYER_ENABLED";
16561666
const incomeLayerStartUsdVariable = "INCOME_LAYER_START_USD";
16571667
const incomeLayerMaxRatioVariable = "INCOME_LAYER_MAX_RATIO";
1658-
const dcaProfileDefaults = {
1668+
const dcaProfileDefaults = window.__DCA_PROFILE_DEFAULTS__ || {
16591669
nasdaq_sp500_smart_dca: { defaultMode: "fixed", defaultBaseInvestmentUsd: "1000" },
16601670
ibit_smart_dca: { defaultMode: "fixed", defaultBaseInvestmentUsd: "1000" },
16611671
crypto_btc_dca: { defaultMode: "fixed", defaultBaseInvestmentUsd: "100" },
@@ -1733,8 +1743,8 @@ <h2 data-i18n="summary">切换摘要</h2>
17331743
maxRatio: "0.25",
17341744
allocations: { SCHD: 0.25, DGRO: 0.25, SGOV: 0.20, SPYI: 0.15, QQQI: 0.15 },
17351745
}};
1736-
let incomeLayerDefaults = {};
1737-
const fallbackOptionOverlayDefaults = {
1746+
let incomeLayerDefaults = window.__INCOME_LAYER_DEFAULTS__ || {};
1747+
const fallbackOptionOverlayDefaults = window.__OPTION_OVERLAY_DEFAULTS__ || {
17381748
tqqq_growth_income: {
17391749
liveGate: "promotion_required",
17401750
liveStatus: "research_only",

web/strategy-switch-console/page_asset.js

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)