qka 2.3.2.dev2__py3-none-any.whl → 2.3.3.dev2__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.
- qka/__init__.py +4 -0
- qka/core/data.py +124 -11
- qka/core/tdx.py +207 -0
- qka/skills/qka/references/data.md +27 -3
- {qka-2.3.2.dev2.dist-info → qka-2.3.3.dev2.dist-info}/METADATA +3 -1
- {qka-2.3.2.dev2.dist-info → qka-2.3.3.dev2.dist-info}/RECORD +9 -8
- {qka-2.3.2.dev2.dist-info → qka-2.3.3.dev2.dist-info}/WHEEL +0 -0
- {qka-2.3.2.dev2.dist-info → qka-2.3.3.dev2.dist-info}/entry_points.txt +0 -0
- {qka-2.3.2.dev2.dist-info → qka-2.3.3.dev2.dist-info}/licenses/LICENSE +0 -0
qka/__init__.py
CHANGED
|
@@ -20,6 +20,8 @@ from qka.core.strategy import Strategy
|
|
|
20
20
|
from qka.core.broker import Broker
|
|
21
21
|
from qka.core.sizing import SizingAccessor
|
|
22
22
|
from qka.core.analysis import Analysis, Segment, AlphaBeta
|
|
23
|
+
# 通达信数据源:安装目录的设置与查询(source='tdx' 时使用)
|
|
24
|
+
from qka.core.tdx import set_tdx_root, get_tdx_root_info
|
|
23
25
|
|
|
24
26
|
# 子模块导入
|
|
25
27
|
from qka import core, utils
|
|
@@ -29,6 +31,8 @@ __all__ = [
|
|
|
29
31
|
'Data', 'Simulate', 'Backtest', 'Strategy', 'Broker', 'DataAccessor', 'SizingAccessor',
|
|
30
32
|
# 分析
|
|
31
33
|
'Analysis', 'Segment', 'AlphaBeta',
|
|
34
|
+
# 通达信数据源
|
|
35
|
+
'set_tdx_root', 'get_tdx_root_info',
|
|
32
36
|
# 子模块
|
|
33
37
|
'core', 'utils'
|
|
34
38
|
]
|
qka/core/data.py
CHANGED
|
@@ -21,6 +21,7 @@ from typing import List, Optional, Callable, Union
|
|
|
21
21
|
from qka.utils.logger import logger
|
|
22
22
|
from qka.core import indicator
|
|
23
23
|
from qka.core.simulate import Simulate
|
|
24
|
+
from qka.core import tdx
|
|
24
25
|
|
|
25
26
|
# qka 内置指标裸名集合,用于 dispatch 识别(无需 qka. 前缀)
|
|
26
27
|
_QKA_BUILTIN_NAMES = frozenset({
|
|
@@ -64,16 +65,17 @@ class Data():
|
|
|
64
65
|
period (str): 数据周期,如 '1d'、'1m' 等
|
|
65
66
|
adjust (str): 复权方式,如 'qfq'、'hfq'、'bfq'
|
|
66
67
|
indicators (dict | Callable): 预计算指标/因子
|
|
67
|
-
source (str): 数据源,默认 'tickflow'(可选 'baostock')
|
|
68
|
+
source (str): 数据源,默认 'tickflow'(可选 'baostock'、'tdx')
|
|
68
69
|
pool_size (int): 并发下载线程数
|
|
69
70
|
datadir (Path): 数据缓存目录
|
|
70
71
|
target_dir (Path): 目标存储目录
|
|
71
72
|
extra_fields (List[str]): 扩展字段(选股/估值用),如 ['peTTM', 'pbMRQ', 'turn']。
|
|
72
|
-
仅 'baostock' 源提供(可选值见 BAOSTOCK_EXTRA_FIELDS);'tickflow'
|
|
73
|
+
仅 'baostock' 源提供(可选值见 BAOSTOCK_EXTRA_FIELDS);'tickflow' 与
|
|
74
|
+
'tdx' 源无扩展列,会忽略
|
|
73
75
|
"""
|
|
74
76
|
|
|
75
77
|
# 支持的行情源
|
|
76
|
-
SOURCES = ('tickflow', 'baostock')
|
|
78
|
+
SOURCES = ('tickflow', 'baostock', 'tdx')
|
|
77
79
|
|
|
78
80
|
# baostock query_history_k_data_plus 完整支持的基础字段(除 date 索引外)
|
|
79
81
|
BAOSTOCK_BASE_FIELDS = ["open", "high", "low", "close", "volume", "amount"]
|
|
@@ -92,6 +94,8 @@ class Data():
|
|
|
92
94
|
# TickFlow 可提供的扩展字段:其历史日线固定返回 OHLCV + amount,
|
|
93
95
|
# 无估值/换手率等扩展列(季报级 financials 不是每日序列,不接入)。
|
|
94
96
|
TICKFLOW_EXTRA_FIELDS = []
|
|
97
|
+
# 通达信源当前只接行情(OHLCV + amount),暂无扩展列
|
|
98
|
+
TDX_EXTRA_FIELDS = []
|
|
95
99
|
|
|
96
100
|
# parquet 元数据键:这只标的「已确认起点」——数据源在该日期之前没有更早的数据。
|
|
97
101
|
# 没有它,请求起点只要早于缓存首日(往往只是撞上长假)就得白跑一趟网络去确认。
|
|
@@ -152,14 +156,17 @@ class Data():
|
|
|
152
156
|
仅供辅助计算(β/α 等),不参与指标计算
|
|
153
157
|
period: 数据周期,如 '1d'(日线)、'1m'(分钟)
|
|
154
158
|
adjust: 复权方式,'qfq'(前复权)、'hfq'(后复权)、'bfq'(不复权)
|
|
155
|
-
source: 数据来源,默认 'tickflow'(可选 'baostock'
|
|
156
|
-
|
|
159
|
+
source: 数据来源,默认 'tickflow'(可选 'baostock'、'tdx')。各源返回结构一致,
|
|
160
|
+
切换源不影响上层调用;差异见各源的扩展字段能力。'tdx' 走本机安装的
|
|
161
|
+
「支持 TQ 策略」通达信客户端(仅 Windows,需客户端在运行),安装目录的定位
|
|
162
|
+
方式见 `qka.set_tdx_root` / `qka.get_tdx_root_info`
|
|
157
163
|
pool_size: 并发下载线程数
|
|
158
164
|
datadir: 缓存目录路径
|
|
159
165
|
indicators: 预计算指标/因子,支持三种格式:
|
|
160
166
|
extra_fields: 扩展字段列表(选股/估值用,如 ['peTTM', 'pbMRQ', 'turn']),
|
|
161
167
|
可选值见 BAOSTOCK_EXTRA_FIELDS —— 仅 'baostock' 源提供;
|
|
162
|
-
'tickflow'
|
|
168
|
+
'tickflow' 与 'tdx' 源无扩展列,传入的扩展字段会被静默忽略
|
|
169
|
+
(不报错、不生成该列)。
|
|
163
170
|
追加的列同样遵循 {symbol}|{field} 命名,如 '600519.SH|peTTM'。注意:
|
|
164
171
|
首次下载后缓存字段固定,变更 extra_fields 会自动检测列缺失并重新下载对应股票。
|
|
165
172
|
|
|
@@ -217,8 +224,7 @@ class Data():
|
|
|
217
224
|
self.warmup = warmup
|
|
218
225
|
|
|
219
226
|
# 当前数据源实际支持的扩展字段(源能力不同,按源过滤)
|
|
220
|
-
whitelist =
|
|
221
|
-
else self.TICKFLOW_EXTRA_FIELDS)
|
|
227
|
+
whitelist = self._extra_fields_whitelist(source)
|
|
222
228
|
|
|
223
229
|
# extra_fields 校验 + 去重
|
|
224
230
|
# 未知字段(拼写错误)报错;当前源提供不了的字段静默忽略
|
|
@@ -448,6 +454,14 @@ class Data():
|
|
|
448
454
|
return True
|
|
449
455
|
return any(f not in cols for f in self.extra_fields)
|
|
450
456
|
|
|
457
|
+
def _extra_fields_whitelist(self, source: str) -> List[str]:
|
|
458
|
+
"""该数据源实际支持的扩展字段(源能力不同,按源过滤)。"""
|
|
459
|
+
if source == 'baostock':
|
|
460
|
+
return self.BAOSTOCK_EXTRA_FIELDS
|
|
461
|
+
if source == 'tdx':
|
|
462
|
+
return self.TDX_EXTRA_FIELDS
|
|
463
|
+
return self.TICKFLOW_EXTRA_FIELDS
|
|
464
|
+
|
|
451
465
|
def _merged_extra_fields(self, path: Path) -> List[str]:
|
|
452
466
|
"""
|
|
453
467
|
计算本次下载实际请求的扩展字段:当前 extra_fields 与缓存已有扩展列的并集。
|
|
@@ -462,8 +476,7 @@ class Data():
|
|
|
462
476
|
existing = set(pq.read_schema(path).names)
|
|
463
477
|
except Exception:
|
|
464
478
|
return merged
|
|
465
|
-
whitelist =
|
|
466
|
-
else self.TICKFLOW_EXTRA_FIELDS)
|
|
479
|
+
whitelist = self._extra_fields_whitelist(self.source)
|
|
467
480
|
for f in whitelist:
|
|
468
481
|
if f in existing and f not in merged:
|
|
469
482
|
merged.append(f)
|
|
@@ -712,7 +725,10 @@ class Data():
|
|
|
712
725
|
if need_calendar:
|
|
713
726
|
sim_index = self._trading_index(sim_start, sim_end)
|
|
714
727
|
if need_download:
|
|
715
|
-
|
|
728
|
+
# baostock 与 tdx 都是「进程级单例连接」(tdx 的 tq 类方法
|
|
729
|
+
# 共用同一 run_id),并发调用会共用同一连接状态,故逐个下载;
|
|
730
|
+
# tickflow 的客户端无此约束,可并发。
|
|
731
|
+
if self.source in ('baostock', 'tdx'):
|
|
716
732
|
for symbol in tqdm(need_download, desc="下载数据"):
|
|
717
733
|
try:
|
|
718
734
|
self._download(symbol, download_start, download_end)
|
|
@@ -1106,6 +1122,8 @@ class Data():
|
|
|
1106
1122
|
"""按当前数据源分发到对应实现,返回结构一致(date 索引 + OHLCV/amount)。"""
|
|
1107
1123
|
if self.source == 'baostock':
|
|
1108
1124
|
return self._get_from_baostock(symbol, start_date, end_date, extra_fields)
|
|
1125
|
+
if self.source == 'tdx':
|
|
1126
|
+
return self._get_from_tdx(symbol, start_date, end_date, extra_fields)
|
|
1109
1127
|
return self._get_from_tickflow(symbol, start_date, end_date, extra_fields)
|
|
1110
1128
|
|
|
1111
1129
|
# ── TickFlow 数据源 ────────────────────────────────────────
|
|
@@ -1164,6 +1182,11 @@ class Data():
|
|
|
1164
1182
|
market, code = cls._split_symbol(symbol)
|
|
1165
1183
|
return f"{code}.{market.upper()}"
|
|
1166
1184
|
|
|
1185
|
+
@classmethod
|
|
1186
|
+
def to_tdx_symbol(cls, symbol: str) -> str:
|
|
1187
|
+
"""任意写法 → 通达信写法,如 600519.SH(与 tickflow 同形)"""
|
|
1188
|
+
return cls.to_tickflow_symbol(symbol)
|
|
1189
|
+
|
|
1167
1190
|
def _get_from_tickflow(
|
|
1168
1191
|
self, symbol: str,
|
|
1169
1192
|
start_date: str = '1990-01-01',
|
|
@@ -1235,3 +1258,93 @@ class Data():
|
|
|
1235
1258
|
for col in self.BAOSTOCK_BASE_FIELDS:
|
|
1236
1259
|
df[col] = pd.to_numeric(df[col], errors='coerce')
|
|
1237
1260
|
return df
|
|
1261
|
+
|
|
1262
|
+
# ── 通达信数据源(本机 TQ 版客户端,仅 Windows)────────────────
|
|
1263
|
+
|
|
1264
|
+
# 通达信 get_market_data 字段名 → qka 标准列名
|
|
1265
|
+
TDX_FIELD_MAP = {
|
|
1266
|
+
'Open': 'open', 'High': 'high', 'Low': 'low',
|
|
1267
|
+
'Close': 'close', 'Volume': 'volume', 'Amount': 'amount',
|
|
1268
|
+
}
|
|
1269
|
+
|
|
1270
|
+
def _get_from_tdx(
|
|
1271
|
+
self, symbol: str,
|
|
1272
|
+
start_date: str = '1990-01-01',
|
|
1273
|
+
end_date: str = '2050-12-31',
|
|
1274
|
+
extra_fields: Optional[List[str]] = None,
|
|
1275
|
+
) -> pd.DataFrame:
|
|
1276
|
+
"""
|
|
1277
|
+
从本机「支持 TQ 策略」的通达信客户端获取单只标的的日线数据。
|
|
1278
|
+
|
|
1279
|
+
返回结构与 _get_from_baostock 一致(date 索引 + open/high/low/close/
|
|
1280
|
+
volume/amount),两处口径在本源内直接对齐:
|
|
1281
|
+
- **volume**:通达信给的就是「股」,与 baostock 口径一致,原样使用
|
|
1282
|
+
- **amount**:通达信单位是「万元」,×10000 换算成「元」
|
|
1283
|
+
|
|
1284
|
+
复权由通达信自己按 dividend_type 算好(front=前复权 / back=后复权 /
|
|
1285
|
+
none=不复权),本处只做口径映射,不自行计算复权。
|
|
1286
|
+
|
|
1287
|
+
取数前必须先 ``refresh_kline``:通达信本地只存「下载过」的 K 线,直接查
|
|
1288
|
+
没下载过的标的只会得到最新一根(历史静默缺失)。refresh_kline 让客户端
|
|
1289
|
+
把该标的的 K 线补齐,本地已有时也会很快返回。
|
|
1290
|
+
|
|
1291
|
+
extra_fields 对通达信行情无意义(当前仅接行情,无扩展列),此处不处理。
|
|
1292
|
+
"""
|
|
1293
|
+
# 周期映射(当前仅日线)
|
|
1294
|
+
period_map = {'1d': '1d'}
|
|
1295
|
+
if self.period not in period_map:
|
|
1296
|
+
raise RuntimeError(
|
|
1297
|
+
f"tdx 源暂不支持周期 {self.period!r}(可选: {list(period_map)})"
|
|
1298
|
+
)
|
|
1299
|
+
|
|
1300
|
+
# 复权映射:通达信 front=前复权 / back=后复权 / none=不复权
|
|
1301
|
+
adjust_map = {'bfq': 'none', 'qfq': 'front', 'hfq': 'back'}
|
|
1302
|
+
dividend_type = adjust_map.get(self.adjust, 'front')
|
|
1303
|
+
|
|
1304
|
+
tdx_symbol = self.to_tdx_symbol(symbol)
|
|
1305
|
+
tq = tdx.get_tq()
|
|
1306
|
+
|
|
1307
|
+
# 通达信只认 YYYYMMDD
|
|
1308
|
+
start = pd.Timestamp(start_date).strftime('%Y%m%d')
|
|
1309
|
+
end = pd.Timestamp(end_date).strftime('%Y%m%d')
|
|
1310
|
+
|
|
1311
|
+
# 先让客户端补齐该标的的 K 线(失败返回 None),否则历史会静默缺失
|
|
1312
|
+
if tq.refresh_kline(stock_list=[tdx_symbol],
|
|
1313
|
+
period=period_map[self.period]) is None:
|
|
1314
|
+
raise RuntimeError(
|
|
1315
|
+
f"通达信刷新 K 线缓存失败({tdx_symbol}):"
|
|
1316
|
+
"请确认「TQ 版通达信」客户端已启动并已登录。"
|
|
1317
|
+
)
|
|
1318
|
+
|
|
1319
|
+
try:
|
|
1320
|
+
raw = tq.get_market_data(
|
|
1321
|
+
stock_list=[tdx_symbol],
|
|
1322
|
+
start_time=start,
|
|
1323
|
+
end_time=end,
|
|
1324
|
+
dividend_type=dividend_type,
|
|
1325
|
+
period=period_map[self.period],
|
|
1326
|
+
)
|
|
1327
|
+
except Exception as e: # noqa: BLE001
|
|
1328
|
+
raise RuntimeError(f"通达信查询 {symbol}({tdx_symbol}) 失败: {e}") from e
|
|
1329
|
+
|
|
1330
|
+
# 无任何数据时返回空 dict
|
|
1331
|
+
if not raw:
|
|
1332
|
+
return pd.DataFrame()
|
|
1333
|
+
|
|
1334
|
+
# 每张字段表都是「日期为索引、列为代码」的宽表,只取当前这一列
|
|
1335
|
+
cols = {}
|
|
1336
|
+
for src, dst in self.TDX_FIELD_MAP.items():
|
|
1337
|
+
tbl = raw.get(src)
|
|
1338
|
+
if tbl is None or tdx_symbol not in getattr(tbl, 'columns', []):
|
|
1339
|
+
return pd.DataFrame()
|
|
1340
|
+
cols[dst] = tbl[tdx_symbol]
|
|
1341
|
+
|
|
1342
|
+
df = pd.DataFrame(cols)
|
|
1343
|
+
df.index = pd.DatetimeIndex(df.index)
|
|
1344
|
+
df.index.name = 'date'
|
|
1345
|
+
df['amount'] = df['amount'] * 10000 # 万元 → 元
|
|
1346
|
+
df = df[df['close'].notna()] # 去掉多标的并集索引带出的空行
|
|
1347
|
+
df = df[~df.index.duplicated(keep='last')].sort_index()
|
|
1348
|
+
for col in self.BAOSTOCK_BASE_FIELDS:
|
|
1349
|
+
df[col] = pd.to_numeric(df[col], errors='coerce')
|
|
1350
|
+
return df
|
qka/core/tdx.py
ADDED
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
"""
|
|
2
|
+
通达信(TQ)数据源支持
|
|
3
|
+
|
|
4
|
+
qka 通过本机安装的「支持 TQ 策略」的通达信客户端取数:在进程内 ``import tqcenter``
|
|
5
|
+
(该模块随客户端自带),由 ``tqcenter`` 加载客户端目录下的 ``TPythClient.dll`` 与本机
|
|
6
|
+
运行中的 ``TdxW.exe`` 通信。因此本数据源:
|
|
7
|
+
|
|
8
|
+
- 仅支持 Windows;
|
|
9
|
+
- 要求用它的机器上装着 TQ 版通达信,且客户端在运行。
|
|
10
|
+
|
|
11
|
+
「通达信装在哪」是**这台机器怎么连上它**的问题,与「这次要取什么数据」(Data 的
|
|
12
|
+
symbols/period/adjust 等)无关,故独立于 Data 管理:优先用代码显式设置的,其次看
|
|
13
|
+
环境变量 ``QKA_TDX_ROOT``,最后自动从注册表识别。
|
|
14
|
+
"""
|
|
15
|
+
|
|
16
|
+
import os
|
|
17
|
+
import sys
|
|
18
|
+
import threading
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
from typing import List, Optional
|
|
21
|
+
|
|
22
|
+
from qka.utils.logger import logger
|
|
23
|
+
|
|
24
|
+
# 环境变量:指向「支持 TQ 策略」的通达信安装目录
|
|
25
|
+
ENV_VAR = 'QKA_TDX_ROOT'
|
|
26
|
+
|
|
27
|
+
# 判定「是不是 TQ 版」的标志文件(相对安装目录)。普通版没有这个模块。
|
|
28
|
+
_TQ_MARKER = ('PYPlugins', 'user', 'tqcenter.py')
|
|
29
|
+
|
|
30
|
+
# 代码级设置(优先级最高,见 set_tdx_root)
|
|
31
|
+
_code_root: Optional[Path] = None
|
|
32
|
+
|
|
33
|
+
# 进程级复用:tqcenter 的 tq 是类级单例连接,全进程共用一个实例
|
|
34
|
+
_tq_client = None
|
|
35
|
+
_tq_lock = threading.Lock()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
# ── 路径:识别 / 设置 / 查询 ────────────────────────────────────
|
|
39
|
+
|
|
40
|
+
def _is_tq_install(root: Path) -> bool:
|
|
41
|
+
"""目录是否是一套带 TQ 组件的通达信安装(依据标志文件是否存在)。"""
|
|
42
|
+
try:
|
|
43
|
+
return root.joinpath(*_TQ_MARKER).is_file()
|
|
44
|
+
except OSError:
|
|
45
|
+
return False
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _iter_registry_tdx_dirs() -> List[str]:
|
|
49
|
+
"""枚举注册表「卸载」表里所有含「通达信」的安装目录。
|
|
50
|
+
|
|
51
|
+
覆盖 HKLM / HKCU 两个配置单元,并显式尝试 32 位与 64 位两个视图
|
|
52
|
+
(TQ 版注册名形如「通达信金融终端64」)。非 Windows 或读不到时返回空列表。
|
|
53
|
+
"""
|
|
54
|
+
try:
|
|
55
|
+
import winreg
|
|
56
|
+
except ImportError:
|
|
57
|
+
return []
|
|
58
|
+
|
|
59
|
+
sub = r"SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall"
|
|
60
|
+
hives = (winreg.HKEY_LOCAL_MACHINE, winreg.HKEY_CURRENT_USER)
|
|
61
|
+
views = (winreg.KEY_WOW64_64KEY, winreg.KEY_WOW64_32KEY, 0)
|
|
62
|
+
|
|
63
|
+
found: List[str] = []
|
|
64
|
+
for hive in hives:
|
|
65
|
+
for view in views:
|
|
66
|
+
try:
|
|
67
|
+
key = winreg.OpenKey(hive, sub, 0, winreg.KEY_READ | view)
|
|
68
|
+
except OSError:
|
|
69
|
+
continue
|
|
70
|
+
try:
|
|
71
|
+
idx = 0
|
|
72
|
+
while True:
|
|
73
|
+
try:
|
|
74
|
+
name = winreg.EnumKey(key, idx)
|
|
75
|
+
except OSError:
|
|
76
|
+
break
|
|
77
|
+
idx += 1
|
|
78
|
+
try:
|
|
79
|
+
sk = winreg.OpenKey(key, name)
|
|
80
|
+
except OSError:
|
|
81
|
+
continue
|
|
82
|
+
display = _reg_value(sk, "DisplayName")
|
|
83
|
+
location = _reg_value(sk, "InstallLocation")
|
|
84
|
+
if location and display and "通达信" in str(display):
|
|
85
|
+
found.append(str(location))
|
|
86
|
+
finally:
|
|
87
|
+
key.Close()
|
|
88
|
+
return found
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
def _reg_value(key, name: str):
|
|
92
|
+
"""读一个注册表值,不存在返回 None。"""
|
|
93
|
+
import winreg
|
|
94
|
+
try:
|
|
95
|
+
return winreg.QueryValueEx(key, name)[0]
|
|
96
|
+
except OSError:
|
|
97
|
+
return None
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def set_tdx_root(path) -> Path:
|
|
101
|
+
"""显式指定「支持 TQ 策略」的通达信安装目录(优先级最高)。
|
|
102
|
+
|
|
103
|
+
Args:
|
|
104
|
+
path: 安装目录,如 ``r'D:\\new_tdx64'``
|
|
105
|
+
|
|
106
|
+
Returns:
|
|
107
|
+
Path: 规范化后的目录
|
|
108
|
+
|
|
109
|
+
Raises:
|
|
110
|
+
ValueError: 目录下没有 TQ 组件(``PYPlugins/user/tqcenter.py``)
|
|
111
|
+
"""
|
|
112
|
+
global _code_root
|
|
113
|
+
root = Path(path).expanduser()
|
|
114
|
+
if not _is_tq_install(root):
|
|
115
|
+
raise ValueError(
|
|
116
|
+
f"{root} 不是支持 TQ 策略的通达信目录(未找到 "
|
|
117
|
+
f"{Path(*_TQ_MARKER)})。请指向装有 TQ 组件的那套安装。"
|
|
118
|
+
)
|
|
119
|
+
_code_root = root
|
|
120
|
+
logger.info(f"已指定通达信安装目录: {root}")
|
|
121
|
+
return root
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def get_tdx_root_info() -> dict:
|
|
125
|
+
"""查询「当前会使用哪个通达信目录、以及它是怎么来的」。
|
|
126
|
+
|
|
127
|
+
只做识别、**不建立连接**,因此可在客户端未启动时安全调用(如界面展示)。
|
|
128
|
+
|
|
129
|
+
Returns:
|
|
130
|
+
dict: ``{'root': str | None, 'source': 'code' | 'env' | 'auto' | None}``
|
|
131
|
+
|
|
132
|
+
- ``source='code'``:代码里 ``set_tdx_root()`` 指定的
|
|
133
|
+
- ``source='env'``:环境变量 ``QKA_TDX_ROOT`` 指定的
|
|
134
|
+
- ``source='auto'``:从注册表自动识别到的
|
|
135
|
+
- ``root=None``:以上都没有找到
|
|
136
|
+
"""
|
|
137
|
+
if _code_root is not None:
|
|
138
|
+
return {'root': str(_code_root), 'source': 'code'}
|
|
139
|
+
|
|
140
|
+
env = os.environ.get(ENV_VAR)
|
|
141
|
+
if env and _is_tq_install(Path(env)):
|
|
142
|
+
return {'root': str(Path(env)), 'source': 'env'}
|
|
143
|
+
|
|
144
|
+
for candidate in _iter_registry_tdx_dirs():
|
|
145
|
+
root = Path(str(candidate).rstrip('\\/'))
|
|
146
|
+
if _is_tq_install(root):
|
|
147
|
+
return {'root': str(root), 'source': 'auto'}
|
|
148
|
+
|
|
149
|
+
return {'root': None, 'source': None}
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
def resolve_tdx_root() -> Path:
|
|
153
|
+
"""取当前生效的通达信目录;找不到时给出可照做的三条出路。"""
|
|
154
|
+
info = get_tdx_root_info()
|
|
155
|
+
if info['root']:
|
|
156
|
+
return Path(info['root'])
|
|
157
|
+
raise RuntimeError(
|
|
158
|
+
"未找到支持 TQ 策略的通达信安装目录。请任选其一:\n"
|
|
159
|
+
" 1) 安装「支持 TQ 策略」的通达信(new_tdx64 版)\n"
|
|
160
|
+
f" 2) 设置环境变量 {ENV_VAR} 指向其安装目录\n"
|
|
161
|
+
" 3) 在代码里调用 qka.set_tdx_root(r'...')"
|
|
162
|
+
)
|
|
163
|
+
|
|
164
|
+
|
|
165
|
+
# ── 连接:惰性加载 tqcenter 并初始化 ────────────────────────────
|
|
166
|
+
|
|
167
|
+
def get_tq():
|
|
168
|
+
"""惰性加载并返回通达信的 TQ 客户端(进程级复用、线程安全)。
|
|
169
|
+
|
|
170
|
+
首次调用时:定位安装目录 → 把 ``PYPlugins/user`` 加入 sys.path → ``import tqcenter``
|
|
171
|
+
→ ``tq.initialize(...)``。之后复用同一实例。
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
tqcenter.tq: 通达信数据访问类
|
|
175
|
+
|
|
176
|
+
Raises:
|
|
177
|
+
RuntimeError: 未找到安装目录 / 加载 TQ 模块失败 / 初始化失败(客户端未启动等)
|
|
178
|
+
"""
|
|
179
|
+
global _tq_client
|
|
180
|
+
if _tq_client is not None:
|
|
181
|
+
return _tq_client
|
|
182
|
+
|
|
183
|
+
with _tq_lock:
|
|
184
|
+
if _tq_client is not None:
|
|
185
|
+
return _tq_client
|
|
186
|
+
|
|
187
|
+
root = resolve_tdx_root()
|
|
188
|
+
plugin_dir = root.joinpath('PYPlugins', 'user')
|
|
189
|
+
if str(plugin_dir) not in sys.path:
|
|
190
|
+
sys.path.insert(0, str(plugin_dir))
|
|
191
|
+
|
|
192
|
+
try:
|
|
193
|
+
from tqcenter import tq
|
|
194
|
+
except Exception as e: # noqa: BLE001 - 原样上报给调用方
|
|
195
|
+
raise RuntimeError(f"加载通达信 TQ 模块失败({plugin_dir}): {e}") from e
|
|
196
|
+
|
|
197
|
+
# 初始化参数只是一个标识用的「策略文件路径」,不要求文件真实存在
|
|
198
|
+
try:
|
|
199
|
+
tq.initialize(str(Path(__file__).resolve()))
|
|
200
|
+
except Exception as e: # noqa: BLE001
|
|
201
|
+
raise RuntimeError(
|
|
202
|
+
"通达信 TQ 初始化失败:请确认客户端(TdxW.exe)已启动。"
|
|
203
|
+
f"\n原始错误: {e}"
|
|
204
|
+
) from e
|
|
205
|
+
|
|
206
|
+
_tq_client = tq
|
|
207
|
+
return _tq_client
|
|
@@ -23,10 +23,10 @@ data = Data(
|
|
|
23
23
|
| `symbols` | `list[str \| Simulate]` | `None` | 标的列表。元素可以是 A 股代码(**`代码.市场`**,如 `'000001.SZ'`、`'600000.SH'`;也兼容 `sz.000001` 旧写法),也可以是 `Simulate` 对象(模拟标的),两者可混排,见下方「模拟标的」 |
|
|
24
24
|
| `period` | `str` | `'1d'` | 数据周期,当前仅支持 `'1d'` |
|
|
25
25
|
| `adjust` | `str` | `'qfq'` | 复权方式:`'qfq'` 前复权,`'hfq'` 后复权,`'bfq'` 不复权 |
|
|
26
|
-
| `source` | `str` | `'tickflow'` | 数据源。`'tickflow'`(默认)行情覆盖全,**含 ETF / 可转债全历史**;`'baostock'` 提供估值等扩展字段(见 `extra_fields
|
|
26
|
+
| `source` | `str` | `'tickflow'` | 数据源。`'tickflow'`(默认)行情覆盖全,**含 ETF / 可转债全历史**;`'baostock'` 提供估值等扩展字段(见 `extra_fields`);`'tdx'` 走本机「TQ 版」通达信客户端(仅 Windows,需客户端在运行),仅行情、无扩展字段,安装目录见下方「通达信数据源」 |
|
|
27
27
|
| `benchmark` | `str` | `None` | 基准指数代码,如 `'000300.SH'`。下载后在 `get()` 结果中追加 `benchmark|returns` 列 |
|
|
28
28
|
| `indicators` | `dict` | `None` | 预计算指标,见下方 |
|
|
29
|
-
| `extra_fields` | `list[str]` | `None` | 扩展字段(选股/估值用)。**仅 `source='baostock'` 提供**;`tickflow` 源无这些列,传入会被静默忽略,见下方 |
|
|
29
|
+
| `extra_fields` | `list[str]` | `None` | 扩展字段(选股/估值用)。**仅 `source='baostock'` 提供**;`tickflow` / `tdx` 源无这些列,传入会被静默忽略,见下方 |
|
|
30
30
|
| `warmup` | `int` | `0` | 指标预热天数。回测/取数时自动多读 `warmup` 个交易日历史用于计算指标,使第 1 个交易日即拿到有效指标值(无需手写跳过前 N 根 bar)。仅用于计算,不增加回测 bar 数 |
|
|
31
31
|
|
|
32
32
|
> **warmup(指标预热)**:若指标/因子需要较长历史窗口(如动量排名需看前 200 天),设 `warmup=200`。
|
|
@@ -47,6 +47,30 @@ data = Data(
|
|
|
47
47
|
|------|------|------|
|
|
48
48
|
| `returns` | `close.diff() / close.shift(1)` | 日收益率,始终存在,无需在 indicators 中声明 |
|
|
49
49
|
|
|
50
|
+
### 通达信数据源
|
|
51
|
+
|
|
52
|
+
`source='tdx'` 走本机安装的「支持 TQ 策略」通达信客户端(在进程内 `import tqcenter`,由客户端与本机 `TdxW.exe` 通信)。前提与边界:
|
|
53
|
+
|
|
54
|
+
- **仅 Windows**,且用它的机器需装着 TQ 版通达信、**客户端在运行**、并已登录;
|
|
55
|
+
- **只接行情**(日线 OHLCV + 成交额),**没有** `extra_fields` 扩展列;
|
|
56
|
+
- 复权由通达信自己按 `adjust` 算好(`qfq`→前复权 / `hfq`→后复权 / `bfq`→不复权),qka 不另算。
|
|
57
|
+
|
|
58
|
+
安装目录的定位与查询(只有该源需要):
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
import qka
|
|
62
|
+
|
|
63
|
+
qka.get_tdx_root_info() # {'root': 'D:\\new_tdx64', 'source': 'auto'} —— 只做识别,不建连接,可随时查
|
|
64
|
+
qka.set_tdx_root(r'D:\new_tdx64') # 代码里显式指定(优先级最高)
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
优先级:`set_tdx_root()` > 环境变量 `QKA_TDX_ROOT` > 自动读注册表识别。三者都没有时,取数会抛出带解决步骤的 `RuntimeError`。
|
|
68
|
+
|
|
69
|
+
```python
|
|
70
|
+
data = Data(symbols=['600519.SH'], source='tdx', adjust='qfq')
|
|
71
|
+
df = data.get(start_date='2024-01-01', end_date='2026-10-09')
|
|
72
|
+
```
|
|
73
|
+
|
|
50
74
|
### 模拟标的
|
|
51
75
|
|
|
52
76
|
`symbols` 的元素除了真实代码字符串,还可以是 `Simulate` 对象——一只由 qka **现场生成**的行情,不下载、不读缓存。写法与真实标的完全同构:
|
|
@@ -98,7 +122,7 @@ r_t = drift + reversion * (logAnchor - logP_{t-1}) + vol * eps_t eps_t ~
|
|
|
98
122
|
|
|
99
123
|
`extra_fields` 追加扩展列(选股/估值用),列名同样遵循 `{symbol}|{field}` 约定(如 `000001.SZ|peTTM`),数值自动转为 `float64`。**不是指标,不参与 indicators 预计算**,是随行情一起下载的原始字段。
|
|
100
124
|
|
|
101
|
-
> ⚠️ **仅 `source='baostock'` 提供**:这些扩展列来自 baostock 的 `query_history_k_data_plus` 接口。默认源 `'tickflow'` 的历史日线固定只有 OHLCV + 成交额,**没有**这些扩展列——此时传入的 `extra_fields` 会被静默忽略(不报错、也不生成对应列)。需要估值/换手率等字段时,显式用 `Data(..., source='baostock')`。
|
|
125
|
+
> ⚠️ **仅 `source='baostock'` 提供**:这些扩展列来自 baostock 的 `query_history_k_data_plus` 接口。默认源 `'tickflow'` 与 `'tdx'` 的历史日线固定只有 OHLCV + 成交额,**没有**这些扩展列——此时传入的 `extra_fields` 会被静默忽略(不报错、也不生成对应列)。需要估值/换手率等字段时,显式用 `Data(..., source='baostock')`。
|
|
102
126
|
|
|
103
127
|
**不传 `extra_fields` 时,每只股票默认只有 6 个行情列 + 1 个常驻列:**
|
|
104
128
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: qka
|
|
3
|
-
Version: 2.3.
|
|
3
|
+
Version: 2.3.3.dev2
|
|
4
4
|
Summary: QKA(快量化 / Quant Kit for A-shares)- 简洁易用的 A 股量化回测框架
|
|
5
5
|
Project-URL: Home, https://github.com/zsrl/qka
|
|
6
6
|
Project-URL: Repository, https://github.com/zsrl/qka
|
|
@@ -137,6 +137,7 @@ print(bt.metrics['sharpe_ratio']) # 夏普比率
|
|
|
137
137
|
## 核心能力
|
|
138
138
|
|
|
139
139
|
- **数据获取** — 写个股票代码就能取到本地,自动缓存、自动补齐最新数据,重复使用秒读
|
|
140
|
+
- **多数据源** — 内置 `tickflow`、`baostock`、`tdx` 三个行情源,改一个 `source=` 即可切换,各源缓存分目录存放、互不干扰
|
|
140
141
|
- **技术指标** — 均线、MACD、RSI、ATR 等 ta 库全部指标,外加 α/β、夏普、最大回撤等内置指标,声明一下即可,策略里不用自己 rolling
|
|
141
142
|
- **估值字段** — 换手率、涨跌幅、停牌与 ST 标记、市盈率、市净率等,需要哪些就取哪些
|
|
142
143
|
- **模拟行情** — 一条 `Simulate` 就能造出单边上涨、单边下跌、来回震荡的走势,不必等真实行情,用来验证策略是不是真在按逻辑交易
|
|
@@ -200,6 +201,7 @@ print(bt.metrics['total_return_pct'])
|
|
|
200
201
|
|
|
201
202
|
- [tickflow](https://tickflow.org) — 免费 A 股 / ETF / 可转债行情(默认数据源)
|
|
202
203
|
- [baostock](http://baostock.com) — 免费 A 股数据(估值等扩展字段)
|
|
204
|
+
- [通达信](https://www.tdx.com.cn) — 本机「TQ 版」客户端的行情(`source='tdx'`,仅 Windows)
|
|
203
205
|
- [ta](https://github.com/bukosabino/ta) — 技术指标库
|
|
204
206
|
|
|
205
207
|
---
|
|
@@ -1,27 +1,28 @@
|
|
|
1
|
-
qka/__init__.py,sha256=
|
|
1
|
+
qka/__init__.py,sha256=2EgIGy3W2NFvxniqHKpCYRhRIOSGRrkEa94gr3UEezo,1087
|
|
2
2
|
qka/cli.py,sha256=12ZkbSZmE7hSmz5_O67gOXVKROIdymCQevnNfEOApd8,466
|
|
3
3
|
qka/core/__init__.py,sha256=kVhvzSA915MXOk9axq9T-COlv3OeTPfv2avX58WxElE,578
|
|
4
4
|
qka/core/accessor.py,sha256=iEZrteVe8nqv3s5Va2l3Vz50N2S6X0IOElKq9Ik3Wno,2893
|
|
5
5
|
qka/core/analysis.py,sha256=EXCO4qhzd2av5Yb4jihcNji1V-VFPOceUHKtg-q8bLs,11142
|
|
6
6
|
qka/core/backtest.py,sha256=kzMAFbyGFvGE--Qypiot6wAp0jTiEsOPbaPd8Zs3o1M,13541
|
|
7
7
|
qka/core/broker.py,sha256=FxeHh9qZ9M6FNFLd8EXwztA4kmC9zZzkrXin2hdVFa4,9662
|
|
8
|
-
qka/core/data.py,sha256=
|
|
8
|
+
qka/core/data.py,sha256=HIoLjxKcp4FZ9i7G_xOcb4JXsdCRrI9AHhakFKul_HI,61295
|
|
9
9
|
qka/core/indicator.py,sha256=l6KxGw75YbnDGzVs04--f-9gLiwAYKfQ8JhuTTNHADU,6453
|
|
10
10
|
qka/core/report.py,sha256=TaZoxWk7ZIPLhA9Ss9UOmxWY2ymzKJRUnxSzmyPcBTI,22386
|
|
11
11
|
qka/core/simulate.py,sha256=bdThckUFj0Y3GyAj5ahLIGv3HetJFTbyxbEcX2SUKn0,2915
|
|
12
12
|
qka/core/sizing.py,sha256=xMLHUPnmmwnm51EaJu9cgsN3Yp7t9pVt8yv-yJ6X4wc,6334
|
|
13
13
|
qka/core/strategy.py,sha256=0cG8wHaaD_f0Xe0PQ374T0BDL7MvdML-jwA1YCLBNNM,1938
|
|
14
|
+
qka/core/tdx.py,sha256=fx1J57Pt0fAraKbV-PSkpCZB2EiixFFrLtPVQ_TDRwc,7405
|
|
14
15
|
qka/utils/__init__.py,sha256=UjWK6PtXSD4PIa20gyx_zGyAixHPcl7YKXqEiHq1Atg,334
|
|
15
16
|
qka/utils/logger.py,sha256=TrUtfVQ0g6yNgklDkjeaSj1Jwf7bkzRDXJKhVRaJQkY,7888
|
|
16
17
|
qka/skills/qka/SKILL.md,sha256=lddd_JvGZ0x4JBlz_IB1cd4ScHRZ30-IC9SWI9Tjbvw,2132
|
|
17
18
|
qka/skills/qka/references/analysis.md,sha256=GdFbK3fa8sFgvcxJ1xzkToF4QwsCFXf_EgKb9bbRgwo,3587
|
|
18
19
|
qka/skills/qka/references/backtest.md,sha256=znu3vW3GPyqXNDsGz5vHinFE6_xBjLJBObQHMqsHDzc,4806
|
|
19
20
|
qka/skills/qka/references/broker.md,sha256=wr62v1asRM-wBdsADIsB406eXm_ENYcrwGMANQobaa0,1083
|
|
20
|
-
qka/skills/qka/references/data.md,sha256=
|
|
21
|
+
qka/skills/qka/references/data.md,sha256=gSNIAgmr_BMuZQS0N0YEGTbcV9-J6LSSyd5UElos1Hg,23104
|
|
21
22
|
qka/skills/qka/references/sizing.md,sha256=3SsX2ddQoHjE177dberW0Q7wYxW-M_2DG_-sV3CCo9A,656
|
|
22
23
|
qka/skills/qka/references/strategy.md,sha256=e6nZHaWu8SDbbka_nX6XJslTRwp-oOQEMLZS10FZMog,4461
|
|
23
|
-
qka-2.3.
|
|
24
|
-
qka-2.3.
|
|
25
|
-
qka-2.3.
|
|
26
|
-
qka-2.3.
|
|
27
|
-
qka-2.3.
|
|
24
|
+
qka-2.3.3.dev2.dist-info/METADATA,sha256=jBQzUgWa8iToIqSFeapQ6J-O9yD-gv56r_fQLg2Io9c,7409
|
|
25
|
+
qka-2.3.3.dev2.dist-info/WHEEL,sha256=W3fkpkm7-wf9vBI5Z-7s0eWkeM-spu78I8Neb98DeEg,87
|
|
26
|
+
qka-2.3.3.dev2.dist-info/entry_points.txt,sha256=quG9zg-rOKKtbg-6FiXkoxkUUTMltMBD0hZ0QUnJ6RM,37
|
|
27
|
+
qka-2.3.3.dev2.dist-info/licenses/LICENSE,sha256=3iIG35SnJNF11yIzm3vxa2QUvy9SS5rktM3O1DL2vqM,1075
|
|
28
|
+
qka-2.3.3.dev2.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|