From aed8948b793932fe8d66ba584309faaadfbb85f9 Mon Sep 17 00:00:00 2001 From: yuxuanhui Date: Sun, 9 Aug 2026 09:44:46 +0800 Subject: [PATCH] =?UTF-8?q?feat(sync):=20=E4=BC=98=E5=8C=96=E6=97=A5?= =?UTF-8?q?=E5=BF=97=E6=A0=BC=E5=BC=8F=E5=92=8C=E8=BF=9B=E5=BA=A6=E8=AE=B0?= =?UTF-8?q?=E5=BD=95=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- CONTEXT.md | 10 ++++++ ...la-semantics-and-independent-subsignals.md | 31 +++++++++++++++++++ .../modules/market_data/application/sync.py | 17 ++++++---- .../tests/unit/market_data/test_sync.py | 3 +- 4 files changed, 53 insertions(+), 8 deletions(-) create mode 100644 docs/adr/0005-selection-formula-semantics-and-independent-subsignals.md diff --git a/CONTEXT.md b/CONTEXT.md index 1be0b48..185c62e 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -7,6 +7,16 @@ **历史可复现分析**:给定一个历史交易日,在当前目标股票池范围内重建该日的行情与估值状态;价格采用数据库中当前最新修订的前复权行情,不要求还原某次同步时的价格版本。 _Avoid_: 用当前市值回填历史、把当前目标股票池当作无幸存者偏差的长期回测宇宙 +**选股策略**:一组针对明确目标交易日、依赖该日之前历史行情的选股规则;它产出可解释的选股信号,不等同于交易执行指令或收益承诺。 + +**知行 B1**:当前迁移中的知行选股策略名称;它按知行公式语义评估目标交易日,并包含七种可以独立命中的子信号。 + +**子信号**:选股公式中具有独立业务含义的命中分支;同一只股票在同一交易日可以同时命中多个子信号,不能因为存在合并条件而丢失分类信息。 + +**公式语义**:公式原文及已确认业务调整所表达的规则意图;当旧代码与公式意图冲突时,公式语义是迁移后的判断依据,旧代码只作为实现参考和差异证据。 + +**历史样本验证**:使用固定的历史行情样本和固定期望结果验证策略规则;它用于发现规则迁移偏差,不等同于回测收益评价。 + ## 市场数据 **股票主数据**:描述证券标识及其在市场中的基础状态,例如代码、名称、交易所和上市状态。 diff --git a/docs/adr/0005-selection-formula-semantics-and-independent-subsignals.md b/docs/adr/0005-selection-formula-semantics-and-independent-subsignals.md new file mode 100644 index 0000000..3e35424 --- /dev/null +++ b/docs/adr/0005-selection-formula-semantics-and-independent-subsignals.md @@ -0,0 +1,31 @@ +--- +status: accepted +--- + +# 选股策略以公式语义为准并独立保留子信号 + +知行选股策略迁移时,以通达信公式原文和已确认的业务调整作为规则语义来源, +旧项目 Python 实现只作为迁移参考与差异证据。原因是旧实现存在无法历史重放的 +数据行为,也会在多个 XG 条件同时命中时只保留第一个分类;直接追求旧输出兼容 +会把实现缺陷固化到新系统。 + +新策略使用 `zhixing_b1` 作为业务标识,并把七种子信号作为独立分类。同一股票、 +同一交易日可以产生多条不同分类的信号;每条信号的稳定身份由股票、交易日、 +策略标识和子信号分类共同确定。 + +## Considered Options + +- **完全兼容旧 Python 输出**:迁移成本较低,但会保留旧的首个分类截断和历史 + 数据前视风险。 +- **只按公式返回一条合并信号**:实现简单,但会丢失子信号的解释性和后续统计 + 维度。 +- **按公式语义计算并独立返回全部子信号**:需要增加分类结果和差异测试,但能 + 保持规则意图、解释能力和历史重放边界。 + +## Consequences + +- golden 验证需要同时记录命中分类集合和有意的旧实现差异。 +- 后续信号持久化可以使用 `(ts_code, trade_date, strategy, category)` 作为唯一 + 键,但本 ADR 不决定具体数据库表或 HTTP 契约。 +- 其他策略迁移时仍需逐一确认其公式来源与历史依赖,不能自动套用旧策略的输出 + 兼容规则。 diff --git a/zhixing-server/src/zhixing_server/modules/market_data/application/sync.py b/zhixing-server/src/zhixing_server/modules/market_data/application/sync.py index b4a28c8..8d0f1f0 100644 --- a/zhixing-server/src/zhixing_server/modules/market_data/application/sync.py +++ b/zhixing-server/src/zhixing_server/modules/market_data/application/sync.py @@ -425,8 +425,7 @@ class SyncMarketData: failure = self._failure("daily_basic", key, exc) failures.append(failure) logger.warning( - "market_data_sync_item_failed batch_id=%s stage=daily_basic item=%s " - "error_type=%s", + "market_data_sync_item_failed batch_id=%s stage=daily_basic item=%s error_type=%s", batch_id, key, failure.error_type, @@ -520,10 +519,16 @@ class SyncMarketData: ) -> None: """Log bounded, secret-free progress for a batch stage.""" - if not force and total > _PROGRESS_LOG_INTERVAL and current not in { - 1, - total, - } and current % _PROGRESS_LOG_INTERVAL != 0: + if ( + not force + and total > _PROGRESS_LOG_INTERVAL + and current + not in { + 1, + total, + } + and current % _PROGRESS_LOG_INTERVAL != 0 + ): return logger.info( "market_data_sync_progress batch_id=%s stage=%s progress=%d/%d item=%s " diff --git a/zhixing-server/tests/unit/market_data/test_sync.py b/zhixing-server/tests/unit/market_data/test_sync.py index 44358fe..e21b7a8 100644 --- a/zhixing-server/tests/unit/market_data/test_sync.py +++ b/zhixing-server/tests/unit/market_data/test_sync.py @@ -190,8 +190,7 @@ def test_sync_logs_progress_for_initialize_and_daily_update( assert initialize.status == "success" assert any("stage=daily_basic" in message for message in initialize_messages) assert any( - "stage=bar" in message and "progress=1/1" in message - for message in initialize_messages + "stage=bar" in message and "progress=1/1" in message for message in initialize_messages ) caplog.clear()