mirror of
https://ghfast.top/https://github.com/aeroxw/easy-tdx.git
synced 2026-09-12 16:54:17 +08:00
feat(chanlun): 分钟级别日期自适应输出时分 YYYY-MM-DD HH:MM
This commit is contained in:
@@ -1618,6 +1618,12 @@ ruff format --check src/ tests/ # format check
|
||||
|
||||
## Changelog
|
||||
|
||||
### 1.14.5 (2026-06-17)
|
||||
|
||||
**缠论可视化日期自适应时分** — 响应网友反馈,分钟级别(1/5/15/30/60min)的缠论结果日期字段现在输出完整时分 `YYYY-MM-DD HH:MM`,日/周/月/年级别仍只输出日期 `YYYY-MM-DD`(无多余 `00:00`)。
|
||||
|
||||
新增 `ChanlunResult._fmt_dt()` 按 `frequency` 自适应格式化,统一作用于 `bis` / `zss` / `mmds` / `bcs` / `xds` 所有日期字段。兼容 CLI 原始值(`5MIN`/`30MIN`)与 Web 映射值(`5min`/`30min`)的大小写。三层接入同步生效。
|
||||
|
||||
### 1.14.4 (2026-06-16)
|
||||
|
||||
**CI 修复** — 修复 v1.14.3 中 `cmd_chanlun.py` 两处 `click.echo(...)` 未按 `ruff format` 行宽规则合并导致的 CI 格式检查失败(纯格式调整,无功能变化)。
|
||||
|
||||
+1
-1
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
|
||||
|
||||
[project]
|
||||
name = "easy-tdx"
|
||||
version = "1.14.4"
|
||||
version = "1.14.5"
|
||||
description = "通达信 TCP 协议行情数据客户端,支持在线行情、离线数据读取与写入同步"
|
||||
readme = "README.md"
|
||||
requires-python = ">=3.10"
|
||||
|
||||
@@ -8,6 +8,7 @@ K线合并 → 分型识别 → 笔计算 → 中枢计算 → 线段 → 买卖
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
from typing import Any
|
||||
|
||||
import pandas as pd
|
||||
@@ -66,6 +67,17 @@ class ChanlunResult:
|
||||
bcs: list[BC] = field(default_factory=list)
|
||||
macd: dict[str, list[float]] = field(default_factory=dict)
|
||||
|
||||
def _fmt_dt(self, dt: datetime) -> str:
|
||||
"""按 frequency 自适应格式化日期。
|
||||
|
||||
分钟级别(1/5/15/30/60min)输出完整时分 YYYY-MM-DD HH:MM,
|
||||
日/周/月/年级别只输出日期 YYYY-MM-DD(无多余 00:00)。
|
||||
frequency 来自 CLI 原始值(如 5MIN/30MIN)或 Web 映射值(5min/30min),
|
||||
统一转小写后判断是否含 'min'。
|
||||
"""
|
||||
fmt = "%Y-%m-%d %H:%M" if "min" in self.frequency.lower() else "%Y-%m-%d"
|
||||
return dt.strftime(fmt)
|
||||
|
||||
def to_dict(self) -> dict[str, Any]:
|
||||
"""将结果转为可序列化的字典(用于 JSON 输出)。"""
|
||||
return {
|
||||
@@ -83,8 +95,8 @@ class ChanlunResult:
|
||||
{
|
||||
"index": bi.index,
|
||||
"direction": bi.direction.value,
|
||||
"start_date": bi.start.k.date.strftime("%Y-%m-%d"),
|
||||
"end_date": bi.end.k.date.strftime("%Y-%m-%d"),
|
||||
"start_date": self._fmt_dt(bi.start.k.date),
|
||||
"end_date": self._fmt_dt(bi.end.k.date),
|
||||
"high": round(bi.high, 2),
|
||||
"low": round(bi.low, 2),
|
||||
"done": bi.is_done(),
|
||||
@@ -99,8 +111,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,
|
||||
"start_date": self._fmt_dt(zs.start.k.date) if zs.start else None,
|
||||
"end_date": self._fmt_dt(zs.end.k.date) if zs.end else None,
|
||||
"done": zs.done,
|
||||
}
|
||||
for zs in self.zss
|
||||
@@ -109,8 +121,8 @@ class ChanlunResult:
|
||||
{
|
||||
"index": xd.index,
|
||||
"direction": xd.direction.value,
|
||||
"start_date": xd.start.k.date.strftime("%Y-%m-%d"),
|
||||
"end_date": xd.end.k.date.strftime("%Y-%m-%d"),
|
||||
"start_date": self._fmt_dt(xd.start.k.date),
|
||||
"end_date": self._fmt_dt(xd.end.k.date),
|
||||
"high": round(xd.high, 2),
|
||||
"low": round(xd.low, 2),
|
||||
}
|
||||
@@ -119,7 +131,7 @@ class ChanlunResult:
|
||||
"mmds": [
|
||||
{
|
||||
"type": mmd.mmd_type.value,
|
||||
"date": mmd.bi.end.k.date.strftime("%Y-%m-%d") if mmd.bi else None,
|
||||
"date": self._fmt_dt(mmd.bi.end.k.date) if mmd.bi else None,
|
||||
"msg": mmd.msg,
|
||||
}
|
||||
for mmd in self.mmds
|
||||
@@ -128,8 +140,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,
|
||||
"curr_date": self._fmt_dt(bc.curr.end.k.date) if bc.curr else None,
|
||||
"prev_date": self._fmt_dt(bc.prev.end.k.date) if bc.prev else None,
|
||||
"msg": bc.msg,
|
||||
}
|
||||
for bc in self.bcs
|
||||
|
||||
@@ -629,6 +629,59 @@ class TestChanlunAnalyser:
|
||||
assert date_re.match(bc["curr_date"])
|
||||
assert date_re.match(bc["prev_date"])
|
||||
|
||||
def test_result_to_dict_minute_frequency_includes_time(self) -> None:
|
||||
"""分钟级别 frequency 下,日期字段应输出完整时分 YYYY-MM-DD HH:MM。
|
||||
|
||||
对应网友反馈:分钟/低级别也需要时分用于分时可视化。
|
||||
覆盖 CLI 原始值(5MIN/30MIN)与 Web 映射值(5min/30min)两种大小写。
|
||||
"""
|
||||
import math
|
||||
import re
|
||||
|
||||
import pandas as pd
|
||||
|
||||
from easy_tdx.chanlun.analyser import ChanlunAnalyser
|
||||
|
||||
dates = pd.date_range("2025-01-02 09:30", periods=60, freq="5min")
|
||||
highs = [15 + 5 * math.sin(i / 2) + i * 0.01 for i in range(60)]
|
||||
lows = [highs[i] - 1.5 for i in range(60)]
|
||||
df = pd.DataFrame(
|
||||
{
|
||||
"datetime": dates,
|
||||
"open": [h - 0.5 for h in highs],
|
||||
"close": [h - 0.2 for h in highs],
|
||||
"high": highs,
|
||||
"low": lows,
|
||||
"vol": [1000] * 60,
|
||||
}
|
||||
)
|
||||
datetime_re = re.compile(r"^\d{4}-\d{2}-\d{2} \d{2}:\d{2}$")
|
||||
|
||||
# CLI 原始值(大写 5MIN)与 Web 映射值(小写 5min)应行为一致
|
||||
for freq in ("5MIN", "5min"):
|
||||
d = ChanlunAnalyser(code="SZ000001", frequency=freq).process_klines(df).to_dict()
|
||||
|
||||
# bis 日期应带时分
|
||||
assert len(d["bis"]) > 0
|
||||
for bi in d["bis"]:
|
||||
assert datetime_re.match(bi["start_date"])
|
||||
assert datetime_re.match(bi["end_date"])
|
||||
|
||||
# zss/mmds/bcs 若产出,同样应带时分(有就检查)
|
||||
for zs in d["zss"]:
|
||||
if zs["start_date"] is not None:
|
||||
assert datetime_re.match(zs["start_date"])
|
||||
if zs["end_date"] is not None:
|
||||
assert datetime_re.match(zs["end_date"])
|
||||
for mmd in d["mmds"]:
|
||||
if mmd["date"] is not None:
|
||||
assert datetime_re.match(mmd["date"])
|
||||
for bc in d["bcs"]:
|
||||
if bc["curr_date"] is not None:
|
||||
assert datetime_re.match(bc["curr_date"])
|
||||
if bc["prev_date"] is not None:
|
||||
assert datetime_re.match(bc["prev_date"])
|
||||
|
||||
def test_print_table_with_dates(self) -> None:
|
||||
"""CLI table 模式应正确消费 zss/mmds/bcs 的日期字段。
|
||||
|
||||
|
||||
Reference in New Issue
Block a user