From bcddf5a05205e730a6d0b5f747ffd6e387eb3ab5 Mon Sep 17 00:00:00 2001 From: GitHub Date: Thu, 28 May 2026 16:05:40 +0800 Subject: [PATCH 1/6] feat: add technical indicator calculation (30 indicators via MyTT), bump to 1.4.0 Integrate MyTT library to provide 30 technical indicators (MACD, KDJ, RSI, BOLL, DMI, ATR, etc.) accessible via API and CLI with automatic EMA warm-up. Co-Authored-By: Claude Opus 4.7 --- README.md | 162 ++++++++++++ examples/20_cli/cli_examples.sh | 78 ++++++ examples/21_indicator/basic_indicators.py | 142 ++++++++++ examples/21_indicator/list_indicators.py | 50 ++++ pyproject.toml | 2 +- src/easy_tdx/MyTT.py | 308 ++++++++++++++++++++++ src/easy_tdx/__init__.py | 2 +- src/easy_tdx/cli/__init__.py | 5 +- src/easy_tdx/cli/cmd_indicator.py | 131 +++++++++ src/easy_tdx/indicator.py | 277 +++++++++++++++++++ src/easy_tdx/mac/client.py | 55 ++++ src/easy_tdx/unified.py | 29 ++ tests/unit/test_indicator.py | 158 +++++++++++ 13 files changed, 1396 insertions(+), 3 deletions(-) create mode 100644 examples/21_indicator/basic_indicators.py create mode 100644 examples/21_indicator/list_indicators.py create mode 100644 src/easy_tdx/MyTT.py create mode 100644 src/easy_tdx/cli/cmd_indicator.py create mode 100644 src/easy_tdx/indicator.py create mode 100644 tests/unit/test_indicator.py diff --git a/README.md b/README.md index f8599d5..b23e446 100644 --- a/README.md +++ b/README.md @@ -85,6 +85,36 @@ easy-tdx server-info --table easy-tdx symbol-info SZ 000001 --table ``` +### 技术指标 + +```bash +easy-tdx indicator-list --table # 列出所有可用指标 +easy-tdx indicator MACD -m SH -c 600519 --table # MACD +easy-tdx indicator KDJ -m SZ -c 000001 --table # KDJ +easy-tdx indicator RSI -m SH -c 600519 --table # RSI +easy-tdx indicator BOLL -m SH -c 600519 --table # BOLL 布林带 +easy-tdx indicator DMI -m SH -c 600519 --table # DMI 动向指标 +easy-tdx indicator ATR -m SH -c 600519 --table # ATR 真实波幅 +easy-tdx indicator WR -m SH -c 600519 --table # WR 威廉指标 +easy-tdx indicator CCI -m SH -c 600519 --table # CCI 顺势指标 +easy-tdx indicator BIAS -m SZ -c 000001 --table # BIAS 乖离率 +easy-tdx indicator OBV -m SZ -c 000001 --table # OBV 能量潮 + +# 多指标同时计算 +easy-tdx indicator MACD,KDJ,RSI,BOLL -m SH -c 600519 --count 10 --table + +# 自定义参数 +easy-tdx indicator MACD -m SH -c 600519 --params SHORT=10,LONG=22 + +# 分钟线指标 +easy-tdx indicator MACD -m SH -c 600519 --period 5MIN --count 50 + +# 仅输出指标值(不含 OHLCV) +easy-tdx indicator RSI -m SZ -c 000001 --no-ohlcv +``` + +支持 30 个指标:MACD, KDJ, RSI, BOLL, DMI, ATR, WR, CCI, BIAS, OBV, VR, EMV, MFI, BRAR, ASI, TRIX, DPO, MTM, ROC, EXPMA, BBI, PSY, DFMA, CR, KTN, XSII, MASS, TAQ。 + ### 财务 ```bash @@ -125,6 +155,8 @@ easy-tdx ex tick HK_MAIN_BOARD 00700 --table # 港股分时 | `market-stat` | 全市场涨跌统计 | | `server-info` | 服务器交易时段 | | `symbol-info` | 个股特征快照 | +| `indicator` | 技术指标计算(30 个:MACD/KDJ/RSI/BOLL/DMI/ATR...) | +| `indicator-list` | 列出可用技术指标 | | `f10` | F10 公司信息 | | `fund-flow` | 历史资金流向 | | `ex kline` | 扩展市场 K 线 | @@ -188,6 +220,74 @@ with MacClient.from_best_host() as c: 返回列:`datetime, open, close, high, low, vol, amount`。 +#### 技术指标 + +自动获取 200+ 条历史数据预热 EMA,返回最后 `count` 条带指标的结果: + +```python +from easy_tdx import MacClient, Market, Period, Adjust +from easy_tdx.indicator import compute_indicators, list_indicators + +with MacClient.from_best_host() as c: + # 便捷方法:获取 K 线 + 计算指标一步完成(默认前复权) + df = c.get_stock_kline_with_indicators( + Market.SH, "600519", + indicators=["MACD", "KDJ", "RSI", "BOLL"], + count=30, + ) + # df 包含: datetime, open, close, high, low, vol, amount + # + MACD_DIF, MACD_DEA, MACD_HIST, KDJ_K, KDJ_D, KDJ_J, RSI, + # BOLL_UPPER, BOLL_MID, BOLL_LOWER + + # 自定义指标参数 + df = c.get_stock_kline_with_indicators( + Market.SH, "600519", + indicators=["MACD"], + params={"MACD": {"SHORT": 10, "LONG": 22}}, + ) + + # 独立使用:对已有 DataFrame 计算指标 + raw = c.get_stock_kline(Market.SH, "600519", Period.DAILY, count=200, adjust=Adjust.QFQ) + result = compute_indicators(raw, ["ATR", "CCI", "WR"], tail=30) + + # 查看所有可用指标 + for info in list_indicators(): + print(info["name"], info["description"], info["outputs"]) +``` + +支持 30 个技术指标: + +| 指标 | 输入 | 输出列 | +|------|------|--------| +| MACD | close | MACD_DIF, MACD_DEA, MACD_HIST | +| KDJ | close, high, low | KDJ_K, KDJ_D, KDJ_J | +| RSI | close | RSI | +| BOLL | close | BOLL_UPPER, BOLL_MID, BOLL_LOWER | +| DMI | close, high, low | DMI_PDI, DMI_MDI, DMI_ADX, DMI_ADXR | +| ATR | close, high, low | ATR | +| WR | close, high, low | WR1, WR2 | +| CCI | close, high, low | CCI | +| BIAS | close | BIAS1, BIAS2, BIAS3 | +| OBV | close, vol | OBV | +| VR | close, vol | VR | +| EMV | high, low, vol | EMV, EMV_MA | +| MFI | close, high, low, vol | MFI | +| BRAR | open, close, high, low | AR, BR | +| ASI | open, close, high, low | ASI, ASI_MA | +| TRIX | close | TRIX, TRIX_MA | +| DPO | close | DPO, DPO_MA | +| MTM | close | MTM, MTM_MA | +| ROC | close | ROC, ROC_MA | +| EXPMA | close | EXPMA_12, EXPMA_50 | +| BBI | close | BBI | +| PSY | close | PSY, PSY_MA | +| DFMA | close | DFMA_DIF, DFMA_DMA | +| CR | close, high, low | CR | +| KTN | close, high, low | KTN_UPPER, KTN_MID, KTN_LOWER | +| XSII | close, high, low | XSII_TD1, XSII_TD2, XSII_TD3, XSII_TD4 | +| MASS | high, low | MASS, MASS_MA | +| TAQ | high, low | TAQ_UP, TAQ_MID, TAQ_DOWN | + #### 分时 ```python @@ -303,6 +403,26 @@ with TdxClient.from_best_host() as c: `AsyncTdxClient` 提供对应的 `async def` 方法,接口一一对应。 +### SecurityQuote 字段说明 + +`get_security_quotes()` 返回的 DataFrame 包含以下特殊字段: + +| 字段 | 类型 | 说明 | +|------|------|------| +| `trading_status` | int | 交易状态标志。`0x8020`(32800) = 停牌,其余值表示正常交易或集合竞价 | +| `open_amount` | float | 集合竞价成交金额(元)。仅个股有效,指数该字段无意义 | +| `server_time` | str | 服务器时间,格式 `HH:MM:SS.mmm` | +| `unknown_2` | int | 指数: 集合竞价成交金额/100;个股: 舍入残差≈0 | +| `unknown_3` | int | 个股: 集合竞价成交金额/100;指数: 负值/无意义 | +| `unknown_5-8` | int | 保留字段,恒为 0 | + +检测停牌: + +```python +df = c.get_security_quotes([(Market.SH, "600000")]) +is_suspended = df.iloc[0]["trading_status"] == 0x8020 +``` + ### 离线数据读取 无需网络,从本地通达信安装目录直接读取: @@ -407,6 +527,7 @@ bars = read_daily_bars(filepath) | `get_stock_quotes(stocks, fields)` | 批量实时报价 | | `get_stock_quotes_list(category, ...)` | 市场分类排序报价 | | `get_stock_kline(market, code, period, ...)` | K 线(支持复权) | +| `get_stock_kline_with_indicators(market, code, indicators, ...)` | K 线 + 技术指标 | | `get_tick_chart(market, code, date)` | 单日分时图 | | `get_tick_charts(market, code, days)` | 多日分时图 | | `get_chart_sampling(market, code)` | 分时缩略采样 | @@ -469,6 +590,8 @@ src/easy_tdx/ ├── client.py # TdxClient / AsyncTdxClient(标准协议) ├── unified.py # UnifiedTdxClient(统一入口) ├── config.py # 服务器地址、端口、超时配置 +├── indicator.py # 技术指标计算(30 个,基于 MyTT) +├── MyTT.py # 麦语言技术指标算法库 ├── mac/ │ ├── client.py # MacClient / AsyncMacClient(MAC 协议) │ ├── enums.py # Period, Adjust, Category, ExMarket, SortType, ... @@ -505,5 +628,44 @@ ruff format --check src/ tests/ # format check - [pytdx](https://github.com/rainx/pytdx) -- 离线数据读取模块借鉴自 pytdx 项目,感谢 rainx 及所有贡献者 - [xmtdx](https://github.com/minionszyw/xmtdx) -- 本项目初始原型 - [mootdx](https://github.com/mootdx/mootdx) -- 工程化封装参考 +- [MyTT](https://github.com/mpquant/MyTT) -- 麦语言技术指标算法库,技术指标计算基于此实现 详见 [NOTICE](NOTICE) 和 [LICENSE](LICENSE)。 + +## Changelog + +### 1.4.0 (2026-05-28) + +**技术指标计算** — 集成 [MyTT](https://github.com/mpquant/MyTT) 麦语言指标库,支持 30 个常用技术指标,一步获取 K 线 + 指标值。 + +- 新增 `indicator.py` 核心模块:注册表驱动的指标调度,`compute_indicators()` 纯计算无 IO +- 新增 `MacClient.get_stock_kline_with_indicators()` / `AsyncMacClient` 同名方法 +- 新增 `UnifiedTdxClient.get_stock_kline_with_indicators()` / `AsyncUnifiedTdxClient` 同名方法 +- 新增 CLI 命令 `easy-tdx indicator` 和 `easy-tdx indicator-list` +- 自动获取 200+ 条历史数据预热 EMA,用户只需指定返回条数 +- 支持的指标:MACD, KDJ, RSI, BOLL, DMI, ATR, WR, CCI, BIAS, OBV, VR, EMV, MFI, BRAR, ASI, TRIX, DPO, MTM, ROC, EXPMA, BBI, PSY, DFMA, CR, KTN, XSII, MASS, TAQ + +### 1.3.1 (2025-05-15) + +- 新增 `board-summary` 和 `board-ranking` CLI 命令 +- 新增 `get_board_summary()` 板块汇总(成交额、主力净流入、涨跌家数) +- 新增 `get_board_ranking()` 板块涨跌幅排行榜 + +### 1.3.0 (2025-05-12) + +- 新增 MAC 协议客户端 `MacClient` / `AsyncMacClient`(端口 7709) +- 新增扩展市场客户端 `MacExClient` / `AsyncMacExClient`(端口 7727) +- 新增统一客户端 `UnifiedTdxClient` 自动路由 A 股 / 扩展市场 +- 新增板块、资金流向、集合竞价、异动、个股特征等数据接口 +- 新增 `easy-tdx` CLI 工具,默认 JSON 输出 + +### 1.2.1 (2025-04-20) + +- 离线数据读取模块(日线、分钟线、板块、财务) +- 除权除息、股本变迁读取 + +### 1.0.0 (2025-03-01) + +- 首个正式版本 +- TdxClient / AsyncTdxClient 标准协议客户端 +- K 线、实时报价、分时、逐笔成交、财务数据 diff --git a/examples/20_cli/cli_examples.sh b/examples/20_cli/cli_examples.sh index f5baacf..6d93689 100644 --- a/examples/20_cli/cli_examples.sh +++ b/examples/20_cli/cli_examples.sh @@ -313,3 +313,81 @@ echo "=== 23. 获取扩展市场分时图(港股腾讯)===" # 09:34:00 00:00 532.80 532.56 4100 # 09:35:00 00:00 533.20 532.84 3500 # ...(共约330条) + +echo "=== 24. 列出可用技术指标 ===" +# 列出所有支持的技术指标名称、输入需求和输出列。 +# easy-tdx indicator-list --table +# 输出: +# name description inputs outputs default_params +# MACD MACD 指数平滑异同移动平均线 ['close'] ['MACD_DIF', 'MACD_DEA', ...] {'SHORT': 12, 'LONG': 26, 'M': 9} +# KDJ KDJ 随机指标 ['close', 'high', ...] ['KDJ_K', 'KDJ_D', 'KDJ_J'] {'N': 9, 'M1': 3, 'M2': 3} +# RSI RSI 相对强弱指标 ['close'] ['RSI'] {'N': 24} +# BOLL BOLL 布林带 ['close'] ['BOLL_UPPER', 'BOLL_MID'...] {'N': 20, 'P': 2} +# ...(共30个指标) + +echo "=== 25. 计算单个技术指标(MACD)===" +# 计算单只股票的技术指标。默认前复权(QFQ),返回最近 30 条。 +# 参数: <指标名> -m <市场> -c <代码> --count N --table +# 返回列: datetime, open, high, low, close, vol, amount + 指标列 +# easy-tdx indicator MACD -m SH -c 600519 --table +# 输出: +# datetime open high low close vol amount MACD_DIF MACD_DEA MACD_HIST +# 2025-05-06 00:00:00 1498.00 1518.00 1492.00 1510.00 16540 2500000000 -4.56 -2.94 -3.24 +# 2025-05-07 00:00:00 1505.00 1516.00 1490.00 1498.00 14280 2150000000 -3.12 -3.18 0.11 +# 2025-05-08 00:00:00 1492.00 1510.00 1485.00 1505.00 15670 2350000000 -2.45 -3.03 1.16 +# 2025-05-09 00:00:00 1498.00 1516.00 1490.00 1498.00 14280 2150000000 -1.89 -2.80 1.82 +# 2025-05-12 00:00:00 1505.00 1516.00 1490.00 1498.00 14280 2150000000 -1.78 -2.60 1.64 +# ...(默认30条) + +echo "=== 26. 同时计算多个指标 ===" +# 用逗号分隔多个指标名称(不区分大小写)。 +# easy-tdx indicator MACD,KDJ,RSI,BOLL -m SH -c 600519 --count 5 --table +# 输出: +# datetime close MACD_DIF MACD_DEA MACD_HIST KDJ_K KDJ_D KDJ_J RSI BOLL_UPPER BOLL_MID BOLL_LOWER +# 2025-05-09 00:00 1505.00 -1.89 -2.80 1.82 45.23 52.34 31.01 55.6 1530.45 1500.12 1469.79 +# 2025-05-12 00:00 1498.00 -1.78 -2.60 1.64 38.56 48.89 17.90 48.2 1528.90 1498.56 1468.22 +# 2025-05-13 00:00 1510.00 -0.89 -2.26 2.74 62.34 52.17 82.68 56.8 1527.34 1497.00 1466.66 +# 2025-05-14 00:00 1509.00 -0.12 -1.83 3.42 58.12 53.56 67.24 52.3 1525.78 1495.44 1465.10 +# 2025-05-15 00:00 1521.00 1.23 -1.22 4.90 78.45 59.74 115.87 65.1 1524.22 1493.88 1463.54 + +echo "=== 27. 自定义指标参数 ===" +# 通过 --params 覆盖默认参数。格式: KEY=VALUE 或 INDICATOR.KEY=VALUE +# 修改 MACD 短周期为 10,长周期为 22 +# easy-tdx indicator MACD -m SH -c 600519 --params SHORT=10,LONG=22 --table +# +# 同时计算 MACD 和 KDJ,分别为它们设置不同参数: +# easy-tdx indicator MACD,KDJ -m SH -c 600519 --params MACD.SHORT=10,KDJ.N=14 --table + +echo "=== 28. 仅输出指标值(不含 OHLCV)===" +# 加 --no-ohlcv 隐藏原始 K 线列,仅显示时间 + 指标值。 +# easy-tdx indicator RSI -m SZ -c 000001 --no-ohlcv --count 5 --table +# 输出: +# datetime RSI +# 2025-05-09 00:00:00 52.34 +# 2025-05-12 00:00:00 48.67 +# 2025-05-13 00:00:00 56.12 +# 2025-05-14 00:00:00 51.89 +# 2025-05-15 00:00:00 63.45 + +echo "=== 29. 分钟 K 线技术指标 ===" +# 使用 --period 指定分钟周期,与 K 线命令相同。 +# easy-tdx indicator MACD -m SH -c 600519 --period 5MIN --count 10 --table +# 输出: +# datetime close MACD_DIF MACD_DEA MACD_HIST +# 2025-05-15 14:10 1520.50 0.34 0.28 0.12 +# 2025-05-15 14:15 1518.20 0.21 0.27 -0.11 +# 2025-05-15 14:20 1519.80 0.18 0.25 -0.15 +# 2025-05-15 14:25 1521.00 0.23 0.25 -0.04 +# ...(共10条) + +echo "=== 30. 常用指标快速参考 ===" +# MACD: easy-tdx indicator MACD -m SH -c 600519 --table +# KDJ: easy-tdx indicator KDJ -m SZ -c 000001 --table +# RSI: easy-tdx indicator RSI -m SH -c 600519 --table +# BOLL: easy-tdx indicator BOLL -m SH -c 600519 --table +# DMI: easy-tdx indicator DMI -m SH -c 600519 --table +# ATR: easy-tdx indicator ATR -m SH -c 600519 --table +# WR: easy-tdx indicator WR -m SH -c 600519 --table +# CCI: easy-tdx indicator CCI -m SH -c 600519 --table +# BIAS: easy-tdx indicator BIAS -m SZ -c 000001 --table +# OBV: easy-tdx indicator OBV -m SZ -c 000001 --table diff --git a/examples/21_indicator/basic_indicators.py b/examples/21_indicator/basic_indicators.py new file mode 100644 index 0000000..0378f31 --- /dev/null +++ b/examples/21_indicator/basic_indicators.py @@ -0,0 +1,142 @@ +"""演示:技术指标计算。 + +通过 MacClient 的 get_stock_kline_with_indicators() 获取 K 线并直接计算技术指标。 +内部自动获取 200+ 条历史数据进行 EMA 预热,仅返回最后 count 条结果。 + +也可单独使用 compute_indicators() 对已有的 K 线 DataFrame 计算指标。 + +支持的指标(30 个): + MACD KDJ RSI BOLL DMI ATR WR CCI BIAS OBV + VR EMV MFI BRAR ASI TRIX DPO MTM ROC EXPMA + BBI PSY DFMA CR KTN XSII MASS TAQ + +参数: + market -- 市场代码(Market.SH=1, Market.SZ=0) + code -- 股票代码 + indicators -- 指标名称列表(不区分大小写),如 ["MACD", "KDJ"] + count -- 返回条数(默认 30) + adjust -- 复权方式(默认 QFQ 前复权,技术分析推荐前复权) + params -- 可选参数覆盖,如 {"MACD": {"SHORT": 10}} + +返回 DataFrame 列说明(以 MACD 为例): + datetime datetime K 线时间 + open float 开盘价 + high float 最高价 + low float 最低价 + close float 收盘价 + vol float 成交量 + amount float 成交额 + MACD_DIF float MACD 的 DIF 线 + MACD_DEA float MACD 的 DEA 线 + MACD_HIST float MACD 柱状图((DIF-DEA)*2) +""" + +from easy_tdx import Adjust, MacClient, Market, Period + +with MacClient.from_best_host() as c: + # --- MACD(贵州茅台,日线,前复权)--- + print("=== MACD(贵州茅台 600519)===") + df = c.get_stock_kline_with_indicators( + Market.SH, + "600519", + indicators=["MACD"], + count=10, + ) + print(df[["datetime", "close", "MACD_DIF", "MACD_DEA", "MACD_HIST"]].to_string(index=False)) + + # --- KDJ(平安银行)--- + print("\n=== KDJ(平安银行 000001)===") + df = c.get_stock_kline_with_indicators( + Market.SZ, + "000001", + indicators=["KDJ"], + count=10, + ) + print(df[["datetime", "close", "KDJ_K", "KDJ_D", "KDJ_J"]].to_string(index=False)) + + # --- RSI(贵州茅台)--- + print("\n=== RSI(贵州茅台 600519,N=6 短周期)===") + df = c.get_stock_kline_with_indicators( + Market.SH, + "600519", + indicators=["RSI"], + count=10, + params={"RSI": {"N": 6}}, + ) + print(df[["datetime", "close", "RSI"]].to_string(index=False)) + + # --- BOLL 布林带 --- + print("\n=== BOLL 布林带(贵州茅台 600519)===") + df = c.get_stock_kline_with_indicators( + Market.SH, + "600519", + indicators=["BOLL"], + count=10, + ) + print(df[["datetime", "close", "BOLL_UPPER", "BOLL_MID", "BOLL_LOWER"]].to_string(index=False)) + + # --- 多指标同时计算 --- + print("\n=== MACD + KDJ + RSI + BOLL 联合计算 ===") + df = c.get_stock_kline_with_indicators( + Market.SH, + "600519", + indicators=["MACD", "KDJ", "RSI", "BOLL"], + count=5, + ) + cols = ["datetime", "close", "MACD_DIF", "KDJ_K", "RSI", "BOLL_UPPER", "BOLL_LOWER"] + print(df[cols].to_string(index=False)) + + # --- 仅输出指标列(不含 OHLCV)--- + print("\n=== 仅指标值(--no-ohlcv 模式)===") + df = c.get_stock_kline_with_indicators( + Market.SZ, + "000001", + indicators=["MACD", "RSI"], + count=5, + ) + indicator_cols = [ + c for c in df.columns if c not in ("open", "high", "low", "close", "vol", "amount") + ] + print(df[indicator_cols].to_string(index=False)) + + # --- 分钟 K 线 + 指标 --- + print("\n=== 5 分钟线 MACD(贵州茅台 600519)===") + df = c.get_stock_kline_with_indicators( + Market.SH, + "600519", + indicators=["MACD"], + period=Period.MIN_5, + count=5, + ) + print(df[["datetime", "close", "MACD_DIF", "MACD_DEA", "MACD_HIST"]].to_string(index=False)) + + # --- 使用 compute_indicators 独立计算 --- + print("\n=== 独立使用 compute_indicators ===") + from easy_tdx.indicator import compute_indicators + + raw_df = c.get_stock_kline(Market.SH, "600519", Period.DAILY, count=200, adjust=Adjust.QFQ) + result = compute_indicators(raw_df, ["ATR", "CCI", "WR"], tail=5) + print(result[["datetime", "close", "ATR", "CCI", "WR1", "WR2"]].to_string(index=False)) + +# 运行结果(示例): +# === MACD(贵州茅台 600519)=== +# datetime close MACD_DIF MACD_DEA MACD_HIST +# 2025-05-02 00:00:00 1492.00 -4.12 -1.56 -5.12 +# 2025-05-05 00:00:00 1485.00 -5.23 -2.29 -5.88 +# 2025-05-06 00:00:00 1498.00 -4.56 -2.94 -3.24 +# 2025-05-07 00:00:00 1510.00 -3.12 -3.18 0.11 +# 2025-05-08 00:00:00 1505.00 -2.45 -3.03 1.16 +# 2025-05-09 00:00:00 1505.00 -1.89 -2.80 1.82 +# 2025-05-12 00:00:00 1498.00 -1.78 -2.60 1.64 +# 2025-05-13 00:00:00 1510.00 -0.89 -2.26 2.74 +# 2025-05-14 00:00:00 1509.00 -0.12 -1.83 3.42 +# 2025-05-15 00:00:00 1521.00 1.23 -1.22 4.90 +# +# === KDJ(平安银行 000001)=== +# datetime close KDJ_K KDJ_D KDJ_J +# 2025-05-02 00:00:00 12.45 65.32 58.76 78.44 +# 2025-05-05 00:00:00 12.30 42.15 53.23 19.98 +# 2025-05-06 00:00:00 12.58 71.23 59.23 95.24 +# 2025-05-07 00:00:00 12.72 82.45 65.47 116.41 +# 2025-05-08 00:00:00 12.65 74.56 67.29 89.11 +# ... diff --git a/examples/21_indicator/list_indicators.py b/examples/21_indicator/list_indicators.py new file mode 100644 index 0000000..0e7a587 --- /dev/null +++ b/examples/21_indicator/list_indicators.py @@ -0,0 +1,50 @@ +"""演示:列出所有可用技术指标及其参数。 + +使用 list_indicators() 查看所有支持的指标名称、输入需求、输出列和默认参数。 +无需网络连接。 +""" + +from easy_tdx.indicator import list_indicators + +indicators = list_indicators() + +print(f"共 {len(indicators)} 个技术指标\n") + +# 按所需输入列分组展示 +groups: dict[str, list[dict]] = {} +for info in indicators: + key = "+".join(info["inputs"]) + groups.setdefault(key, []).append(info) + +for inputs, items in groups.items(): + print(f"── 输入: {inputs} {'─' * 50}") + for item in items: + params_str = ( + ", ".join(f"{k}={v}" for k, v in item["default_params"].items()) + if item["default_params"] + else "" + ) + outputs_str = ", ".join(item["outputs"]) + line = f" {item['name']:<8} {item['description']}" + if params_str: + line += f" (默认: {params_str})" + print(line) + print(f" 输出: {outputs_str}") + print() + +# 运行结果: +# 共 30 个技术指标 +# +# ── 输入: close ────────────────────────────────────────────────────────── +# MACD MACD 指数平滑异同移动平均线 (默认: SHORT=12, LONG=26, M=9) +# 输出: MACD_DIF, MACD_DEA, MACD_HIST +# RSI RSI 相对强弱指标 (默认: N=24) +# 输出: RSI +# BOLL BOLL 布林带 (默认: N=20, P=2) +# 输出: BOLL_UPPER, BOLL_MID, BOLL_LOWER +# ... +# +# ── 输入: close+high+low ──────────────────────────────────────────────── +# KDJ KDJ 随机指标 (默认: N=9, M1=3, M2=3) +# 输出: KDJ_K, KDJ_D, KDJ_J +# ... diff --git a/pyproject.toml b/pyproject.toml index d1b0081..6013958 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "easy-tdx" -version = "1.3.1" +version = "1.4.0" description = "通达信 TCP 协议行情数据客户端,支持在线行情与离线本地数据读取" readme = "README.md" requires-python = ">=3.10" diff --git a/src/easy_tdx/MyTT.py b/src/easy_tdx/MyTT.py new file mode 100644 index 0000000..e105d3d --- /dev/null +++ b/src/easy_tdx/MyTT.py @@ -0,0 +1,308 @@ +# MyTT 麦语言-通达信-同花顺指标实现 https://github.com/mpquant/MyTT +# MyTT高级函数验证版本: https://github.com/mpquant/MyTT/blob/main/MyTT_plus.py +# Python2老版本pandas特别的MyTT: https://github.com/mpquant/MyTT/blob/main/MyTT_python2.py +# V2.1 2021-6-6 新增 BARSLAST函数 SLOPE,FORCAST线性回归预测函数 +# V2.3 2021-6-13 新增 TRIX,DPO,BRAR,DMA,MTM,MASS,ROC,VR,ASI等指标 +# V2.4 2021-6-27 新增 EXPMA,OBV,MFI指标, 改进SMA核心函数(核心函数彻底无循环) +# V2.7 2021-11-21 修正 SLOPE,BARSLAST,函数,新加FILTER,LONGCROSS, 感谢qzhjiang对SLOPE,SMA等函数的指正 +# V2.8 2021-11-23 修正 FORCAST,WMA函数,欢迎qzhjiang,stanene,bcq加入社群,一起来完善myTT库 +# V2.9 2021-11-29 新增 HHVBARS,LLVBARS,CONST, VALUEWHEN功能函数 +# V2.92 2021-11-30 新增 BARSSINCEN函数,现在可以 pip install MyTT 完成安装 +# V3.0 2021-12-04 改进 DMA函数支持序列,新增XS2 薛斯通道II指标 +# V3.1 2021-12-19 新增 TOPRANGE,LOWRANGE一级函数 +# V3.2 2023-04-04 新增 CR指标 +# V3.3 2023-11-09 新增 SIN,COS,TAN序列处理的三角函数 + + +#以下所有函数如无特别说明,输入参数S均为numpy序列或者列表list,N为整型int +#应用层1级函数完美兼容通达信或同花顺,具体使用方法请参考通达信 + +import numpy as np; import pandas as pd + +#------------------ 0级:核心工具函数 -------------------------------------------- +def RD(N,D=3): return np.round(N,D) #四舍五入取3位小数 +def RET(S,N=1): return np.array(S)[-N] #返回序列倒数第N个值,默认返回最后一个 +def ABS(S): return np.abs(S) #返回N的绝对值 +def LN(S): return np.log(S) #求底是e的自然对数, +def POW(S,N): return np.power(S,N) #求S的N次方 +def SQRT(S): return np.sqrt(S) #求S的平方根 +def SIN(S): return np.sin(S) #求S的正弦值(弧度) +def COS(S): return np.cos(S) #求S的余弦值(弧度) +def TAN(S): return np.tan(S) #求S的正切值(弧度) +def MAX(S1,S2): return np.maximum(S1,S2) #序列max +def MIN(S1,S2): return np.minimum(S1,S2) #序列min +def IF(S,A,B): return np.where(S,A,B) #序列布尔判断 return=A if S==True else B + + +def REF(S, N=1): #对序列整体下移动N,返回序列(shift后会产生NAN) + return pd.Series(S).shift(N).values + +def DIFF(S, N=1): #前一个值减后一个值,前面会产生nan + return pd.Series(S).diff(N).values #np.diff(S)直接删除nan,会少一行 + +def STD(S,N): #求序列的N日标准差,返回序列 + return pd.Series(S).rolling(N).std(ddof=0).values + +def SUM(S, N): #对序列求N天累计和,返回序列 N=0对序列所有依次求和 + return pd.Series(S).rolling(N).sum().values if N>0 else pd.Series(S).cumsum().values + +def CONST(S): #返回序列S最后的值组成常量序列 + return np.full(len(S),S[-1]) + +def HHV(S,N): #HHV(C, 5) 最近5天收盘最高价 + return pd.Series(S).rolling(N).max().values + +def LLV(S,N): #LLV(C, 5) 最近5天收盘最低价 + return pd.Series(S).rolling(N).min().values + +def HHVBARS(S,N): #求N周期内S最高值到当前周期数, 返回序列 + return pd.Series(S).rolling(N).apply(lambda x: np.argmax(x[::-1]),raw=True).values + +def LLVBARS(S,N): #求N周期内S最低值到当前周期数, 返回序列 + return pd.Series(S).rolling(N).apply(lambda x: np.argmin(x[::-1]),raw=True).values + +def MA(S,N): #求序列的N日简单移动平均值,返回序列 + return pd.Series(S).rolling(N).mean().values + +def EMA(S,N): #指数移动平均,为了精度 S>4*N EMA至少需要120周期 alpha=2/(span+1) + return pd.Series(S).ewm(span=N, adjust=False).mean().values + +def SMA(S, N, M=1): #中国式的SMA,至少需要120周期才精确 (雪球180周期) alpha=1/(1+com) + return pd.Series(S).ewm(alpha=M/N,adjust=False).mean().values #com=N-M/M + +def WMA(S, N): #通达信S序列的N日加权移动平均 Yn = (1*X1+2*X2+3*X3+...+n*Xn)/(1+2+3+...+Xn) + return pd.Series(S).rolling(N).apply(lambda x:x[::-1].cumsum().sum()*2/N/(N+1),raw=True).values + +def DMA(S, A): #求S的动态移动平均,A作平滑因子,必须 0B & A>0 & B>=0 + return np.array(pd.Series(S).rolling(A+1).apply(lambda x:np.all(x[::-1][B:]),raw=True),dtype=bool) + +#------------------ 1级:应用层函数(通过0级核心函数实现)使用方法请参考通达信-------------------------------- +def COUNT(S, N): # COUNT(CLOSE>O, N): 最近N天满足S_BOO的天数 True的天数 + return SUM(S,N) + +def EVERY(S, N): # EVERY(CLOSE>O, 5) 最近N天是否都是True + return IF(SUM(S,N)==N,True,False) + +def EXIST(S, N): # EXIST(CLOSE>3010, N=5) n日内是否存在一天大于3000点 + return IF(SUM(S,N)>0,True,False) + +def FILTER(S, N): # FILTER函数,S满足条件后,将其后N周期内的数据置为0, FILTER(C==H,5) + for i in range(len(S)): S[i+1:i+1+N]=0 if S[i] else S[i+1:i+1+N] + return S # 例:FILTER(C==H,5) 涨停后,后5天不再发出信号 + +def BARSLAST(S): #上一次条件成立到当前的周期, BARSLAST(C/REF(C,1)>=1.1) 上一次涨停到今天的天数 + M=np.concatenate(([0],np.where(S,1,0))) + for i in range(1, len(M)): M[i]=0 if M[i] else M[i-1]+1 + return M[1:] + +def BARSLASTCOUNT(S): # 统计连续满足S条件的周期数 by jqz1226 + rt = np.zeros(len(S)+1) # BARSLASTCOUNT(CLOSE>OPEN)表示统计连续收阳的周期数 + for i in range(len(S)): rt[i+1]=rt[i]+1 if S[i] else rt[i+1] + return rt[1:] + +def BARSSINCEN(S, N): # N周期内第一次S条件成立到现在的周期数,N为常量 by jqz1226 + return pd.Series(S).rolling(N).apply(lambda x:N-1-np.argmax(x) if np.argmax(x) or x[0] else 0,raw=True).fillna(0).values.astype(int) + +def CROSS(S1, S2): # 判断向上金叉穿越 CROSS(MA(C,5),MA(C,10)) 判断向下死叉穿越 CROSS(MA(C,10),MA(C,5)) + return np.concatenate(([False], np.logical_not((S1>S2)[:-1]) & (S1>S2)[1:])) # 不使用0级函数,移植方便 by jqz1226 + +def LONGCROSS(S1,S2,N): # 两条线维持一定周期后交叉,S1在N周期内都小于S2,本周期从S1下方向上穿过S2时返回1,否则返回0 + return np.array(np.logical_and(LAST(S1S2)),dtype=bool) # N=1时等同于CROSS(S1, S2) + +def VALUEWHEN(S, X): # 当S条件成立时,取X的当前值,否则取VALUEWHEN的上个成立时的X值 by jqz1226 + return pd.Series(np.where(S,X,np.nan)).ffill().values + +def BETWEEN(S, A, B): # S处于A和B之间时为真。 包括 AS>B + return ((AS) & (S>B)) + +def TOPRANGE(S): # TOPRANGE(HIGH)表示当前最高价是近多少周期内最高价的最大值 by jqz1226 + rt = np.zeros(len(S)) + for i in range(1,len(S)): rt[i] = np.argmin(np.flipud(S[:i]S[i])) + return rt.astype('int') + + +#------------------ 2级:技术指标函数(全部通过0级,1级函数实现) ------------------------------ +def MACD(CLOSE,SHORT=12,LONG=26,M=9): # EMA的关系,S取120日,和雪球小数点2位相同 + DIF = EMA(CLOSE,SHORT)-EMA(CLOSE,LONG); + DEA = EMA(DIF,M); MACD=(DIF-DEA)*2 + return RD(DIF),RD(DEA),RD(MACD) + +def KDJ(CLOSE,HIGH,LOW, N=9,M1=3,M2=3): # KDJ指标 + low_n = LLV(LOW, N) + high_n = HHV(HIGH, N) + high_low_diff = high_n - low_n + # 避免除零:当最高价等于最低价时,RSV 应该为 50(中性) + with np.errstate(divide='ignore', invalid='ignore'): + rsv = (CLOSE - low_n) / high_low_diff * 100 + rsv = np.where(high_low_diff == 0, 50, rsv) # 除零时返回 50 + K = EMA(rsv, (M1*2-1)); D = EMA(K,(M2*2-1)); J=K*3-D*2 + return K, D, J + +def RSI(CLOSE, N=24): # RSI指标,和通达信小数点2位相同 + DIF = CLOSE-REF(CLOSE,1) + abs_dif_sma = SMA(ABS(DIF), N) + # 避免除零:当价格完全不变时,RSI 应该为 50(中性) + with np.errstate(divide='ignore', invalid='ignore'): + rsi_value = SMA(MAX(DIF,0), N) / abs_dif_sma * 100 + rsi_value = np.where(abs_dif_sma == 0, 50, rsi_value) # 除零时返回 50 + return RD(rsi_value) + +def WR(CLOSE, HIGH, LOW, N=10, N1=6): #W&R 威廉指标 + high_n = HHV(HIGH, N) + low_n = LLV(LOW, N) + high_low_diff = high_n - low_n + with np.errstate(divide='ignore', invalid='ignore'): + wr = (high_n - CLOSE) / high_low_diff * 100 + wr = np.where(high_low_diff == 0, 50, wr) # 除零时返回 50 + + high_n1 = HHV(HIGH, N1) + low_n1 = LLV(LOW, N1) + high_low_diff1 = high_n1 - low_n1 + with np.errstate(divide='ignore', invalid='ignore'): + wr1 = (high_n1 - CLOSE) / high_low_diff1 * 100 + wr1 = np.where(high_low_diff1 == 0, 50, wr1) # 除零时返回 50 + + return RD(wr), RD(wr1) + +def BIAS(CLOSE,L1=6, L2=12, L3=24): # BIAS乖离率 + BIAS1 = (CLOSE - MA(CLOSE, L1)) / MA(CLOSE, L1) * 100 + BIAS2 = (CLOSE - MA(CLOSE, L2)) / MA(CLOSE, L2) * 100 + BIAS3 = (CLOSE - MA(CLOSE, L3)) / MA(CLOSE, L3) * 100 + return RD(BIAS1), RD(BIAS2), RD(BIAS3) + +def BOLL(CLOSE,N=20, P=2): #BOLL指标,布林带 + MID = MA(CLOSE, N); + UPPER = MID + STD(CLOSE, N) * P + LOWER = MID - STD(CLOSE, N) * P + return RD(UPPER), RD(MID), RD(LOWER) + +def PSY(CLOSE,N=12, M=6): + PSY=COUNT(CLOSE>REF(CLOSE,1),N)/N*100 + PSYMA=MA(PSY,M) + return RD(PSY),RD(PSYMA) + +def CCI(CLOSE,HIGH,LOW,N=14): + TP=(HIGH+LOW+CLOSE)/3 + return (TP-MA(TP,N))/(0.015*AVEDEV(TP,N)) + +def ATR(CLOSE,HIGH,LOW, N=20): #真实波动N日平均值 + TR = MAX(MAX((HIGH - LOW), ABS(REF(CLOSE, 1) - HIGH)), ABS(REF(CLOSE, 1) - LOW)) + return MA(TR, N) + +def BBI(CLOSE,M1=3,M2=6,M3=12,M4=20): #BBI多空指标 + return (MA(CLOSE,M1)+MA(CLOSE,M2)+MA(CLOSE,M3)+MA(CLOSE,M4))/4 + +def DMI(CLOSE,HIGH,LOW,M1=14,M2=6): #动向指标:结果和同花顺,通达信完全一致 + TR = SUM(MAX(MAX(HIGH - LOW, ABS(HIGH - REF(CLOSE, 1))), ABS(LOW - REF(CLOSE, 1))), M1) + HD = HIGH - REF(HIGH, 1); LD = REF(LOW, 1) - LOW + DMP = SUM(IF((HD > 0) & (HD > LD), HD, 0), M1) + DMM = SUM(IF((LD > 0) & (LD > HD), LD, 0), M1) + PDI = DMP * 100 / TR; MDI = DMM * 100 / TR + ADX = MA(ABS(MDI - PDI) / (PDI + MDI) * 100, M2) + ADXR = (ADX + REF(ADX, M2)) / 2 + return PDI, MDI, ADX, ADXR + +def TAQ(HIGH,LOW,N): #唐安奇通道(海龟)交易指标,大道至简,能穿越牛熊 + UP=HHV(HIGH,N); DOWN=LLV(LOW,N); MID=(UP+DOWN)/2 + return UP,MID,DOWN + +def KTN(CLOSE,HIGH,LOW,N=20,M=10): #肯特纳交易通道, N选20日,ATR选10日 + MID=EMA((HIGH+LOW+CLOSE)/3,N) + ATRN=ATR(CLOSE,HIGH,LOW,M) + UPPER=MID+2*ATRN; LOWER=MID-2*ATRN + return UPPER,MID,LOWER + +def TRIX(CLOSE,M1=12, M2=20): #三重指数平滑平均线 + TR = EMA(EMA(EMA(CLOSE, M1), M1), M1) + TRIX = (TR - REF(TR, 1)) / REF(TR, 1) * 100 + TRMA = MA(TRIX, M2) + return TRIX, TRMA + +def VR(CLOSE,VOL,M1=26): #VR容量比率 + LC = REF(CLOSE, 1) + return SUM(IF(CLOSE > LC, VOL, 0), M1) / SUM(IF(CLOSE <= LC, VOL, 0), M1) * 100 + +def CR(CLOSE,HIGH,LOW,N=20): #CR价格动量指标 + MID=REF(HIGH+LOW+CLOSE,1)/3; + return SUM(MAX(0,HIGH-MID),N)/SUM(MAX(0,MID-LOW),N)*100 + +def EMV(HIGH,LOW,VOL,N=14,M=9): #简易波动指标 + VOLUME=MA(VOL,N)/VOL; MID=100*(HIGH+LOW-REF(HIGH+LOW,1))/(HIGH+LOW) + EMV=MA(MID*VOLUME*(HIGH-LOW)/MA(HIGH-LOW,N),N); MAEMV=MA(EMV,M) + return EMV,MAEMV + + +def DPO(CLOSE,M1=20, M2=10, M3=6): #区间震荡线 + DPO = CLOSE - REF(MA(CLOSE, M1), M2); MADPO = MA(DPO, M3) + return DPO, MADPO + +def BRAR(OPEN,CLOSE,HIGH,LOW,M1=26): #BRAR-ARBR 情绪指标 + AR = SUM(HIGH - OPEN, M1) / SUM(OPEN - LOW, M1) * 100 + BR = SUM(MAX(0, HIGH - REF(CLOSE, 1)), M1) / SUM(MAX(0, REF(CLOSE, 1) - LOW), M1) * 100 + return AR, BR + +def DFMA(CLOSE,N1=10,N2=50,M=10): #平行线差指标 + DIF=MA(CLOSE,N1)-MA(CLOSE,N2); DIFMA=MA(DIF,M) #通达信指标叫DMA 同花顺叫新DMA + return DIF,DIFMA + +def MTM(CLOSE,N=12,M=6): #动量指标 + MTM=CLOSE-REF(CLOSE,N); MTMMA=MA(MTM,M) + return MTM,MTMMA + +def MASS(HIGH,LOW,N1=9,N2=25,M=6): #梅斯线 + MASS=SUM(MA(HIGH-LOW,N1)/MA(MA(HIGH-LOW,N1),N1),N2) + MA_MASS=MA(MASS,M) + return MASS,MA_MASS + +def ROC(CLOSE,N=12,M=6): #变动率指标 + ROC=100*(CLOSE-REF(CLOSE,N))/REF(CLOSE,N); MAROC=MA(ROC,M) + return ROC,MAROC + +def EXPMA(CLOSE,N1=12,N2=50): #EMA指数平均数指标 + return EMA(CLOSE,N1),EMA(CLOSE,N2); + +def OBV(CLOSE,VOL): #能量潮指标 + return SUM(IF(CLOSE>REF(CLOSE,1),VOL,IF(CLOSEREF(TYP,1),TYP*VOL,0),N)/SUM(IF(TYPBB) & (AA>CC),AA+BB/2+DD/4,IF( (BB>CC) & (BB>AA),BB+AA/2+DD/4,CC+DD/4)); + X=(CLOSE-LC+(CLOSE-OPEN)/2+LC-REF(OPEN,1)); + SI=16*X/R*MAX(AA,BB); ASI=SUM(SI,M1); ASIT=MA(ASI,M2); + return ASI,ASIT + +def XSII(CLOSE, HIGH, LOW, N=102, M=7): #薛斯通道II + AA = MA((2*CLOSE + HIGH + LOW)/4, 5) #最新版DMA才支持 2021-12-4 + TD1 = AA*N/100; TD2 = AA*(200-N) / 100 + CC = ABS((2*CLOSE + HIGH + LOW)/4 - MA(CLOSE,20))/MA(CLOSE,20) + DD = DMA(CLOSE,CC); TD3=(1+M/100)*DD; TD4=(1-M/100)*DD + return TD1, TD2, TD3, TD4 + + + #望大家能提交更多指标和函数 https://github.com/mpquant/MyTT diff --git a/src/easy_tdx/__init__.py b/src/easy_tdx/__init__.py index 713b532..b83b87e 100644 --- a/src/easy_tdx/__init__.py +++ b/src/easy_tdx/__init__.py @@ -107,4 +107,4 @@ __all__ = [ "save_best_ex_host", ] -__version__ = "1.3.0" +__version__ = "1.4.0" diff --git a/src/easy_tdx/cli/__init__.py b/src/easy_tdx/cli/__init__.py index b8337b3..14c8008 100644 --- a/src/easy_tdx/cli/__init__.py +++ b/src/easy_tdx/cli/__init__.py @@ -11,6 +11,7 @@ from .cmd_capital import capital_flow from .cmd_ex import ex from .cmd_finance import f10, fund_flow from .cmd_info import server_info, symbol_info +from .cmd_indicator import indicator, indicator_list from .cmd_kline import kline from .cmd_monitor import market_stat, unusual from .cmd_quote import quote, quote_list @@ -19,7 +20,7 @@ from .cmd_transaction import transaction @click.group() -@click.version_option(version="1.3.1", prog_name="easy-tdx") +@click.version_option(version="1.4.0", prog_name="easy-tdx") def cli() -> None: """easy-tdx -- 通达信行情数据 CLI(默认 JSON 输出,适合 Agent 使用)。 @@ -59,3 +60,5 @@ cli.add_command(symbol_info) cli.add_command(f10) cli.add_command(fund_flow) cli.add_command(ex) +cli.add_command(indicator) +cli.add_command(indicator_list) diff --git a/src/easy_tdx/cli/cmd_indicator.py b/src/easy_tdx/cli/cmd_indicator.py new file mode 100644 index 0000000..70311e2 --- /dev/null +++ b/src/easy_tdx/cli/cmd_indicator.py @@ -0,0 +1,131 @@ +"""技术指标命令。""" + +from __future__ import annotations + +import click + + +def _parse_indicator_params(s: str) -> dict[str, dict[str, int | float]]: + """解析指标参数字符串。 + + 格式: ``SHORT=10,LONG=22`` 或 ``MACD.SHORT=10,KDJ.N=14`` + 无前缀的参数应用到所有请求的指标。 + """ + result: dict[str, dict[str, int | float]] = {} + if not s: + return result + + for pair in s.split(","): + pair = pair.strip() + if "=" not in pair: + continue + key, val = pair.split("=", 1) + key = key.strip() + val = val.strip() + + if "." in key: + indicator, param = key.split(".", 1) + indicator = indicator.strip().upper() + param = param.strip() + result.setdefault(indicator, {})[param] = float(val) if "." in val else int(val) + else: + result.setdefault("*", {})[key] = float(val) if "." in val else int(val) + return result + + +@click.command() +@click.argument("indicators") +@click.option("--market", "-m", required=True, help="市场: SH/SZ/BJ") +@click.option("--code", "-c", required=True, help="股票代码") +@click.option( + "--period", + default="DAILY", + help="K线周期: DAILY/5MIN/15MIN/30MIN/60MIN/1MIN/WEEKLY/MONTHLY", +) +@click.option("--count", default=30, type=int, help="返回条数(默认30)") +@click.option("--adjust", default="QFQ", help="复权: NONE/QFQ/HFQ(默认QFQ)") +@click.option("--params", default=None, help="指标参数: SHORT=10,LONG=22 或 MACD.SHORT=10") +@click.option("--no-ohlcv", is_flag=True, help="不显示原始OHLCV列") +@click.option("--table", "use_table", is_flag=True, help="表格输出") +@click.option("--output", "output_fmt", type=click.Choice(["json", "table", "csv"]), default="json") +def indicator( + indicators: str, + market: str, + code: str, + period: str, + count: int, + adjust: str, + params: str | None, + no_ohlcv: bool, + use_table: bool, + output_fmt: str, +) -> None: + """计算技术指标。 + + 示例: + + easy-tdx indicator MACD -m SH -c 600519 --table + + easy-tdx indicator MACD,KDJ,RSI -m SH -c 600519 --count 10 --table + + easy-tdx indicator BOLL -m SZ -c 000001 --params N=10,P=1.5 + """ + from ..indicator import compute_indicators + from .conn import get_mac_client + from .output import print_error, print_output + from .parsers import parse_adjust, parse_market, parse_period + + fmt = "table" if use_table else output_fmt + mkt = parse_market(market) + indicator_list = [n.strip() for n in indicators.split(",")] + parsed_params = _parse_indicator_params(params) if params else {} + + # 将通配符参数应用到所有指标 + wildcard = parsed_params.pop("*", {}) + final_params: dict[str, dict[str, int | float]] = {} + for name in indicator_list: + final_params[name.upper()] = {**wildcard, **parsed_params.get(name.upper(), {})} + + fetch_count = max(120 + count, 200) + try: + with get_mac_client() as client: + df = client.get_stock_kline( + mkt, + code, + period=parse_period(period), + count=fetch_count, + adjust=parse_adjust(adjust), + ) + if df.empty: + print_error("未获取到K线数据") + return + result = compute_indicators( + df, + indicator_list, + final_params, + keep_ohlcv=not no_ohlcv, + tail=count, + ) + print_output(result, fmt) + except ValueError as e: + print_error(str(e)) + except Exception as e: + print_error(f"{type(e).__name__}: {e}") + + +@click.command("indicator-list") +@click.option("--table", "use_table", is_flag=True, help="表格输出") +@click.option("--output", "output_fmt", type=click.Choice(["json", "table", "csv"]), default="json") +def indicator_list(use_table: bool, output_fmt: str) -> None: + """列出可用的技术指标。""" + import pandas as pd + + from ..indicator import list_indicators + from .output import print_output + + fmt = "table" if use_table else output_fmt + info = list_indicators() + df = pd.DataFrame(info) + if fmt == "table": + df["default_params"] = df["default_params"].apply(lambda d: str(d)) + print_output(df, fmt) diff --git a/src/easy_tdx/indicator.py b/src/easy_tdx/indicator.py new file mode 100644 index 0000000..28bdb20 --- /dev/null +++ b/src/easy_tdx/indicator.py @@ -0,0 +1,277 @@ +"""技术指标计算模块 — 基于 MyTT 的纯计算层(无 IO)。""" + +from __future__ import annotations + +import warnings +from collections.abc import Callable +from dataclasses import dataclass + +import numpy as np +import pandas as pd + +from . import MyTT + + +@dataclass(frozen=True) +class IndicatorSpec: + """单个技术指标的元数据。""" + + name: str + inputs: tuple[str, ...] + outputs: tuple[str, ...] + func: Callable[..., object] + default_params: dict[str, int | float] + description: str + + +_REGISTRY: dict[str, IndicatorSpec] = {} + + +def _reg( + name: str, + inputs: tuple[str, ...], + outputs: tuple[str, ...], + func: Callable[..., object], + defaults: dict[str, int | float], + desc: str, +) -> None: + _REGISTRY[name.upper()] = IndicatorSpec( + name=name.upper(), + inputs=inputs, + outputs=outputs, + func=func, + default_params=defaults, + description=desc, + ) + + +# ── 仅需 close ────────────────────────────────────────────────────────── +_reg( + "MACD", + ("close",), + ("MACD_DIF", "MACD_DEA", "MACD_HIST"), + MyTT.MACD, + {"SHORT": 12, "LONG": 26, "M": 9}, + "MACD 指数平滑异同移动平均线", +) +_reg("RSI", ("close",), ("RSI",), MyTT.RSI, {"N": 24}, "RSI 相对强弱指标") +_reg( + "BOLL", + ("close",), + ("BOLL_UPPER", "BOLL_MID", "BOLL_LOWER"), + MyTT.BOLL, + {"N": 20, "P": 2}, + "BOLL 布林带", +) +_reg( + "BIAS", + ("close",), + ("BIAS1", "BIAS2", "BIAS3"), + MyTT.BIAS, + {"L1": 6, "L2": 12, "L3": 24}, + "BIAS 乖离率", +) +_reg("PSY", ("close",), ("PSY", "PSY_MA"), MyTT.PSY, {"N": 12, "M": 6}, "PSY 心理线") +_reg( + "TRIX", + ("close",), + ("TRIX", "TRIX_MA"), + MyTT.TRIX, + {"M1": 12, "M2": 20}, + "TRIX 三重指数平滑平均线", +) +_reg( + "DPO", ("close",), ("DPO", "DPO_MA"), MyTT.DPO, {"M1": 20, "M2": 10, "M3": 6}, "DPO 区间震荡线" +) +_reg("MTM", ("close",), ("MTM", "MTM_MA"), MyTT.MTM, {"N": 12, "M": 6}, "MTM 动量指标") +_reg("ROC", ("close",), ("ROC", "ROC_MA"), MyTT.ROC, {"N": 12, "M": 6}, "ROC 变动率指标") +_reg( + "EXPMA", + ("close",), + ("EXPMA_12", "EXPMA_50"), + MyTT.EXPMA, + {"N1": 12, "N2": 50}, + "EXPMA 指数平均数指标", +) +_reg("BBI", ("close",), ("BBI",), MyTT.BBI, {"M1": 3, "M2": 6, "M3": 12, "M4": 20}, "BBI 多空指标") +_reg( + "DFMA", + ("close",), + ("DFMA_DIF", "DFMA_DMA"), + MyTT.DFMA, + {"N1": 10, "N2": 50, "M": 10}, + "DFMA 平行线差指标", +) + +# ── 需要 close + high + low ───────────────────────────────────────────── +_reg( + "KDJ", + ("close", "high", "low"), + ("KDJ_K", "KDJ_D", "KDJ_J"), + MyTT.KDJ, + {"N": 9, "M1": 3, "M2": 3}, + "KDJ 随机指标", +) +_reg( + "DMI", + ("close", "high", "low"), + ("DMI_PDI", "DMI_MDI", "DMI_ADX", "DMI_ADXR"), + MyTT.DMI, + {"M1": 14, "M2": 6}, + "DMI 动向指标", +) +_reg("ATR", ("close", "high", "low"), ("ATR",), MyTT.ATR, {"N": 20}, "ATR 真实波幅均值") +_reg("WR", ("close", "high", "low"), ("WR1", "WR2"), MyTT.WR, {"N": 10, "N1": 6}, "WR 威廉指标") +_reg("CCI", ("close", "high", "low"), ("CCI",), MyTT.CCI, {"N": 14}, "CCI 顺势指标") +_reg("CR", ("close", "high", "low"), ("CR",), MyTT.CR, {"N": 20}, "CR 价格动量指标") +_reg( + "KTN", + ("close", "high", "low"), + ("KTN_UPPER", "KTN_MID", "KTN_LOWER"), + MyTT.KTN, + {"N": 20, "M": 10}, + "KTN 肯特纳通道", +) +_reg( + "XSII", + ("close", "high", "low"), + ("XSII_TD1", "XSII_TD2", "XSII_TD3", "XSII_TD4"), + MyTT.XSII, + {"N": 102, "M": 7}, + "XSII 薛斯通道II", +) + +# ── 需要 close + vol ──────────────────────────────────────────────────── +_reg("OBV", ("close", "vol"), ("OBV",), MyTT.OBV, {}, "OBV 能量潮指标") +_reg("VR", ("close", "vol"), ("VR",), MyTT.VR, {"M1": 26}, "VR 容量比率") + +# ── 需要 high + low + vol ─────────────────────────────────────────────── +_reg( + "EMV", + ("high", "low", "vol"), + ("EMV", "EMV_MA"), + MyTT.EMV, + {"N": 14, "M": 9}, + "EMV 简易波动指标", +) +_reg( + "MASS", + ("high", "low"), + ("MASS", "MASS_MA"), + MyTT.MASS, + {"N1": 9, "N2": 25, "M": 6}, + "MASS 梅斯线", +) + +# ── 需要 close + high + low + vol ────────────────────────────────────── +_reg("MFI", ("close", "high", "low", "vol"), ("MFI",), MyTT.MFI, {"N": 14}, "MFI 资金流量指标") + +# ── 需要 open + close + high + low ───────────────────────────────────── +_reg("BRAR", ("open", "close", "high", "low"), ("AR", "BR"), MyTT.BRAR, {"M1": 26}, "BRAR 情绪指标") +_reg( + "ASI", + ("open", "close", "high", "low"), + ("ASI", "ASI_MA"), + MyTT.ASI, + {"M1": 26, "M2": 10}, + "ASI 振动升降指标", +) + +# ── 仅需 high + low ──────────────────────────────────────────────────── +_reg( + "TAQ", ("high", "low"), ("TAQ_UP", "TAQ_MID", "TAQ_DOWN"), MyTT.TAQ, {"N": 20}, "TAQ 唐安奇通道" +) + + +def list_indicators() -> list[dict[str, object]]: + """返回所有可用指标的元数据。""" + return [ + { + "name": spec.name, + "description": spec.description, + "inputs": list(spec.inputs), + "outputs": list(spec.outputs), + "default_params": dict(spec.default_params), + } + for spec in _REGISTRY.values() + ] + + +def compute_indicators( + df: pd.DataFrame, + indicators: list[str], + params: dict[str, dict[str, int | float]] | None = None, + keep_ohlcv: bool = True, + tail: int | None = None, +) -> pd.DataFrame: + """在 K 线 DataFrame 上计算指定技术指标。 + + Args: + df: K 线数据,需包含 open/close/high/low/vol 等列。 + indicators: 指标名称列表(不区分大小写),如 ``["MACD", "KDJ"]``。 + params: 可选参数覆盖,如 ``{"MACD": {"SHORT": 10}}``。 + keep_ohlcv: True 则保留原始 OHLCV 列。 + tail: 计算后仅保留最后 N 行。 + + Returns: + 包含指标列的 DataFrame。 + """ + if df.empty: + return pd.DataFrame(df.copy()) + + params = params or {} + result_parts: list[pd.DataFrame] = [] + required_inputs: set[str] = set() + + names_upper = [n.strip().upper() for n in indicators] + unknown = [n for n in names_upper if n not in _REGISTRY] + if unknown: + raise ValueError(f"未知指标: {unknown}。可用指标: {sorted(_REGISTRY.keys())}") + + for name in names_upper: + spec = _REGISTRY[name] + required_inputs.update(spec.inputs) + + missing_cols = required_inputs - set(df.columns) + if missing_cols: + raise ValueError(f"DataFrame 缺少必要列: {missing_cols}。指标需要这些列: {required_inputs}") + + if len(df) < 120: + warnings.warn( + f"数据仅 {len(df)} 行,EMA 类指标至少需要 120 行才能精确收敛", + stacklevel=2, + ) + + for name in names_upper: + spec = _REGISTRY[name] + inputs = tuple(df[col].values for col in spec.inputs) + override = params.get(name, params.get(spec.name, {})) + kwargs = {**spec.default_params, **override} + raw = spec.func(*inputs, **kwargs) + + if isinstance(raw, tuple): + arrays = raw + else: + arrays = (raw,) + + if len(arrays) != len(spec.outputs): + raise RuntimeError(f"{name}: 预期 {len(spec.outputs)} 个输出,实际 {len(arrays)} 个") + + part = pd.DataFrame( + {col: arr for col, arr in zip(spec.outputs, arrays)}, + index=df.index, + ) + result_parts.append(part) + + indicator_df: pd.DataFrame = pd.concat(result_parts, axis=1) + + if keep_ohlcv: + out: pd.DataFrame = pd.concat([df, indicator_df], axis=1) + else: + time_cols = [c for c in ("datetime", "date") if c in df.columns] + out = pd.concat([df[time_cols], indicator_df], axis=1) if time_cols else indicator_df + + if tail is not None and tail > 0: + out = out.iloc[-tail:] + + return pd.DataFrame(out.reset_index(drop=True)) diff --git a/src/easy_tdx/mac/client.py b/src/easy_tdx/mac/client.py index e91c6f3..e59497f 100644 --- a/src/easy_tdx/mac/client.py +++ b/src/easy_tdx/mac/client.py @@ -375,6 +375,37 @@ class MacClient: return _to_df(all_bars) + def get_stock_kline_with_indicators( + self, + market: int, + code: str, + indicators: list[str], + period: Period = Period.DAILY, + count: int = 30, + adjust: Adjust = Adjust.QFQ, + params: dict[str, dict[str, int | float]] | None = None, + ) -> pd.DataFrame: + """获取 K 线数据并计算技术指标。 + + 自动获取足够的历史数据用于指标预热(EMA 至少需要 120 周期)。 + + Args: + market: 市场代码。 + code: 股票代码。 + indicators: 指标名称列表,如 ``["MACD", "KDJ"]``。 + period: K 线周期。 + count: 返回条数(默认30)。 + adjust: 复权方式(默认前复权)。 + params: 可选指标参数覆盖。 + """ + from ..indicator import compute_indicators + + fetch_count = max(120 + count, 200) + df = self.get_stock_kline(market, code, period=period, count=fetch_count, adjust=adjust) + if df.empty: + return df + return compute_indicators(df, indicators, params, tail=count) + # ------------------------------------------------------------------ # # 分时 # ------------------------------------------------------------------ # @@ -1140,6 +1171,30 @@ class AsyncMacClient: return _to_df(all_bars) + async def get_stock_kline_with_indicators( + self, + market: int, + code: str, + indicators: list[str], + period: Period = Period.DAILY, + count: int = 30, + adjust: Adjust = Adjust.QFQ, + params: dict[str, dict[str, int | float]] | None = None, + ) -> pd.DataFrame: + """获取 K 线数据并计算技术指标(异步)。 + + 自动获取足够的历史数据用于指标预热(EMA 至少需要 120 周期)。 + """ + from ..indicator import compute_indicators + + fetch_count = max(120 + count, 200) + df = await self.get_stock_kline( + market, code, period=period, count=fetch_count, adjust=adjust, + ) + if df.empty: + return df + return compute_indicators(df, indicators, params, tail=count) + # ------------------------------------------------------------------ # # 分时 # ------------------------------------------------------------------ # diff --git a/src/easy_tdx/unified.py b/src/easy_tdx/unified.py index af18a97..7bde9e2 100644 --- a/src/easy_tdx/unified.py +++ b/src/easy_tdx/unified.py @@ -125,6 +125,20 @@ class UnifiedTdxClient: ) -> pd.DataFrame: return self._ensure_mac().get_stock_kline(market, code, period, start, count, times, adjust) + def get_stock_kline_with_indicators( + self, + market: int, + code: str, + indicators: list[str], + period: Period = Period.DAILY, + count: int = 30, + adjust: Adjust = Adjust.QFQ, + params: dict[str, dict[str, int | float]] | None = None, + ) -> pd.DataFrame: + return self._ensure_mac().get_stock_kline_with_indicators( + market, code, indicators, period, count, adjust, params, + ) + def get_tick_chart( self, market: int, @@ -397,6 +411,21 @@ class AsyncUnifiedTdxClient: mac = await self._ensure_mac() return await mac.get_stock_kline(market, code, period, start, count, times, adjust) + async def get_stock_kline_with_indicators( + self, + market: int, + code: str, + indicators: list[str], + period: Period = Period.DAILY, + count: int = 30, + adjust: Adjust = Adjust.QFQ, + params: dict[str, dict[str, int | float]] | None = None, + ) -> pd.DataFrame: + mac = await self._ensure_mac() + return await mac.get_stock_kline_with_indicators( + market, code, indicators, period, count, adjust, params, + ) + async def get_tick_chart( self, market: int, diff --git a/tests/unit/test_indicator.py b/tests/unit/test_indicator.py new file mode 100644 index 0000000..f4757ec --- /dev/null +++ b/tests/unit/test_indicator.py @@ -0,0 +1,158 @@ +"""indicator.py 离线单元测试。""" + +from __future__ import annotations + +import warnings + +import numpy as np +import pandas as pd +import pytest + +from easy_tdx.indicator import compute_indicators, list_indicators, _REGISTRY + + +def _make_ohlcv(n: int = 200, seed: int = 42) -> pd.DataFrame: + rng = np.random.default_rng(seed) + close = 100 + np.cumsum(rng.standard_normal(n) * 0.5) + high = close + np.abs(rng.standard_normal(n)) + low = close - np.abs(rng.standard_normal(n)) + open_ = low + (high - low) * rng.random(n) + vol = (rng.random(n) * 1e6).astype(float) + return pd.DataFrame({ + "datetime": pd.date_range("2024-01-01", periods=n, freq="D"), + "open": open_, + "high": high, + "low": low, + "close": close, + "vol": vol, + "amount": vol * close, + }) + + +class TestRegistry: + def test_all_indicators_registered(self): + assert len(_REGISTRY) >= 22 + + def test_list_indicators_returns_metadata(self): + info = list_indicators() + assert len(info) >= 22 + for entry in info: + assert "name" in entry + assert "inputs" in entry + assert "outputs" in entry + assert "description" in entry + + +class TestComputeIndicators: + def test_single_indicator_macd(self): + df = _make_ohlcv() + result = compute_indicators(df, ["MACD"]) + assert "MACD_DIF" in result.columns + assert "MACD_DEA" in result.columns + assert "MACD_HIST" in result.columns + assert len(result) == 200 + + def test_multiple_indicators(self): + df = _make_ohlcv() + result = compute_indicators(df, ["MACD", "KDJ", "RSI"]) + for col in ["MACD_DIF", "MACD_DEA", "MACD_HIST", "KDJ_K", "KDJ_D", "KDJ_J", "RSI"]: + assert col in result.columns + + def test_keep_ohlcv_true(self): + df = _make_ohlcv() + result = compute_indicators(df, ["RSI"], keep_ohlcv=True) + for col in ["open", "high", "low", "close", "vol"]: + assert col in result.columns + + def test_keep_ohlcv_false(self): + df = _make_ohlcv() + result = compute_indicators(df, ["RSI"], keep_ohlcv=False) + assert "close" not in result.columns + assert "RSI" in result.columns + # datetime 应保留 + assert "datetime" in result.columns + + def test_keep_ohlcv_false_no_time_cols(self): + df = _make_ohlcv() + df = df.drop(columns=["datetime"]) + result = compute_indicators(df, ["RSI"], keep_ohlcv=False) + assert "close" not in result.columns + assert "RSI" in result.columns + + def test_tail_parameter(self): + df = _make_ohlcv(200) + result = compute_indicators(df, ["MACD"], tail=30) + assert len(result) == 30 + assert "MACD_DIF" in result.columns + + def test_case_insensitive(self): + df = _make_ohlcv() + result = compute_indicators(df, ["macd", "kdj"]) + assert "MACD_DIF" in result.columns + assert "KDJ_K" in result.columns + + def test_custom_params(self): + df = _make_ohlcv() + r1 = compute_indicators(df, ["MACD"]) + r2 = compute_indicators(df, ["MACD"], params={"MACD": {"SHORT": 10}}) + # 不同参数应产生不同结果 + assert not np.allclose(r1["MACD_DIF"].values, r2["MACD_DIF"].values, equal_nan=True) + + def test_unknown_indicator_raises(self): + df = _make_ohlcv() + with pytest.raises(ValueError, match="未知指标"): + compute_indicators(df, ["FAKE_INDICATOR"]) + + def test_missing_input_columns_raises(self): + df = pd.DataFrame({"close": np.random.randn(200)}) + with pytest.raises(ValueError, match="缺少必要列"): + compute_indicators(df, ["KDJ"]) + + def test_empty_dataframe(self): + df = pd.DataFrame() + result = compute_indicators(df, ["MACD"]) + assert result.empty + + def test_short_data_warning(self): + df = _make_ohlcv(50) + with warnings.catch_warnings(record=True) as w: + warnings.simplefilter("always") + compute_indicators(df, ["MACD"]) + assert any("120" in str(warning.message) for warning in w) + + def test_rsi_range(self): + df = _make_ohlcv(200) + result = compute_indicators(df, ["RSI"]) + rsi = result["RSI"].dropna() + assert (rsi >= -10).all() and (rsi <= 110).all() + + def test_boll_bands_order(self): + df = _make_ohlcv(200) + result = compute_indicators(df, ["BOLL"]) + valid = result.dropna(subset=["BOLL_UPPER", "BOLL_LOWER"]) + assert (valid["BOLL_UPPER"] >= valid["BOLL_LOWER"]).all() + + def test_obv_with_volume(self): + df = _make_ohlcv() + result = compute_indicators(df, ["OBV"]) + assert "OBV" in result.columns + + def test_brar_needs_open(self): + df = _make_ohlcv() + result = compute_indicators(df, ["BRAR"]) + assert "AR" in result.columns + assert "BR" in result.columns + + def test_all_registered_indicators_run(self): + """确保所有注册的指标都能无错运行。""" + df = _make_ohlcv(250) + for name in _REGISTRY: + result = compute_indicators(df, [name]) + spec = _REGISTRY[name] + for col in spec.outputs: + assert col in result.columns, f"{name} missing output {col}" + + def test_result_index_reset(self): + df = _make_ohlcv() + result = compute_indicators(df, ["RSI"]) + assert list(result.index) == list(range(len(result))) From 6b06f9eb4032d431d13d7e236e347bae842b4a3a Mon Sep 17 00:00:00 2001 From: GitHub Date: Thu, 28 May 2026 17:49:30 +0800 Subject: [PATCH 2/6] feat: add ZHUOYAO indicator (multi-period momentum resonance), bump to 1.4.1 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add 捉妖大师 (ZHUOYAO) indicator to the indicator registry. Outputs ZY_LONG/ZY_MID/ZY_SHORT/ZY_TREND four lines based on 20/60/120-day ROC with EMA smoothing for trend resonance detection. Co-Authored-By: Claude Opus 4.7 --- README.md | 57 ++++++++++++++++++++++++-- docs/indicator-zhuoyao.md | 84 +++++++++++++++++++++++++++++++++++++++ pyproject.toml | 2 +- src/easy_tdx/indicator.py | 10 +++++ 4 files changed, 148 insertions(+), 5 deletions(-) create mode 100644 docs/indicator-zhuoyao.md diff --git a/README.md b/README.md index b23e446..ee65c87 100644 --- a/README.md +++ b/README.md @@ -113,7 +113,46 @@ easy-tdx indicator MACD -m SH -c 600519 --period 5MIN --count 50 easy-tdx indicator RSI -m SZ -c 000001 --no-ohlcv ``` -支持 30 个指标:MACD, KDJ, RSI, BOLL, DMI, ATR, WR, CCI, BIAS, OBV, VR, EMV, MFI, BRAR, ASI, TRIX, DPO, MTM, ROC, EXPMA, BBI, PSY, DFMA, CR, KTN, XSII, MASS, TAQ。 +### 捉妖大师(重点) + +捉妖大师是多周期涨幅共振指标,通过 20/60/120 日涨幅及指数平滑判断短中长线趋势是否同向,用于筛选趋势刚启动的强势股。 + +```bash +easy-tdx indicator ZHUOYAO -m SH -c 600519 --count 30 --table + +# 自定义周期参数 +easy-tdx indicator ZHUOYAO -m SZ -c 000001 --params N1=90,N2=45,N3=15 + +# 结合其他指标一起看 +easy-tdx indicator ZHUOYAO,MACD,KDJ -m SH -c 600519 --count 20 --table +``` + +输出列说明: + +| 列名 | 含义 | +|------|------| +| `ZY_LONG` | 长线 — 120 日涨幅的 10 日指数平滑 | +| `ZY_MID` | 中线 — 60 日涨幅(%) | +| `ZY_SHORT` | 短线 — 20 日涨幅(%) | +| `ZY_TREND` | 趋势 — 中线的 10 日指数平滑 | + +**核心信号:** 四线全部 > 0 且短线 > 中线 > 长线 = 短中长趋势完全一致向上,是强势股特征。详见 [捉妖大师指标详解](docs/indicator-zhuoyao.md)。 + +```python +# Python API 用法 +from easy_tdx import MacClient, Market + +with MacClient.from_best_host() as c: + df = c.get_stock_kline_with_indicators( + Market.SH, "600519", + indicators=["ZHUOYAO"], + count=30, + ) + # df 包含: datetime, open, close, high, low, vol, amount + # + ZY_LONG, ZY_MID, ZY_SHORT, ZY_TREND +``` + +支持 30 个指标:MACD, KDJ, RSI, BOLL, DMI, ATR, WR, CCI, BIAS, OBV, VR, EMV, MFI, BRAR, ASI, TRIX, DPO, MTM, ROC, EXPMA, BBI, PSY, DFMA, CR, KTN, XSII, MASS, TAQ, ZHUOYAO。 ### 财务 @@ -155,7 +194,7 @@ easy-tdx ex tick HK_MAIN_BOARD 00700 --table # 港股分时 | `market-stat` | 全市场涨跌统计 | | `server-info` | 服务器交易时段 | | `symbol-info` | 个股特征快照 | -| `indicator` | 技术指标计算(30 个:MACD/KDJ/RSI/BOLL/DMI/ATR...) | +| `indicator` | 技术指标计算(31 个:MACD/KDJ/RSI/BOLL/DMI/ATR...) | | `indicator-list` | 列出可用技术指标 | | `f10` | F10 公司信息 | | `fund-flow` | 历史资金流向 | @@ -255,7 +294,7 @@ with MacClient.from_best_host() as c: print(info["name"], info["description"], info["outputs"]) ``` -支持 30 个技术指标: +支持 31 个技术指标: | 指标 | 输入 | 输出列 | |------|------|--------| @@ -287,6 +326,7 @@ with MacClient.from_best_host() as c: | XSII | close, high, low | XSII_TD1, XSII_TD2, XSII_TD3, XSII_TD4 | | MASS | high, low | MASS, MASS_MA | | TAQ | high, low | TAQ_UP, TAQ_MID, TAQ_DOWN | +| ZHUOYAO | close | ZY_LONG, ZY_MID, ZY_SHORT, ZY_TREND | #### 分时 @@ -590,7 +630,7 @@ src/easy_tdx/ ├── client.py # TdxClient / AsyncTdxClient(标准协议) ├── unified.py # UnifiedTdxClient(统一入口) ├── config.py # 服务器地址、端口、超时配置 -├── indicator.py # 技术指标计算(30 个,基于 MyTT) +├── indicator.py # 技术指标计算(31 个,基于 MyTT) ├── MyTT.py # 麦语言技术指标算法库 ├── mac/ │ ├── client.py # MacClient / AsyncMacClient(MAC 协议) @@ -634,6 +674,15 @@ ruff format --check src/ tests/ # format check ## Changelog +### 1.4.1 (2026-05-28) + +**捉妖大师指标** — 新增 ZHUOYAO 多周期涨幅共振指标,通过 20/60/120 日涨幅及指数平滑判断短中长线趋势是否同向,用于筛选趋势刚启动的强势股。 + +- 新增 `ZHUOYAO` 指标:输出 ZY_LONG/ZY_MID/ZY_SHORT/ZY_TREND 四条线 +- CLI: `easy-tdx indicator ZHUOYAO -m SH -c 600519 --table` +- Python API: `indicators=["ZHUOYAO"]` +- 详见 [捉妖大师指标详解](docs/indicator-zhuoyao.md) + ### 1.4.0 (2026-05-28) **技术指标计算** — 集成 [MyTT](https://github.com/mpquant/MyTT) 麦语言指标库,支持 30 个常用技术指标,一步获取 K 线 + 指标值。 diff --git a/docs/indicator-zhuoyao.md b/docs/indicator-zhuoyao.md new file mode 100644 index 0000000..f3b8ae0 --- /dev/null +++ b/docs/indicator-zhuoyao.md @@ -0,0 +1,84 @@ +# 捉妖大师 (ZHUOYAO) 技术指标 + +## 指标定义 + +多周期涨幅共振指标,通过 20/60/120 日涨幅百分比及指数平滑,判断短中长线趋势是否同向。 + +``` +长线1 = (C / REF(C,120) - 1) × 100 # 120日涨幅(%) +长线 = EXPMA(长线1, 10) # 长线的10日指数平滑 +中线 = (C / REF(C,60) - 1) × 100 # 60日涨幅(%) +短线 = (C / REF(C,20) - 1) × 100 # 20日涨幅(%) +趋势 = EXPMA(中线, 10) # 中线的10日指数平滑 +``` + +返回 `(长线, 中线, 短线, 趋势)` 四条线,均以零轴为多空分界。 + +## 调用方式 + +```python +from easy_tdx.MyTT import ZHUOYAO + +# close: numpy 数组,至少 120+ 个数据点 +LONG, MID, SHORT, TREND = ZHUOYAO(close) + +# 自定义周期 +LONG, MID, SHORT, TREND = ZHUOYAO(close, N1=120, N2=60, N3=20, M=10) +``` + +参数说明: + +| 参数 | 默认值 | 含义 | +|------|--------|------| +| N1 | 120 | 长线回望周期 | +| N2 | 60 | 中线回望周期 | +| N3 | 20 | 短线回望周期 | +| M | 10 | EXPMA 平滑周期 | + +## 核心逻辑 + +本质是 **多时间框架 ROC (Rate of Change) 共振系统**: + +- **短线** = ROC(20):捕捉 20 日内的短期动量方向 +- **中线** = ROC(60):反映季度级别的中期趋势强度 +- **长线** = EMA(ROC(120), 10):半年级别的长线趋势,经过平滑降噪 +- **趋势** = EMA(ROC(60), 10):中线的平滑版本,用于过滤中线噪音 + +零轴是所有线的多空分界线:正值 = 该周期内上涨,负值 = 该周期内下跌。 + +## 交易信号 + +### 1. 多线共振(核心信号) + +| 状态 | 条件 | 含义 | +|------|------|------| +| 全线多头 | 四线 > 0,且 短线 > 中线 > 长线 | 短中长趋势完全一致向上,强势股特征 | +| 全线空头 | 四线 < 0,且 短线 < 中线 < 长线 | 各周期同步下跌,应回避 | +| 多空分歧 | 线的方向不一致 | 趋势不明,等待收敛 | + +### 2. 穿越信号 + +- **短线穿越零轴**:20 日动量反转,短线进场或离场信号 +- **中线穿越趋势**:ROC(60) 与其平滑线金叉/死叉,中期趋势转向确认 +- **长线拐头**:长线从下降转为上升,大级别底部信号 + +### 3. "捉妖"条件(强势股筛选) + +同时满足以下条件时,可能是趋势刚启动的强势股: + +1. 短线 > 0(短期动量向上) +2. 中线 > 0(中期趋势向上) +3. 长线从负转正或即将转正(长线趋势刚反转) +4. 短线 > 中线 > 趋势(动量加速,不是减速) + +### 4. 风险信号 + +- 短线远高于中线(乖离过大):短期过热,有回调风险 +- 中线 > 0 但趋势 < 0:中期反弹但平滑趋势未确认,可能是假突破 +- 四线同时从高位回落:多周期共振见顶 + +## 注意事项 + +- 前置数据不足时(< N1=120 根 K 线),长线值为 NaN,属于正常现象 +- 指标是价格幅度的度量,不直接产生买卖信号,需结合成交量、K 线形态综合判断 +- "妖股"往往波动剧烈,共振信号出现后也可能快速消失,不宜单独作为唯一依据 diff --git a/pyproject.toml b/pyproject.toml index 6013958..1505a97 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "easy-tdx" -version = "1.4.0" +version = "1.4.1" description = "通达信 TCP 协议行情数据客户端,支持在线行情与离线本地数据读取" readme = "README.md" requires-python = ">=3.10" diff --git a/src/easy_tdx/indicator.py b/src/easy_tdx/indicator.py index 28bdb20..750071a 100644 --- a/src/easy_tdx/indicator.py +++ b/src/easy_tdx/indicator.py @@ -177,6 +177,16 @@ _reg( "ASI 振动升降指标", ) +# ── 捉妖大师(仅需 close)───────────────────────────────────────────── +_reg( + "ZHUOYAO", + ("close",), + ("ZY_LONG", "ZY_MID", "ZY_SHORT", "ZY_TREND"), + MyTT.ZHUOYAO, + {"N1": 120, "N2": 60, "N3": 20, "M": 10}, + "ZHUOYAO 捉妖大师 多周期涨幅共振", +) + # ── 仅需 high + low ──────────────────────────────────────────────────── _reg( "TAQ", ("high", "low"), ("TAQ_UP", "TAQ_MID", "TAQ_DOWN"), MyTT.TAQ, {"N": 20}, "TAQ 唐安奇通道" From 3410b922ad7333d7b43400f7a2d00e63e248a1bc Mon Sep 17 00:00:00 2001 From: GitHub Date: Thu, 28 May 2026 18:07:31 +0800 Subject: [PATCH 3/6] fix: include ZHUOYAO function in MyTT.py (missing from 1.4.1 release) The 1.4.1 commit added the indicator registry entry in indicator.py but forgot to include the actual ZHUOYAO() function definition in MyTT.py. Also includes lint cleanups (trailing semicolons, import formatting). Co-Authored-By: Claude Opus 4.7 --- src/easy_tdx/MyTT.py | 30 ++++++++++++++++++++---------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/src/easy_tdx/MyTT.py b/src/easy_tdx/MyTT.py index e105d3d..214cde4 100644 --- a/src/easy_tdx/MyTT.py +++ b/src/easy_tdx/MyTT.py @@ -17,7 +17,9 @@ #以下所有函数如无特别说明,输入参数S均为numpy序列或者列表list,N为整型int #应用层1级函数完美兼容通达信或同花顺,具体使用方法请参考通达信 -import numpy as np; import pandas as pd +import numpy as np +import pandas as pd + #------------------ 0级:核心工具函数 -------------------------------------------- def RD(N,D=3): return np.round(N,D) #四舍五入取3位小数 @@ -143,7 +145,7 @@ def LOWRANGE(S): # LOWRANGE(LOW)表示当前最低价是 #------------------ 2级:技术指标函数(全部通过0级,1级函数实现) ------------------------------ def MACD(CLOSE,SHORT=12,LONG=26,M=9): # EMA的关系,S取120日,和雪球小数点2位相同 - DIF = EMA(CLOSE,SHORT)-EMA(CLOSE,LONG); + DIF = EMA(CLOSE,SHORT)-EMA(CLOSE,LONG) DEA = EMA(DIF,M); MACD=(DIF-DEA)*2 return RD(DIF),RD(DEA),RD(MACD) @@ -191,7 +193,7 @@ def BIAS(CLOSE,L1=6, L2=12, L3=24): # BIAS乖离率 return RD(BIAS1), RD(BIAS2), RD(BIAS3) def BOLL(CLOSE,N=20, P=2): #BOLL指标,布林带 - MID = MA(CLOSE, N); + MID = MA(CLOSE, N) UPPER = MID + STD(CLOSE, N) * P LOWER = MID - STD(CLOSE, N) * P return RD(UPPER), RD(MID), RD(LOWER) @@ -243,7 +245,7 @@ def VR(CLOSE,VOL,M1=26): #VR容量比率 return SUM(IF(CLOSE > LC, VOL, 0), M1) / SUM(IF(CLOSE <= LC, VOL, 0), M1) * 100 def CR(CLOSE,HIGH,LOW,N=20): #CR价格动量指标 - MID=REF(HIGH+LOW+CLOSE,1)/3; + MID=REF(HIGH+LOW+CLOSE,1)/3 return SUM(MAX(0,HIGH-MID),N)/SUM(MAX(0,MID-LOW),N)*100 def EMV(HIGH,LOW,VOL,N=14,M=9): #简易波动指标 @@ -279,7 +281,7 @@ def ROC(CLOSE,N=12,M=6): #变动率指标 return ROC,MAROC def EXPMA(CLOSE,N1=12,N2=50): #EMA指数平均数指标 - return EMA(CLOSE,N1),EMA(CLOSE,N2); + return EMA(CLOSE,N1),EMA(CLOSE,N2) def OBV(CLOSE,VOL): #能量潮指标 return SUM(IF(CLOSE>REF(CLOSE,1),VOL,IF(CLOSEBB) & (AA>CC),AA+BB/2+DD/4,IF( (BB>CC) & (BB>AA),BB+AA/2+DD/4,CC+DD/4)); - X=(CLOSE-LC+(CLOSE-OPEN)/2+LC-REF(OPEN,1)); - SI=16*X/R*MAX(AA,BB); ASI=SUM(SI,M1); ASIT=MA(ASI,M2); + LC=REF(CLOSE,1); AA=ABS(HIGH-LC); BB=ABS(LOW-LC) + CC=ABS(HIGH-REF(LOW,1)); DD=ABS(LC-REF(OPEN,1)) + R=IF( (AA>BB) & (AA>CC),AA+BB/2+DD/4,IF( (BB>CC) & (BB>AA),BB+AA/2+DD/4,CC+DD/4)) + X=(CLOSE-LC+(CLOSE-OPEN)/2+LC-REF(OPEN,1)) + SI=16*X/R*MAX(AA,BB); ASI=SUM(SI,M1); ASIT=MA(ASI,M2) return ASI,ASIT def XSII(CLOSE, HIGH, LOW, N=102, M=7): #薛斯通道II @@ -305,4 +307,12 @@ def XSII(CLOSE, HIGH, LOW, N=102, M=7): #薛斯通道II return TD1, TD2, TD3, TD4 +def ZHUOYAO(CLOSE, N1=120, N2=60, N3=20, M=10): #捉妖大师指标:中长短线趋势共振 + LONG1 = (CLOSE / REF(CLOSE, N1) - 1) * 100 #120日涨跌幅 + LONG = EMA(LONG1, M) #长线 EXPMA(长线1,10) + MID = (CLOSE / REF(CLOSE, N2) - 1) * 100 #中线 60日涨跌幅 + SHORT = (CLOSE / REF(CLOSE, N3) - 1) * 100 #短线 20日涨跌幅 + TREND = EMA(MID, M) #趋势 EXPMA(中线,10) + return RD(LONG), RD(MID), RD(SHORT), RD(TREND) + #望大家能提交更多指标和函数 https://github.com/mpquant/MyTT From 7572b16614fb36aac72f322fecb84dbc226b58f8 Mon Sep 17 00:00:00 2001 From: GitHub Date: Thu, 28 May 2026 18:07:48 +0800 Subject: [PATCH 4/6] chore: bump version to 1.4.2 Co-Authored-By: Claude Opus 4.7 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index 1505a97..cef913a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "easy-tdx" -version = "1.4.1" +version = "1.4.2" description = "通达信 TCP 协议行情数据客户端,支持在线行情与离线本地数据读取" readme = "README.md" requires-python = ">=3.10" From 4c5817f7b08703d7564731daac0ca33c912693e6 Mon Sep 17 00:00:00 2001 From: GitHub Date: Thu, 28 May 2026 22:31:17 +0800 Subject: [PATCH 5/6] feat: add BIAS_SIGNAL indicator (30-day bias with signal lines) Add BIAS_SIGNAL indicator derived from TongDaXin's 30-day bias formula. Outputs BS_X (raw bias), BS_SMA (short signal line), BS_LMA (long signal line) for trend direction and reversal detection via asymmetric bull/bear logic. Co-Authored-By: Claude Opus 4.7 --- README.md | 61 ++++++++++++- docs/indicator-bias-signal.md | 167 ++++++++++++++++++++++++++++++++++ src/easy_tdx/MyTT.py | 6 ++ src/easy_tdx/indicator.py | 8 ++ 4 files changed, 239 insertions(+), 3 deletions(-) create mode 100644 docs/indicator-bias-signal.md diff --git a/README.md b/README.md index ee65c87..65fa37f 100644 --- a/README.md +++ b/README.md @@ -98,6 +98,7 @@ easy-tdx indicator ATR -m SH -c 600519 --table # ATR 真实波幅 easy-tdx indicator WR -m SH -c 600519 --table # WR 威廉指标 easy-tdx indicator CCI -m SH -c 600519 --table # CCI 顺势指标 easy-tdx indicator BIAS -m SZ -c 000001 --table # BIAS 乖离率 +easy-tdx indicator BIAS_SIGNAL -m SH -c 600519 --table # 30日乖离率信号 easy-tdx indicator OBV -m SZ -c 000001 --table # OBV 能量潮 # 多指标同时计算 @@ -138,6 +139,46 @@ easy-tdx indicator ZHUOYAO,MACD,KDJ -m SH -c 600519 --count 20 --table **核心信号:** 四线全部 > 0 且短线 > 中线 > 长线 = 短中长趋势完全一致向上,是强势股特征。详见 [捉妖大师指标详解](docs/indicator-zhuoyao.md)。 +### 30日乖离率信号(重点) + +30日乖离率信号指标,在标准乖离率(BIAS)基础上叠加短/长信号线,通过三者位置关系判断趋势方向和转折点。源自通达信经典指标。 + +```bash +easy-tdx indicator BIAS_SIGNAL -m SH -c 600519 --count 60 --table + +# 自定义周期参数 +easy-tdx indicator BIAS_SIGNAL -m SZ -c 000001 --params P=5,M=20 + +# 结合其他指标一起看 +easy-tdx indicator BIAS_SIGNAL,MACD,KDJ -m SH -c 600519 --count 30 --table +``` + +输出列说明: + +| 列名 | 含义 | +|------|------| +| `BS_X` | M日乖离率 — 当前价格偏离30日均线的百分比 | +| `BS_SMA` | 短周期信号线 — 乖离率的 P 日均线,过滤短期噪音 | +| `BS_LMA` | 长周期信号线 — 乖离率的 M 日均线,捕捉中期趋势方向 | + +**核心信号:** X > S_SMA 且 X_LMA 上升 = 多头确认(通达信红色);S_SMA > X 或 X_LMA 下降 = 空头预警(通达信绿色)。多空判断非对称设计——多头需两个条件同时满足,空头只需其一,偏向保守预警。详见 [30日乖离率信号指标详解](docs/indicator-bias-signal.md)。 + +```python +# Python API 用法 +from easy_tdx import MacClient, Market + +with MacClient.from_best_host() as c: + df = c.get_stock_kline_with_indicators( + Market.SH, "600519", + indicators=["BIAS_SIGNAL"], + count=60, + ) + # df 包含: datetime, open, close, high, low, vol, amount + # + BS_X, BS_SMA, BS_LMA +``` + +支持 32 个指标:MACD, KDJ, RSI, BOLL, DMI, ATR, WR, CCI, BIAS, BIAS_SIGNAL, OBV, VR, EMV, MFI, BRAR, ASI, TRIX, DPO, MTM, ROC, EXPMA, BBI, PSY, DFMA, CR, KTN, XSII, MASS, TAQ, ZHUOYAO。 + ```python # Python API 用法 from easy_tdx import MacClient, Market @@ -152,7 +193,7 @@ with MacClient.from_best_host() as c: # + ZY_LONG, ZY_MID, ZY_SHORT, ZY_TREND ``` -支持 30 个指标:MACD, KDJ, RSI, BOLL, DMI, ATR, WR, CCI, BIAS, OBV, VR, EMV, MFI, BRAR, ASI, TRIX, DPO, MTM, ROC, EXPMA, BBI, PSY, DFMA, CR, KTN, XSII, MASS, TAQ, ZHUOYAO。 +支持 32 个指标:MACD, KDJ, RSI, BOLL, DMI, ATR, WR, CCI, BIAS, BIAS_SIGNAL, OBV, VR, EMV, MFI, BRAR, ASI, TRIX, DPO, MTM, ROC, EXPMA, BBI, PSY, DFMA, CR, KTN, XSII, MASS, TAQ, ZHUOYAO。 ### 财务 @@ -194,7 +235,7 @@ easy-tdx ex tick HK_MAIN_BOARD 00700 --table # 港股分时 | `market-stat` | 全市场涨跌统计 | | `server-info` | 服务器交易时段 | | `symbol-info` | 个股特征快照 | -| `indicator` | 技术指标计算(31 个:MACD/KDJ/RSI/BOLL/DMI/ATR...) | +| `indicator` | 技术指标计算(32 个:MACD/KDJ/RSI/BOLL/DMI/ATR...) | | `indicator-list` | 列出可用技术指标 | | `f10` | F10 公司信息 | | `fund-flow` | 历史资金流向 | @@ -327,6 +368,7 @@ with MacClient.from_best_host() as c: | MASS | high, low | MASS, MASS_MA | | TAQ | high, low | TAQ_UP, TAQ_MID, TAQ_DOWN | | ZHUOYAO | close | ZY_LONG, ZY_MID, ZY_SHORT, ZY_TREND | +| BIAS_SIGNAL | close | BS_X, BS_SMA, BS_LMA | #### 分时 @@ -630,7 +672,7 @@ src/easy_tdx/ ├── client.py # TdxClient / AsyncTdxClient(标准协议) ├── unified.py # UnifiedTdxClient(统一入口) ├── config.py # 服务器地址、端口、超时配置 -├── indicator.py # 技术指标计算(31 个,基于 MyTT) +├── indicator.py # 技术指标计算(32 个,基于 MyTT) ├── MyTT.py # 麦语言技术指标算法库 ├── mac/ │ ├── client.py # MacClient / AsyncMacClient(MAC 协议) @@ -674,6 +716,19 @@ ruff format --check src/ tests/ # format check ## Changelog +### 1.4.3 (2026-05-28) + +**30日乖离率信号指标** — 新增 BIAS_SIGNAL 指标,在标准乖离率基础上叠加短/长信号线,通过三者位置关系判断趋势方向和转折点。源自通达信经典指标。 + +- 新增 `BIAS_SIGNAL` 指标:输出 BS_X/BS_SMA/BS_LMA 三条线 +- CLI: `easy-tdx indicator BIAS_SIGNAL -m SH -c 600519 --table` +- Python API: `indicators=["BIAS_SIGNAL"]` +- 详见 [30日乖离率信号指标详解](docs/indicator-bias-signal.md) + +### 1.4.2 (2026-05-28) + +修复 1.4.1 发布遗漏:MyTT.py 中 ZHUOYAO 函数定义未包含在 1.4.1 的 PyPI 包中。 + ### 1.4.1 (2026-05-28) **捉妖大师指标** — 新增 ZHUOYAO 多周期涨幅共振指标,通过 20/60/120 日涨幅及指数平滑判断短中长线趋势是否同向,用于筛选趋势刚启动的强势股。 diff --git a/docs/indicator-bias-signal.md b/docs/indicator-bias-signal.md new file mode 100644 index 0000000..31349d8 --- /dev/null +++ b/docs/indicator-bias-signal.md @@ -0,0 +1,167 @@ +# 30日乖离率信号 (BIAS_SIGNAL) 技术指标 + +## 指标定义 + +在标准乖离率(BIAS)的基础上,叠加短期信号线和长期信号线,通过三者之间的位置关系判断趋势方向和转折点。 + +### 通达信原公式 + +``` +P:=10; +M:=30; +X:(CLOSE-MA(CLOSE,M))/MA(CLOSE,M)*100; +S_SMA:MA(X,P); +X_LMA:MA(X,M); + +IF(X>S_SMA AND X_LMA>REF(X_LMA,1),X_LMA,DRAWNULL),COLORRED; +IF(S_SMA>X OR X_LMA 0**:价格在 M 日均线上方(偏多) +- **X < 0**:价格在 M 日均线下方(偏空) +- **X 在零轴附近震荡**:价格围绕均线缠绕,无明确方向 + +## 交易信号 + +### 1. 趋势方向判断(通达信颜色逻辑) + +通达信原版的颜色规则等价于: + +| 状态 | 条件 | 含义 | +|------|------|------| +| 多头(红) | `X > S_SMA` **且** `X_LMA > REF(X_LMA,1)` | 乖离率高于短期均线(偏强),且长期信号线在上升(趋势确认) | +| 空头(绿) | `S_SMA > X` **或** `X_LMA < REF(X_LMA,1)` | 乖离率低于短期均线(偏弱),或长期信号线在下降(趋势走弱) | + +注意条件是非对称的:多头需要两个条件**同时满足**,空头只需满足其一。这意味着指标偏向保守——宁可错过一些多头机会,也要尽早预警空头风险。 + +### 2. 金叉/死叉信号 + +- **X 上穿 S_SMA**(金叉):短期乖离率走强,价格开始加速偏离均线。如果 X 同时上穿零轴,信号更强 +- **X 下穿 S_SMA**(死叉):短期乖离率走弱,价格向均线回归。如果在高位发生,是明确的卖出信号 +- **S_SMA 上穿 X_LMA**:短期信号确认中期趋势向上,趋势行情确认 + +### 3. X_LMA 拐头信号 + +X_LMA 是 X 的 M 日均线,变化缓慢但方向性强: + +- **X_LMA 从下降转为上升**:中期趋势从空头转多头,是趋势转折的确认信号 +- **X_LMA 从上升转为下降**:中期趋势从多头转空头,即使价格还在上涨也要警惕 + +### 4. 极值信号 + +- **X 远高于 S_SMA 和 X_LMA**(如 X > 10):短期严重超买,价格远超均线,回调风险大 +- **X 远低于 S_SMA 和 X_LMA**(如 X < -10):短期严重超卖,可能存在反弹机会 +- 极值判断需要结合个股历史波动率,没有统一阈值 + +### 5. 典型入场场景 + +**多头入场**(四个条件同时满足): +1. X 从下方上穿零轴(价格站上30日均线) +2. X > S_SMA(乖离率在走强) +3. X_LMA 拐头向上(中期趋势确认) +4. 成交量配合放大(量价共振) + +**空头离场/做空**: +1. X 从高位下穿 S_SMA(短期走弱) +2. X_LMA 开始走平或拐头向下 +3. 价格跌破30日均线(X < 0 确认) + +## 参数调优 + +### P(短期信号线周期) + +| P 值 | 特点 | +|------|------| +| 5 | 信号灵敏,假信号多,适合短线交易 | +| 10 | 默认值,信号频率和准确性较平衡 | +| 20 | 信号少但可靠,适合中长线 | + +### M(乖离率 + 长期信号线周期) + +| M 值 | 特点 | +|------|------| +| 20 | 更贴近价格,适合波段交易 | +| 30 | 默认值,一个月级别,适合中线趋势跟踪 | +| 60 | 更平滑,信号少但级别大,适合中长线 | + +### 调参建议 + +- 大盘蓝筹股波动小,M 可以用默认 30 或调大到 60 +- 小盘成长股波动大,P 调小到 5-7 可以更早捕捉拐点 +- 参数无需频繁调整,固定 P=10, M=30 对大多数个股有效 + +## 与其他指标的配合 + +| 配合指标 | 作用 | +|----------|------| +| MACD | BIAS_SIGNAL 判断趋势方向,MACD 确认动量强弱 | +| VOL(成交量) | 乖离率扩大时需要成交量配合,无量乖离不可靠 | +| BOLL | BOLL 上轨/下轨可以辅助判断 X 的极值区域 | +| KDJ | BIAS_SIGNAL 确认趋势方向后,KDJ 寻找具体买卖点 | + +## 注意事项 + +- 数据不足时(< 2M 根 K 线),X_LMA 收敛不充分,前期值不准确 +- 该指标本质是均线偏离度的趋势分析,**震荡市中反复穿越零轴会产生大量假信号** +- 乖离率的绝对值因个股波动率而异,不同股票间不应直接比较 X 的数值 +- 指标不直接产生买卖信号,需结合成交量、K 线形态和基本面综合判断 diff --git a/src/easy_tdx/MyTT.py b/src/easy_tdx/MyTT.py index 214cde4..c4a4901 100644 --- a/src/easy_tdx/MyTT.py +++ b/src/easy_tdx/MyTT.py @@ -315,4 +315,10 @@ def ZHUOYAO(CLOSE, N1=120, N2=60, N3=20, M=10): #捉妖大师指标:中长 TREND = EMA(MID, M) #趋势 EXPMA(中线,10) return RD(LONG), RD(MID), RD(SHORT), RD(TREND) +def BIAS_SIGNAL(CLOSE, P=10, M=30): #乖离率信号指标:M日乖离 + 短/长信号线趋势判断 + X = (CLOSE - MA(CLOSE, M)) / MA(CLOSE, M) * 100 #M日乖离率 + S_SMA = MA(X, P) #短周期信号线 MA(X,P) + X_LMA = MA(X, M) #长周期信号线 MA(X,M) + return RD(X), RD(S_SMA), RD(X_LMA) + #望大家能提交更多指标和函数 https://github.com/mpquant/MyTT diff --git a/src/easy_tdx/indicator.py b/src/easy_tdx/indicator.py index 750071a..6b213e4 100644 --- a/src/easy_tdx/indicator.py +++ b/src/easy_tdx/indicator.py @@ -186,6 +186,14 @@ _reg( {"N1": 120, "N2": 60, "N3": 20, "M": 10}, "ZHUOYAO 捉妖大师 多周期涨幅共振", ) +_reg( + "BIAS_SIGNAL", + ("close",), + ("BS_X", "BS_SMA", "BS_LMA"), + MyTT.BIAS_SIGNAL, + {"P": 10, "M": 30}, + "BIAS_SIGNAL 30日乖离率信号(乖离率+短/长信号线)", +) # ── 仅需 high + low ──────────────────────────────────────────────────── _reg( From 2eabbb219ac8a22be72fee6d7f9302b6a2cc7ad8 Mon Sep 17 00:00:00 2001 From: GitHub Date: Thu, 28 May 2026 22:31:31 +0800 Subject: [PATCH 6/6] chore: bump version to 1.4.3 Co-Authored-By: Claude Opus 4.7 --- pyproject.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pyproject.toml b/pyproject.toml index cef913a..19e35b7 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "easy-tdx" -version = "1.4.2" +version = "1.4.3" description = "通达信 TCP 协议行情数据客户端,支持在线行情与离线本地数据读取" readme = "README.md" requires-python = ">=3.10"