data-backtest-live-common 0.2.0__py3-none-any.whl
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.
- data_backtest_live_common-0.2.0.dist-info/METADATA +143 -0
- data_backtest_live_common-0.2.0.dist-info/RECORD +22 -0
- data_backtest_live_common-0.2.0.dist-info/WHEEL +5 -0
- data_backtest_live_common-0.2.0.dist-info/licenses/LICENSE +21 -0
- data_backtest_live_common-0.2.0.dist-info/top_level.txt +1 -0
- dbl_common/__init__.py +93 -0
- dbl_common/adjust.py +74 -0
- dbl_common/boards.py +162 -0
- dbl_common/circuit_breaker.py +82 -0
- dbl_common/corporate_actions.py +177 -0
- dbl_common/fees.py +171 -0
- dbl_common/halts.py +182 -0
- dbl_common/io_utils.py +70 -0
- dbl_common/lots.py +110 -0
- dbl_common/price_limit.py +326 -0
- dbl_common/proc.py +130 -0
- dbl_common/settlement.py +108 -0
- dbl_common/ticks.py +57 -0
- dbl_common/trade_calendar.py +128 -0
- dbl_common/trading_calendar.py +202 -0
- dbl_common/trading_hours.py +159 -0
- dbl_common/vec.py +134 -0
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: data-backtest-live-common
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: 数据(Data)/回测(Backtest)/实盘(Live)三系统公共库:复权、A股交易规则全集(涨跌幅/时段/费用/交收/熔断/除权)、交易日历、原子写、无黑窗子进程
|
|
5
|
+
Author: dbl_common maintainers
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2026 data-backtest-live-common contributors
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Keywords: quant,data,backtest,live-trading,a-share,tushare,akshare
|
|
29
|
+
Classifier: Development Status :: 4 - Beta
|
|
30
|
+
Classifier: Intended Audience :: Developers
|
|
31
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
32
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
33
|
+
Classifier: Operating System :: Microsoft :: Windows
|
|
34
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
40
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
41
|
+
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
42
|
+
Requires-Python: >=3.10
|
|
43
|
+
Description-Content-Type: text/markdown
|
|
44
|
+
License-File: LICENSE
|
|
45
|
+
Requires-Dist: pandas
|
|
46
|
+
Requires-Dist: numpy
|
|
47
|
+
Requires-Dist: duckdb
|
|
48
|
+
Provides-Extra: test
|
|
49
|
+
Requires-Dist: pytest; extra == "test"
|
|
50
|
+
Dynamic: license-file
|
|
51
|
+
|
|
52
|
+
# data-backtest-live-common(导入名 `dbl_common`)
|
|
53
|
+
|
|
54
|
+
数据(Data)/ 回测(Backtest)/ 实盘(Live Trading)三个系统**共用**的公共库。
|
|
55
|
+
独立项目,像 akshare 一样 pip 安装后直接 import 使用。
|
|
56
|
+
|
|
57
|
+
- **发行名(pip 名)**:`data-backtest-live-common` —— 数据/回测/实盘三个英文单词 + common(用户 2026-09-26 指定口径)
|
|
58
|
+
- **导入名**:`dbl_common` —— 三词首字母 + common(对齐 `beautifulsoup4 → import bs4` 的短名惯例,免得每次 import 敲 24 个字符)
|
|
59
|
+
- **版本**:0.2.0 —— 2026-09-26 从 `quant_data_center/common` 抽出独立成库;**2026-09-27 并入原 `market_common`(A 股交易规则库)两库合一**,`f:/code/finance/market_common` 内容已全部迁入本库并停止维护(原目录删除被环境安全删除守卫拦截,只剩弃用标记,可随时人工删除)
|
|
60
|
+
|
|
61
|
+
## 收录标准(硬规则)
|
|
62
|
+
|
|
63
|
+
只收「**数据、回测、实盘三个系统都会用到**」的代码。
|
|
64
|
+
单系统专属的公共代码放回各系统自己的 common 包
|
|
65
|
+
(例如 quant_data_center 的 throttle 限频 / pagination 翻页是采集专属,留在那边)。
|
|
66
|
+
|
|
67
|
+
## 模块清单
|
|
68
|
+
|
|
69
|
+
| 模块 | 干什么 | 三系统各自的用途 |
|
|
70
|
+
|---|---|---|
|
|
71
|
+
| `adjust` | 本地复权引擎(hfq = raw×adj_factor 锚定上市日;qfq 锚定最新) | 数据:清洗层标准化;回测:价格连续序列;实盘:指标口径 |
|
|
72
|
+
| `boards` | 证券代码规范化 / 品种识别 / 板块判定(规则库最底层,纯函数零依赖) | 所有系统问「这代码是什么品种、哪个板块」 |
|
|
73
|
+
| `price_limit` | 涨跌停**规则全集**(主板/科创/创业/北交/ST/ETF/转债/B股/新股首日非对称档,含历史制度变更,返回 `LimitSpec` 值对象) | 数据:审核规则;回测:判板;实盘:委托价边界 |
|
|
74
|
+
| `vec` | 涨跌停**向量化引擎唯一实现**(`limit_prices`/`flags_from_bars`/`board_flags`/`hfq_ret_on`,spec 驱动,支持 ETF 三位报价) | 回测全市场判板(5000+ 只 × 数千日) |
|
|
75
|
+
| `trade_calendar` | 交易日历读取(Tushare cal Parquet;`cal_glob` 必传,不绑定任何系统路径) | 三系统同一套日历口径 |
|
|
76
|
+
| `trading_calendar` | 交易日历**注入契约**(`InjectedCalendar`:BaoStock CSV / QDC raw 层 Parquet,三态诚实) | 回测/实盘注入自己的日历数据源 |
|
|
77
|
+
| `trading_hours` | 交易时段(集合竞价/连续竞价/收盘集合竞价,`Phase` 枚举) | 实盘下单时段校验;回测撮合时点 |
|
|
78
|
+
| `settlement` | 交收制度(T+1/T+0/T+3,`STOCK_T1_START`) | 实盘可卖判断;回测成交约束 |
|
|
79
|
+
| `lots` / `ticks` | 申报单位(手数)/ 最小变动价位(含 snap/校验) | 实盘委托合法性;回测撮合精度 |
|
|
80
|
+
| `fees` | 交易费用(印花税/过户费**时间线**,佣金必显式传) | 回测净收益;实盘成本预估 |
|
|
81
|
+
| `circuit_breaker` | 2016 熔断制度(历史触发记录) | 回测跨 2016-01 必须剔除熔断时段 |
|
|
82
|
+
| `halts` | 盘中临时停牌(新股/转债临停规则与复牌时间) | 回测撮合跳过临停;实盘委托拦截 |
|
|
83
|
+
| `corporate_actions` | 除权除息参考价官方公式 + 理论因子跳变 + 数据中心核验入口(容差 0.5%;B股/转债拒答) | 数据:dividend 对账;回测:复权核验 |
|
|
84
|
+
| `proc` | 子进程无黑窗(`no_window`/`no_window_if_detached`/`pid_alive`,Windows) | 任何会起子进程的系统(调度/并行网格/网关进程) |
|
|
85
|
+
| `io_utils` | 原子写文件(tmp + os.replace + WinError5 重试) | 任何写「会被并发读」的状态/数据文件的系统 |
|
|
86
|
+
|
|
87
|
+
依赖:`pandas` / `numpy` / `duckdb`(pip install 自动拉齐;已有这些包的系统可加 `--no-deps`)。
|
|
88
|
+
|
|
89
|
+
## 安装(三种姿势)
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
# ① 开发期(editable:改源码立即生效,无需重装)
|
|
93
|
+
"D:\Program Files\Python314\python.exe" -m pip install -e f:/code/finance/data-backtest-live-common --no-deps
|
|
94
|
+
|
|
95
|
+
# ② 正式(打 wheel 安装)
|
|
96
|
+
python -m pip wheel . --no-deps -w dist
|
|
97
|
+
python -m pip install dist/data_backtest_live_common-0.2.0-py3-none-any.whl
|
|
98
|
+
|
|
99
|
+
# ③ 未来发布 PyPI 后(像 akshare 一样)
|
|
100
|
+
pip install data-backtest-live-common
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
quant_data_center 当前用姿势 ①(editable,装在 `D:\Program Files\Python314` 解释器上,
|
|
104
|
+
tests / uvicorn / scheduler 子进程全部可见)。
|
|
105
|
+
|
|
106
|
+
## 开发与测试
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
# 需要解释器里有 pandas / numpy / duckdb / pytest(vendor PYTHONPATH 或已 pip 安装)
|
|
110
|
+
$env:PYTHONPATH = "F:\code\finance\quant_data_center\vendor"
|
|
111
|
+
"D:\Program Files\Python314\python.exe" -m pytest tests -q
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
## 诚实边界
|
|
115
|
+
|
|
116
|
+
- `trade_calendar` 只读「已经落盘的日历 Parquet」,不联网、不猜测:文件缺失返回 `[]` 由调用方降级;**参数(cal_glob)缺失直接 ValueError**(程序员错误必须响亮);
|
|
117
|
+
- `price_limit` 的 ST 判定只有当前名单、没有逐日历史 → 5% 档用「恰好贴板」精确判定(详见模块 docstring 的血泪教训:用 `≥` 会把普通涨超 5% 的交易日成片误判);
|
|
118
|
+
- `adjust` 的 hfq 锚定上市日(历史行永不变,适合增量落盘),qfq 锚定最新(历史随分红改写,只适合展示);
|
|
119
|
+
- 1996-12-16 之前 A 股无涨跌停制度,`vec.flags_from_bars` 一律不判;
|
|
120
|
+
- `corporate_actions` 对 B 股(需当日汇率折算,属运行期数据非规则)与可转债(不除权)显式 raise 拒答,不猜;
|
|
121
|
+
- 新股「首 N 日不设涨跌幅」没有交易日序号时按自然日近似,`reason` 里如实标注「近似」。
|
|
122
|
+
|
|
123
|
+
## 与兄弟项目的关系(互不 import)
|
|
124
|
+
|
|
125
|
+
| 项目 | 定位 | 关系 |
|
|
126
|
+
|---|---|---|
|
|
127
|
+
| `quant_data_center` | 数据系统 | 本库母体;现为 pip 依赖 `dbl_common` |
|
|
128
|
+
| 回测 / 实盘系统(未来) | — | 直接 `pip install` 本库即可(原需另装 market_common,2026-09-27 已并入) |
|
|
129
|
+
|
|
130
|
+
> **两库合一记录(2026-09-27)**:用户对比本库与 `market_common` 后指令「如有重复删除
|
|
131
|
+
> market」。实测仅 `price_limit` 领域同源重复(本库=引擎+简化规则,market=规则全集),
|
|
132
|
+
> 其余 10 个交易规则模块是独有内容 → **整体并入**:`price_limit` 取超集版(含
|
|
133
|
+
> `round_half_up` 浮点修正),向量化引擎统一进 `vec.py`(spec 驱动签名),83 项守卫
|
|
134
|
+
> 测试随迁为 `tests/test_trading_rules.py`,原 market_common 目录弃用(AI 删除时被环境
|
|
135
|
+
> 安全删除守卫拦截,内容已 100% 迁入本库并经测试验证,该目录可随时人工删除)。
|
|
136
|
+
|
|
137
|
+
## 怎么加新模块(给 AI 的守则)
|
|
138
|
+
|
|
139
|
+
1. 先判定「三个系统都用到吗」——不是就放回各系统自己的 common,别塞进来;
|
|
140
|
+
2. 纯函数优先、零系统路径耦合(需要路径/配置就做成必传参数,参照 `trade_calendar.cal_glob`);
|
|
141
|
+
3. 配 `tests/` 用例,锚点用真实制度事实(如创业板 2020-08-24 改 20%、round_half_up(2.675)=2.68);
|
|
142
|
+
4. 版本号 +1(`pyproject.toml` 与 `dbl_common/__init__.py` 同步改),更新本 README 模块表;
|
|
143
|
+
5. Windows 环境跑命令一律后台无窗(`Start-Process -WindowStyle Hidden`)。
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
data_backtest_live_common-0.2.0.dist-info/licenses/LICENSE,sha256=B01Wsyf9hVVsEvHxNQOkawcuyLt2jZwkJPJnmOTwn7I,1116
|
|
2
|
+
dbl_common/__init__.py,sha256=MkNAeU8OQuFHdvScjuh5F4wjwqZJSmN6Wiya6aWfV1I,5769
|
|
3
|
+
dbl_common/adjust.py,sha256=V8aRJatuGo_p0CtPZtuVUIZA2gBpavRxvtKHg89QV10,3305
|
|
4
|
+
dbl_common/boards.py,sha256=zAij-SQF7WdvzymNeyxgAge3DS6GdsKGEFHzR7-7x4o,7203
|
|
5
|
+
dbl_common/circuit_breaker.py,sha256=mNfBzeRBhpucjccczH9w6qPqg2sl-4CoLr39p2yB7sM,4029
|
|
6
|
+
dbl_common/corporate_actions.py,sha256=mrWuYcNwHJdf34C5g36BVQNgb4zMkF7N4MHx8u-LNQw,8061
|
|
7
|
+
dbl_common/fees.py,sha256=OlV0eayw48GV8dpClGi9caWQ26c8DqF_xq950-aOxoo,8083
|
|
8
|
+
dbl_common/halts.py,sha256=ngJnCBZ0wN_LQUM_VgUwcHRuvtDR9vsNDXP1Z03z35Y,9070
|
|
9
|
+
dbl_common/io_utils.py,sha256=a6Dx-UKRE4XCkxrLoexy4O7UXQ5RBxbDSbJvfc9GNhE,3162
|
|
10
|
+
dbl_common/lots.py,sha256=xy_rX3sHNYP-7DDdsCyA-6tQGSuULN5u54uZN0rPQns,5403
|
|
11
|
+
dbl_common/price_limit.py,sha256=vAxDt0fuwv7OQrKKJ6etDbCzCyCSDEkZqfHKanu_EkY,16993
|
|
12
|
+
dbl_common/proc.py,sha256=mrMeL7l0z-PrOZmtCcCM9KFk0JysgFBMpyxrIl076ZM,6024
|
|
13
|
+
dbl_common/settlement.py,sha256=TTmYS20MoXylCywg4Xshv3ZMoRuA5c6sAWXO0Gf4qfo,5219
|
|
14
|
+
dbl_common/ticks.py,sha256=MjGy8v6LqNkr69D5tgvVXArSSkKxwKhHt0dwg5WFXRk,2376
|
|
15
|
+
dbl_common/trade_calendar.py,sha256=638u9bkIsVuhs8jhc7Jj2O1hOGUYqd8UDrIOLESI0bw,5046
|
|
16
|
+
dbl_common/trading_calendar.py,sha256=LMROwgyD_PPgAca1ntnowFnkikbKSiBl_QnLlrgER8U,8470
|
|
17
|
+
dbl_common/trading_hours.py,sha256=5EiCUmAZAZsWTJin28nHqukYTesXxF0kzM1tEGWQqA0,7151
|
|
18
|
+
dbl_common/vec.py,sha256=K9vgYhOJLGXhDQMGtJXzQtR0sTKpittQCOMIYouZ1X0,6452
|
|
19
|
+
data_backtest_live_common-0.2.0.dist-info/METADATA,sha256=gyj0x4LTgC9hFrpUryPtWxcYYTXSj9-Vsnq79EwYBqg,9854
|
|
20
|
+
data_backtest_live_common-0.2.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
21
|
+
data_backtest_live_common-0.2.0.dist-info/top_level.txt,sha256=dDBhJoMO_TN-iJxJE2lF2FYZ_V2RViuA60QOhSPzq-A,11
|
|
22
|
+
data_backtest_live_common-0.2.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 data-backtest-live-common contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
dbl_common
|
dbl_common/__init__.py
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""dbl_common — 数据 / 回测 / 实盘 三系统公共库(Data Backtest Live Common)
|
|
3
|
+
|
|
4
|
+
命名口径(用户 2026-09-26 指定):
|
|
5
|
+
· 发行名(pip 名)= 数据/回测/实盘三个英文单词 + common → ``data-backtest-live-common``;
|
|
6
|
+
· 导入名 = 三词首字母 + common → ``dbl_common``(对齐 beautifulsoup4 → ``import bs4``
|
|
7
|
+
的短名惯例,避免每次 import 敲 24 个字符)。
|
|
8
|
+
|
|
9
|
+
收录标准(硬规则):**只收「数据、回测、实盘三个系统都会用到」的代码**;
|
|
10
|
+
单系统专属的公共代码放回各系统自己的 common 包(如 quant_data_center 的
|
|
11
|
+
throttle 限频 / pagination 翻页是采集专属,留在那边,不进本库)。
|
|
12
|
+
|
|
13
|
+
模块清单(2026-09-27 起 = 原 market_common 交易规则库整体并入,两库合一):
|
|
14
|
+
- adjust : 本地复权引擎(hfq = raw × adj_factor,锚定上市日,增量追加安全)
|
|
15
|
+
- boards : 证券代码规范化 / 品种识别 / 板块判定(规则库最底层,纯函数零依赖)
|
|
16
|
+
- price_limit : 涨跌停规则全集(主板/科创/创业/北交/ST/ETF/转债/B股/新股首日,含历史制度变更)
|
|
17
|
+
- vec : numpy 向量化涨跌停引擎(limit_prices / flags_from_bars / board_flags / hfq_ret_on)
|
|
18
|
+
- trade_calendar : 交易日历(Tushare cal Parquet 读取;cal_glob 必传,不绑定任何系统路径)
|
|
19
|
+
- trading_calendar : 交易日历注入契约(InjectedCalendar:BaoStock CSV / QDC raw 层 Parquet)
|
|
20
|
+
- trading_hours : 交易时段(集合竞价 / 连续竞价 / 收盘集合竞价)
|
|
21
|
+
- settlement : 交收制度(T+1 / T+0 / T+3)
|
|
22
|
+
- lots / ticks : 申报单位(手数)/ 价位(最小变动价位)
|
|
23
|
+
- fees : 交易费用(印花税 / 过户费时间线;佣金必显式传)
|
|
24
|
+
- circuit_breaker : 2016 熔断制度
|
|
25
|
+
- halts : 盘中临时停牌(新股 / 转债临停)
|
|
26
|
+
- corporate_actions: 除权除息参考价官方公式 + 理论因子跳变 + 数据中心核验入口
|
|
27
|
+
- proc : 子进程无黑窗唯一实现(no_window / no_window_if_detached / pid_alive)
|
|
28
|
+
- io_utils : 原子写文件(atomic_write_text / atomic_write_bytes,tmp+os.replace+重试)
|
|
29
|
+
|
|
30
|
+
历史:
|
|
31
|
+
· 2026-09-26 从 quant_data_center/common 抽出独立成库(adjust/price_limit/
|
|
32
|
+
trade_calendar/proc/io_utils 五模块);
|
|
33
|
+
· 2026-09-27 用户指令「两库如有重复则删除 market」→ 实测仅 price_limit 领域
|
|
34
|
+
同源重复(规则超集 + 引擎),其余 10 个交易规则模块为本库没有的独有内容 →
|
|
35
|
+
**整体并入**(price_limit 取超集版 + round_half_up 浮点修正;引擎统一进
|
|
36
|
+
vec.py;83 项守卫测试随迁为 tests/test_trading_rules.py),market_common
|
|
37
|
+
目录删除。再往前溯源是 global_market_dashboard 的成熟代码「复制适配」。
|
|
38
|
+
"""
|
|
39
|
+
from .boards import Board, BOARD_LABEL, SecurityType, board_of, exchange_of, \
|
|
40
|
+
normalize_code, security_type
|
|
41
|
+
from .price_limit import LimitSpec, limit_spec, round_half_up, \
|
|
42
|
+
limit_up_price, limit_down_price, price_decimals, candidates_of, \
|
|
43
|
+
LIMIT_ERA_START, ST_5PCT_START, GEM_20PCT_START, MAIN_REG_START, \
|
|
44
|
+
MAIN_IPO_4436_START, CB_20PCT_START, CENT_TOL, EPS
|
|
45
|
+
from .trading_hours import Phase, PHASE_LABEL, phase_at, is_auction, \
|
|
46
|
+
is_continuous, can_cancel
|
|
47
|
+
from .settlement import settle_rule, can_sell_today, STOCK_T1_START
|
|
48
|
+
from .lots import lot_rule, validate_qty
|
|
49
|
+
from .ticks import tick_size, snap_to_tick, is_valid_price
|
|
50
|
+
from .fees import FeeResult, calc_fees, stamp_tax, transfer_fee_rate, \
|
|
51
|
+
commission_cap, stamp_tax_timeline
|
|
52
|
+
from .circuit_breaker import is_active as cb_is_active, market_state as cb_state, \
|
|
53
|
+
CB_START, CB_END, LEVEL1, LEVEL2, HISTORICAL_TRIGGERS
|
|
54
|
+
from .halts import HaltRule, intraday_halt_rule, resume_time, halt_price
|
|
55
|
+
from .trading_calendar import InjectedCalendar, from_baostock_csv, \
|
|
56
|
+
from_parquet_dataset, weekend_is_trading_day, weekend_next_trading_day, \
|
|
57
|
+
weekend_prev_trading_day
|
|
58
|
+
from .corporate_actions import CorporateAction, from_per_ten, \
|
|
59
|
+
ex_right_price, factor_jump, validate_against_factor, check_applicable
|
|
60
|
+
|
|
61
|
+
__version__ = "0.2.0"
|
|
62
|
+
|
|
63
|
+
__all__ = [
|
|
64
|
+
# boards
|
|
65
|
+
"Board", "BOARD_LABEL", "SecurityType", "board_of", "exchange_of",
|
|
66
|
+
"normalize_code", "security_type",
|
|
67
|
+
# price_limit
|
|
68
|
+
"LimitSpec", "limit_spec", "round_half_up", "limit_up_price",
|
|
69
|
+
"limit_down_price", "price_decimals", "candidates_of",
|
|
70
|
+
"LIMIT_ERA_START", "ST_5PCT_START", "GEM_20PCT_START", "MAIN_REG_START",
|
|
71
|
+
"MAIN_IPO_4436_START", "CB_20PCT_START", "CENT_TOL", "EPS",
|
|
72
|
+
# trading_hours
|
|
73
|
+
"Phase", "PHASE_LABEL", "phase_at", "is_auction", "is_continuous", "can_cancel",
|
|
74
|
+
# settlement
|
|
75
|
+
"settle_rule", "can_sell_today", "STOCK_T1_START",
|
|
76
|
+
# lots / ticks
|
|
77
|
+
"lot_rule", "validate_qty", "tick_size", "snap_to_tick", "is_valid_price",
|
|
78
|
+
# fees
|
|
79
|
+
"FeeResult", "calc_fees", "stamp_tax", "transfer_fee_rate",
|
|
80
|
+
"commission_cap", "stamp_tax_timeline",
|
|
81
|
+
# circuit breaker
|
|
82
|
+
"cb_is_active", "cb_state", "CB_START", "CB_END", "LEVEL1", "LEVEL2",
|
|
83
|
+
"HISTORICAL_TRIGGERS",
|
|
84
|
+
# halts(盘中临时停牌)
|
|
85
|
+
"HaltRule", "intraday_halt_rule", "resume_time", "halt_price",
|
|
86
|
+
# calendar(注入契约;Tushare cal Parquet 读取见 dbl_common.trade_calendar)
|
|
87
|
+
"InjectedCalendar", "from_baostock_csv", "from_parquet_dataset",
|
|
88
|
+
"weekend_is_trading_day", "weekend_next_trading_day",
|
|
89
|
+
"weekend_prev_trading_day",
|
|
90
|
+
# corporate actions(除权除息参考价)
|
|
91
|
+
"CorporateAction", "from_per_ten", "ex_right_price", "factor_jump",
|
|
92
|
+
"validate_against_factor", "check_applicable",
|
|
93
|
+
]
|
dbl_common/adjust.py
ADDED
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""dbl_common.adjust — 本地复权引擎(数据/回测/实盘共用)
|
|
3
|
+
|
|
4
|
+
溯源:global_market_dashboard tushare_fetcher.compute_hfq →
|
|
5
|
+
quant_data_center/common/adjust.py → 2026-09-26 抽入本独立库(内容不变)。
|
|
6
|
+
|
|
7
|
+
核心设计(为什么锚定上市日、而不是「÷ 最新因子」锚定最新):
|
|
8
|
+
adj_factor 的历史值**永不回改**(新分红只抬高未来行的因子),
|
|
9
|
+
所以 hfq 的历史行也永不变 —— 增量追加天然安全。
|
|
10
|
+
若锚定最新,每次分红都会改写全部历史,合并接缝处必出人工断崖。
|
|
11
|
+
|
|
12
|
+
连续性保证:除权日 raw 跳空、因子同幅度反向跳变,乘积天然连续 ——
|
|
13
|
+
这就是 92 只 known-list 断崖(sina/腾讯因子基准漂移)的治本项。
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
import pandas as pd
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def compute_hfq(daily_df: pd.DataFrame, factor_df: pd.DataFrame) -> pd.DataFrame:
|
|
21
|
+
"""本地后复权引擎:**hfq = raw × adj_factor**(锚定上市日)。
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
daily_df: 日线表,需含 date + OHLC 列
|
|
25
|
+
factor_df: 复权因子表,需含 date + adj_factor 列
|
|
26
|
+
|
|
27
|
+
Returns:
|
|
28
|
+
合并后的 DataFrame,OHLC 已乘上 adj_factor(后复权);
|
|
29
|
+
因子缺失/非法(≤0)的行 OHLC 置 NaN 如实缺失(不冒充数据),
|
|
30
|
+
该行仍保留(volume/amount 是真实成交,不因价格缺失而丢)。
|
|
31
|
+
"""
|
|
32
|
+
if daily_df is None or daily_df.empty:
|
|
33
|
+
return daily_df
|
|
34
|
+
m = daily_df.copy()
|
|
35
|
+
if factor_df is None or factor_df.empty:
|
|
36
|
+
for c in ("open", "high", "low", "close"):
|
|
37
|
+
if c in m.columns:
|
|
38
|
+
m[c] = float("nan")
|
|
39
|
+
m["adj_factor"] = float("nan")
|
|
40
|
+
return m
|
|
41
|
+
f = factor_df[["date", "adj_factor"]].copy()
|
|
42
|
+
f["adj_factor"] = pd.to_numeric(f["adj_factor"], errors="coerce")
|
|
43
|
+
m = m.merge(f, on="date", how="left").sort_values(
|
|
44
|
+
"date", kind="stable").reset_index(drop=True)
|
|
45
|
+
# 因子与日线同为逐交易日行,merge 应 1:1;个别日期缺因子时用前一交易日
|
|
46
|
+
# 因子兜底(因子在除权日之间保持不变,ffill 语义正确)。
|
|
47
|
+
m["adj_factor"] = m["adj_factor"].ffill()
|
|
48
|
+
bad = m["adj_factor"].isna() | (m["adj_factor"] <= 0)
|
|
49
|
+
for c in ("open", "high", "low", "close"):
|
|
50
|
+
if c in m.columns:
|
|
51
|
+
v = pd.to_numeric(m[c], errors="coerce") * m["adj_factor"]
|
|
52
|
+
m[c] = v.mask(bad)
|
|
53
|
+
return m
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
def compute_qfq(daily_df: pd.DataFrame, factor_df: pd.DataFrame) -> pd.DataFrame:
|
|
57
|
+
"""本地前复权引擎:**qfq = raw × adj_factor ÷ 最新 adj_factor**(锚定最新价)。
|
|
58
|
+
|
|
59
|
+
与 hfq 的区别:锚定最新价,历史行会随新分红而改写。
|
|
60
|
+
适合「按最新价看历史」的展示场景,不适合增量落盘(接缝会断)。
|
|
61
|
+
"""
|
|
62
|
+
m = compute_hfq(daily_df, factor_df)
|
|
63
|
+
if m is None or m.empty or "adj_factor" not in m.columns:
|
|
64
|
+
return m
|
|
65
|
+
valid = m["adj_factor"].dropna()
|
|
66
|
+
if valid.empty:
|
|
67
|
+
return m
|
|
68
|
+
latest = valid.iloc[-1]
|
|
69
|
+
if latest <= 0:
|
|
70
|
+
return m
|
|
71
|
+
for c in ("open", "high", "low", "close"):
|
|
72
|
+
if c in m.columns:
|
|
73
|
+
m[c] = pd.to_numeric(m[c], errors="coerce") / latest
|
|
74
|
+
return m
|
dbl_common/boards.py
ADDED
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""boards.py — 证券代码规范化 / 品种识别 / 板块判定(公共模块 · 纯函数零依赖)。
|
|
3
|
+
|
|
4
|
+
为什么必须在公共模块最底层
|
|
5
|
+
--------------------------
|
|
6
|
+
回测、实盘、模拟盘、数据中心都会问同一个问题:「这只代码是什么品种、哪个板块、
|
|
7
|
+
适用哪套规则」。各系统各写一份就会口径漂移(GMD 项目实测:同一交易日历被写成
|
|
8
|
+
5 份、同一涨跌停规则被写成 2 份说法)。本模块是**唯一真相源**。
|
|
9
|
+
|
|
10
|
+
设计约定
|
|
11
|
+
--------
|
|
12
|
+
· **纯函数 + 零第三方依赖**:只用标准库,任何系统 vendor / sys.path 直接可用。
|
|
13
|
+
· **诚实边界**:代码前缀只能给出「最可能」的判定,无法识别的返回 None 并说明,
|
|
14
|
+
绝不猜。特别地:6 位代码 000001 既可能是上证指数也可能是平安银行——
|
|
15
|
+
**不带交易所信息时一律按股票处理**,跟踪指数的调用方必须显式传
|
|
16
|
+
`security_type=INDEX`。
|
|
17
|
+
· 与时间相关的板块规则(如创业板 2020-08-24 注册制改革)不在本模块判定,
|
|
18
|
+
见 `price_limit.py`(板块归属是静态的,制度是随时间变的——分开管)。
|
|
19
|
+
|
|
20
|
+
代码前缀事实表(沪深交易所公开规则,2026 年口径)
|
|
21
|
+
------------------------------------------------
|
|
22
|
+
· 60xxxx 沪市主板 A 股 00xxxx 深市主板 A 股(000/001/002/003)
|
|
23
|
+
· 300/301 深市创业板 688/689 沪市科创板
|
|
24
|
+
· 43/83/87/88/92 北交所(2021-11-15 开市,由新三板精选层平移)
|
|
25
|
+
· 900xxx 沪 B / 200xxx 深 B
|
|
26
|
+
· ETF:沪 51x/56x/58x(588=科创板ETF);深 15x(159=股票ETF 等)
|
|
27
|
+
· LOF:沪 501/502;深 16x
|
|
28
|
+
· 可转债:沪 11x(110/111/113/118);深 12x(123/127/128)
|
|
29
|
+
· 国债逆回购:沪 204xxx;深 1318xx
|
|
30
|
+
· 封闭式基金/其他:沪 50xxxx 等
|
|
31
|
+
"""
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
import enum
|
|
35
|
+
from typing import Optional
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
class SecurityType(enum.Enum):
|
|
39
|
+
"""品种大类(决定涨跌幅/交收/手数/价位整套规则)。"""
|
|
40
|
+
|
|
41
|
+
STOCK = "stock" # A 股股票(含各板块)
|
|
42
|
+
B_SHARE = "b_share" # B 股
|
|
43
|
+
INDEX = "index" # 指数(无涨跌幅限制)
|
|
44
|
+
ETF = "etf" # 交易型开放式指数基金
|
|
45
|
+
LOF = "lof" # 上市开放式基金
|
|
46
|
+
CONVERTIBLE_BOND = "cb" # 可转换公司债券
|
|
47
|
+
REPO = "repo" # 国债逆回购(质押式回购)
|
|
48
|
+
FUND_OTHER = "fund_other" # 封基/其他场内基金
|
|
49
|
+
UNKNOWN = "unknown"
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
class Board(enum.Enum):
|
|
53
|
+
"""交易板块(股票类适用;决定涨跌幅档位与申报单位)。"""
|
|
54
|
+
|
|
55
|
+
MAIN_SH = "main_sh" # 沪市主板(60)
|
|
56
|
+
MAIN_SZ = "main_sz" # 深市主板(000/001/002/003)
|
|
57
|
+
GEM = "gem" # 创业板(300/301,2009-10-30 开市)
|
|
58
|
+
STAR = "star" # 科创板(688/689,2019-07-22 开市)
|
|
59
|
+
BSE = "bse" # 北交所(2021-11-15 开市)
|
|
60
|
+
B_SHARE = "b_share" # B 股(900 沪 / 200 深)
|
|
61
|
+
NONE = "none" # 非股票类
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def normalize_code(code) -> str:
|
|
65
|
+
"""任意常见代码形态 → 6 位数字字符串。
|
|
66
|
+
|
|
67
|
+
`sh600660` / `600660.SS` / `600660.SH` / `600660` / `sh.600660` → `600660`。
|
|
68
|
+
B 股 / 北交所同样归一到 6 位。非数字字符全部剔除后取末 6 位(前导补零)。
|
|
69
|
+
"""
|
|
70
|
+
s = str(code).strip().upper()
|
|
71
|
+
for pre in ("SH", "SZ", "BJ"):
|
|
72
|
+
if s.startswith(pre):
|
|
73
|
+
s = s[len(pre):]
|
|
74
|
+
s = s.lstrip(".").split(".")[0] # "SH.600519"→"600519";"600519.SS"→"600519"
|
|
75
|
+
digits = "".join(ch for ch in s if ch.isdigit())
|
|
76
|
+
if not digits:
|
|
77
|
+
return ""
|
|
78
|
+
return digits.zfill(6)[-6:]
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def exchange_of(code) -> Optional[str]:
|
|
82
|
+
"""尽量识别交易所:`SH` / `SZ` / `BJ` / None(判不了就 None,不猜)。
|
|
83
|
+
|
|
84
|
+
只对**前缀能唯一定位**的代码可靠(60/68/90/51/56/58/11/20x→SH;
|
|
85
|
+
00/30/15/12/1318→SZ;43/83/87/88/92→BJ)。000001 这类深市前缀若调用方
|
|
86
|
+
实际跟踪的是沪指数,请显式传参,本函数管不了语义。
|
|
87
|
+
"""
|
|
88
|
+
c = normalize_code(code)
|
|
89
|
+
if not c:
|
|
90
|
+
return None
|
|
91
|
+
if c[:2] in ("60", "68") or c[:3] in ("900", "510", "511", "512", "513",
|
|
92
|
+
"515", "516", "517", "518", "560",
|
|
93
|
+
"561", "562", "563", "588", "589",
|
|
94
|
+
"110", "111", "113", "118", "204",
|
|
95
|
+
"501", "502", "506", "588"):
|
|
96
|
+
return "SH"
|
|
97
|
+
if c[:2] in ("00", "30", "15", "16", "20") or c[:3] in ("123", "127", "128") \
|
|
98
|
+
or c[:4] == "1318":
|
|
99
|
+
return "SZ"
|
|
100
|
+
if c[:2] in ("43", "83", "87", "88", "92"):
|
|
101
|
+
return "BJ"
|
|
102
|
+
return None
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
def security_type(code, explicit: Optional[SecurityType] = None) -> SecurityType:
|
|
106
|
+
"""品种大类。`explicit` 优先(调用方明确知道时别让前缀猜)。"""
|
|
107
|
+
if explicit is not None:
|
|
108
|
+
return explicit
|
|
109
|
+
c = normalize_code(code)
|
|
110
|
+
if not c:
|
|
111
|
+
return SecurityType.UNKNOWN
|
|
112
|
+
if c[:3] in ("900",):
|
|
113
|
+
return SecurityType.B_SHARE
|
|
114
|
+
if c[:3] == "200":
|
|
115
|
+
return SecurityType.B_SHARE
|
|
116
|
+
if c[:3] in ("510", "511", "512", "513", "515", "516", "517", "518",
|
|
117
|
+
"560", "561", "562", "563", "588", "589", "159"):
|
|
118
|
+
return SecurityType.ETF
|
|
119
|
+
if c[:3] in ("501", "502", "160", "161", "162", "163", "164", "165",
|
|
120
|
+
"166", "167", "168"):
|
|
121
|
+
return SecurityType.LOF
|
|
122
|
+
if c[:3] in ("110", "111", "113", "118", "123", "127", "128"):
|
|
123
|
+
return SecurityType.CONVERTIBLE_BOND
|
|
124
|
+
if c[:3] == "204" or c[:4] == "1318":
|
|
125
|
+
return SecurityType.REPO
|
|
126
|
+
if c[:3] == "505" or c[:2] == "50":
|
|
127
|
+
return SecurityType.FUND_OTHER
|
|
128
|
+
if c[:2] in ("60", "00", "30", "68") or c[:3] in ("688", "689") \
|
|
129
|
+
or c[:2] in ("43", "83", "87", "88", "92"):
|
|
130
|
+
return SecurityType.STOCK
|
|
131
|
+
return SecurityType.UNKNOWN
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
def board_of(code, explicit_type: Optional[SecurityType] = None) -> Board:
|
|
135
|
+
"""股票类板块。非股票类返回 `Board.NONE`。"""
|
|
136
|
+
st = security_type(code, explicit_type)
|
|
137
|
+
if st != SecurityType.STOCK:
|
|
138
|
+
return Board.NONE if st != SecurityType.B_SHARE else Board.B_SHARE
|
|
139
|
+
c = normalize_code(code)
|
|
140
|
+
if not c:
|
|
141
|
+
return Board.NONE
|
|
142
|
+
if c.startswith("688") or c.startswith("689"):
|
|
143
|
+
return Board.STAR
|
|
144
|
+
if c.startswith("300") or c.startswith("301"):
|
|
145
|
+
return Board.GEM
|
|
146
|
+
if c[:2] in ("43", "83", "87", "88", "92"):
|
|
147
|
+
return Board.BSE
|
|
148
|
+
if c[:2] == "60":
|
|
149
|
+
return Board.MAIN_SH
|
|
150
|
+
if c[:2] == "00":
|
|
151
|
+
return Board.MAIN_SZ
|
|
152
|
+
return Board.NONE
|
|
153
|
+
|
|
154
|
+
|
|
155
|
+
BOARD_LABEL = {
|
|
156
|
+
Board.MAIN_SH: "沪市主板", Board.MAIN_SZ: "深市主板", Board.GEM: "创业板",
|
|
157
|
+
Board.STAR: "科创板", Board.BSE: "北交所", Board.B_SHARE: "B股",
|
|
158
|
+
Board.NONE: "非股票类",
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
__all__ = ["SecurityType", "Board", "BOARD_LABEL", "normalize_code",
|
|
162
|
+
"exchange_of", "security_type", "board_of"]
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
"""circuit_breaker.py — 指数熔断制度(2016 年实施 4 个交易日后暂停)。
|
|
3
|
+
|
|
4
|
+
为什么公共模块要收录一段只活了 4 天的制度:**回测要跨 2016 年 1 月**。
|
|
5
|
+
2016-01-04 与 2016-01-07 两天沪深 300 触发 7% 阈值**提前收市**——
|
|
6
|
+
这两天「下午的价格根本不存在」,按全天数据回测撮合就是作弊。
|
|
7
|
+
|
|
8
|
+
制度原文(指数熔断机制,2016-01-01 起):
|
|
9
|
+
· 触发基准:沪深 300 指数较前一交易日收盘价;
|
|
10
|
+
· ±5%(一级):熔断 15 分钟;14:45 及之后触发 → 直接收市;
|
|
11
|
+
· ±7%(二级):当日直接收市;
|
|
12
|
+
· 两级熔断每交易日最多各触发一次(15:00 前未触发过一级仍可触发)。
|
|
13
|
+
· 2016-01-08 起暂停实施(1月4日、1月7日两度触发二级熔断后连夜暂停)。
|
|
14
|
+
"""
|
|
15
|
+
from __future__ import annotations
|
|
16
|
+
|
|
17
|
+
from typing import Optional
|
|
18
|
+
|
|
19
|
+
CB_START = "2016-01-01"
|
|
20
|
+
CB_END = "2016-01-07" # 实施的最后一个交易日(1月8日起暂停)
|
|
21
|
+
LEVEL1 = 0.05
|
|
22
|
+
LEVEL2 = 0.07
|
|
23
|
+
LEVEL1_HALT_MINUTES = 15
|
|
24
|
+
LEVEL1_CUTOFF = "14:45" # 此后触发一级 → 直接收市
|
|
25
|
+
|
|
26
|
+
# 实际触发历史(公开行情事实,回测对账锚点)
|
|
27
|
+
HISTORICAL_TRIGGERS = {
|
|
28
|
+
"2016-01-04": {"level": 2, "note": "沪深300跌7.02%,13:33 收市"},
|
|
29
|
+
"2016-01-07": {"level": 2, "note": "沪深300跌7.21%,09:59 收市"},
|
|
30
|
+
"2016-01-05": {"level": 1, "note": "午后触及-5.03%,熔断15分钟后恢复交易"},
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def is_active(date: Optional[str]) -> bool:
|
|
35
|
+
"""该交易日熔断制度是否有效(仅 2016-01-01 ~ 2016-01-07)。"""
|
|
36
|
+
if not date:
|
|
37
|
+
return False
|
|
38
|
+
d = str(date)[:10]
|
|
39
|
+
return CB_START <= d <= CB_END
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def market_state(date: str, hs300_change_pct: Optional[float],
|
|
43
|
+
trigger_time: Optional[str] = None) -> dict:
|
|
44
|
+
"""判定熔断状态(回测撮合闸门用)。
|
|
45
|
+
|
|
46
|
+
Args:
|
|
47
|
+
date: 交易日。
|
|
48
|
+
hs300_change_pct: 沪深300 当日涨跌幅(较前收,百分数如 -7.02 传 -7.02);
|
|
49
|
+
None = 无数据 → 「未判」,调用方必须显式披露,不许当没触发。
|
|
50
|
+
trigger_time: 触发时点 "HH:MM"(判一级熔断后 14:45 直接收市的分支)。
|
|
51
|
+
|
|
52
|
+
Returns:
|
|
53
|
+
{"active": bool, "halted": bool, "closed_early": bool,
|
|
54
|
+
"resume_time": "HH:MM"|None, "reason": str}
|
|
55
|
+
"""
|
|
56
|
+
if not is_active(date):
|
|
57
|
+
return {"active": False, "halted": False, "closed_early": False,
|
|
58
|
+
"resume_time": None, "reason": "熔断制度未实施(仅 2016-01-01~01-07)"}
|
|
59
|
+
if hs300_change_pct is None:
|
|
60
|
+
return {"active": True, "halted": False, "closed_early": False,
|
|
61
|
+
"resume_time": None,
|
|
62
|
+
"reason": "熔断制度有效但沪深300涨跌幅未知——未判,须披露"}
|
|
63
|
+
chg = float(hs300_change_pct)
|
|
64
|
+
if chg <= -LEVEL2 * 100 or chg >= LEVEL2 * 100:
|
|
65
|
+
return {"active": True, "halted": True, "closed_early": True,
|
|
66
|
+
"resume_time": None,
|
|
67
|
+
"reason": f"沪深300 {chg:+.2f}% 触及±7%:当日直接收市"}
|
|
68
|
+
if chg <= -LEVEL1 * 100 or chg >= LEVEL1 * 100:
|
|
69
|
+
if trigger_time is not None and str(trigger_time)[:5] >= LEVEL1_CUTOFF:
|
|
70
|
+
return {"active": True, "halted": True, "closed_early": True,
|
|
71
|
+
"resume_time": None,
|
|
72
|
+
"reason": f"14:45 后触及±5%:直接收市(触发 {trigger_time})"}
|
|
73
|
+
return {"active": True, "halted": True, "closed_early": False,
|
|
74
|
+
"resume_time": None,
|
|
75
|
+
"reason": f"触及±5%:熔断 15 分钟(trigger={trigger_time},"
|
|
76
|
+
f"恢复时点请调用方按 trigger_time+15m 计算)"}
|
|
77
|
+
return {"active": True, "halted": False, "closed_early": False,
|
|
78
|
+
"resume_time": None, "reason": "未触及阈值"}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
__all__ = ["CB_START", "CB_END", "LEVEL1", "LEVEL2", "is_active",
|
|
82
|
+
"market_state", "HISTORICAL_TRIGGERS"]
|