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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -137,3 +137,5 @@ uv run --no-sync python scripts/check_qpk_pin_consistency.py
## License

See [LICENSE](LICENSE).

Display/notification naming configuration: [display names](docs/display-names.md).
11 changes: 11 additions & 0 deletions docs/display-names.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Configurable display names

QRS `platform-config.json` owns public strategy/platform labels. `display-names.config.json` owns display-only account aliases, lanes, legacy labels and aliases. `python/scripts/build_display_names.py` combines them into one fixed mapping; it never changes trading catalog metadata or runtime configuration hashes.

Run `python3 python/scripts/build_display_names.py --repos-root /path/to/source/checkouts` from QRS, then `--check` with the same arguments. Review and commit each repository separately. Consumer `display_names.py/json` files are generated snapshots, not independently maintained maps. They avoid upgrading pinned trading dependencies just for labels. No runtime fetch is performed. Refresh the snapshots together when labels change.

Python: optional `QSL_DISPLAY_NAMES_JSON` JSON object. Console: optional `VITE_QSL_DISPLAY_NAMES_JSON` at build time. Example (synthetic): `{"strategies":{"custom_profile":{"zh":"ETF-轮动-202610","en":"ETF-Rotation-202610"}},"accounts":{"binance":{"crypto_combo":"live"}}}`. Invalid JSON falls back to defaults; unknown labels retain the supplied fallback. Set private aliases only in the existing protected configuration, never in public source or PR bodies.

Precedence: explicit override, generated map, caller fallback, unchanged technical key. Locale falls back to English. New strategy/variant display names use `<instrument>-<what it does>-<YYYYMM>` when the actual origin month is established. Existing reviewed names stay until an explicit dated name is provided; do not invent a month or rename a technical ID to fit the pattern. Keep legacy aliases/descriptions. `crypto_combo` is displayed as `live` only under Binance; `paper` remains `paper`. Binance notifications can explicitly select another display alias with `QSL_NOTIFICATION_ACCOUNT_ALIAS` (for example `paper` in an isolated preview).

Resolved names are for display only. Never pass them to trading selection, account selection, target IDs, fingerprints, release/continuity bindings, Secret names, storage/object keys, claim keys or notification deduplication. Workflow/environment IDs and report profile IDs retain their operational meaning. CI and source merges do not establish production adoption. Trading runtime changes require their separate reviewed deployment; this naming work deploys no trading service.
14 changes: 14 additions & 0 deletions docs/naming-trading-diff.zh-CN.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# 交易差异说明:LongBridgePlatform

基线:`e991261738006ddeaee9b085ad9b783ce8b6dcf7`。作者自审与 AST 检查,不声称独立人工审查。

只改通知显示名解析、固定显示配置副本、测试和说明。账户 key、策略 profile、RuntimeTarget、fingerprint、release/continuity binding、Secret 名称、存储 key、通知去重键均不改。交易依赖及锁文件未改;SOXL 0.679 / 总仓位 0.97 / 现金 0.03 未改。不部署交易服务。

现有源码中的券商、订单、风险、状态和报告持久化调用表达式及其参数比较结果:

| 文件 | 敏感调用数 | 参数不变 |
|---|---:|---|
| `notifications/display_names.py` | 0 | 是 |
| `notifications/telegram.py` | 0 | 是 |

