mirror of
https://ghfast.top/https://github.com/aeroxw/easy_tdx_max.git
synced 2026-09-12 14:34:18 +08:00
feat(indicator): 新增 SAR/VWAP/AROON 三指标 + 注册 FK(30 → 34)
This commit is contained in:
@@ -14,7 +14,7 @@ easy-tdx 要做的事很简单:**把机构的数据锁砸开,扔到每个普
|
||||
它是一个完全免费、无需注册、无需 API Key、纯开源的行**情核武器**。
|
||||
一行命令,A股、港股、美股、期货——K线、报价、资金流向、板块轮动、分时明细、逐笔成交,**毫秒级拉满**。
|
||||
|
||||
**32个技术指标**(MACD、KDJ、RSI、BOLL……连”捉妖大师”和”30日乖离率信号”都给你算好)开箱即用。
|
||||
**34个技术指标**(MACD、KDJ、RSI、BOLL……连”捉妖大师”和”30日乖离率信号”都给你算好)开箱即用。
|
||||
**缠论分析**(笔、中枢、买卖点、背驰)一键出结果——你不再需要手画分型、猜线段。
|
||||
**内置回测引擎**——写个策略文件,一行命令跑回测,16 个经典策略自带,多因子组合、策略选股扫描,批量对比哪个最赚钱一目了然。
|
||||
|
||||
@@ -709,7 +709,7 @@ with MacClient.from_best_host() as c:
|
||||
# + 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。
|
||||
支持 34 个指标: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, SAR, VWAP, AROON, FK。
|
||||
|
||||
```python
|
||||
# Python API 用法
|
||||
@@ -725,7 +725,7 @@ with MacClient.from_best_host() as c:
|
||||
# + 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。
|
||||
支持 34 个指标: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, SAR, VWAP, AROON, FK。
|
||||
|
||||
### 财务
|
||||
|
||||
@@ -941,7 +941,7 @@ uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
| `market-stat` | 全市场涨跌统计 |
|
||||
| `server-info` | 服务器交易时段 |
|
||||
| `symbol-info` | 个股特征快照 |
|
||||
| `indicator` | 技术指标计算(32 个:MACD/KDJ/RSI/BOLL/DMI/ATR...) |
|
||||
| `indicator` | 技术指标计算(34 个:MACD/KDJ/RSI/BOLL/DMI/ATR...) |
|
||||
| `indicator-list` | 列出可用技术指标 |
|
||||
| `backtest` | 回测引擎(加载策略文件,输出绩效报告) |
|
||||
| `portfolio` | 多标的组合回测(共享资金池,均等分配,汇总绩效) |
|
||||
@@ -1060,7 +1060,7 @@ with MacClient.from_best_host() as c:
|
||||
print(info["name"], info["description"], info["outputs"])
|
||||
```
|
||||
|
||||
支持 31 个技术指标:
|
||||
支持 34 个技术指标:
|
||||
|
||||
| 指标 | 输入 | 输出列 |
|
||||
|------|------|--------|
|
||||
@@ -1094,6 +1094,10 @@ with MacClient.from_best_host() as c:
|
||||
| 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 |
|
||||
| SAR | high, low | SAR(抛物线转向/动态止损位) |
|
||||
| VWAP | close, high, low, vol | VWAP(N日滚动成交量加权均价) |
|
||||
| AROON | high, low | AROON_UP, AROON_DOWN, AROON_OSC |
|
||||
| FK | close | FK(EMA(2) 突破斜率外推 EMA(42)) |
|
||||
|
||||
#### 分时
|
||||
|
||||
@@ -1460,7 +1464,7 @@ src/easy_tdx/
|
||||
├── client.py # TdxClient / AsyncTdxClient(标准协议)
|
||||
├── unified.py # UnifiedTdxClient(统一入口)
|
||||
├── config.py # 服务器地址、端口、超时配置
|
||||
├── indicator.py # 技术指标计算(32 个,基于 MyTT)
|
||||
├── indicator.py # 技术指标计算(34 个,基于 MyTT)
|
||||
├── MyTT.py # 麦语言技术指标算法库
|
||||
├── mac/
|
||||
│ ├── client.py # MacClient / AsyncMacClient(MAC 协议)
|
||||
@@ -1511,6 +1515,27 @@ ruff format --check src/ tests/ # format check
|
||||
|
||||
## Changelog
|
||||
|
||||
### 1.12.0 (2026-06-14)
|
||||
|
||||
**新增 4 个技术指标(30 → 34)** — 按"语义空白"补齐三类现有指标库缺失的维度:止损位、机构成本价、趋势启动时机。均为纯 numpy 实现,零新依赖。
|
||||
|
||||
**新增指标**:
|
||||
- **SAR 抛物线转向**(`high, low` → `SAR`):基于 Wilder 加速因子的动态止损位,填补 32 个指标里"止损位"语义的空白。可直接喂给 `BacktestEngine` 做动态 `stop_loss`。实现含反转检测、AF 加速/封顶、SAR 不穿越前两根 K 线极值的限制。
|
||||
- **VWAP 成交量加权均价**(`close, high, low, vol` → `VWAP`):N 日滚动机构基准成本价,填补"机构成本"维度空白。用典型价格 `(H+L+C)/3` 加权,含除零保护(零成交量返回 nan)。
|
||||
- **AROON 阿隆指标**(`high, low` → `AROON_UP, AROON_DOWN, AROON_OSC`):用"N 周期内新高/新低距今多少根"识别趋势启动时机,与现有 DMI(判断趋势强度但滞后)互补而非冗余。
|
||||
- **FK 趋势指标**(`close` → `FK`):清理孤儿函数——`MyTT.FK` 此前已实现但未在 `indicator.py` 注册,用户通过 CLI/API 无法调用。现正式注册暴露。语义为 EMA(2) 是否突破斜率外推 EMA(42),本质是动量偏离检测。
|
||||
|
||||
**架构**:所有新指标沿用现有 `IndicatorSpec` 注册模式,`compute_indicators()` / `get_stock_kline_with_indicators()` / CLI `easy-tdx indicator` 自动可用,无需改动调度层。
|
||||
|
||||
**除零与边界保护**:
|
||||
- SAR:一字板/停牌(高低价相同)不崩溃、不产生 inf;空输入返回空数组
|
||||
- VWAP:零成交量返回 nan(不产生 inf);前 N-1 根为 nan(rolling 窗口)
|
||||
- AROON:输出严格落在 [0, 100] 区间
|
||||
|
||||
**类型存根**:`MyTT.pyi` 同步补充 SAR/VWAP/AROON/FK 四个函数签名,mypy strict 零错误。
|
||||
|
||||
**测试**:新增 `tests/unit/test_mytt.py`,22 个用例覆盖三个新指标 + FK 的数值正确性、单边行情行为、除零/空输入边界。注册层端到端覆盖复用 `test_indicator.py::test_all_registered_indicators_run`。
|
||||
|
||||
### 1.11.6 (2026-06-13)
|
||||
|
||||
**CI 类型与格式修复** — 修复 CI 流水线 mypy strict(13 errors)和 ruff format(8 files)失败,全部为类型标注与存根问题,无运行时行为变更。
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "easy-tdx"
|
||||
version = "1.11.6"
|
||||
version = "1.12.0"
|
||||
description = "通达信 TCP 协议行情数据客户端,支持在线行情、离线数据读取与写入同步"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
@@ -14,6 +14,7 @@
|
||||
# V3.2 2023-04-04 新增 CR指标
|
||||
# V3.3 2023-11-09 新增 SIN,COS,TAN序列处理的三角函数
|
||||
# V4.0 2026-06-02 handsomejustin 新增 ZHUOYAO,BIAS_SIGNAL两个自创函数
|
||||
# V4.1 2026-06-14 新增 SAR(抛物线转向), VWAP(成交量加权均价), AROON(阿隆指标); 注册 FK
|
||||
|
||||
# 以下所有函数如无特别说明,输入参数S均为numpy序列或者列表list,N为整型int
|
||||
# 应用层1级函数完美兼容通达信或同花顺,具体使用方法请参考通达信
|
||||
@@ -496,4 +497,66 @@ def OUTPERFORM_20D(CLOSE, INDEX_CLOSE): # 20日相对强度:个股涨幅跑
|
||||
return IF(stock_ret > index_ret, 1, 0)
|
||||
|
||||
|
||||
def SAR(HIGH, LOW, AF_STEP=0.02, AF_MAX=0.2): # 抛物线转向指标:基于 ATR 思想的动态止损位
|
||||
HIGH = np.asarray(HIGH, dtype=float)
|
||||
LOW = np.asarray(LOW, dtype=float)
|
||||
n = len(HIGH)
|
||||
sar = np.full(n, np.nan)
|
||||
if n == 0:
|
||||
return sar
|
||||
# 初始假设上涨趋势:SAR 起点取首根低点,极值点取首根高点
|
||||
bull = True
|
||||
af = AF_STEP
|
||||
ep = HIGH[0]
|
||||
sar[0] = LOW[0]
|
||||
for i in range(1, n):
|
||||
# 下一根 SAR = 前一根 SAR + AF * (EP - 前一根 SAR)
|
||||
new_sar = sar[i - 1] + af * (ep - sar[i - 1])
|
||||
# SAR 不能进入前两根 K 线极值范围(Wilder 标准限制,避免 SAR 被价格穿越)
|
||||
prev2 = max(i - 2, 0)
|
||||
if bull:
|
||||
new_sar = min(new_sar, LOW[i - 1], LOW[prev2])
|
||||
else:
|
||||
new_sar = max(new_sar, HIGH[i - 1], HIGH[prev2])
|
||||
sar[i] = new_sar
|
||||
# 反转判断:上涨时 LOW 穿越止损位 / 下跌时 HIGH 穿越止损位
|
||||
if bull and LOW[i] <= new_sar:
|
||||
bull = False
|
||||
sar[i] = ep # 反转点 SAR = 前极值点
|
||||
ep = LOW[i]
|
||||
af = AF_STEP
|
||||
elif not bull and HIGH[i] >= new_sar:
|
||||
bull = True
|
||||
sar[i] = ep
|
||||
ep = HIGH[i]
|
||||
af = AF_STEP
|
||||
else:
|
||||
# 无反转,更新极值点和加速因子
|
||||
if bull and HIGH[i] > ep:
|
||||
ep = HIGH[i]
|
||||
af = min(af + AF_STEP, AF_MAX)
|
||||
elif not bull and LOW[i] < ep:
|
||||
ep = LOW[i]
|
||||
af = min(af + AF_STEP, AF_MAX)
|
||||
return sar
|
||||
|
||||
|
||||
def VWAP(CLOSE, HIGH, LOW, VOL, N=20): # 成交量加权均价:N日滚动机构基准成本价
|
||||
TP = (HIGH + LOW + CLOSE) / 3.0 # 典型价格
|
||||
num = pd.Series(TP * VOL).rolling(N).sum().values
|
||||
den = pd.Series(VOL).rolling(N).sum().values
|
||||
with np.errstate(divide="ignore", invalid="ignore"):
|
||||
return np.where(den > 0, num / den, np.nan)
|
||||
|
||||
|
||||
def AROON(HIGH, LOW, N=25): # 阿隆指标:趋势启动时机识别(N周期内新高/新低距今多少根)
|
||||
# HHVBARS/LLVBARS 返回极值距今的周期数
|
||||
up_bars = HHVBARS(HIGH, N) # N周期最高价距今周期数
|
||||
down_bars = LLVBARS(LOW, N) # N周期最低价距今周期数
|
||||
AROON_UP = (N - up_bars) / N * 100 # 越接近100=近期创新高=上涨动能强
|
||||
AROON_DOWN = (N - down_bars) / N * 100 # 越接近100=近期创新低=下跌动能强
|
||||
OSC = AROON_UP - AROON_DOWN # 震荡指标:正值多头,负值空头
|
||||
return RD(AROON_UP), RD(AROON_DOWN), RD(OSC)
|
||||
|
||||
|
||||
# 望大家能提交更多指标和函数 https://github.com/mpquant/MyTT
|
||||
|
||||
@@ -91,6 +91,20 @@ def PSY(CLOSE: npt.ArrayLike, N: int = ...) -> NDArray: ...
|
||||
def TAQ(CLOSE: npt.ArrayLike, N: int = ...) -> NDArray: ...
|
||||
def XSII(CLOSE: npt.ArrayLike, HIGH: npt.ArrayLike, LOW: npt.ArrayLike, N: int = ...) -> NDArray: ...
|
||||
def ZHUOYAO(CLOSE: npt.ArrayLike, HIGH: npt.ArrayLike, LOW: npt.ArrayLike, VOL: npt.ArrayLike) -> NDArray: ...
|
||||
def SAR(HIGH: npt.ArrayLike, LOW: npt.ArrayLike, AF_STEP: float = ..., AF_MAX: float = ...) -> NDArray: ...
|
||||
def VWAP(
|
||||
CLOSE: npt.ArrayLike,
|
||||
HIGH: npt.ArrayLike,
|
||||
LOW: npt.ArrayLike,
|
||||
VOL: npt.ArrayLike,
|
||||
N: int = ...,
|
||||
) -> NDArray: ...
|
||||
def AROON(
|
||||
HIGH: npt.ArrayLike,
|
||||
LOW: npt.ArrayLike,
|
||||
N: int = ...,
|
||||
) -> tuple[NDArray, NDArray, NDArray]: ...
|
||||
def FK(CLOSE: npt.ArrayLike) -> NDArray: ...
|
||||
|
||||
# ── Utility Functions ────────────────────────────────────────────────────────
|
||||
|
||||
|
||||
@@ -199,6 +199,46 @@ _reg(
|
||||
"TAQ", ("high", "low"), ("TAQ_UP", "TAQ_MID", "TAQ_DOWN"), MyTT.TAQ, {"N": 20}, "TAQ 唐安奇通道"
|
||||
)
|
||||
|
||||
# ── SAR 抛物线转向(仅需 high + low)─────────────────────────────────
|
||||
_reg(
|
||||
"SAR",
|
||||
("high", "low"),
|
||||
("SAR",),
|
||||
MyTT.SAR,
|
||||
{"AF_STEP": 0.02, "AF_MAX": 0.2},
|
||||
"SAR 抛物线转向(动态止损位)",
|
||||
)
|
||||
|
||||
# ── VWAP 成交量加权均价(close + high + low + vol)────────────────────
|
||||
_reg(
|
||||
"VWAP",
|
||||
("close", "high", "low", "vol"),
|
||||
("VWAP",),
|
||||
MyTT.VWAP,
|
||||
{"N": 20},
|
||||
"VWAP 成交量加权均价(N日滚动机构基准成本)",
|
||||
)
|
||||
|
||||
# ── Aroon 阿隆指标(仅需 high + low)────────────────────────────────
|
||||
_reg(
|
||||
"AROON",
|
||||
("high", "low"),
|
||||
("AROON_UP", "AROON_DOWN", "AROON_OSC"),
|
||||
MyTT.AROON,
|
||||
{"N": 25},
|
||||
"AROON 阿隆指标(趋势启动时机)",
|
||||
)
|
||||
|
||||
# ── FK 趋势快线慢线(仅需 close,清理孤儿函数)──────────────────────
|
||||
_reg(
|
||||
"FK",
|
||||
("close",),
|
||||
("FK",),
|
||||
MyTT.FK,
|
||||
{},
|
||||
"FK 趋势指标(EMA(2) 突破斜率外推 EMA(42),动量偏离检测)",
|
||||
)
|
||||
|
||||
|
||||
def list_indicators() -> list[dict[str, object]]:
|
||||
"""返回所有可用指标的元数据。"""
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
"""MyTT.py 新增指标函数(SAR/VWAP/AROON/FK)的数值正确性与边界测试。
|
||||
|
||||
这些测试针对 MyTT.py 里函数本身,不经过 indicator.py 注册层。
|
||||
注册层的端到端覆盖在 test_indicator.py::TestComputeIndicators::test_all_registered_indicators_run。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
import pytest
|
||||
|
||||
from easy_tdx import MyTT
|
||||
|
||||
|
||||
def _ohlcv(n: int = 200, seed: int = 42) -> tuple[np.ndarray, ...]:
|
||||
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 + 1.0).astype(float) # +1 避免全零
|
||||
return open_, high, low, close, vol
|
||||
|
||||
|
||||
class TestSAR:
|
||||
"""SAR 抛物线转向指标。"""
|
||||
|
||||
def test_returns_same_length(self):
|
||||
_, high, low, _, _ = _ohlcv()
|
||||
sar = MyTT.SAR(high, low)
|
||||
assert len(sar) == len(high)
|
||||
|
||||
def test_first_value_is_low(self):
|
||||
# 默认假设上涨趋势,SAR 起点取首根低点
|
||||
_, high, low, _, _ = _ohlcv()
|
||||
sar = MyTT.SAR(high, low)
|
||||
assert sar[0] == pytest.approx(low[0])
|
||||
|
||||
def test_empty_input(self):
|
||||
sar = MyTT.SAR(np.array([]), np.array([]))
|
||||
assert len(sar) == 0
|
||||
|
||||
def test_flat_market_no_crash(self):
|
||||
# 一字板/停牌:高低价完全相同,不应崩溃或产生 inf
|
||||
flat = np.full(50, 10.0)
|
||||
sar = MyTT.SAR(flat, flat)
|
||||
assert len(sar) == 50
|
||||
assert np.isfinite(sar[1:]).all(), "SAR 不应产生 inf/nan(首值外)"
|
||||
|
||||
def test_rising_market_sar_below_price(self):
|
||||
# 持续上涨时 SAR 应在价格下方(上涨止损位)
|
||||
high = np.arange(50, dtype=float) + 1
|
||||
low = np.arange(50, dtype=float)
|
||||
sar = MyTT.SAR(high, low)
|
||||
# 前 5 根建立趋势后,SAR 应低于对应低点
|
||||
assert (sar[5:] <= low[5:] + 1e-6).all()
|
||||
|
||||
def test_falling_market_sar_above_price(self):
|
||||
# 持续下跌时 SAR 应在价格上方(下跌止损位)
|
||||
low = np.array([100 - i for i in range(50)], dtype=float)
|
||||
high = low + 1
|
||||
sar = MyTT.SAR(high, low)
|
||||
# 确认在某处发生反转(趋势从上涨初判切换)
|
||||
# 不强求全程在上方(初判是上涨),但尾部下跌段 SAR 应高于 low
|
||||
assert sar[-1] > low[-1]
|
||||
|
||||
def test_reversal_resets_af(self):
|
||||
# 反转时加速因子应回到 AF_STEP(无法直接观测,间接验证:反转后第一步 SAR 等于前极值点)
|
||||
# 构造 V 型反转:先涨后跌
|
||||
rise_h = np.arange(25, dtype=float) + 1
|
||||
fall_h = np.array([25 - i + 1 for i in range(1, 25)])
|
||||
high = np.concatenate([rise_h, fall_h])
|
||||
rise_l = np.arange(25, dtype=float)
|
||||
fall_l = np.array([25 - i for i in range(1, 25)])
|
||||
low = np.concatenate([rise_l, fall_l])
|
||||
sar = MyTT.SAR(high, low)
|
||||
assert np.isfinite(sar).all()
|
||||
|
||||
def test_acceleration_factor_capped(self):
|
||||
# 长期单边上涨,AF 不应超过 AF_MAX(通过 SAR 增量间接验证不发散)
|
||||
high = np.cumsum(np.ones(100)) + 1 # 每根 +1
|
||||
low = np.cumsum(np.ones(100))
|
||||
sar = MyTT.SAR(high, low, AF_STEP=0.02, AF_MAX=0.2)
|
||||
assert np.isfinite(sar).all()
|
||||
# SAR 全程应在 low 之下(持续上涨不反转)
|
||||
valid = sar[2:]
|
||||
assert (valid <= low[2:] + 1e-6).all()
|
||||
|
||||
|
||||
class TestVWAP:
|
||||
"""VWAP 成交量加权均价。"""
|
||||
|
||||
def test_returns_same_length(self):
|
||||
_, high, low, close, vol = _ohlcv()
|
||||
vwap = MyTT.VWAP(close, high, low, vol, N=20)
|
||||
assert len(vwap) == len(close)
|
||||
|
||||
def test_leading_nan(self):
|
||||
# 前 N-1 根应为 nan(rolling 窗口未填满)
|
||||
_, high, low, close, vol = _ohlcv()
|
||||
vwap = MyTT.VWAP(close, high, low, vol, N=20)
|
||||
assert np.isnan(vwap[:19]).all()
|
||||
assert not np.isnan(vwap[19])
|
||||
|
||||
def test_constant_price(self):
|
||||
# 价格、量都恒定时,VWAP 应等于典型价格
|
||||
n = 50
|
||||
close = np.full(n, 10.0)
|
||||
high = np.full(n, 11.0)
|
||||
low = np.full(n, 9.0)
|
||||
vol = np.full(n, 1000.0)
|
||||
vwap = MyTT.VWAP(close, high, low, vol, N=20)
|
||||
expected_tp = (11 + 9 + 10) / 3.0 # =10.0
|
||||
assert np.allclose(vwap[19:], expected_tp, equal_nan=True)
|
||||
|
||||
def test_uniform_volume_equals_typical_price_mean(self):
|
||||
# 等量时 VWAP = 典型价格的 N 日均值
|
||||
n = 100
|
||||
rng = np.random.default_rng(1)
|
||||
close = 100 + rng.standard_normal(n)
|
||||
high = close + 1
|
||||
low = close - 1
|
||||
vol = np.full(n, 500.0)
|
||||
tp = (high + low + close) / 3.0
|
||||
vwap = MyTT.VWAP(close, high, low, vol, N=10)
|
||||
tp_ma = pd.Series(tp).rolling(10).mean().values
|
||||
assert np.allclose(vwap, tp_ma, equal_nan=True)
|
||||
|
||||
def test_zero_volume_returns_nan(self):
|
||||
# 全零成交量时,VWAP 应为 nan(除零保护)
|
||||
n = 30
|
||||
close = np.full(n, 10.0)
|
||||
high = np.full(n, 11.0)
|
||||
low = np.full(n, 9.0)
|
||||
vol = np.zeros(n)
|
||||
vwap = MyTT.VWAP(close, high, low, vol, N=20)
|
||||
assert np.isnan(vwap[19:]).all()
|
||||
|
||||
|
||||
class TestAROON:
|
||||
"""Aroon 阿隆指标。"""
|
||||
|
||||
def test_returns_three_arrays(self):
|
||||
_, high, low, _, _ = _ohlcv()
|
||||
up, down, osc = MyTT.AROON(high, low, N=25)
|
||||
assert len(up) == len(high)
|
||||
assert len(down) == len(high)
|
||||
assert len(osc) == len(high)
|
||||
|
||||
def test_range_zero_to_hundred(self):
|
||||
# AROON_UP/DOWN 应在 [0, 100] 区间
|
||||
_, high, low, _, _ = _ohlcv()
|
||||
up, down, _ = MyTT.AROON(high, low, N=25)
|
||||
# 跳过 rolling 窗口前的 nan
|
||||
valid_up = up[24:]
|
||||
valid_down = down[24:]
|
||||
assert (valid_up >= 0).all() and (valid_up <= 100).all()
|
||||
assert (valid_down >= 0).all() and (valid_down <= 100).all()
|
||||
|
||||
def test_new_high_gives_full_up(self):
|
||||
# 在窗口末端创新高时,AROON_UP 应 = 100
|
||||
n = 50
|
||||
high = np.linspace(1, 30, n) # 单调上升,末根创新高
|
||||
low = high - 0.5
|
||||
up, down, _ = MyTT.AROON(high, low, N=25)
|
||||
assert up[-1] == pytest.approx(100.0)
|
||||
|
||||
def test_new_low_gives_full_down(self):
|
||||
# 在窗口末端创新低时,AROON_DOWN 应 = 100
|
||||
n = 50
|
||||
low = np.linspace(30, 1, n) # 单调下降
|
||||
high = low + 0.5
|
||||
_, down, _ = MyTT.AROON(high, low, N=25)
|
||||
assert down[-1] == pytest.approx(100.0)
|
||||
|
||||
def test_osc_is_difference(self):
|
||||
# OSC = UP - DOWN
|
||||
_, high, low, _, _ = _ohlcv()
|
||||
up, down, osc = MyTT.AROON(high, low, N=25)
|
||||
assert np.allclose(osc[24:], (up - down)[24:], equal_nan=True)
|
||||
|
||||
def test_leading_nan(self):
|
||||
_, high, low, _, _ = _ohlcv()
|
||||
up, down, _ = MyTT.AROON(high, low, N=25)
|
||||
# HHVBARS/LLVBARS 在 N-1 根前为 nan
|
||||
assert np.isnan(up[:24]).all()
|
||||
|
||||
|
||||
class TestFK:
|
||||
"""FK 趋势指标(布尔输出)。
|
||||
|
||||
慢线用 SLOPE(CLOSE,21)*20 做斜率外推:上涨时慢线被正斜率推高,
|
||||
下跌时被负斜率压低。FK = fast(EMA2) > slow(外推 EMA42),
|
||||
语义是"价格是否突破趋势外推线",本质是动量/反转偏离检测:
|
||||
- 强下跌时 fast 相对外推慢线偏高 → FK=True(超卖/反弹信号)
|
||||
- 强上涨时慢线被推高,fast 难以超越 → FK=False(未超买或接近超买)
|
||||
"""
|
||||
|
||||
def test_returns_boolean_array(self):
|
||||
close = _ohlcv()[3]
|
||||
fk = MyTT.FK(close)
|
||||
assert len(fk) == len(close)
|
||||
assert fk.dtype == bool
|
||||
|
||||
def test_rising_market_returns_false(self):
|
||||
# 强上涨:正斜率外推把慢线推高,fast < slow → FK=False
|
||||
close = np.cumsum(np.ones(100)) # 每根 +1
|
||||
fk = MyTT.FK(close)
|
||||
assert bool(fk[-1]) is False
|
||||
|
||||
def test_falling_market_returns_true(self):
|
||||
# 强下跌:负斜率外推把慢线压低,fast > slow → FK=True
|
||||
close = np.array([100 - i for i in range(100)], dtype=float)
|
||||
fk = MyTT.FK(close)
|
||||
assert bool(fk[-1]) is True
|
||||
Reference in New Issue
Block a user