feat: 增加命令行参数以支持自定义枢轴点窗口、收敛比和最小收敛比例

docs: 更新 README 示例以包含比亚迪的日、周、月 K 线图
fix: 修复趋势线绘制逻辑,支持周K/月K聚合后的日期匹配
docs: 添加 K 线形态参数调整建议文档
This commit is contained in:
褚宏光 2026-03-04 17:39:27 +08:00
parent 6f7bdcc9c1
commit 59622a6ef7
4 changed files with 136 additions and 35 deletions

View File

@ -43,8 +43,9 @@ pip install requests mplfinance
python validate.py 中远海控
# 使用中文名称(自动搜索转换为股票代码)
python validate.py 中控技术 日 --window 3Y --save
python validate.py 比亚迪 日 --window 3Y --save --no-show
python validate.py 比亚迪 周 --window 10Y --save --no-show
python validate.py 比亚迪 月 --window 3Y --save --no-show
# 美股也支持
python validate.py 英伟达 日 --window 3Y --save

View File

@ -163,6 +163,10 @@ def draw_triangle_chart(
"""
与前端 TechPattern.vue getExtendedLinePoints 函数逻辑完全一致
趋势线从 window_start_date 延伸到 window_end_date
关键修复
1. 使用 K 线数据的实际索引而不是公式内部的 index
2. 使用 get_closest_date_idx 查找日期支持周K/月K 聚合后的日期匹配
"""
if not line_pts or len(line_pts) < 2:
return None
@ -170,36 +174,35 @@ def draw_triangle_chart(
p1 = line_pts[0]
p2 = line_pts[-1]
# 检查是否有 index 字段(公式返回的数据)
has_index = 'index' in p1 and 'index' in p2 and pivot_point and 'index' in pivot_point
if window_start and window_end:
# 将枢轴点日期映射到 K 线数据的实际索引
# 使用 get_closest_date_idx 支持周K/月K 聚合后的日期匹配
idx1 = get_closest_date_idx(p1['date'], dates_str)
idx2 = get_closest_date_idx(p2['date'], dates_str)
if has_index and window_start and window_end:
# 使用原始 index 计算斜率(与前端一致)
slope = (p2['price'] - p1['price']) / (p2['index'] - p1['index']) if p2['index'] != p1['index'] else 0
d1 = _date_int_to_str(p1['date'])
d2 = _date_int_to_str(p2['date'])
# 前端: startIdx = 0 (window_start_date 对应 index 0)
start_idx = 0
print(f'[chart] 枢轴点日期映射: {d1} -> K线索引{idx1}, {d2} -> K线索引{idx2}')
# 查找 window_end_date 对应的 index与前端一致
end_idx = p2['index']
all_points = (chart.get('upper_line', []) + chart.get('lower_line', []) +
chart.get('upper_pivots', []) + chart.get('lower_pivots', []))
for pt in all_points:
if pt.get('date') == window_end:
end_idx = pt.get('index', end_idx)
break
# 使用 K 线数据的实际索引计算斜率
slope = (p2['price'] - p1['price']) / (idx2 - idx1) if idx2 != idx1 else 0
# 计算延伸后的价格(与前端一致)
pivot_index = pivot_point['index']
pivot_price = pivot_point['price']
start_price = pivot_price + slope * (start_idx - pivot_index)
end_price = pivot_price + slope * (end_idx - pivot_index)
# 将 window_start_date 和 window_end_date 映射到 K 线数据的索引
# 找到 window_start_date 和 window_end_date 在 K 线数据中的索引
kline_start_idx = get_closest_date_idx(window_start, dates_str)
kline_end_idx = get_closest_date_idx(window_end, dates_str)
# 使用 K 线数据的索引计算延伸后的价格
# 价格 = pivot_price + slope * (目标索引 - pivot索引)
pivot_kline_idx = idx1 # 使用第一个枢轴点的 K 线索引
pivot_price = p1['price']
start_price = pivot_price + slope * (kline_start_idx - pivot_kline_idx)
end_price = pivot_price + slope * (kline_end_idx - pivot_kline_idx)
print(f'[chart] 趋势线延伸: {window_start_str}({kline_start_idx}) -> {window_end_str}({kline_end_idx})')
print(f'[chart] 枢轴点: {d1}({idx1}) 价格={pivot_price:.2f}')
print(f'[chart] 斜率: {slope:.4f}')
print(f'[chart] 价格: {start_price:.2f} -> {end_price:.2f}')
return (
@ -207,16 +210,13 @@ def draw_triangle_chart(
(kline_end_idx, end_price)
)
# 降级处理:仅连接两点(与前端一致)
# 降级处理:仅连接两点
d1 = _date_int_to_str(p1['date'])
d2 = _date_int_to_str(p2['date'])
idx1 = date_to_idx.get(d1)
idx2 = date_to_idx.get(d2)
if idx1 is None or idx2 is None:
print(f'[chart] 警告: 趋势线日期不在 K 线数据中: {d1}{d2}')
return None
# 使用 get_closest_date_idx 支持日期模糊匹配
idx1 = get_closest_date_idx(p1['date'], dates_str)
idx2 = get_closest_date_idx(p2['date'], dates_str)
return (
(idx1, p1['price']),

View File

@ -0,0 +1,67 @@
根据你提供的比亚迪BYD日、周、月三个周期的 K 线图以及形态验证文档,我们先来复盘一下现状:
* **日K线**:成功识别,强度 **0.285**。上沿触碰 6 次(非常稳固),下沿仅 2 次。
* **周K线**:成功识别,强度 **0.345**。上下沿各触碰 2 次,刚好达到形态成立的最低门槛。
* **月K线****未识别到形态**(强度 0.000,触碰点均为 0
结合 `triangle-validator-guide` 文档,针对如何优化参数以获得更精准或更全面的识别结果,建议从以下几个维度进行调整:
---
## 1. 解决月K线“零识别”问题
月K线识别不到形态通常是因为月度数据太稀疏默认参数过于严格。
* **调小 `pivot_k` (枢轴点窗口)**
* **现状**:默认值为 3意味着一个高点必须比前后各 3 个月(共 7 个月)的价格都高。
* **建议**:修改为 **2**。月线级别 7 个月的跨度太长,很多波段高点会被过滤掉。调小它可以增加候选枢轴点,从而更容易连成三角形。
* **放宽 `min_convergence` (收敛比例)**
* **现状**:默认 0.50。
* **建议**:如果比亚迪目前的震荡幅度依然较大,未达到“缩减一半”的程度,可以调高到 **0.60**
---
## 2. 优化日K线的“触碰不均衡”
日线图显示上沿(红色)被多次验证,但下沿(绿色)的斜率非常陡峭且触碰点极少,这说明形态可能有些“勉强”。
* **增加 `pivot_k` (日线级)**
* **现状**:默认 15。
* **建议**:如果你希望过滤掉近期细微的波动,寻找更宏观、更稳健的支撑线,可以尝试调大到 **20****25**。这会强制程序寻找更具“重量级”的低点。
* **调整 `window` (检测窗口)**
* **现状**:默认 240 根(约 1 年)。
* **建议**:比亚迪的这一波整理从 2025 年初就开始了,可以尝试缩短窗口至 **180**,聚焦于最近半年的收敛状态,可能会得到一个斜率更平缓、更符合直觉的底部支撑线。
---
## 3. 提高识别强度Intensity与质量
如果你觉得当前的识别结果“噪音”太多,或者想提高所谓的强度数值:
* **收紧 `shrink_ratio` (收敛比)**
* **逻辑**:文档提到默认是 0.8。将此值调低(例如 **0.7**)会过滤掉那些收敛不明显的三角形,只留下最“尖”的形态,这样识别出的强度通常会更高。
* **提高 `breakout_threshold` (突破阈值)**
* **建议**对于周K线如果想过滤假突破可以将 **0.010 (1%)** 提高到 **0.015**
---
## 参数调整建议总结表
| 目标周期 | 建议修改参数 | 推荐值 | 修改目的 |
| --- | --- | --- | --- |
| **月K (M)** | `pivot_k` | 3 → **2** | 增加枢轴点,解决数据稀疏无法连线的问题 |
| **月K (M)** | `min_convergence` | 0.50 → **0.60** | 容忍更宽的三角形,适合大周期早期识别 |
| **日K (D)** | `pivot_k` | 15 → **20** | 过滤小波动,寻找更扎实的下沿支撑 |
| **通用** | `shrink_ratio` | 0.8 → **0.7** | 提升形态质量,过滤掉收敛不明显的案例 |

View File

@ -182,11 +182,19 @@ def parse_args():
parser.add_argument('--window', type=str, default='3Y',
choices=['1Y', '3Y', '5Y', 'ALL'],
help='K线时间窗口1Y=1年 3Y=3年 5Y=5年 ALL=全部默认3Y与前端一致')
parser.add_argument('--pivot-k', type=int, default=-1, metavar='N',
help='枢轴点窗口默认日K=15, 周K=5, 月K=3')
parser.add_argument('--shrink-ratio', type=float, default=-1.0, metavar='R',
help='收敛比三角形开口缩小程度默认0.8')
parser.add_argument('--min-convergence', type=float, default=-1.0, metavar='C',
help='最小收敛比例默认日K=0.45, 周K=0.45, 月K=0.50')
return parser.parse_args()
# ── Step 1执行公式拿 _id ──────────────────────────────────────────────────
def run_formula(ticker: str, freq: str, mode: int, date) -> str:
def run_formula(ticker: str, freq: str, mode: int, date,
pivot_k: int = -1, shrink_ratio: float = -1.0,
min_convergence: float = -1.0) -> str:
"""
innerServer/runOneFormula 接口执行收敛三角形详情公式
Returns:
@ -194,10 +202,30 @@ def run_formula(ticker: str, freq: str, mode: int, date) -> str:
"""
import uuid
# 构建参数列表
params = [ticker, freq, 'True', str(mode)]
# target_date
if date:
formula = f'结果=收敛三角形详情({ticker},{freq},True,{mode},{date})'
params.append(str(date))
else:
formula = f'结果=收敛三角形详情({ticker},{freq},True,{mode})'
params.append('None')
# window, min_convergence, breakout_threshold, volume_multiplier 使用默认值
params.extend(['-1', '-1', '-1', '-1'])
# pivot_k, shrink_ratio
params.append(str(pivot_k))
params.append(str(shrink_ratio))
# 如果有自定义 min_convergence需要调整参数位置
# 参数顺序: ticker, freq, include_klines, mode, target_date,
# window, min_convergence, breakout_threshold, volume_multiplier,
# pivot_k, shrink_ratio, display_days, custom_weights, fast
if min_convergence > 0:
params[6] = str(min_convergence) # min_convergence 在索引 6
formula = f'结果=收敛三角形详情({",".join(params)})'
url = f'{GPT_SERVER_URL}/innerServer/runOneFormula'
payload = {
@ -357,7 +385,12 @@ def run(args):
# 1. 执行公式
try:
index_id = run_formula(args.ticker, args.freq, args.mode, args.date)
index_id = run_formula(
args.ticker, args.freq, args.mode, args.date,
pivot_k=args.pivot_k,
shrink_ratio=args.shrink_ratio,
min_convergence=args.min_convergence,
)
except Exception as e:
print(f'[ERROR] 公式执行失败: {e}')
sys.exit(1)