新增解析器只读同目录的生成 JSON 及可选显示环境配置,不联网、不调用券商、不写持久状态。显示值不得作为机器标识再输入交易调用。固定副本来自 QRS 的生成器,避免只为改名升级交易依赖。PR 保持 draft;即使 CI 通过,也未批准实际运行版本采用。
241 changes: 241 additions & 0 deletions notifications/display_names.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
{
"account_aliases": {
"hk": {
"en": "HK",
"zh": "香港"
},
"live": {
"en": "live",
"zh": "实盘"
},
"paper": {
"en": "paper",
"zh": "模拟"
},
"sg": {
"en": "SG",
"zh": "新加坡"
},
"soxl": {
"en": "SOXL",
"zh": "SOXL"
},
"tqqq": {
"en": "TQQQ",
"zh": "TQQQ"
}
},
"accounts": {
"binance": {
"Binance": {
"en": "live",
"zh": "实盘"
},
"binance": {
"en": "live",
"zh": "实盘"
},
"crypto_combo": {
"en": "live",
"zh": "实盘"
},
"default": {
"en": "live",
"zh": "实盘"
}
}
},
"domains": {
"cn_equity": {
"en": "CN A-share",
"zh": "A股"
},
"crypto": {
"en": "Crypto",
"zh": "加密"
},
"hk_equity": {
"en": "HK Equity",
"zh": "港股"
},
"us_equity": {
"en": "US Equity",
"zh": "美股"
}
},
"lanes": {
"dry_run": {
"en": "dry run",
"zh": "演练"
},
"live": {
"en": "live",
"zh": "实盘"
},
"paper": {
"en": "paper",
"zh": "模拟"
},
"research": {
"en": "research",
"zh": "研究"
},
"shadow": {
"en": "shadow",
"zh": "影子"
}
},
"platforms": {
"binance": "Binance",
"firstrade": "Firstrade",
"ibkr": "IBKR",
"longbridge": "LongBridge",
"qmt": "QMT",
"schwab": "Schwab"
},
"senders": {
"quant_sentinel": {
"en": "QuantSentinel",
"zh": "量化哨兵"
}
},
"strategies": {
"cn_chinext_growth_momentum_quality": {
"en": "CN ChiNext Growth Momentum Quality",
"zh": "创业板成长动量质量"
},
"cn_chinext_growth_momentum_quality_snapshot": {
"en": "CN ChiNext Growth Quality Snapshot",
"zh": "创业板成长质量快照"
},
"cn_chinext_tactical_rotation": {
"en": "CN ChiNext Tactical Rotation",
"zh": "创业板战术轮动"
},
"cn_csi500_multi_factor_snapshot": {
"en": "CN CSI 500 Multi-Factor Snapshot",
"zh": "A股中证500多因子"
},
"cn_dividend_quality_snapshot": {
"en": "CN Dividend Quality",
"zh": "A股红利质量"
},
"cn_equity_combo": {
"en": "CN Alpha Combo",
"zh": "A股进取组合"
},
"cn_index_etf_tactical_rotation": {
"en": "CN Index ETF Tactical Rotation",
"zh": "A股宽基ETF战术轮动"
},
"cn_industry_etf_rotation": {
"en": "CN Industry ETF Rotation",
"zh": "A股行业ETF轮动"
},
"cn_industry_etf_rotation_aggressive": {
"en": "CN ETF Rotation",
"zh": "A股ETF轮动"
},
"cn_star_growth_momentum_quality": {
"en": "CN STAR Growth Momentum Quality",
"zh": "科创板成长动量质量"
},
"cn_stock_momentum_rotation": {
"en": "CN Stock Momentum Rotation",
"zh": "A股股票动量轮动"
},
"crypto_btc_dca": {
"en": "BTC DCA",
"zh": "BTC定投"
},
"crypto_equity_combo": {
"en": "Crypto Core Combo",
"zh": "加密动量组合"
},
"crypto_leader_rotation": {
"en": "Crypto Leader Rotation",
"zh": "加密领涨轮动"
},
"crypto_live_pool_rotation": {
"en": "Crypto Pool Rotation",
"zh": "加密池轮动"
},
"crypto_trend_rotation": {
"en": "Altcoin Trend",
"zh": "山寨趋势轮动"
},
"global_etf_confidence_vol_gate": {
"en": "Global ETF Confidence Vol Gate",
"zh": "全球 ETF 置信波动门控"
},
"global_etf_rotation": {
"en": "Global ETF Rotation",
"zh": "全球ETF轮动"
},
"hk_equity_combo": {
"en": "HK Core Combo",
"zh": "港股恒生组合"
},
"hk_global_etf_tactical_rotation": {
"en": "HK ETF Tactical Rotation",
"zh": "港股ETF战术轮动"
},
"hk_low_vol_dividend_quality_snapshot": {
"en": "HK Dividend Quality",
"zh": "港股红利质量"
},
"ibit_smart_dca": {
"en": "IBIT Bitcoin DCA",
"zh": "IBIT比特币定投"
},
"nasdaq_sp500_smart_dca": {
"en": "NASDAQ/S&P 500 DCA",
"zh": "纳指标普定投"
},
"qqq_tech_enhancement": {
"en": "Tech/Communication Pullback Enhancement",
"zh": "科技通信回调增强"
},
"russell_top50_leader_rotation": {
"en": "Russell Top50 Leaders",
"zh": "罗素Top50领涨"
},
"soxl_soxx_core_only_p2_v7_longterm_compounding_cash_reserve": {
"en": "SOXL/SOXX Core Compounding (Cash Reserve)",
"zh": "SOXL/SOXX 核心复利(现金保留)"
},
"soxl_soxx_trend_income": {
"en": "Semiconductor Trend Income",
"zh": "半导体趋势收益"
},
"tech_communication_pullback_enhancement": {
"en": "Tech/Communication Pullback Enhancement",
"zh": "科技通信回调增强"
},
"tecl_xlk_trend_income": {
"en": "TECL/XLK Trend Income",
"zh": "TECL/XLK趋势收益"
},
"tqqq_growth_income": {
"en": "NASDAQ Growth Income",
"zh": "纳斯达克增长收益"
},
"us_equity_combo": {
"en": "US Core Income Combo",
"zh": "美股核心收益组合"
},
"us_equity_combo_core": {
"en": "US Core Combo",
"zh": "美股核心组合"
},
"us_equity_combo_leveraged": {
"en": "US Alpha Combo",
"zh": "美股加速组合"
}
},
"strategy_aliases": {
"bitcoin_etf_smart_dca": "ibit_smart_dca",
"global_etf_confidence_vol_gate": "global_etf_rotation",
"global_macro_etf_rotation": "global_etf_rotation"
}
}
54 changes: 54 additions & 0 deletions notifications/display_names.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
"""Display-only resolver; generated defaults come from QRS platform-config.json.

Never use resolved names for execution identity, bindings, keys or deduplication.
QSL_DISPLAY_NAMES_JSON accepts an optional JSON object with per-section overrides.
Invalid overrides fall back to bundled names so an urgent alert can still render.
"""
from __future__ import annotations

