diff --git a/README.md b/README.md index d3744dc..5ff47ac 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/chart.py b/chart.py index 71b8740..219b9f4 100644 --- a/chart.py +++ b/chart.py @@ -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']), diff --git a/docs/K线形态参数调整建议_202603041557.md b/docs/K线形态参数调整建议_202603041557.md new file mode 100644 index 0000000..e526b4d --- /dev/null +++ b/docs/K线形态参数调整建议_202603041557.md @@ -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** | 提升形态质量,过滤掉收敛不明显的案例 | \ No newline at end of file diff --git a/validate.py b/validate.py index 969c0d7..9ad49cc 100644 --- a/validate.py +++ b/validate.py @@ -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)