mirror of
https://ghfast.top/https://github.com/aeroxw/easy-tdx.git
synced 2026-09-12 15:44:15 +08:00
Merge branch 'main' of https://github.com/handsomejustin/easy_tdx
This commit is contained in:
@@ -85,6 +85,116 @@ 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 BIAS_SIGNAL -m SH -c 600519 --table # 30日乖离率信号
|
||||
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
|
||||
```
|
||||
|
||||
### 捉妖大师(重点)
|
||||
|
||||
捉妖大师是多周期涨幅共振指标,通过 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)。
|
||||
|
||||
### 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
|
||||
|
||||
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
|
||||
```
|
||||
|
||||
支持 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。
|
||||
|
||||
### 财务
|
||||
|
||||
```bash
|
||||
@@ -125,6 +235,8 @@ easy-tdx ex tick HK_MAIN_BOARD 00700 --table # 港股分时
|
||||
| `market-stat` | 全市场涨跌统计 |
|
||||
| `server-info` | 服务器交易时段 |
|
||||
| `symbol-info` | 个股特征快照 |
|
||||
| `indicator` | 技术指标计算(32 个:MACD/KDJ/RSI/BOLL/DMI/ATR...) |
|
||||
| `indicator-list` | 列出可用技术指标 |
|
||||
| `f10` | F10 公司信息 |
|
||||
| `fund-flow` | 历史资金流向 |
|
||||
| `ex kline` | 扩展市场 K 线 |
|
||||
@@ -188,6 +300,76 @@ 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"])
|
||||
```
|
||||
|
||||
支持 31 个技术指标:
|
||||
|
||||
| 指标 | 输入 | 输出列 |
|
||||
|------|------|--------|
|
||||
| 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 |
|
||||
| ZHUOYAO | close | ZY_LONG, ZY_MID, ZY_SHORT, ZY_TREND |
|
||||
| BIAS_SIGNAL | close | BS_X, BS_SMA, BS_LMA |
|
||||
|
||||
#### 分时
|
||||
|
||||
```python
|
||||
@@ -303,6 +485,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 +609,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 +672,8 @@ src/easy_tdx/
|
||||
├── client.py # TdxClient / AsyncTdxClient(标准协议)
|
||||
├── unified.py # UnifiedTdxClient(统一入口)
|
||||
├── config.py # 服务器地址、端口、超时配置
|
||||
├── indicator.py # 技术指标计算(32 个,基于 MyTT)
|
||||
├── MyTT.py # 麦语言技术指标算法库
|
||||
├── mac/
|
||||
│ ├── client.py # MacClient / AsyncMacClient(MAC 协议)
|
||||
│ ├── enums.py # Period, Adjust, Category, ExMarket, SortType, ...
|
||||
@@ -505,5 +710,66 @@ 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.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 日涨幅及指数平滑判断短中长线趋势是否同向,用于筛选趋势刚启动的强势股。
|
||||
|
||||
- 新增 `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 线 + 指标值。
|
||||
|
||||
- 新增 `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 线、实时报价、分时、逐笔成交、财务数据
|
||||
|
||||
@@ -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<REF(X_LMA,1),X_LMA,DRAWNULL),COLORGREEN;
|
||||
ZERO:0;
|
||||
```
|
||||
|
||||
### MyTT 等价实现
|
||||
|
||||
```python
|
||||
def BIAS_SIGNAL(CLOSE, P=10, M=30):
|
||||
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)
|
||||
```
|
||||
|
||||
输出三条线:`(X, S_SMA, X_LMA)`。
|
||||
|
||||
## 调用方式
|
||||
|
||||
```python
|
||||
from easy_tdx.MyTT import BIAS_SIGNAL
|
||||
|
||||
# close: numpy 数组,建议至少 60+ 个数据点(M=30 的两倍以确保信号线收敛)
|
||||
X, S_SMA, X_LMA = BIAS_SIGNAL(close)
|
||||
|
||||
# 自定义周期
|
||||
X, S_SMA, X_LMA = BIAS_SIGNAL(close, P=10, M=30)
|
||||
```
|
||||
|
||||
参数说明:
|
||||
|
||||
| 参数 | 默认值 | 含义 |
|
||||
|------|--------|------|
|
||||
| P | 10 | 短期信号线平滑周期 |
|
||||
| M | 30 | 乖离率计算周期 + 长期信号线平滑周期 |
|
||||
|
||||
CLI 使用:
|
||||
|
||||
```bash
|
||||
easy-tdx indicator BIAS_SIGNAL -m SH -c 601088 --count 120 --table
|
||||
```
|
||||
|
||||
## 核心逻辑
|
||||
|
||||
### 三线含义
|
||||
|
||||
| 线 | 公式 | 含义 |
|
||||
|----|------|------|
|
||||
| X | `(C - MA(C,M)) / MA(C,M) × 100` | M日乖离率原始值,衡量当前价格偏离30日均线的百分比 |
|
||||
| S_SMA | `MA(X, P)` | X 的 P 日均线,过滤短期噪音,作为短期趋势参考 |
|
||||
| X_LMA | `MA(X, M)` | X 的 M 日均线,捕捉乖离率本身的中期趋势方向 |
|
||||
|
||||
### 与标准 BIAS 的关系
|
||||
|
||||
标准 BIAS 指标(MyTT 已有)直接返回 6/12/24 日乖离率原始值。`BIAS_SIGNAL` 的不同之处在于:
|
||||
|
||||
1. **聚焦单一周期**(M=30),而非多周期并列
|
||||
2. **叠加信号线**,S_SMA 和 X_LMA 构成双重平滑,过滤假信号
|
||||
3. **自带趋势判断**,通过 X 与 S_SMA 的位置关系 + X_LMA 的方向来判断多空
|
||||
|
||||
本质上这是一个 **乖离率的二阶分析系统**:先算乖离率,再对乖离率做趋势分析。
|
||||
|
||||
### 零轴意义
|
||||
|
||||
- **X > 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 线形态和基本面综合判断
|
||||
@@ -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 线形态综合判断
|
||||
- "妖股"往往波动剧烈,共振信号出现后也可能快速消失,不宜单独作为唯一依据
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
# ...
|
||||
@@ -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
|
||||
# ...
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "easy-tdx"
|
||||
version = "1.3.1"
|
||||
version = "1.4.3"
|
||||
description = "通达信 TCP 协议行情数据客户端,支持在线行情与离线本地数据读取"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
@@ -0,0 +1,324 @@
|
||||
# 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作平滑因子,必须 0<A<1 (此为核心函数,非指标)
|
||||
if isinstance(A,(int,float)): return pd.Series(S).ewm(alpha=A,adjust=False).mean().values
|
||||
A=np.array(A); A[np.isnan(A)]=1.0; Y= np.zeros(len(S)); Y[0]=S[0]
|
||||
for i in range(1,len(S)): Y[i]=A[i]*S[i]+(1-A[i])*Y[i-1] #A支持序列 by jqz1226
|
||||
return Y
|
||||
|
||||
def AVEDEV(S, N): #平均绝对偏差 (序列与其平均值的绝对差的平均值)
|
||||
return pd.Series(S).rolling(N).apply(lambda x: (np.abs(x - x.mean())).mean()).values
|
||||
|
||||
def SLOPE(S, N): #返S序列N周期回线性回归斜率
|
||||
return pd.Series(S).rolling(N).apply(lambda x: np.polyfit(range(N),x,deg=1)[0],raw=True).values
|
||||
|
||||
def FORCAST(S, N): #返回S序列N周期回线性回归后的预测值, jqz1226改进成序列出
|
||||
return pd.Series(S).rolling(N).apply(lambda x:np.polyval(np.polyfit(range(N),x,deg=1),N-1),raw=True).values
|
||||
|
||||
def LAST(S, A, B): #从前A日到前B日一直满足S_BOOL条件, 要求A>B & 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(S1<S2,N,1),(S1>S2)),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之间时为真。 包括 A<S<B 或 A>S>B
|
||||
return ((A<S) & (S<B)) | ((A>S) & (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')
|
||||
|
||||
def LOWRANGE(S): # LOWRANGE(LOW)表示当前最低价是近多少周期内最低价的最小值 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(CLOSE<REF(CLOSE,1),-VOL,0)),0)/10000
|
||||
|
||||
def MFI(CLOSE,HIGH,LOW,VOL,N=14): #MFI指标是成交量的RSI指标
|
||||
TYP = (HIGH + LOW + CLOSE)/3
|
||||
V1=SUM(IF(TYP>REF(TYP,1),TYP*VOL,0),N)/SUM(IF(TYP<REF(TYP,1),TYP*VOL,0),N)
|
||||
return 100-(100/(1+V1))
|
||||
|
||||
def ASI(OPEN,CLOSE,HIGH,LOW,M1=26,M2=10): #振动升降指标
|
||||
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
|
||||
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
|
||||
|
||||
|
||||
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)
|
||||
|
||||
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
|
||||
@@ -107,4 +107,4 @@ __all__ = [
|
||||
"save_best_ex_host",
|
||||
]
|
||||
|
||||
__version__ = "1.3.0"
|
||||
__version__ = "1.4.0"
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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)
|
||||
@@ -0,0 +1,295 @@
|
||||
"""技术指标计算模块 — 基于 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 振动升降指标",
|
||||
)
|
||||
|
||||
# ── 捉妖大师(仅需 close)─────────────────────────────────────────────
|
||||
_reg(
|
||||
"ZHUOYAO",
|
||||
("close",),
|
||||
("ZY_LONG", "ZY_MID", "ZY_SHORT", "ZY_TREND"),
|
||||
MyTT.ZHUOYAO,
|
||||
{"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(
|
||||
"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))
|
||||
@@ -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)
|
||||
|
||||
# ------------------------------------------------------------------ #
|
||||
# 分时
|
||||
# ------------------------------------------------------------------ #
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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)))
|
||||
Reference in New Issue
Block a user