import json
import os
from functools import lru_cache
from pathlib import Path


@lru_cache(maxsize=1)
def _defaults():
return json.loads(Path(__file__).with_suffix(".json").read_text(encoding="utf-8"))


@lru_cache(maxsize=8)
def _overrides(raw):
try:
value = json.loads(raw or "{}")
except (ValueError, TypeError):
return {}
return value if isinstance(value, dict) else {}


def _text(value, locale):
if isinstance(value, dict):
language = "zh" if str(locale).lower().startswith("zh") else "en"
value = value.get(language) or value.get("en")
return value.strip() if isinstance(value, str) else ""


def display_name(kind, key, locale="en", *, fallback="", platform=""):
"""Resolve an explicit display key, retaining unknown operator labels."""
key = str(key or "").strip()
defaults = _defaults()
overrides = _overrides(os.environ.get("QSL_DISPLAY_NAMES_JSON", ""))
aliases = {**defaults.get("strategy_aliases", {}), **(overrides.get("strategy_aliases", {}) if isinstance(overrides.get("strategy_aliases"), dict) else {})}
canonical = aliases.get(key, key) if kind == "strategies" else key
if not isinstance(canonical, str):
canonical = key
for source in (overrides, defaults):
section = source.get(kind, {})
if kind == "accounts" and isinstance(section, dict):
section = section.get(str(platform).lower(), {})
if isinstance(section, dict):
first, second = (key, canonical) if source is overrides else (canonical, key)
result = _text(section.get(first), locale) or _text(section.get(second), locale)
if result:
return result
return str(fallback or key)
Loading
Loading