mirror of
https://ghfast.top/https://github.com/aeroxw/easy_tdx_max.git
synced 2026-09-12 14:34:18 +08:00
release: v1.14.2 — 缠论 JSON 可视化字段增强(中枢/买卖点/背驰补日期)
This commit is contained in:
@@ -1618,6 +1618,18 @@ ruff format --check src/ tests/ # format check
|
||||
|
||||
## Changelog
|
||||
|
||||
### 1.14.2 (2026-06-16)
|
||||
|
||||
**缠论结果可视化字段增强** — 响应 [Discussion #2](https://github.com/handsomejustin/easy-tdx/discussions/2),为缠论分析 JSON 输出(`ChanlunResult.to_dict()`)中的中枢 / 买卖点 / 背驰补上对应 K 线日期,方便前端/可视化工具直接用来标点画图。纯增量、向后兼容,不破坏任何已有 JSON 字段。
|
||||
|
||||
新增字段:
|
||||
|
||||
- **中枢 `zss`**:输出起始笔与结束笔的日期 `start_date` / `end_date`(第一笔起点 → 最后一笔终点)。
|
||||
- **买卖点 `mmds`**:输出触发该买卖点的笔确认日期 `date`(买卖点确立时刻的 K 线日期)。
|
||||
- **背驰 `bcs`**:输出背驰对照两笔的日期 `curr_date`(当前背驰笔)/ `prev_date`(对照基准笔)。
|
||||
|
||||
日期统一采用 `YYYY-MM-DD` 格式(与已有 `bis` / `xds` 输出一致),全部字段对 `None` 做了兜底。三层接入(Python API / CLI `easy-tdx chanlun` / Web `/chanlun/analyze`)同步生效,Web 接口直接返回新字段无需改动。
|
||||
|
||||
### 1.14.1 (2026-06-15)
|
||||
|
||||
**高级回测 ExecutionModel 路径 3 个真实数据兼容 Bug 修复** — 实测 `601088` 高级回测(方根滑点 + TWAP)暴露:权益曲线恒定、收益归零。根因为 ExecutionModel 路径与真实行情数据的格式/列名/类型脱节。
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "easy-tdx"
|
||||
version = "1.14.1"
|
||||
version = "1.14.2"
|
||||
description = "通达信 TCP 协议行情数据客户端,支持在线行情、离线数据读取与写入同步"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
@@ -99,6 +99,8 @@ class ChanlunResult:
|
||||
"gg": round(zs.gg, 2),
|
||||
"dd": round(zs.dd, 2),
|
||||
"line_count": zs.line_count,
|
||||
"start_date": zs.start.k.date.strftime("%Y-%m-%d") if zs.start else None,
|
||||
"end_date": zs.end.k.date.strftime("%Y-%m-%d") if zs.end else None,
|
||||
"done": zs.done,
|
||||
}
|
||||
for zs in self.zss
|
||||
@@ -117,6 +119,7 @@ class ChanlunResult:
|
||||
"mmds": [
|
||||
{
|
||||
"type": mmd.mmd_type.value,
|
||||
"date": mmd.bi.end.k.date.strftime("%Y-%m-%d") if mmd.bi else None,
|
||||
"msg": mmd.msg,
|
||||
}
|
||||
for mmd in self.mmds
|
||||
@@ -125,6 +128,8 @@ class ChanlunResult:
|
||||
{
|
||||
"type": bc.bc_type.value,
|
||||
"bc": bc.bc,
|
||||
"curr_date": bc.curr.end.k.date.strftime("%Y-%m-%d") if bc.curr else None,
|
||||
"prev_date": bc.prev.end.k.date.strftime("%Y-%m-%d") if bc.prev else None,
|
||||
"msg": bc.msg,
|
||||
}
|
||||
for bc in self.bcs
|
||||
|
||||
@@ -75,6 +75,8 @@ def _check_bi_level_beichi(bis: list[BI]) -> list[BC]:
|
||||
bc_type=BCType.BI,
|
||||
bc=True,
|
||||
zs=None,
|
||||
curr=curr,
|
||||
prev=prev,
|
||||
msg=(
|
||||
f"笔背驰: 笔[{curr.index}] 力度={curr_force:.2f} "
|
||||
f"< 笔[{prev.index}] 力度={prev_force:.2f}"
|
||||
@@ -108,6 +110,8 @@ def _check_pz_beichi(bis: list[BI], zss: list[ZS]) -> list[BC]:
|
||||
bc_type=BCType.PZ,
|
||||
bc=True,
|
||||
zs=zs,
|
||||
curr=last_bi,
|
||||
prev=first_bi,
|
||||
msg=(
|
||||
f"盘整背驰: 中枢[{zs.index}] 内末笔力度={last_force:.2f} "
|
||||
f"< 首笔力度={first_force:.2f}"
|
||||
@@ -138,6 +142,8 @@ def _check_qs_beichi(bis: list[BI], zss: list[ZS]) -> list[BC]:
|
||||
bc_type=BCType.QS,
|
||||
bc=True,
|
||||
zs=curr_zs,
|
||||
curr=curr_zs.lines[-1],
|
||||
prev=prev_zs.lines[-1],
|
||||
msg=(
|
||||
f"趋势背驰(下): 中枢[{curr_zs.index}] 离开力度={curr_exit_force:.2f} "
|
||||
f"< 中枢[{prev_zs.index}] 离开力度={prev_exit_force:.2f}"
|
||||
@@ -155,6 +161,8 @@ def _check_qs_beichi(bis: list[BI], zss: list[ZS]) -> list[BC]:
|
||||
bc_type=BCType.QS,
|
||||
bc=True,
|
||||
zs=curr_zs,
|
||||
curr=curr_zs.lines[-1],
|
||||
prev=prev_zs.lines[-1],
|
||||
msg=(
|
||||
f"趋势背驰(上): 中枢[{curr_zs.index}] 离开力度={curr_exit_force:.2f} "
|
||||
f"< 中枢[{prev_zs.index}] 离开力度={prev_exit_force:.2f}"
|
||||
|
||||
@@ -68,6 +68,7 @@ def _check_buy_point(bi: BI, zs: ZS, all_bis: list[BI]) -> MMD | None:
|
||||
return MMD(
|
||||
mmd_type=MMDType.BUY_1,
|
||||
zs=zs,
|
||||
bi=bi,
|
||||
msg=f"中枢下方力度衰减,一类买点 (l={bi.low:.2f} < zd={zs.zd:.2f})",
|
||||
)
|
||||
|
||||
@@ -82,6 +83,7 @@ def _check_buy_point(bi: BI, zs: ZS, all_bis: list[BI]) -> MMD | None:
|
||||
return MMD(
|
||||
mmd_type=MMDType.BUY_2,
|
||||
zs=zs,
|
||||
bi=bi,
|
||||
msg=f"回调不创新低,二类买点 (l={bi.low:.2f})",
|
||||
)
|
||||
|
||||
@@ -90,6 +92,7 @@ def _check_buy_point(bi: BI, zs: ZS, all_bis: list[BI]) -> MMD | None:
|
||||
return MMD(
|
||||
mmd_type=MMDType.BUY_3,
|
||||
zs=zs,
|
||||
bi=bi,
|
||||
msg=f"回调不破中枢上沿,三类买点 (l={bi.low:.2f} > zg={zs.zg:.2f})",
|
||||
)
|
||||
|
||||
@@ -104,6 +107,7 @@ def _check_sell_point(bi: BI, zs: ZS, all_bis: list[BI]) -> MMD | None:
|
||||
return MMD(
|
||||
mmd_type=MMDType.SELL_1,
|
||||
zs=zs,
|
||||
bi=bi,
|
||||
msg=f"中枢上方力度衰减,一类卖点 (h={bi.high:.2f} > zg={zs.zg:.2f})",
|
||||
)
|
||||
|
||||
@@ -117,6 +121,7 @@ def _check_sell_point(bi: BI, zs: ZS, all_bis: list[BI]) -> MMD | None:
|
||||
return MMD(
|
||||
mmd_type=MMDType.SELL_2,
|
||||
zs=zs,
|
||||
bi=bi,
|
||||
msg=f"反弹不创新高,二类卖点 (h={bi.high:.2f})",
|
||||
)
|
||||
|
||||
@@ -125,6 +130,7 @@ def _check_sell_point(bi: BI, zs: ZS, all_bis: list[BI]) -> MMD | None:
|
||||
return MMD(
|
||||
mmd_type=MMDType.SELL_3,
|
||||
zs=zs,
|
||||
bi=bi,
|
||||
msg=f"反弹不破中枢下沿,三类卖点 (h={bi.high:.2f} < zd={zs.zd:.2f})",
|
||||
)
|
||||
|
||||
|
||||
@@ -183,6 +183,7 @@ class MMD:
|
||||
|
||||
mmd_type: MMDType
|
||||
zs: ZS | None = None
|
||||
bi: BI | None = None # 触发该买卖点的笔(用于可视化锚定日期)
|
||||
msg: str = ""
|
||||
|
||||
def __str__(self) -> str:
|
||||
@@ -204,6 +205,8 @@ class BC:
|
||||
bc_type: BCType
|
||||
bc: bool = False # 是否背驰
|
||||
zs: ZS | None = None
|
||||
curr: BI | XD | None = None # 当前背驰笔/线段(用于可视化锚定日期)
|
||||
prev: BI | XD | None = None # 前一同向笔/线段(力度对照基准)
|
||||
msg: str = ""
|
||||
|
||||
def __str__(self) -> str:
|
||||
|
||||
@@ -549,3 +549,82 @@ class TestChanlunAnalyser:
|
||||
assert "zs_count" in d
|
||||
assert "bis" in d
|
||||
assert "zss" in d
|
||||
|
||||
# 可视化字段:中枢/买卖点/背驰应携带对应 K 线日期(若该样本产出了它们)
|
||||
import re
|
||||
|
||||
date_re = re.compile(r"^\d{4}-\d{2}-\d{2}$")
|
||||
|
||||
for zs in d["zss"]:
|
||||
assert "start_date" in zs
|
||||
assert "end_date" in zs
|
||||
if zs["start_date"] is not None:
|
||||
assert date_re.match(zs["start_date"])
|
||||
if zs["end_date"] is not None:
|
||||
assert date_re.match(zs["end_date"])
|
||||
|
||||
for mmd in d["mmds"]:
|
||||
assert "date" in mmd
|
||||
if mmd["date"] is not None:
|
||||
assert date_re.match(mmd["date"])
|
||||
|
||||
for bc in d["bcs"]:
|
||||
assert "curr_date" in bc
|
||||
assert "prev_date" in bc
|
||||
if bc["curr_date"] is not None:
|
||||
assert date_re.match(bc["curr_date"])
|
||||
if bc["prev_date"] is not None:
|
||||
assert date_re.match(bc["prev_date"])
|
||||
|
||||
def test_result_to_dict_with_visual_dates(self) -> None:
|
||||
"""可视化字段:足够数据下 zss/mmds/bcs 应携带合法 K 线日期。
|
||||
|
||||
用一段振荡+趋势的数据,确保能确定性产出中枢/买卖点/背驰,
|
||||
从而真正覆盖 to_dict() 的日期输出分支。
|
||||
"""
|
||||
import math
|
||||
import re
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from easy_tdx.chanlun.analyser import ChanlunAnalyser
|
||||
|
||||
dates = pd.date_range("2025-01-02", periods=40, freq="B")
|
||||
highs = [15 + 5 * math.sin(i / 2) + i * 0.2 for i in range(40)]
|
||||
lows = [highs[i] - 4 for i in range(40)]
|
||||
df = pd.DataFrame(
|
||||
{
|
||||
"datetime": dates,
|
||||
"open": [h - 2 for h in highs],
|
||||
"close": [h - 1 for h in highs],
|
||||
"high": highs,
|
||||
"low": lows,
|
||||
"vol": [1000] * 40,
|
||||
}
|
||||
)
|
||||
analyser = ChanlunAnalyser(code="SZ000001")
|
||||
d = analyser.process_klines(df).to_dict()
|
||||
|
||||
date_re = re.compile(r"^\d{4}-\d{2}-\d{2}$")
|
||||
|
||||
# 中枢必须有起止日期
|
||||
assert len(d["zss"]) > 0
|
||||
for zs in d["zss"]:
|
||||
assert zs["start_date"] is not None
|
||||
assert zs["end_date"] is not None
|
||||
assert date_re.match(zs["start_date"])
|
||||
assert date_re.match(zs["end_date"])
|
||||
|
||||
# 买卖点必须有触发日期
|
||||
assert len(d["mmds"]) > 0
|
||||
for mmd in d["mmds"]:
|
||||
assert mmd["date"] is not None
|
||||
assert date_re.match(mmd["date"])
|
||||
|
||||
# 背驰必须有当前笔 + 对照笔日期
|
||||
assert len(d["bcs"]) > 0
|
||||
for bc in d["bcs"]:
|
||||
assert bc["curr_date"] is not None
|
||||
assert bc["prev_date"] is not None
|
||||
assert date_re.match(bc["curr_date"])
|
||||
assert date_re.match(bc["prev_date"])
|
||||
|
||||
Reference in New Issue
Block a user