增强绘图功能:支持自定义边界线拟合数据源
新增功能: - 添加 --plot-boundary-source 参数,允许选择边界线拟合数据源(高低价/收盘价) - 添加 --show-high-low 参数,可选显示日内高低价范围 - 当使用收盘价拟合时,自动重新计算贴合度指标 - 图例自动标注当前使用的数据源 - 详细模式下,枢轴点标注位置根据数据源自适应调整 技术细节: - 检测算法仍使用高低价(保持一致性) - 新参数仅影响图表展示,不影响检测结果 - 贴合度计算逻辑根据数据源动态调整 文件修改: - scripts/pipeline_converging_triangle.py: 添加参数支持和参数传递 - scripts/plot_converging_triangles.py: 实现核心绘图逻辑增强 使用示例: python scripts/pipeline_converging_triangle.py --plot-boundary-source close python scripts/plot_converging_triangles.py --plot-boundary-source close --show-high-low
This commit is contained in:
parent
24652b5790
commit
e5788b8811
@ -10,6 +10,7 @@
|
|||||||
python scripts/pipeline_converging_triangle.py
|
python scripts/pipeline_converging_triangle.py
|
||||||
python scripts/pipeline_converging_triangle.py --date 20260120
|
python scripts/pipeline_converging_triangle.py --date 20260120
|
||||||
python scripts/pipeline_converging_triangle.py --show-details # 生成详情模式图片
|
python scripts/pipeline_converging_triangle.py --show-details # 生成详情模式图片
|
||||||
|
python scripts/pipeline_converging_triangle.py --plot-boundary-source close # 使用收盘价拟合边界线
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
@ -91,6 +92,12 @@ def main() -> None:
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
help="运行前清空outputs文件夹",
|
help="运行前清空outputs文件夹",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--plot-boundary-source",
|
||||||
|
choices=["hl", "close"],
|
||||||
|
default="hl",
|
||||||
|
help="绘图时边界线拟合数据源: hl=高低价, close=收盘价(不影响检测)",
|
||||||
|
)
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
pipeline_start = time.time()
|
pipeline_start = time.time()
|
||||||
@ -109,6 +116,8 @@ def main() -> None:
|
|||||||
print(f"图表范围: 所有108只股票(包括不满足条件的)")
|
print(f"图表范围: 所有108只股票(包括不满足条件的)")
|
||||||
else:
|
else:
|
||||||
print(f"图表范围: 仅满足收敛三角形条件的股票")
|
print(f"图表范围: 仅满足收敛三角形条件的股票")
|
||||||
|
boundary_source_name = "收盘价" if args.plot_boundary_source == "close" else "高低价"
|
||||||
|
print(f"边界线拟合数据源: {boundary_source_name}")
|
||||||
print("=" * 80)
|
print("=" * 80)
|
||||||
|
|
||||||
# ========================================================================
|
# ========================================================================
|
||||||
@ -199,6 +208,8 @@ def main() -> None:
|
|||||||
cmd_args.append("--show-details")
|
cmd_args.append("--show-details")
|
||||||
if args.all_stocks:
|
if args.all_stocks:
|
||||||
cmd_args.append("--all-stocks")
|
cmd_args.append("--all-stocks")
|
||||||
|
if args.plot_boundary_source:
|
||||||
|
cmd_args.extend(["--plot-boundary-source", args.plot_boundary_source])
|
||||||
|
|
||||||
sys.argv = cmd_args
|
sys.argv = cmd_args
|
||||||
|
|
||||||
|
|||||||
@ -33,6 +33,7 @@ sys.path.append(os.path.join(os.path.dirname(__file__), "..", "src"))
|
|||||||
|
|
||||||
from converging_triangle import (
|
from converging_triangle import (
|
||||||
ConvergingTriangleParams,
|
ConvergingTriangleParams,
|
||||||
|
calc_fitting_adherence,
|
||||||
detect_converging_triangle,
|
detect_converging_triangle,
|
||||||
fit_pivot_line_dispatch,
|
fit_pivot_line_dispatch,
|
||||||
line_y,
|
line_y,
|
||||||
@ -120,6 +121,8 @@ def plot_triangle(
|
|||||||
display_window: int = 500, # 显示窗口大小
|
display_window: int = 500, # 显示窗口大小
|
||||||
show_details: bool = False, # 是否显示详细调试信息
|
show_details: bool = False, # 是否显示详细调试信息
|
||||||
force_plot: bool = False, # 强制绘图(即使不满足三角形条件)
|
force_plot: bool = False, # 强制绘图(即使不满足三角形条件)
|
||||||
|
plot_boundary_source: str = "hl", # 边界线拟合数据源: "hl" | "close"
|
||||||
|
show_high_low: bool = False, # 是否显示日内高低价范围
|
||||||
) -> None:
|
) -> None:
|
||||||
"""绘制单只股票的收敛三角形图"""
|
"""绘制单只股票的收敛三角形图"""
|
||||||
|
|
||||||
@ -183,6 +186,7 @@ def plot_triangle(
|
|||||||
# ========================================================================
|
# ========================================================================
|
||||||
result = None
|
result = None
|
||||||
has_triangle = False
|
has_triangle = False
|
||||||
|
fitting_adherence_plot = 0.0 # 初始化贴合度
|
||||||
|
|
||||||
if has_enough_data:
|
if has_enough_data:
|
||||||
result = detect_converging_triangle(
|
result = detect_converging_triangle(
|
||||||
@ -231,21 +235,32 @@ def plot_triangle(
|
|||||||
|
|
||||||
# 使用枢轴点连线法拟合边界线(与检测算法一致)
|
# 使用枢轴点连线法拟合边界线(与检测算法一致)
|
||||||
# 注意:绘图用的是检测窗口数据,因此 window_start=0, window_end=n-1
|
# 注意:绘图用的是检测窗口数据,因此 window_start=0, window_end=n-1
|
||||||
|
if plot_boundary_source == "close":
|
||||||
|
upper_fit_values = close_win[ph_idx]
|
||||||
|
lower_fit_values = close_win[pl_idx]
|
||||||
|
upper_all_prices = close_win
|
||||||
|
lower_all_prices = close_win
|
||||||
|
else:
|
||||||
|
upper_fit_values = high_win[ph_idx]
|
||||||
|
lower_fit_values = low_win[pl_idx]
|
||||||
|
upper_all_prices = high_win
|
||||||
|
lower_all_prices = low_win
|
||||||
|
|
||||||
a_u, b_u, selected_ph = fit_pivot_line_dispatch(
|
a_u, b_u, selected_ph = fit_pivot_line_dispatch(
|
||||||
pivot_indices=ph_idx,
|
pivot_indices=ph_idx,
|
||||||
pivot_values=high_win[ph_idx],
|
pivot_values=upper_fit_values,
|
||||||
mode="upper",
|
mode="upper",
|
||||||
method=params.fitting_method,
|
method=params.fitting_method,
|
||||||
all_prices=high_win,
|
all_prices=upper_all_prices,
|
||||||
window_start=0,
|
window_start=0,
|
||||||
window_end=n - 1,
|
window_end=n - 1,
|
||||||
)
|
)
|
||||||
a_l, b_l, selected_pl = fit_pivot_line_dispatch(
|
a_l, b_l, selected_pl = fit_pivot_line_dispatch(
|
||||||
pivot_indices=pl_idx,
|
pivot_indices=pl_idx,
|
||||||
pivot_values=low_win[pl_idx],
|
pivot_values=lower_fit_values,
|
||||||
mode="lower",
|
mode="lower",
|
||||||
method=params.fitting_method,
|
method=params.fitting_method,
|
||||||
all_prices=low_win,
|
all_prices=lower_all_prices,
|
||||||
window_start=0,
|
window_start=0,
|
||||||
window_end=n - 1,
|
window_end=n - 1,
|
||||||
)
|
)
|
||||||
@ -267,17 +282,50 @@ def plot_triangle(
|
|||||||
# 三角形检测窗口的日期范围(用于标题)
|
# 三角形检测窗口的日期范围(用于标题)
|
||||||
detect_dates = dates[valid_indices[detect_start:valid_end + 1]]
|
detect_dates = dates[valid_indices[detect_start:valid_end + 1]]
|
||||||
|
|
||||||
|
# 如果使用收盘价拟合,重新计算贴合度(基于实际拟合线)
|
||||||
|
if plot_boundary_source == "close" and len(selected_ph) > 0 and len(selected_pl) > 0:
|
||||||
|
# 使用收盘价重新计算贴合度
|
||||||
|
adherence_upper_close = calc_fitting_adherence(
|
||||||
|
pivot_indices=selected_ph_pos.astype(float),
|
||||||
|
pivot_values=close_win[selected_ph_pos],
|
||||||
|
slope=a_u,
|
||||||
|
intercept=b_u,
|
||||||
|
)
|
||||||
|
adherence_lower_close = calc_fitting_adherence(
|
||||||
|
pivot_indices=selected_pl_pos.astype(float),
|
||||||
|
pivot_values=close_win[selected_pl_pos],
|
||||||
|
slope=a_l,
|
||||||
|
intercept=b_l,
|
||||||
|
)
|
||||||
|
fitting_adherence_plot = (adherence_upper_close + adherence_lower_close) / 2.0
|
||||||
|
else:
|
||||||
|
# 使用检测算法计算的贴合度
|
||||||
|
fitting_adherence_plot = result.fitting_score if result else 0.0
|
||||||
|
else:
|
||||||
|
# 无三角形时,贴合度为0
|
||||||
|
fitting_adherence_plot = 0.0
|
||||||
|
|
||||||
# 创建图表
|
# 创建图表
|
||||||
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 8),
|
fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(14, 8),
|
||||||
gridspec_kw={'height_ratios': [3, 1]})
|
gridspec_kw={'height_ratios': [3, 1]})
|
||||||
|
|
||||||
# 主图:价格和趋势线(使用显示窗口数据)
|
# 主图:价格和趋势线(使用显示窗口数据)
|
||||||
ax1.plot(x_display, display_close, linewidth=1.5, label='收盘价', color='black', alpha=0.7)
|
ax1.plot(x_display, display_close, linewidth=1.5, label='收盘价', color='black', alpha=0.7)
|
||||||
|
if show_high_low:
|
||||||
|
ax1.fill_between(
|
||||||
|
x_display,
|
||||||
|
display_low,
|
||||||
|
display_high,
|
||||||
|
color='gray',
|
||||||
|
alpha=0.12,
|
||||||
|
label='日内高低范围',
|
||||||
|
)
|
||||||
|
|
||||||
# 只在有三角形时绘制趋势线
|
# 只在有三角形时绘制趋势线
|
||||||
if has_triangle and has_enough_data:
|
if has_triangle and has_enough_data:
|
||||||
ax1.plot(xw_in_display, upper_line, linewidth=2, label='上沿', color='red', linestyle='--')
|
boundary_label = "收盘价" if plot_boundary_source == "close" else "高低价"
|
||||||
ax1.plot(xw_in_display, lower_line, linewidth=2, label='下沿', color='green', linestyle='--')
|
ax1.plot(xw_in_display, upper_line, linewidth=2, label=f'上沿({boundary_label})', color='red', linestyle='--')
|
||||||
|
ax1.plot(xw_in_display, lower_line, linewidth=2, label=f'下沿({boundary_label})', color='green', linestyle='--')
|
||||||
|
|
||||||
ax1.axvline(len(display_close) - 1, color='gray', linestyle=':', linewidth=1, alpha=0.5)
|
ax1.axvline(len(display_close) - 1, color='gray', linestyle=':', linewidth=1, alpha=0.5)
|
||||||
|
|
||||||
@ -285,11 +333,23 @@ def plot_triangle(
|
|||||||
# 详细模式:显示拟合点(仅在 show_details=True 且有三角形时)
|
# 详细模式:显示拟合点(仅在 show_details=True 且有三角形时)
|
||||||
# ========================================================================
|
# ========================================================================
|
||||||
if show_details and has_triangle and has_enough_data:
|
if show_details and has_triangle and has_enough_data:
|
||||||
|
# 根据数据源选择枢轴点的Y坐标
|
||||||
|
if plot_boundary_source == "close":
|
||||||
|
ph_pivot_y = close_win[ph_idx]
|
||||||
|
pl_pivot_y = close_win[pl_idx]
|
||||||
|
selected_ph_y = close_win[selected_ph_pos] if len(selected_ph_pos) > 0 else np.array([])
|
||||||
|
selected_pl_y = close_win[selected_pl_pos] if len(selected_pl_pos) > 0 else np.array([])
|
||||||
|
else:
|
||||||
|
ph_pivot_y = high_win[ph_idx]
|
||||||
|
pl_pivot_y = low_win[pl_idx]
|
||||||
|
selected_ph_y = high_win[selected_ph_pos] if len(selected_ph_pos) > 0 else np.array([])
|
||||||
|
selected_pl_y = low_win[selected_pl_pos] if len(selected_pl_pos) > 0 else np.array([])
|
||||||
|
|
||||||
# 标注所有枢轴点(用于查看拐点分布)
|
# 标注所有枢轴点(用于查看拐点分布)
|
||||||
if len(ph_display_idx) > 0:
|
if len(ph_display_idx) > 0:
|
||||||
ax1.scatter(
|
ax1.scatter(
|
||||||
ph_display_idx,
|
ph_display_idx,
|
||||||
high_win[ph_idx],
|
ph_pivot_y,
|
||||||
marker='x',
|
marker='x',
|
||||||
s=60,
|
s=60,
|
||||||
color='red',
|
color='red',
|
||||||
@ -300,7 +360,7 @@ def plot_triangle(
|
|||||||
if len(pl_display_idx) > 0:
|
if len(pl_display_idx) > 0:
|
||||||
ax1.scatter(
|
ax1.scatter(
|
||||||
pl_display_idx,
|
pl_display_idx,
|
||||||
low_win[pl_idx],
|
pl_pivot_y,
|
||||||
marker='x',
|
marker='x',
|
||||||
s=60,
|
s=60,
|
||||||
color='green',
|
color='green',
|
||||||
@ -313,7 +373,7 @@ def plot_triangle(
|
|||||||
if len(selected_ph_display) >= 2:
|
if len(selected_ph_display) >= 2:
|
||||||
ax1.scatter(
|
ax1.scatter(
|
||||||
selected_ph_display,
|
selected_ph_display,
|
||||||
high_win[selected_ph_pos],
|
selected_ph_y,
|
||||||
marker='o',
|
marker='o',
|
||||||
s=120,
|
s=120,
|
||||||
facecolors='none',
|
facecolors='none',
|
||||||
@ -325,7 +385,7 @@ def plot_triangle(
|
|||||||
if len(selected_pl_display) >= 2:
|
if len(selected_pl_display) >= 2:
|
||||||
ax1.scatter(
|
ax1.scatter(
|
||||||
selected_pl_display,
|
selected_pl_display,
|
||||||
low_win[selected_pl_pos],
|
selected_pl_y,
|
||||||
marker='o',
|
marker='o',
|
||||||
s=120,
|
s=120,
|
||||||
facecolors='none',
|
facecolors='none',
|
||||||
@ -356,6 +416,14 @@ def plot_triangle(
|
|||||||
else:
|
else:
|
||||||
utilization_penalty = 1.0
|
utilization_penalty = 1.0
|
||||||
|
|
||||||
|
# 选择显示的贴合度:如果使用收盘价拟合,显示重新计算的贴合度
|
||||||
|
if plot_boundary_source == "close" and has_triangle and has_enough_data:
|
||||||
|
display_fitting_score = fitting_adherence_plot
|
||||||
|
fitting_note = f"拟合贴合度(收盘价): {display_fitting_score:.3f}"
|
||||||
|
else:
|
||||||
|
display_fitting_score = result.fitting_score
|
||||||
|
fitting_note = f"拟合贴合度: {display_fitting_score:.3f}"
|
||||||
|
|
||||||
ax1.set_title(
|
ax1.set_title(
|
||||||
f"{stock_code} {stock_name} - 收敛三角形 (检测窗口: {detect_dates[0]} ~ {detect_dates[-1]})\n"
|
f"{stock_code} {stock_name} - 收敛三角形 (检测窗口: {detect_dates[0]} ~ {detect_dates[-1]})\n"
|
||||||
f"显示范围: {display_dates[0]} ~ {display_dates[-1]} ({len(display_dates)}个交易日) "
|
f"显示范围: {display_dates[0]} ~ {display_dates[-1]} ({len(display_dates)}个交易日) "
|
||||||
@ -364,7 +432,7 @@ def plot_triangle(
|
|||||||
f"放量确认: {'是' if result.volume_confirmed else '否' if result.volume_confirmed is False else '-'}\n"
|
f"放量确认: {'是' if result.volume_confirmed else '否' if result.volume_confirmed is False else '-'}\n"
|
||||||
f"强度分: {strength:.3f} "
|
f"强度分: {strength:.3f} "
|
||||||
f"(价格: {price_score:.3f}×50% + 收敛: {result.convergence_score:.3f}×15% + "
|
f"(价格: {price_score:.3f}×50% + 收敛: {result.convergence_score:.3f}×15% + "
|
||||||
f"成交量: {result.volume_score:.3f}×10% + 拟合贴合度: {result.fitting_score:.3f}×10% + "
|
f"成交量: {result.volume_score:.3f}×10% + {fitting_note}×10% + "
|
||||||
f"边界利用率: {boundary_util:.3f}×15%) × 利用率惩罚: {utilization_penalty:.2f}",
|
f"边界利用率: {boundary_util:.3f}×15%) × 利用率惩罚: {utilization_penalty:.2f}",
|
||||||
fontsize=11, pad=10
|
fontsize=11, pad=10
|
||||||
)
|
)
|
||||||
@ -424,6 +492,17 @@ def main() -> None:
|
|||||||
action="store_true",
|
action="store_true",
|
||||||
help="显示详细调试信息(枢轴点、拟合点、分段线等)",
|
help="显示详细调试信息(枢轴点、拟合点、分段线等)",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--plot-boundary-source",
|
||||||
|
choices=["hl", "close"],
|
||||||
|
default="hl",
|
||||||
|
help="绘图时边界线拟合数据源: hl=高低价, close=收盘价(不影响检测)",
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--show-high-low",
|
||||||
|
action="store_true",
|
||||||
|
help="显示日内高低价范围(仅影响图形展示)",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--all-stocks",
|
"--all-stocks",
|
||||||
action="store_true",
|
action="store_true",
|
||||||
@ -439,6 +518,8 @@ def main() -> None:
|
|||||||
# 确定是否显示详细信息(命令行参数优先)
|
# 确定是否显示详细信息(命令行参数优先)
|
||||||
show_details = args.show_details if hasattr(args, 'show_details') else SHOW_CHART_DETAILS
|
show_details = args.show_details if hasattr(args, 'show_details') else SHOW_CHART_DETAILS
|
||||||
all_stocks = args.all_stocks if hasattr(args, 'all_stocks') else False
|
all_stocks = args.all_stocks if hasattr(args, 'all_stocks') else False
|
||||||
|
plot_boundary_source = args.plot_boundary_source if hasattr(args, 'plot_boundary_source') else "hl"
|
||||||
|
show_high_low = args.show_high_low if hasattr(args, 'show_high_low') else False
|
||||||
|
|
||||||
print("=" * 70)
|
print("=" * 70)
|
||||||
print("收敛三角形图表生成")
|
print("收敛三角形图表生成")
|
||||||
@ -565,6 +646,8 @@ def main() -> None:
|
|||||||
display_window=DISPLAY_WINDOW, # 从配置文件读取
|
display_window=DISPLAY_WINDOW, # 从配置文件读取
|
||||||
show_details=show_details, # 传递详细模式参数
|
show_details=show_details, # 传递详细模式参数
|
||||||
force_plot=all_stocks, # 在all_stocks模式下强制绘图
|
force_plot=all_stocks, # 在all_stocks模式下强制绘图
|
||||||
|
plot_boundary_source=plot_boundary_source,
|
||||||
|
show_high_low=show_high_low,
|
||||||
)
|
)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
print(f" [错误] {stock_code} {stock_name}: {e}")
|
print(f" [错误] {stock_code} {stock_name}: {e}")
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user