Add initial implementation of converging triangle detection algorithm and related documentation
- Created README.md and USAGE.md for project overview and usage instructions. - Added core algorithm in src/converging_triangle.py for batch processing of stock data. - Introduced data files (open.pkl, high.pkl, low.pkl, close.pkl, volume.pkl) for OHLCV data. - Developed output documentation for results and breakout strength calculations. - Implemented scripts for running the detection and generating reports. - Added SVG visualizations and markdown documentation for algorithm details and usage examples.
This commit is contained in:
parent
f04a22c3d8
commit
543572667b
54
README.md
54
README.md
@ -0,0 +1,54 @@
|
||||
# Technical Patterns Lab
|
||||
|
||||
技术形态识别研究与实验仓库,聚焦收敛三角形等形态的批量检测、突破强度计算与可视化验证。
|
||||
|
||||
## 核心功能
|
||||
|
||||
- **收敛三角形检测**:基于枢轴点 + 边界线拟合 + 几何约束
|
||||
- **批量滚动计算**:支持多股票 × 多交易日的历史回测
|
||||
- **突破强度评分**:0~1 连续分数,综合价格、收敛度、成交量
|
||||
|
||||
## 目录结构
|
||||
|
||||
```
|
||||
technical-patterns-lab/
|
||||
├── src/ # 核心算法
|
||||
│ ├── converging_triangle.py # 收敛三角形检测
|
||||
│ └── archive/ # 归档代码
|
||||
├── scripts/ # 运行脚本
|
||||
│ ├── run_converging_triangle.py # 批量检测(主脚本)
|
||||
│ └── run_sym_triangle_pkl.py # 对称三角形检测
|
||||
├── data/ # 数据文件
|
||||
│ ├── open.pkl, high.pkl, low.pkl, close.pkl, volume.pkl
|
||||
├── outputs/ # 输出结果
|
||||
│ ├── converging_triangles/ # 收敛三角形结果
|
||||
│ └── sym_triangles/ # 对称三角形结果
|
||||
└── docs/ # 文档
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
```powershell
|
||||
# 1. 激活环境
|
||||
.\.venv\Scripts\Activate.ps1
|
||||
|
||||
# 2. 安装依赖(首次)
|
||||
pip install numpy pandas matplotlib
|
||||
|
||||
# 3. 运行批量检测
|
||||
python scripts/run_converging_triangle.py
|
||||
```
|
||||
|
||||
## 输出示例
|
||||
|
||||
```
|
||||
检测结果: 1837 个有效三角形
|
||||
- 向上突破: 258 次
|
||||
- 向下突破: 251 次
|
||||
- 高强度突破: 375 个 (strength > 0.3)
|
||||
```
|
||||
|
||||
## 相关文档
|
||||
|
||||
- `docs/突破强度计算方法.md` - 突破强度的计算逻辑
|
||||
- `docs/对称三角形识别-Python实现.md` - 算法原理说明
|
||||
84
USAGE.md
Normal file
84
USAGE.md
Normal file
@ -0,0 +1,84 @@
|
||||
# 使用说明
|
||||
|
||||
## 快速启动
|
||||
|
||||
```powershell
|
||||
.\.venv\Scripts\Activate.ps1
|
||||
python scripts/run_converging_triangle.py
|
||||
```
|
||||
|
||||
## 1. 创建与激活虚拟环境
|
||||
|
||||
```powershell
|
||||
# 创建环境(首次)
|
||||
python -m venv .venv
|
||||
|
||||
# 激活环境
|
||||
.\.venv\Scripts\Activate.ps1
|
||||
```
|
||||
|
||||
## 2. 安装依赖
|
||||
|
||||
```powershell
|
||||
pip install numpy pandas matplotlib
|
||||
```
|
||||
|
||||
## 3. 运行脚本
|
||||
|
||||
### 收敛三角形批量检测(主脚本)
|
||||
|
||||
```powershell
|
||||
python scripts/run_converging_triangle.py
|
||||
```
|
||||
|
||||
输出:
|
||||
- `outputs/converging_triangles/all_results.csv` - 全部检测结果
|
||||
- `outputs/converging_triangles/strong_breakout_up.csv` - 高强度向上突破
|
||||
- `outputs/converging_triangles/strong_breakout_down.csv` - 高强度向下突破
|
||||
|
||||
### 对称三角形检测(单点检测)
|
||||
|
||||
```powershell
|
||||
python scripts/run_sym_triangle_pkl.py
|
||||
```
|
||||
|
||||
输出:
|
||||
- `outputs/sym_triangles/*.png` - 各股票图表
|
||||
- `outputs/sym_triangles/summary.csv` - 汇总表
|
||||
|
||||
## 4. 参数调整
|
||||
|
||||
编辑 `scripts/run_converging_triangle.py` 顶部的参数区:
|
||||
|
||||
```python
|
||||
PARAMS = ConvergingTriangleParams(
|
||||
window=400, # 分析窗口大小
|
||||
pivot_k=20, # 枢轴点检测窗口
|
||||
shrink_ratio=0.8, # 收敛比阈值
|
||||
# ...
|
||||
)
|
||||
|
||||
RECENT_DAYS = 500 # 计算最近 N 天(None=全部历史)
|
||||
ONLY_VALID = True # 只输出有效三角形
|
||||
```
|
||||
|
||||
## 5. 数据格式
|
||||
|
||||
数据文件位于 `data/` 目录,格式为 pkl:
|
||||
|
||||
```python
|
||||
{
|
||||
'mtx': ndarray (n_stocks, n_days), # 数据矩阵
|
||||
'dtes': ndarray (n_days,), # 日期 (20050104)
|
||||
'tkrs': ndarray (n_stocks,), # 股票代码 (SH600000)
|
||||
'tkrs_name': ndarray (n_stocks,), # 股票名称
|
||||
}
|
||||
```
|
||||
|
||||
## 6. 备注
|
||||
|
||||
- 关闭环境:`deactivate`
|
||||
- 权限问题(PowerShell):
|
||||
```powershell
|
||||
Set-ExecutionPolicy -ExecutionPolicy RemoteSigned -Scope CurrentUser
|
||||
```
|
||||
BIN
data/close.pkl
Normal file
BIN
data/close.pkl
Normal file
Binary file not shown.
BIN
data/high.pkl
Normal file
BIN
data/high.pkl
Normal file
Binary file not shown.
BIN
data/low.pkl
Normal file
BIN
data/low.pkl
Normal file
Binary file not shown.
BIN
data/open.pkl
Normal file
BIN
data/open.pkl
Normal file
Binary file not shown.
BIN
data/volume.pkl
Normal file
BIN
data/volume.pkl
Normal file
Binary file not shown.
299
docs/2026-01-16_converging_triangle_algorithm.md
Normal file
299
docs/2026-01-16_converging_triangle_algorithm.md
Normal file
@ -0,0 +1,299 @@
|
||||
# 收敛三角形算法实现日志
|
||||
|
||||
**日期**: 2026-01-16
|
||||
**任务**: 基于对称三角形算法,开发支持批量滚动计算的收敛三角形检测算法
|
||||
|
||||
---
|
||||
|
||||
## 1. 需求概述
|
||||
|
||||
- 函数名: `detect_converging_triangle` (收敛三角形)
|
||||
- 参数和返回值使用对象结构
|
||||
- 支持二维矩阵数据处理 (n_stocks × n_days)
|
||||
- 历史滚动计算: 每个交易日往过去看 window 天,不使用未来数据
|
||||
- 返回突破强度指标 (0~1 连续分数)
|
||||
|
||||
---
|
||||
|
||||
## 2. 新建文件
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `src/converging_triangle.py` | 核心算法模块 |
|
||||
| `scripts/run_converging_triangle.py` | 批量运行脚本 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 参数对象设计
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class ConvergingTriangleParams:
|
||||
# 窗口设置
|
||||
window: int = 400
|
||||
|
||||
# 枢轴点检测
|
||||
pivot_k: int = 20
|
||||
|
||||
# 边界线拟合
|
||||
boundary_n_segments: int = 2
|
||||
boundary_source: str = "full" # "full" | "pivots"
|
||||
|
||||
# 斜率约束
|
||||
upper_slope_max: float = 0.10
|
||||
lower_slope_min: float = -0.10
|
||||
|
||||
# 触碰判定
|
||||
touch_tol: float = 0.10
|
||||
touch_loss_max: float = 0.10
|
||||
|
||||
# 收敛要求
|
||||
shrink_ratio: float = 0.8
|
||||
|
||||
# 突破判定
|
||||
break_tol: float = 0.001
|
||||
vol_window: int = 20
|
||||
vol_k: float = 1.3
|
||||
false_break_m: int = 5
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 返回对象设计
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class ConvergingTriangleResult:
|
||||
# 基础标识
|
||||
stock_idx: int
|
||||
date_idx: int
|
||||
is_valid: bool # 是否识别到有效三角形
|
||||
|
||||
# 突破强度 (0~1 连续分数)
|
||||
breakout_strength_up: float # 向上突破强度
|
||||
breakout_strength_down: float # 向下突破强度
|
||||
|
||||
# 几何属性
|
||||
upper_slope: float
|
||||
lower_slope: float
|
||||
width_ratio: float
|
||||
touches_upper: int
|
||||
touches_lower: int
|
||||
apex_x: float
|
||||
|
||||
# 突破状态
|
||||
breakout_dir: str # "up" | "down" | "none"
|
||||
volume_confirmed: Optional[bool]
|
||||
false_breakout: Optional[bool]
|
||||
|
||||
# 窗口范围
|
||||
window_start: int
|
||||
window_end: int
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 突破强度计算公式
|
||||
|
||||
```python
|
||||
def calc_breakout_strength(
|
||||
close: float,
|
||||
upper_line: float,
|
||||
lower_line: float,
|
||||
volume_ratio: float,
|
||||
width_ratio: float,
|
||||
) -> Tuple[float, float]:
|
||||
"""
|
||||
计算向上/向下突破强度 (0~1)
|
||||
|
||||
综合考虑:
|
||||
- 价格突破幅度 (close 相对于上/下沿的距离)
|
||||
- 成交量放大倍数
|
||||
- 收敛程度 (width_ratio 越小越强)
|
||||
"""
|
||||
# 价格突破分数
|
||||
price_up = max(0, (close - upper_line) / upper_line)
|
||||
price_down = max(0, (lower_line - close) / lower_line)
|
||||
|
||||
# 收敛加成 (越收敛, 突破越有效)
|
||||
convergence_bonus = max(0, 1 - width_ratio)
|
||||
|
||||
# 成交量加成 (放量2倍=满分)
|
||||
vol_bonus = min(1, max(0, volume_ratio - 1))
|
||||
|
||||
# 加权合成
|
||||
strength_up = min(1.0, price_up * 5 * (1 + convergence_bonus * 0.5) * (1 + vol_bonus * 0.5))
|
||||
strength_down = min(1.0, price_down * 5 * (1 + convergence_bonus * 0.5) * (1 + vol_bonus * 0.5))
|
||||
|
||||
return strength_up, strength_down
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 核心函数签名
|
||||
|
||||
### 单点检测
|
||||
|
||||
```python
|
||||
def detect_converging_triangle(
|
||||
high: np.ndarray,
|
||||
low: np.ndarray,
|
||||
close: np.ndarray,
|
||||
volume: Optional[np.ndarray],
|
||||
params: ConvergingTriangleParams,
|
||||
stock_idx: int = 0,
|
||||
date_idx: int = 0,
|
||||
) -> ConvergingTriangleResult:
|
||||
```
|
||||
|
||||
### 批量滚动检测
|
||||
|
||||
```python
|
||||
def detect_converging_triangle_batch(
|
||||
open_mtx: np.ndarray, # (n_stocks, n_days)
|
||||
high_mtx: np.ndarray,
|
||||
low_mtx: np.ndarray,
|
||||
close_mtx: np.ndarray,
|
||||
volume_mtx: np.ndarray,
|
||||
params: ConvergingTriangleParams,
|
||||
start_day: Optional[int] = None,
|
||||
end_day: Optional[int] = None,
|
||||
only_valid: bool = False,
|
||||
verbose: bool = False,
|
||||
) -> pd.DataFrame:
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 滚动计算逻辑
|
||||
|
||||
```
|
||||
遍历 stock_idx: 0..107
|
||||
遍历 date_idx: start_day..end_day
|
||||
提取窗口数据 ohlcv[:, date_idx-window+1 : date_idx+1]
|
||||
如果数据足够 (date_idx >= window):
|
||||
调用 detect_converging_triangle()
|
||||
计算突破强度
|
||||
添加到结果列表
|
||||
转换为 DataFrame 返回
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 测试运行结果
|
||||
|
||||
**运行命令**:
|
||||
```powershell
|
||||
cd D:\project\technical-patterns-lab
|
||||
.\.venv\Scripts\Activate.ps1
|
||||
python scripts/run_converging_triangle.py
|
||||
```
|
||||
|
||||
**检测范围**:
|
||||
- 股票数量: 108
|
||||
- 计算天数: 最近 500 天
|
||||
- 总检测点: 54,000
|
||||
|
||||
**检测结果**:
|
||||
- 有效三角形: 2,912 个 (5.4%)
|
||||
- 向上突破: 545 次
|
||||
- 向下突破: 273 次
|
||||
- 未突破: 2,094 次
|
||||
|
||||
**突破强度统计** (有效三角形):
|
||||
- 向上强度: mean=0.1315, max=1.0000
|
||||
- 向下强度: mean=0.0473, max=1.0000
|
||||
|
||||
**高强度突破** (strength > 0.3):
|
||||
- 向上: 444 个
|
||||
- 向下: 174 个
|
||||
|
||||
---
|
||||
|
||||
## 9. 输出文件
|
||||
|
||||
```
|
||||
outputs/converging_triangles/
|
||||
├── all_results.csv # 全部有效记录 (2,912 行)
|
||||
├── strong_breakout_up.csv # 高强度向上突破 (444 行)
|
||||
└── strong_breakout_down.csv # 高强度向下突破 (174 行)
|
||||
```
|
||||
|
||||
### CSV 字段说明
|
||||
|
||||
| 字段 | 说明 |
|
||||
|------|------|
|
||||
| stock_idx | 股票索引 (0~107) |
|
||||
| date_idx | 日期索引 |
|
||||
| is_valid | 是否识别到有效三角形 |
|
||||
| breakout_strength_up | 向上突破强度 (0~1) |
|
||||
| breakout_strength_down | 向下突破强度 (0~1) |
|
||||
| upper_slope | 上沿斜率 |
|
||||
| lower_slope | 下沿斜率 |
|
||||
| width_ratio | 收敛比 (末端宽度/起始宽度) |
|
||||
| touches_upper | 上沿触碰次数 |
|
||||
| touches_lower | 下沿触碰次数 |
|
||||
| apex_x | 顶点位置 |
|
||||
| breakout_dir | 突破方向 (up/down/none) |
|
||||
| volume_confirmed | 成交量确认 |
|
||||
| false_breakout | 假突破标记 |
|
||||
| window_start | 窗口起始位置 |
|
||||
| window_end | 窗口结束位置 |
|
||||
|
||||
---
|
||||
|
||||
## 10. 使用说明
|
||||
|
||||
### 修改计算范围
|
||||
|
||||
编辑 `scripts/run_converging_triangle.py`:
|
||||
|
||||
```python
|
||||
# 只算最近 500 天 (默认)
|
||||
RECENT_DAYS = 500
|
||||
|
||||
# 计算全部历史 (耗时较长)
|
||||
RECENT_DAYS = None
|
||||
|
||||
# 只算最近 100 天 (快速测试)
|
||||
RECENT_DAYS = 100
|
||||
```
|
||||
|
||||
### 修改检测参数
|
||||
|
||||
```python
|
||||
PARAMS = ConvergingTriangleParams(
|
||||
window=400, # 调整窗口大小
|
||||
shrink_ratio=0.8, # 调整收敛要求
|
||||
upper_slope_max=0.10, # 调整斜率容差
|
||||
# ...
|
||||
)
|
||||
```
|
||||
|
||||
### 只输出有效记录
|
||||
|
||||
```python
|
||||
ONLY_VALID = True # 只输出识别到三角形的记录
|
||||
ONLY_VALID = False # 输出所有检测点
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 11. 相关文件
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `src/converging_triangle.py` | 收敛三角形核心算法 |
|
||||
| `src/sym_triangle.py` | 对称三角形算法 (原有) |
|
||||
| `scripts/run_converging_triangle.py` | 批量运行脚本 |
|
||||
| `scripts/run_sym_triangle_pkl.py` | 对称三角形脚本 (原有) |
|
||||
|
||||
---
|
||||
|
||||
## 12. 待优化项
|
||||
|
||||
- [ ] 性能优化: 向量化 pivot 检测,减少循环
|
||||
- [ ] 并行计算: 多进程处理多只股票
|
||||
- [ ] 关联股票代码: 结合 `data/map/stock_name.json` 显示真实代码
|
||||
- [ ] 可视化: 为高强度突破生成图表
|
||||
- [ ] 回测验证: 检验突破后 N 天的收益率
|
||||
151
docs/2026-01-16_pkl_batch_detection.md
Normal file
151
docs/2026-01-16_pkl_batch_detection.md
Normal file
@ -0,0 +1,151 @@
|
||||
# 对称三角形批量检测日志
|
||||
|
||||
**日期**: 2026-01-16
|
||||
**任务**: 从 pkl 文件批量检测对称三角形
|
||||
|
||||
---
|
||||
|
||||
## 1. 数据源分析
|
||||
|
||||
### pkl 文件结构
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `open.pkl.pkl` | 开盘价矩阵 |
|
||||
| `high.pkl.pkl` | 最高价矩阵 |
|
||||
| `low.pkl.pkl` | 最低价矩阵 |
|
||||
| `close.pkl.pkl` | 收盘价矩阵 |
|
||||
| `volume.pkl.pkl` | 成交量矩阵 |
|
||||
|
||||
### pkl 内部数据结构
|
||||
|
||||
```python
|
||||
{
|
||||
'mtx_save': ndarray (108, 5113), # 主数据矩阵: 108只股票 × 5113个交易日
|
||||
'tot_c': ndarray (108,), # 列索引 [0,1,2,...,107]
|
||||
'tot_r': ndarray (5113,), # 有效行索引(日期序号,非连续)
|
||||
'shape_c': 108, # 原始列数
|
||||
'shape_r': 7698, # 原始总行数(含空行)
|
||||
}
|
||||
```
|
||||
|
||||
### 加载方式
|
||||
|
||||
两种加载方式对比:
|
||||
|
||||
| 方式 | 返回值 | 适用场景 |
|
||||
|------|--------|----------|
|
||||
| 原生 `pickle.load` | `dict` | 需要完整元数据 |
|
||||
| `g.load_pkl` | `ndarray` | dataServer 环境内,直接使用矩阵 |
|
||||
|
||||
**本次采用**: 原生 `pickle.load` + 空壳模块绕过 `model` 依赖
|
||||
|
||||
```python
|
||||
class FakeModule:
|
||||
ndarray = np.ndarray
|
||||
|
||||
sys.modules['model'] = FakeModule()
|
||||
sys.modules['model.index_info'] = FakeModule()
|
||||
|
||||
with open(pkl_path, 'rb') as f:
|
||||
data = pickle.load(f)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 检测参数
|
||||
|
||||
```python
|
||||
WINDOW = 400 # 分析窗口大小
|
||||
PIVOT_K = 20 # 枢轴点检测窗口
|
||||
BOUNDARY_N_SEGMENTS = 2 # 边界线分段数
|
||||
BOUNDARY_SOURCE = "full" # 使用全量 high/low 拟合
|
||||
UPPER_SLOPE_MAX = 0.10 # 上沿斜率最大值
|
||||
LOWER_SLOPE_MIN = -0.10 # 下沿斜率最小值
|
||||
TOUCH_TOL = 0.10 # 触碰容差
|
||||
TOUCH_LOSS_MAX = 0.10 # 损失函数阈值
|
||||
SHRINK_RATIO = 0.8 # 收敛比阈值
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 运行结果
|
||||
|
||||
**扫描**: 108 只股票
|
||||
**识别成功**: 10 只 (9.26%)
|
||||
|
||||
### 识别结果明细
|
||||
|
||||
| 股票 ID | 上沿斜率 | 下沿斜率 | 收敛比 | 触碰(上/下) | 突破方向 |
|
||||
|---------|----------|----------|--------|-------------|----------|
|
||||
| stock_017 | +0.0025 | +0.0071 | 0.25 | 5/3 | down |
|
||||
| stock_019 | +0.0021 | +0.0056 | 0.63 | 5/4 | none |
|
||||
| stock_025 | +0.0009 | +0.0020 | 0.75 | 5/5 | none |
|
||||
| stock_031 | -0.0097 | -0.0042 | 0.64 | 3/4 | none |
|
||||
| stock_057 | -0.0002 | +0.0036 | 0.46 | 5/4 | none |
|
||||
| stock_063 | +0.0105 | +0.0329 | 0.19 | 7/6 | none |
|
||||
| stock_066 | +0.0089 | +0.0115 | 0.62 | 5/4 | none |
|
||||
| stock_079 | +0.0056 | +0.0081 | 0.55 | 4/5 | down |
|
||||
| stock_081 | -0.0029 | +0.0029 | 0.15 | 4/3 | down |
|
||||
| stock_095 | -0.0028 | +0.0025 | 0.38 | 5/3 | up |
|
||||
|
||||
### 突破统计
|
||||
|
||||
- **向上突破**: 1 只 (stock_095)
|
||||
- **向下突破**: 3 只 (stock_017, stock_079, stock_081)
|
||||
- **未突破**: 6 只
|
||||
|
||||
---
|
||||
|
||||
## 4. 输出文件
|
||||
|
||||
```
|
||||
outputs/sym_triangles/
|
||||
├── stock_017.png
|
||||
├── stock_019.png
|
||||
├── stock_025.png
|
||||
├── stock_031.png
|
||||
├── stock_057.png
|
||||
├── stock_063.png
|
||||
├── stock_066.png
|
||||
├── stock_079.png
|
||||
├── stock_081.png
|
||||
├── stock_095.png
|
||||
└── summary.csv
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 脚本使用方法
|
||||
|
||||
```powershell
|
||||
# 进入项目目录
|
||||
cd D:\project\technical-patterns-lab
|
||||
|
||||
# 激活虚拟环境
|
||||
.\.venv\Scripts\Activate.ps1
|
||||
|
||||
# 运行批量检测
|
||||
python scripts/run_sym_triangle_pkl.py
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 相关文件
|
||||
|
||||
| 文件 | 说明 |
|
||||
|------|------|
|
||||
| `scripts/run_sym_triangle_pkl.py` | 批量检测脚本 (pkl 数据源) |
|
||||
| `scripts/run_sym_triangle_json.py` | 单股检测脚本 (JSON 数据源) |
|
||||
| `src/sym_triangle.py` | 核心算法模块 |
|
||||
| `data/*.pkl.pkl` | OHLCV 数据文件 |
|
||||
| `data/map/stock_name.json` | 股票代码映射表 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 待优化项
|
||||
|
||||
- [ ] 关联 `stock_name.json` 显示真实股票代码/名称
|
||||
- [ ] 图表标题显示真实日期(当前显示索引)
|
||||
- [ ] 支持自定义股票筛选范围
|
||||
- [ ] 添加更多形态参数到汇总表
|
||||
17
docs/20260120-讨论下一步计划.md
Normal file
17
docs/20260120-讨论下一步计划.md
Normal file
@ -0,0 +1,17 @@
|
||||
函数化:收敛三角形
|
||||
|
||||
控制参数:控制三角形的
|
||||
目的参数:向上、向下
|
||||
|
||||
返回参数:可以给前端画图、突破强度指标(向上和向下的突破强度)
|
||||
|
||||
5000只个股-逐资产跑
|
||||
每个个股,历史上所有交易日,每一天都要跑 收敛三角形。历史每一个点往过去滚动区间计算。基于过去数据的筛选。
|
||||
|
||||
LLM:优化算法改迭代算法。
|
||||
|
||||
尽量复用已有函数。需要参考 data_server 的函数实现。
|
||||
|
||||
1. 搭环境 (最高价、最低价、收盘价、成交价 -> 测试集 @永亮 A股里面先给100个:万得全A,等距取100个数据,比如万得全A有5000个,每隔50个取一个个股数据),本地先跑。给我文件来处理。
|
||||
2. 落地函数(可以先取最近两年的窗口),筛选个股。
|
||||
3. 按照强度排序后,画曲线。
|
||||
41
docs/converging_triangles_outputs.md
Normal file
41
docs/converging_triangles_outputs.md
Normal file
@ -0,0 +1,41 @@
|
||||
# 收敛三角形输出说明
|
||||
|
||||
本文档说明 `outputs/converging_triangles` 目录下 CSV 的含义与字段。
|
||||
输出由 `scripts/run_converging_triangle.py` 生成。
|
||||
|
||||
## 输出文件
|
||||
|
||||
- `all_results.csv`:检测结果汇总。默认仅包含有效三角形(`ONLY_VALID=True`)。
|
||||
- `strong_breakout_up.csv`:强势向上突破子集(`breakout_strength_up > 0.3`)。
|
||||
- `strong_breakout_down.csv`:强势向下突破子集(`breakout_strength_down > 0.3`)。
|
||||
|
||||
## 字段说明
|
||||
|
||||
| 字段 | 说明 |
|
||||
| --- | --- |
|
||||
| `stock_idx` | 股票索引(在矩阵中的行号,0 起)。 |
|
||||
| `date_idx` | 日期索引(在日期数组中的列号,0 起)。 |
|
||||
| `is_valid` | 是否识别到有效收敛三角形。 |
|
||||
| `breakout_strength_up` | 向上突破强度(0~1)。 |
|
||||
| `breakout_strength_down` | 向下突破强度(0~1)。 |
|
||||
| `upper_slope` | 上沿趋势线斜率。 |
|
||||
| `lower_slope` | 下沿趋势线斜率。 |
|
||||
| `width_ratio` | 收敛比(末端宽度 / 起始宽度,越小越收敛)。 |
|
||||
| `touches_upper` | 上沿触碰次数(枢轴高点落在容差内的次数)。 |
|
||||
| `touches_lower` | 下沿触碰次数(枢轴低点落在容差内的次数)。 |
|
||||
| `apex_x` | 上下沿交点的 x 坐标(窗口内索引;平行时可能为 `inf`)。 |
|
||||
| `breakout_dir` | 突破方向:`up` / `down` / `none`。 |
|
||||
| `volume_confirmed` | 是否放量确认突破(突破时成交量 > 均量 × `vol_k`)。 |
|
||||
| `false_breakout` | 假突破标记(当前实现为空,占位)。 |
|
||||
| `window_start` | 窗口起始位置(窗口内索引,通常为 0)。 |
|
||||
| `window_end` | 窗口结束位置(窗口内索引,通常为 `window-1`)。 |
|
||||
| `stock_code` | 股票代码(脚本追加)。 |
|
||||
| `stock_name` | 股票名称(脚本追加)。 |
|
||||
| `date` | 真实交易日日期(脚本追加)。 |
|
||||
|
||||
## 备注
|
||||
|
||||
- `breakout_strength_up/down` 由价格突破幅度、收敛程度与成交量放大综合计算,
|
||||
详细公式见 `docs/突破强度计算方法.md`。
|
||||
- `date_idx` 与 `date`、`stock_idx` 与 `stock_code/stock_name` 的映射来自
|
||||
`data/*.pkl` 的元数据(脚本在输出时补充)。
|
||||
248
docs/plans/converging-triangle-algorithm_e49f62bc.plan.md
Normal file
248
docs/plans/converging-triangle-algorithm_e49f62bc.plan.md
Normal file
@ -0,0 +1,248 @@
|
||||
---
|
||||
name: converging-triangle-algorithm
|
||||
overview: 基于现有对称三角形算法,创建一个新的"收敛三角形"算法,支持二维矩阵批量输入,历史滚动计算,返回包含突破强度分数的完整 DataFrame。
|
||||
todos:
|
||||
- id: create-params
|
||||
content: 创建 ConvergingTriangleParams 参数对象
|
||||
status: completed
|
||||
- id: create-result
|
||||
content: 创建 ConvergingTriangleResult 返回对象
|
||||
status: completed
|
||||
- id: impl-strength
|
||||
content: 实现突破强度计算函数 calc_breakout_strength
|
||||
status: completed
|
||||
- id: impl-single
|
||||
content: 实现单点检测函数 detect_converging_triangle
|
||||
status: completed
|
||||
- id: impl-batch
|
||||
content: 实现批量滚动检测函数 detect_converging_triangle_batch
|
||||
status: completed
|
||||
- id: create-script
|
||||
content: 创建运行脚本 run_converging_triangle.py
|
||||
status: completed
|
||||
- id: test-run
|
||||
content: 测试运行并验证输出
|
||||
status: in_progress
|
||||
- id: todo-1768985498252-e4ly4s7r9
|
||||
content: 只计算最近2年,可以变成一个默认的参数,方便后续修改
|
||||
status: pending
|
||||
---
|
||||
|
||||
# 收敛三角形算法设计
|
||||
|
||||
## 1. 核心设计
|
||||
|
||||
### 函数名
|
||||
|
||||
- `detect_converging_triangle` (单点检测)
|
||||
- `detect_converging_triangle_batch` (批量滚动计算)
|
||||
|
||||
### 数据流
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph input [Input]
|
||||
OHLCV["OHLCV Matrices<br/>(n_stocks, n_days)"]
|
||||
Params[ConvergingTriangleParams]
|
||||
end
|
||||
|
||||
subgraph process [Processing]
|
||||
Rolling["Rolling Window<br/>每天往过去看 window 天"]
|
||||
Detect["收敛三角形检测"]
|
||||
Score["突破强度计算"]
|
||||
end
|
||||
|
||||
subgraph output [Output]
|
||||
DF["DataFrame<br/>(stock_idx, date_idx, 各指标...)"]
|
||||
end
|
||||
|
||||
OHLCV --> Rolling
|
||||
Params --> Rolling
|
||||
Rolling --> Detect
|
||||
Detect --> Score
|
||||
Score --> DF
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 参数对象设计
|
||||
|
||||
新建文件: [src/converging_triangle.py](src/converging_triangle.py)
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class ConvergingTriangleParams:
|
||||
# 窗口设置
|
||||
window: int = 400
|
||||
|
||||
# 枢轴点检测
|
||||
pivot_k: int = 20
|
||||
|
||||
# 边界线拟合
|
||||
boundary_n_segments: int = 2
|
||||
boundary_source: str = "full" # "full" | "pivots"
|
||||
|
||||
# 斜率约束
|
||||
upper_slope_max: float = 0.10
|
||||
lower_slope_min: float = -0.10
|
||||
|
||||
# 触碰判定
|
||||
touch_tol: float = 0.10
|
||||
touch_loss_max: float = 0.10
|
||||
|
||||
# 收敛要求
|
||||
shrink_ratio: float = 0.8
|
||||
|
||||
# 突破判定
|
||||
break_tol: float = 0.001
|
||||
vol_window: int = 20
|
||||
vol_k: float = 1.3
|
||||
false_break_m: int = 5
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 返回对象设计
|
||||
|
||||
```python
|
||||
@dataclass
|
||||
class ConvergingTriangleResult:
|
||||
# 基础标识
|
||||
stock_idx: int
|
||||
date_idx: int
|
||||
is_valid: bool # 是否识别到有效三角形
|
||||
|
||||
# 突破强度 (0~1 连续分数)
|
||||
breakout_strength_up: float # 向上突破强度
|
||||
breakout_strength_down: float # 向下突破强度
|
||||
|
||||
# 几何属性
|
||||
upper_slope: float
|
||||
lower_slope: float
|
||||
width_ratio: float
|
||||
touches_upper: int
|
||||
touches_lower: int
|
||||
apex_x: float
|
||||
|
||||
# 突破状态
|
||||
breakout_dir: str # "up" | "down" | "none"
|
||||
volume_confirmed: Optional[bool]
|
||||
false_breakout: Optional[bool]
|
||||
|
||||
# 窗口范围
|
||||
window_start: int
|
||||
window_end: int
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 突破强度计算公式
|
||||
|
||||
```python
|
||||
def calc_breakout_strength(
|
||||
close: float,
|
||||
upper_line: float,
|
||||
lower_line: float,
|
||||
volume_ratio: float,
|
||||
width_ratio: float,
|
||||
) -> tuple[float, float]:
|
||||
"""
|
||||
计算向上/向下突破强度 (0~1)
|
||||
|
||||
综合考虑:
|
||||
- 价格突破幅度 (close 相对于上/下沿的距离)
|
||||
- 成交量放大倍数
|
||||
- 收敛程度 (width_ratio 越小越强)
|
||||
"""
|
||||
# 价格突破分数
|
||||
price_up = max(0, (close - upper_line) / upper_line)
|
||||
price_down = max(0, (lower_line - close) / lower_line)
|
||||
|
||||
# 收敛加成 (越收敛,突破越有效)
|
||||
convergence_bonus = 1 - width_ratio
|
||||
|
||||
# 成交量加成
|
||||
vol_bonus = min(1, volume_ratio / 2) # 放量2倍=满分
|
||||
|
||||
# 加权合成
|
||||
strength_up = min(1, price_up * 5 * (1 + convergence_bonus) * (1 + vol_bonus))
|
||||
strength_down = min(1, price_down * 5 * (1 + convergence_bonus) * (1 + vol_bonus))
|
||||
|
||||
return strength_up, strength_down
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. 核心函数签名
|
||||
|
||||
```python
|
||||
def detect_converging_triangle_batch(
|
||||
open_mtx: np.ndarray, # (n_stocks, n_days)
|
||||
high_mtx: np.ndarray,
|
||||
low_mtx: np.ndarray,
|
||||
close_mtx: np.ndarray,
|
||||
volume_mtx: np.ndarray,
|
||||
params: ConvergingTriangleParams,
|
||||
start_day: int = None, # 从哪一天开始计算 (默认: window)
|
||||
end_day: int = None, # 到哪一天结束 (默认: 最后一天)
|
||||
) -> pd.DataFrame:
|
||||
"""
|
||||
批量滚动检测收敛三角形
|
||||
|
||||
Returns:
|
||||
DataFrame with columns:
|
||||
- stock_idx, date_idx
|
||||
- is_valid
|
||||
- breakout_strength_up, breakout_strength_down
|
||||
- upper_slope, lower_slope, width_ratio
|
||||
- touches_upper, touches_lower, apex_x
|
||||
- breakout_dir, volume_confirmed, false_breakout
|
||||
- window_start, window_end
|
||||
"""
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 滚动计算逻辑
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
Start[开始] --> Loop1["遍历 stock_idx: 0..107"]
|
||||
Loop1 --> Loop2["遍历 date_idx: start_day..end_day"]
|
||||
Loop2 --> Extract["提取窗口数据<br/>ohlcv[:, date_idx-window+1 : date_idx+1]"]
|
||||
Extract --> Check{"数据足够?<br/>date_idx >= window"}
|
||||
Check -->|No| Skip[跳过, is_valid=False]
|
||||
Check -->|Yes| Detect["调用 detect_converging_triangle()"]
|
||||
Detect --> CalcStrength["计算突破强度"]
|
||||
CalcStrength --> Append["添加到结果列表"]
|
||||
Skip --> Append
|
||||
Append --> Loop2
|
||||
Loop2 -->|done| Loop1
|
||||
Loop1 -->|done| ToDF["转换为 DataFrame"]
|
||||
ToDF --> End[返回]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. 文件结构
|
||||
|
||||
```
|
||||
technical-patterns-lab/
|
||||
├── src/
|
||||
│ ├── sym_triangle.py # 现有算法 (保留)
|
||||
│ └── converging_triangle.py # 新算法 (新建)
|
||||
├── scripts/
|
||||
│ ├── run_sym_triangle_pkl.py # 现有脚本 (保留)
|
||||
│ └── run_converging_triangle.py # 新脚本 (新建)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 8. 性能考虑
|
||||
|
||||
- 108 只股票 × 5113 天 = 55 万次检测
|
||||
- 预估耗时: 约 2-5 分钟 (可优化)
|
||||
- 可选优化:
|
||||
- 只计算最近 N 天
|
||||
- 并行处理多只股票
|
||||
- 向量化 pivot 检测
|
||||
104
docs/对称三角形-几何约束示意.svg
Normal file
104
docs/对称三角形-几何约束示意.svg
Normal file
@ -0,0 +1,104 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="980" height="520" viewBox="0 0 980 520">
|
||||
<defs>
|
||||
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto">
|
||||
<path d="M0,0 L10,5 L0,10 Z" fill="#444"/>
|
||||
</marker>
|
||||
<style>
|
||||
.bg { fill:#ffffff; }
|
||||
.axis { stroke:#cfcfcf; stroke-width:1; }
|
||||
.lineU { stroke:#e67e22; stroke-width:4; fill:none; }
|
||||
.lineL { stroke:#e67e22; stroke-width:4; fill:none; }
|
||||
.price { stroke:#c0392b; stroke-width:3; fill:none; }
|
||||
.touch { fill:#ffffff; stroke:#111; stroke-width:3; }
|
||||
.label { font-family: Arial, Helvetica, sans-serif; font-size:18px; fill:#111; font-weight: bold; }
|
||||
.small { font-family: Arial, Helvetica, sans-serif; font-size:15px; fill:#333; }
|
||||
.hint { font-family: Arial, Helvetica, sans-serif; font-size:14px; fill:#555; }
|
||||
.dash { stroke:#666; stroke-width:2; stroke-dasharray:6 6; }
|
||||
.bracket { stroke:#444; stroke-width:2; fill:none; marker-end:url(#arrow); }
|
||||
.box { fill:#f7f7f7; stroke:#ddd; stroke-width:1; }
|
||||
</style>
|
||||
</defs>
|
||||
|
||||
<!-- background -->
|
||||
<rect class="bg" x="0" y="0" width="980" height="520"/>
|
||||
|
||||
<!-- plotting area -->
|
||||
<rect x="40" y="40" width="660" height="420" class="box"/>
|
||||
|
||||
<!-- grid -->
|
||||
<g opacity="0.6">
|
||||
<line class="axis" x1="40" y1="130" x2="700" y2="130"/>
|
||||
<line class="axis" x1="40" y1="220" x2="700" y2="220"/>
|
||||
<line class="axis" x1="40" y1="310" x2="700" y2="310"/>
|
||||
<line class="axis" x1="40" y1="400" x2="700" y2="400"/>
|
||||
<line class="axis" x1="150" y1="40" x2="150" y2="460"/>
|
||||
<line class="axis" x1="260" y1="40" x2="260" y2="460"/>
|
||||
<line class="axis" x1="370" y1="40" x2="370" y2="460"/>
|
||||
<line class="axis" x1="480" y1="40" x2="480" y2="460"/>
|
||||
<line class="axis" x1="590" y1="40" x2="590" y2="460"/>
|
||||
</g>
|
||||
|
||||
<!-- upper / lower trendlines (sym triangle) -->
|
||||
<path class="lineU" d="M70 120 L680 240"/>
|
||||
<path class="lineL" d="M70 430 L680 290"/>
|
||||
|
||||
<!-- apex indication -->
|
||||
<line class="dash" x1="680" y1="240" x2="680" y2="290"/>
|
||||
<text class="small" x="645" y="320">Apex Area</text>
|
||||
|
||||
<!-- price path inside triangle (containment) -->
|
||||
<path class="price"
|
||||
d="M85 330
|
||||
C110 300, 125 240, 150 215
|
||||
C175 240, 195 300, 220 325
|
||||
C245 350, 265 310, 285 290
|
||||
C305 270, 315 250, 330 260
|
||||
C345 270, 360 310, 380 315
|
||||
C400 320, 410 290, 430 285
|
||||
C450 280, 470 270, 490 275
|
||||
C510 280, 525 300, 545 300
|
||||
C565 300, 580 285, 600 285
|
||||
C620 285, 640 275, 660 275"/>
|
||||
|
||||
<!-- touch points on upper line (pivot highs) -->
|
||||
<circle class="touch" cx="150" cy="215" r="10"/>
|
||||
<circle class="touch" cx="330" cy="260" r="10"/>
|
||||
<circle class="touch" cx="600" cy="285" r="10"/>
|
||||
|
||||
<!-- touch points on lower line (pivot lows) -->
|
||||
<circle class="touch" cx="85" cy="330" r="10"/>
|
||||
<circle class="touch" cx="220" cy="325" r="10"/>
|
||||
<circle class="touch" cx="545" cy="300" r="10"/>
|
||||
<circle class="touch" cx="660" cy="275" r="10"/>
|
||||
|
||||
<!-- labels for trendlines -->
|
||||
<text class="label" x="500" y="235">Upper Line (Resistance)</text>
|
||||
<text class="label" x="500" y="340">Lower Line (Support)</text>
|
||||
|
||||
<!-- slope explanation -->
|
||||
<line class="bracket" x1="760" y1="90" x2="930" y2="90"/>
|
||||
<text class="label" x="740" y="75">1. Slope</text>
|
||||
<text class="hint" x="740" y="110">Upper: Downward (a<0)</text>
|
||||
<text class="hint" x="740" y="132">Lower: Upward (a>0)</text>
|
||||
|
||||
<!-- convergence width brackets -->
|
||||
<text class="label" x="740" y="190">2. Convergence</text>
|
||||
<line class="bracket" x1="120" y1="475" x2="120" y2="420"/>
|
||||
<text class="hint" x="140" y="470">Wide Start</text>
|
||||
<line class="bracket" x1="675" y1="475" x2="675" y2="438"/>
|
||||
<text class="hint" x="695" y="470">Narrow End</text>
|
||||
|
||||
<!-- touches explanation -->
|
||||
<text class="label" x="740" y="260">3. Touches</text>
|
||||
<text class="hint" x="740" y="285">Min 2-3 touches (top)</text>
|
||||
<text class="hint" x="740" y="307">Min 2-3 touches (bottom)</text>
|
||||
<text class="hint" x="740" y="329">Touch = Reversal at line</text>
|
||||
|
||||
<!-- containment explanation -->
|
||||
<text class="label" x="740" y="390">4. Containment</text>
|
||||
<text class="hint" x="740" y="415">Price stays inside lines</text>
|
||||
<text class="hint" x="740" y="437">Rare breakouts before apex</text>
|
||||
|
||||
<!-- title -->
|
||||
<text class="label" x="40" y="28">Symmetrical Triangle: Geometric Constraints</text>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.4 KiB |
122
docs/对称三角形-突破与回撤示意.svg
Normal file
122
docs/对称三角形-突破与回撤示意.svg
Normal file
@ -0,0 +1,122 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="980" height="600" viewBox="0 0 980 600">
|
||||
<defs>
|
||||
<marker id="arrow" markerWidth="10" markerHeight="10" refX="8" refY="5" orient="auto">
|
||||
<path d="M0,0 L10,5 L0,10 Z" fill="#444"/>
|
||||
</marker>
|
||||
<style>
|
||||
.bg { fill:#ffffff; }
|
||||
.panel { fill:#f9f9f9; stroke:#ddd; stroke-width:1; }
|
||||
.lineU { stroke:#e67e22; stroke-width:3; fill:none; }
|
||||
.lineL { stroke:#e67e22; stroke-width:3; fill:none; }
|
||||
.price-up { stroke:#27ae60; stroke-width:3; fill:none; }
|
||||
.price-down { stroke:#c0392b; stroke-width:3; fill:none; }
|
||||
.price-neutral { stroke:#7f8c8d; stroke-width:2; fill:none; }
|
||||
.vol-high { fill:#27ae60; opacity:0.7; }
|
||||
.vol-low { fill:#95a5a6; opacity:0.5; }
|
||||
.label-title { font-family: Arial, sans-serif; font-size:18px; fill:#111; font-weight:bold; }
|
||||
.label-sub { font-family: Arial, sans-serif; font-size:14px; fill:#444; }
|
||||
.annotation { font-family: Arial, sans-serif; font-size:12px; fill:#c0392b; }
|
||||
.grid { stroke:#eee; stroke-width:1; }
|
||||
</style>
|
||||
</defs>
|
||||
|
||||
<rect class="bg" x="0" y="0" width="980" height="600"/>
|
||||
|
||||
<!-- PANEL 1: PRICE BREAKOUT -->
|
||||
<g transform="translate(40, 60)">
|
||||
<rect class="panel" x="0" y="0" width="420" height="220"/>
|
||||
<text class="label-title" x="10" y="-15">1. Price Breakout (Identification)</text>
|
||||
|
||||
<!-- Upward Breakout -->
|
||||
<g transform="translate(20, 20)">
|
||||
<text class="label-sub" x="0" y="15">Upward Breakout</text>
|
||||
<!-- Triangle lines -->
|
||||
<path class="lineU" d="M0 60 L150 100"/>
|
||||
<path class="lineL" d="M0 140 L150 100" stroke-dasharray="4,4" opacity="0.3"/> <!-- lower line fade -->
|
||||
<!-- Price action -->
|
||||
<path class="price-neutral" d="M0 100 L40 80 L80 110"/>
|
||||
<path class="price-up" d="M80 110 L120 70 L140 50 L160 55"/>
|
||||
<circle cx="140" cy="50" r="4" fill="#27ae60"/>
|
||||
<text class="annotation" x="100" y="40">Close > Upper</text>
|
||||
</g>
|
||||
|
||||
<!-- Downward Breakout -->
|
||||
<g transform="translate(220, 20)">
|
||||
<text class="label-sub" x="0" y="15">Downward Breakdown</text>
|
||||
<!-- Triangle lines -->
|
||||
<path class="lineL" d="M0 100 L150 60"/>
|
||||
<path class="lineU" d="M0 20 L150 60" stroke-dasharray="4,4" opacity="0.3"/>
|
||||
<!-- Price action -->
|
||||
<path class="price-neutral" d="M0 60 L40 80 L80 50"/>
|
||||
<path class="price-down" d="M80 50 L120 90 L140 110 L160 105"/>
|
||||
<circle cx="140" cy="110" r="4" fill="#c0392b"/>
|
||||
<text class="annotation" x="100" y="130">Close < Lower</text>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- PANEL 2: VOLUME CONFIRMATION -->
|
||||
<g transform="translate(500, 60)">
|
||||
<rect class="panel" x="0" y="0" width="420" height="220"/>
|
||||
<text class="label-title" x="10" y="-15">2. Volume Confirmation</text>
|
||||
|
||||
<!-- Chart area -->
|
||||
<g transform="translate(20, 40)">
|
||||
<!-- Price part -->
|
||||
<path class="lineU" d="M0 50 L200 80"/>
|
||||
<path class="price-up" d="M100 70 L140 40 L160 30 L180 35"/>
|
||||
|
||||
<!-- Volume Bars -->
|
||||
<line x1="0" y1="120" x2="380" y2="120" stroke="#ccc"/>
|
||||
|
||||
<!-- Scenario A: High Vol -->
|
||||
<g transform="translate(0, 0)">
|
||||
<text class="label-sub" x="60" y="10">Ideal: High Volume</text>
|
||||
<rect class="vol-low" x="100" y="100" width="15" height="20"/>
|
||||
<rect class="vol-low" x="120" y="105" width="15" height="15"/>
|
||||
<rect class="vol-high" x="140" y="70" width="15" height="50"/> <!-- Breakout bar -->
|
||||
<text class="annotation" x="135" y="65">Surge!</text>
|
||||
</g>
|
||||
|
||||
<!-- Scenario B: Low Vol -->
|
||||
<g transform="translate(200, 0)">
|
||||
<text class="label-sub" x="20" y="10">Weak: Low Volume</text>
|
||||
<rect class="vol-low" x="60" y="100" width="15" height="20"/>
|
||||
<rect class="vol-low" x="80" y="105" width="15" height="15"/>
|
||||
<rect class="vol-low" x="100" y="102" width="15" height="18"/> <!-- Weak bar -->
|
||||
<text class="annotation" x="80" y="65">No Power?</text>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<!-- PANEL 3: FALSE BREAKOUT -->
|
||||
<g transform="translate(40, 330)">
|
||||
<rect class="panel" x="0" y="0" width="900" height="220"/>
|
||||
<text class="label-title" x="10" y="-15">3. False Breakout (Failed Retest)</text>
|
||||
|
||||
<g transform="translate(50, 40)">
|
||||
<!-- Triangle context -->
|
||||
<path class="lineU" d="M0 100 L600 150"/>
|
||||
<path class="lineL" d="M0 200 L600 150" opacity="0.3"/>
|
||||
|
||||
<!-- Price Path -->
|
||||
<path class="price-neutral" d="M50 150 L100 120 L150 170 L200 130 L250 160"/>
|
||||
|
||||
<!-- The False Breakout -->
|
||||
<path class="price-up" d="M250 160 L300 110 L320 90"/> <!-- Breakout! -->
|
||||
<path class="price-down" d="M320 90 L340 100 L360 130 L400 140"/> <!-- Fall back in -->
|
||||
|
||||
<!-- Markers -->
|
||||
<circle cx="320" cy="90" r="5" fill="#27ae60"/>
|
||||
<text class="annotation" x="300" y="80">Breakout (T)</text>
|
||||
|
||||
<circle cx="360" cy="130" r="5" fill="#c0392b"/>
|
||||
<text class="annotation" x="370" y="130">Back Inside (T+m)</text>
|
||||
|
||||
<!-- Explanation -->
|
||||
<text class="label-sub" x="500" y="80">Definition:</text>
|
||||
<text class="label-sub" x="500" y="105">- Price breaks out at T</text>
|
||||
<text class="label-sub" x="500" y="130">- Returns inside within 'm' bars</text>
|
||||
<text class="label-sub" x="500" y="155">- Signal is invalidated</text>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 5.3 KiB |
320
docs/对称三角形识别-Python实现.md
Normal file
320
docs/对称三角形识别-Python实现.md
Normal file
@ -0,0 +1,320 @@
|
||||
# 对称三角形识别(Symmetrical Triangle)——规则口径 + Python 实现
|
||||
|
||||
> 目标:只实现**对称三角形**的自动识别,并输出可解释结果:上沿/下沿、收敛度、触碰次数、是否突破、是否假突破(失败回撤)。
|
||||
|
||||
---
|
||||
|
||||
## 1. 概念对齐:上沿、下沿、突破、失败回撤
|
||||
|
||||
- **上沿(Upper line / Resistance)**
|
||||
用“枢轴高点”(一串冲高回落的峰)拟合出的压力线;对称三角形要求它**向右下倾**。
|
||||
|
||||
- **下沿(Lower line / Support)**
|
||||
用“枢轴低点”(一串探底回升的谷)拟合出的支撑线;对称三角形要求它**向右上升**。
|
||||
|
||||
- **向上突破(Upward breakout)**
|
||||
价格从两线之间运行,某一根开始**收盘价明显站上上沿**(不是影线擦边)。
|
||||
|
||||
- **向下跌破(Downward breakdown)**
|
||||
收盘价明显跌到下沿之下。
|
||||
|
||||
- **失败回撤 / 假突破(False breakout / Failed retest)**
|
||||
“看起来突破了”,但在接下来 \(m\) 根内又回到三角形内部:
|
||||
- 向上突破后:`close` 又回到 `upper_line` 下方
|
||||
- 向下跌破后:`close` 又回到 `lower_line` 上方
|
||||
含义:突破没站稳,信号可靠性下降(回测/复盘里非常重要)。
|
||||
|
||||
---
|
||||
|
||||
## 2. 输入数据要求
|
||||
|
||||
`df: pandas.DataFrame` 至少包含:
|
||||
- `open`, `high`, `low`, `close`
|
||||
- `volume`(可选;用于“放量确认突破”,没有则跳过确认)
|
||||
|
||||
实现里用 `bar_index = 0..n-1` 作为横轴。
|
||||
|
||||
---
|
||||
|
||||
## 3. 可编码的识别口径(只针对对称三角形)
|
||||
|
||||
### 3.1 枢轴点(Pivot / Fractal)
|
||||
用左右窗口 `k` 识别局部极值:
|
||||
- 枢轴高点:`high[i]` 是 `[i-k, i+k]` 最大值
|
||||
- 枢轴低点:`low[i]` 是 `[i-k, i+k]` 最小值
|
||||
|
||||
> 直觉:枢轴点就是你手工画线时会“圈出来”的那些关键峰/谷。
|
||||
|
||||
### 3.2 趋势线拟合
|
||||
在一个检测窗口 `window` 内:
|
||||
- 取该窗口里的枢轴高点集合拟合上沿:\(y = a_u x + b_u\)
|
||||
- 取该窗口里的枢轴低点集合拟合下沿:\(y = a_l x + b_l\)
|
||||
|
||||
### 3.3 对称三角形硬条件(建议)
|
||||
在窗口内必须满足:
|
||||
- **斜率方向**:`a_u < 0` 且 `a_l > 0`
|
||||
- **收敛**:末端宽度显著小于起始宽度
|
||||
`width_end / width_start <= shrink_ratio`
|
||||
- **触碰次数**:上沿触碰 ≥ 2 且下沿触碰 ≥ 2
|
||||
触碰判定:`abs(pivot_y - line_y) / line_y <= touch_tol`
|
||||
- **Apex 合理性(可选)**:两线交点在窗口右侧不远处,避免两线近似平行导致的伪形态
|
||||
|
||||
### 3.4 突破/确认/假突破(信号层)
|
||||
突破(价格):
|
||||
- 向上:`close[t] > upper[t] * (1 + break_tol)`
|
||||
- 向下:`close[t] < lower[t] * (1 - break_tol)`
|
||||
|
||||
成交量确认(可选):
|
||||
- `volume[t] > MA(volume, vol_window)[t] * vol_k`
|
||||
|
||||
假突破(回测/复盘可用):
|
||||
- 若在 `m` 根内再次回到两线之间,则标记为 `false_breakout=True`
|
||||
|
||||
---
|
||||
|
||||
## 4. 参数建议(先跑通,再调参)
|
||||
|
||||
日线常用默认值(你可以直接用):
|
||||
- `window=120`(约 6 个月)
|
||||
- `pivot_k=3~5`
|
||||
- `touch_tol=0.006`(0.6% 贴线容差)
|
||||
- `shrink_ratio=0.6`(末端宽度 ≤ 起始宽度的 60%)
|
||||
- `break_tol=0.003`(突破需离线 0.3%)
|
||||
- `vol_window=20`, `vol_k=1.5`
|
||||
- `false_break_m=5`
|
||||
|
||||
---
|
||||
|
||||
## 5. Python 实现(可直接复制使用)
|
||||
|
||||
```python
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import Dict, List, Literal, Optional, Tuple
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
|
||||
@dataclass
|
||||
class SymTriangleResult:
|
||||
start: int
|
||||
end: int
|
||||
upper_coef: Tuple[float, float] # (a_u, b_u)
|
||||
lower_coef: Tuple[float, float] # (a_l, b_l)
|
||||
width_start: float
|
||||
width_end: float
|
||||
width_ratio: float
|
||||
touches_upper: int
|
||||
touches_lower: int
|
||||
apex_x: float
|
||||
breakout: Literal["up", "down", "none"]
|
||||
breakout_idx: Optional[int]
|
||||
volume_confirmed: Optional[bool]
|
||||
false_breakout: Optional[bool]
|
||||
|
||||
|
||||
def pivots_fractal(high: np.ndarray, low: np.ndarray, k: int = 3) -> Tuple[np.ndarray, np.ndarray]:
|
||||
"""左右窗口分形:返回 pivot_high_idx, pivot_low_idx。"""
|
||||
n = len(high)
|
||||
ph: List[int] = []
|
||||
pl: List[int] = []
|
||||
for i in range(k, n - k):
|
||||
if high[i] == np.max(high[i - k : i + k + 1]):
|
||||
ph.append(i)
|
||||
if low[i] == np.min(low[i - k : i + k + 1]):
|
||||
pl.append(i)
|
||||
return np.array(ph, dtype=int), np.array(pl, dtype=int)
|
||||
|
||||
|
||||
def fit_line(x: np.ndarray, y: np.ndarray) -> Tuple[float, float]:
|
||||
"""拟合 y = a*x + b"""
|
||||
a, b = np.polyfit(x, y, deg=1)
|
||||
return float(a), float(b)
|
||||
|
||||
|
||||
def line_y(a: float, b: float, x: np.ndarray) -> np.ndarray:
|
||||
return a * x + b
|
||||
|
||||
|
||||
def detect_sym_triangle(
|
||||
df: pd.DataFrame,
|
||||
window: int = 120,
|
||||
pivot_k: int = 3,
|
||||
touch_tol: float = 0.006,
|
||||
shrink_ratio: float = 0.6,
|
||||
break_tol: float = 0.003,
|
||||
vol_window: int = 20,
|
||||
vol_k: float = 1.5,
|
||||
false_break_m: int = 5,
|
||||
) -> Optional[SymTriangleResult]:
|
||||
"""
|
||||
只检测“最近一个窗口”是否存在对称三角形,并给出突破/确认/假突破信息。
|
||||
- 实时:false_breakout 只能返回 None(因为需要未来 m 根确认)
|
||||
- 回测:如果 df 包含突破后的 m 根数据,才会输出 false_breakout True/False
|
||||
"""
|
||||
required = {"open", "high", "low", "close"}
|
||||
if not required.issubset(df.columns):
|
||||
raise ValueError(f"df must contain columns: {sorted(required)}")
|
||||
|
||||
n = len(df)
|
||||
if n < max(window, 2 * pivot_k + 5):
|
||||
return None
|
||||
|
||||
high = df["high"].to_numpy(dtype=float)
|
||||
low = df["low"].to_numpy(dtype=float)
|
||||
close = df["close"].to_numpy(dtype=float)
|
||||
has_volume = "volume" in df.columns and df["volume"].notna().any()
|
||||
volume = df["volume"].to_numpy(dtype=float) if has_volume else None
|
||||
|
||||
ph_idx, pl_idx = pivots_fractal(high, low, k=pivot_k)
|
||||
|
||||
end = n - 1
|
||||
start = max(0, end - window + 1)
|
||||
x_all = np.arange(n, dtype=float)
|
||||
|
||||
ph_in = ph_idx[(ph_idx >= start) & (ph_idx <= end)]
|
||||
pl_in = pl_idx[(pl_idx >= start) & (pl_idx <= end)]
|
||||
if len(ph_in) < 2 or len(pl_in) < 2:
|
||||
return None
|
||||
|
||||
# 拟合两条线
|
||||
a_u, b_u = fit_line(x_all[ph_in], high[ph_in])
|
||||
a_l, b_l = fit_line(x_all[pl_in], low[pl_in])
|
||||
|
||||
# 斜率:对称三角形硬条件
|
||||
if not (a_u < 0 and a_l > 0):
|
||||
return None
|
||||
|
||||
# 宽度收敛
|
||||
upper_start = float(line_y(a_u, b_u, np.array([start]))[0])
|
||||
lower_start = float(line_y(a_l, b_l, np.array([start]))[0])
|
||||
upper_end = float(line_y(a_u, b_u, np.array([end]))[0])
|
||||
lower_end = float(line_y(a_l, b_l, np.array([end]))[0])
|
||||
width_start = upper_start - lower_start
|
||||
width_end = upper_end - lower_end
|
||||
if width_start <= 0 or width_end <= 0:
|
||||
return None
|
||||
width_ratio = width_end / width_start
|
||||
if width_ratio > shrink_ratio:
|
||||
return None
|
||||
|
||||
# 触碰次数(用 pivot 点贴线程度判定)
|
||||
ph_dist = np.abs(high[ph_in] - line_y(a_u, b_u, x_all[ph_in])) / np.maximum(
|
||||
line_y(a_u, b_u, x_all[ph_in]), 1e-9
|
||||
)
|
||||
pl_dist = np.abs(low[pl_in] - line_y(a_l, b_l, x_all[pl_in])) / np.maximum(
|
||||
line_y(a_l, b_l, x_all[pl_in]), 1e-9
|
||||
)
|
||||
touches_upper = int((ph_dist <= touch_tol).sum())
|
||||
touches_lower = int((pl_dist <= touch_tol).sum())
|
||||
if touches_upper < 2 or touches_lower < 2:
|
||||
return None
|
||||
|
||||
# apex(两线交点)仅做合理性输出(可扩展为硬条件)
|
||||
denom = (a_u - a_l)
|
||||
apex_x = float((b_l - b_u) / denom) if abs(denom) > 1e-12 else float("inf")
|
||||
|
||||
# === 突破判定(用最后一根 close 做实时判断)===
|
||||
upper_last = upper_end
|
||||
lower_last = lower_end
|
||||
breakout: Literal["up", "down", "none"] = "none"
|
||||
breakout_idx: Optional[int] = None
|
||||
if close[end] > upper_last * (1 + break_tol):
|
||||
breakout = "up"
|
||||
breakout_idx = end
|
||||
elif close[end] < lower_last * (1 - break_tol):
|
||||
breakout = "down"
|
||||
breakout_idx = end
|
||||
|
||||
# 成交量确认(可选)
|
||||
volume_confirmed: Optional[bool] = None
|
||||
if breakout != "none" and has_volume and volume is not None:
|
||||
vol_ma = pd.Series(volume).rolling(vol_window).mean().to_numpy()
|
||||
if np.isfinite(vol_ma[end]) and vol_ma[end] > 0:
|
||||
volume_confirmed = bool(volume[end] > vol_ma[end] * vol_k)
|
||||
else:
|
||||
volume_confirmed = None
|
||||
|
||||
# 假突破(需要未来 m 根数据,实时无法得知)
|
||||
false_breakout: Optional[bool] = None
|
||||
if breakout != "none" and breakout_idx is not None:
|
||||
# 如果数据不足以观察未来 m 根,则返回 None
|
||||
if breakout_idx + false_break_m < n:
|
||||
false_breakout = False
|
||||
for t in range(breakout_idx + 1, breakout_idx + false_break_m + 1):
|
||||
upper_t = float(line_y(a_u, b_u, np.array([t]))[0])
|
||||
lower_t = float(line_y(a_l, b_l, np.array([t]))[0])
|
||||
if breakout == "up" and close[t] < upper_t:
|
||||
false_breakout = True
|
||||
break
|
||||
if breakout == "down" and close[t] > lower_t:
|
||||
false_breakout = True
|
||||
break
|
||||
else:
|
||||
false_breakout = None
|
||||
|
||||
return SymTriangleResult(
|
||||
start=start,
|
||||
end=end,
|
||||
upper_coef=(a_u, b_u),
|
||||
lower_coef=(a_l, b_l),
|
||||
width_start=float(width_start),
|
||||
width_end=float(width_end),
|
||||
width_ratio=float(width_ratio),
|
||||
touches_upper=touches_upper,
|
||||
touches_lower=touches_lower,
|
||||
apex_x=apex_x,
|
||||
breakout=breakout,
|
||||
breakout_idx=breakout_idx,
|
||||
volume_confirmed=volume_confirmed,
|
||||
false_breakout=false_breakout,
|
||||
)
|
||||
|
||||
|
||||
def plot_sym_triangle(df: pd.DataFrame, res: SymTriangleResult) -> None:
|
||||
"""简单可视化验算(需要 matplotlib)。"""
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
close = df["close"].to_numpy(dtype=float)
|
||||
x = np.arange(len(df), dtype=float)
|
||||
a_u, b_u = res.upper_coef
|
||||
a_l, b_l = res.lower_coef
|
||||
|
||||
start, end = res.start, res.end
|
||||
xw = np.arange(start, end + 1, dtype=float)
|
||||
upper = line_y(a_u, b_u, xw)
|
||||
lower = line_y(a_l, b_l, xw)
|
||||
|
||||
plt.figure(figsize=(12, 5))
|
||||
plt.plot(x, close, linewidth=1.2, label="close")
|
||||
plt.plot(xw, upper, linewidth=2, label="upper")
|
||||
plt.plot(xw, lower, linewidth=2, label="lower")
|
||||
plt.axvline(end, color="gray", linestyle="--", linewidth=1)
|
||||
plt.title(
|
||||
f"sym_triangle: width_ratio={res.width_ratio:.2f}, touches=({res.touches_upper},{res.touches_lower}), "
|
||||
f"breakout={res.breakout}, vol_ok={res.volume_confirmed}, false={res.false_breakout}"
|
||||
)
|
||||
plt.legend()
|
||||
plt.show()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. 输出如何用于“评级/打分”(建议字段)
|
||||
|
||||
你可以直接把 `SymTriangleResult` 的字段映射为评分输入:
|
||||
- **形态质量**:`width_ratio`(越小越收敛)、`touches_upper/lower`(越多越可信)
|
||||
- **突破质量**:`breakout` + `volume_confirmed`(放量加分)
|
||||
- **风险提示**:`false_breakout`(为 True 则大幅降分;为 None 表示实时待确认)
|
||||
|
||||
---
|
||||
|
||||
## 7. 你该如何验证“程序画的线”是否接近你手工画线
|
||||
|
||||
1) 先挑一个你手工画过的例子(如你图中的那段)
|
||||
2) 跑 `detect_sym_triangle` 得到 `res`
|
||||
3) 用 `plot_sym_triangle(df, res)` 画出 close + upper/lower
|
||||
4) 调整 `pivot_k / touch_tol / window / shrink_ratio`,直到“触点选择”和你肉眼一致
|
||||
|
||||
75
docs/对称三角形识别-代码讲解PPT.md
Normal file
75
docs/对称三角形识别-代码讲解PPT.md
Normal file
@ -0,0 +1,75 @@
|
||||
# 对称三角形识别 - 代码讲解一页PPT
|
||||
|
||||
## 1. 算法总体流程(5步)
|
||||
输入数据 → 枢轴点检测 → 边界线拟合 → 几何约束验证 → 突破判定 → 输出结果
|
||||
|
||||
---
|
||||
|
||||
## 2. 核心模块与函数
|
||||
|
||||
**(1) 枢轴点检测**
|
||||
函数:`pivots_fractal(high, low, k)`
|
||||
作用:从高低价序列中提取明显峰谷
|
||||
关键参数:`pivot_k`(越大越严格)
|
||||
|
||||
**(2) 边界线拟合**
|
||||
函数:`fit_boundary_line(x, y, mode, n_segments)`
|
||||
作用:按时间分段取极值点,拟合贴边趋势线
|
||||
关键参数:`boundary_n_segments`(越小越贴边)
|
||||
|
||||
**(3) 形态验证**
|
||||
函数:`detect_sym_triangle(...)`
|
||||
包含:斜率约束、收敛判断、触碰次数、突破确认
|
||||
|
||||
---
|
||||
|
||||
## 3. 几何约束(对称三角形判定)
|
||||
|
||||
- 上沿下降:`a_u <= upper_slope_max`
|
||||
- 下沿上升:`a_l > 0`
|
||||
- 收敛比例:`width_end / width_start <= shrink_ratio`
|
||||
- 触碰次数:上下沿均 ≥ 2 次
|
||||
|
||||
---
|
||||
|
||||
## 4. 突破与确认逻辑
|
||||
|
||||
- 向上突破:`close[end] > upper_end * (1 + break_tol)`
|
||||
- 向下突破:`close[end] < lower_end * (1 - break_tol)`
|
||||
- 成交量确认(可选):`volume[end] > MA(volume) * vol_k`
|
||||
- 假突破过滤:突破后 `m` 根内回到线内
|
||||
|
||||
---
|
||||
|
||||
## 5. 关键参数(调参入口)
|
||||
|
||||
| 参数 | 含义 | 建议范围 |
|
||||
|------|------|---------|
|
||||
| `WINDOW` | 最近窗口长度 | 200~500 |
|
||||
| `PIVOT_K` | 枢轴检测窗口 | 5~30 |
|
||||
| `BOUNDARY_N_SEGMENTS` | 分段数 | 2~5 |
|
||||
| `UPPER_SLOPE_MAX` | 上沿斜率上限 | -0.01~0.10 |
|
||||
| `SHRINK_RATIO` | 收敛比例 | 0.3~0.9 |
|
||||
| `TOUCH_TOL` | 触碰容差 | 0.03~0.15 |
|
||||
|
||||
---
|
||||
|
||||
## 6. 结果输出结构
|
||||
|
||||
`SymTriangleResult` 包含:
|
||||
- 识别区间:`start, end`
|
||||
- 趋势线:`upper_coef, lower_coef`
|
||||
- 收敛程度:`width_ratio`
|
||||
- 触碰次数:`touches_upper, touches_lower`
|
||||
- 突破信息:`breakout, volume_confirmed, false_breakout`
|
||||
|
||||
---
|
||||
|
||||
## 7. 文件组织
|
||||
|
||||
```
|
||||
code/
|
||||
├── sym_triangle.py # 核心算法(检测+拟合+判定)
|
||||
├── run_sym_triangle_json.py # 测试脚本(参数配置+可视化)
|
||||
└── data.json # 输入数据(labels/values)
|
||||
```
|
||||
384
docs/技术形态识别-对称三角形(含三角形家族)-Python实现调研.md
Normal file
384
docs/技术形态识别-对称三角形(含三角形家族)-Python实现调研.md
Normal file
@ -0,0 +1,384 @@
|
||||
ad3590a2-2133-43ee-8ecd-d64a32760224
|
||||
|
||||
# 技术形态识别调研:对称三角形(含三角形家族)如何用 Python 实现
|
||||
|
||||
> 目标:把“图上能看出来的三角形”落地为**可计算、可回测、可解释**的规则与代码框架。本文以**对称三角形**为主,同时覆盖**上升三角形/下降三角形**的差异化判定。
|
||||
|
||||
---
|
||||
|
||||
## 1. 输入数据与基本约定
|
||||
|
||||
### 1.1 数据字段(最低要求)
|
||||
- OHLC:`open/high/low/close`
|
||||
- 成交量:`volume`(可选但强烈建议,用于形态可靠性/突破确认)
|
||||
- 时间索引:`datetime` 或顺序索引(实现里用 `bar_index = 0..n-1`)
|
||||
|
||||
### 1.2 周期选择(建议)
|
||||
- 日线:最常用,适合识别 1~6 个月级别三角形。
|
||||
- 周线:更稳定,噪声更低,触碰次数更少但信号更“硬”。
|
||||
|
||||
### 1.3 你在图里画的三角形是什么(术语对齐)
|
||||
- **对称三角形(Symmetrical Triangle)**:上边线下倾、下边线上升,区间收敛;本身不天然看涨/看跌,方向以突破为准。
|
||||
- **上升三角形(Ascending Triangle,看涨更典型)**:上边线近似水平、下边线上升。
|
||||
- **下降三角形(Descending Triangle,看跌更典型)**:上边线下降、下边线近似水平。
|
||||
|
||||
---
|
||||
|
||||
## 2. 从“肉眼形态”到“可编码规则”的拆解
|
||||
|
||||
形态识别一般分 4 层:
|
||||
1) 先找到“有意义的拐点”(枢轴点 / swing points)
|
||||
2) 用拐点拟合两条趋势线(上沿/下沿)
|
||||
3) 校验是否满足某类三角形的几何约束(斜率、收敛、触碰次数、包含性)
|
||||
4) 识别突破与确认(价格 + 成交量 + 失败回撤)
|
||||
|
||||
下面给出可落地的判定口径。
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## 3. 枢轴点(Pivot / Fractal)的提取方法
|
||||
|
||||
### 3.1 为什么要用枢轴点
|
||||
三角形的上沿/下沿本质是“多次触碰的压力/支撑”,用所有 K 线拟合会被噪声拖偏;用“局部极值点”更接近画线逻辑。
|
||||
|
||||
### 3.2 经典实现:左右窗口分形(推荐作为 baseline)
|
||||
定义窗口 `k`:
|
||||
- **枢轴高点**:`high[i]` 是 `[i-k, i+k]` 区间内的最大值
|
||||
- **枢轴低点**:`low[i]` 是 `[i-k, i+k]` 区间内的最小值
|
||||
|
||||
参数建议:
|
||||
- 日线:`k=3~5`
|
||||
- 周线:`k=2~3`
|
||||
|
||||
注意:该方法会引入 `k` 根的“确认延迟”(右侧需要 `k` 根数据才能确认 pivot)。
|
||||
|
||||
---
|
||||
|
||||
## 4. 趋势线拟合(上沿/下沿)
|
||||
|
||||
### 4.1 最简可用:线性回归/最小二乘
|
||||
对窗口内的枢轴高点集合 \((x_h, y_h)\) 拟合上沿:
|
||||
\[
|
||||
y = a_u x + b_u
|
||||
\]
|
||||
对枢轴低点集合 \((x_l, y_l)\) 拟合下沿:
|
||||
\[
|
||||
y = a_l x + b_l
|
||||
\]
|
||||
|
||||
实现可用:
|
||||
- `numpy.polyfit(x, y, deg=1)`
|
||||
- 或 `scipy.stats.linregress`(可选)
|
||||
|
||||
### 4.2 鲁棒拟合(可选增强)
|
||||
真实行情会有“假突破/尖刺”,可以用:
|
||||
- RANSAC 回归(`sklearn.linear_model.RANSACRegressor`)
|
||||
来降低离群点影响。
|
||||
|
||||
---
|
||||
|
||||
## 5. 三角形家族的“几何判定条件”
|
||||
|
||||
下面所有条件都应在一个**滑动窗口**内判断(例如最近 `W=60~180` 根)。
|
||||
|
||||
### 5.1 基础条件(所有三角形通用)
|
||||
- **拐点数量**:至少 `>=2` 个枢轴高点 + `>=2` 个枢轴低点
|
||||
- **触碰有效性**:枢轴点到趋势线的垂直距离(或相对误差)足够小
|
||||
- 例如:`abs(pivot_y - line_y) / line_y <= tol`,`tol` 可取 `0.3%~1.0%`
|
||||
- **包含性**:窗口内大多数 K 线价格在两条线之间(允许少量穿越)
|
||||
- **收敛性**:两线间距随时间缩小(后段宽度 < 前段宽度)
|
||||
|
||||
### 5.2 对称三角形(Symmetrical)
|
||||
- 斜率:`a_u < 0` 且 `a_l > 0`
|
||||
- 收敛:两线交点(apex)在当前窗口右侧不远处(避免“几乎平行”)
|
||||
- 计算 apex:解 \(a_u x + b_u = a_l x + b_l\)
|
||||
- \(x_{apex} = (b_l - b_u)/(a_u - a_l)\)
|
||||
- 约束:`x_apex` 在窗口右侧的合理范围内,例如 `end < x_apex < end + 2W`
|
||||
- 宽度缩小:`(upper(end) - lower(end)) / (upper(start) - lower(start)) <= shrink_ratio`
|
||||
- `shrink_ratio` 可取 `0.3~0.7`
|
||||
|
||||
### 5.3 上升三角形(Ascending,典型看涨)
|
||||
- 上沿近似水平:`abs(a_u) <= slope_eps`
|
||||
- 下沿上升:`a_l > 0`
|
||||
|
||||
### 5.4 下降三角形(Descending,典型看跌)
|
||||
- 上沿下降:`a_u < 0`
|
||||
- 下沿近似水平:`abs(a_l) <= slope_eps`
|
||||
|
||||
参数建议:
|
||||
- `slope_eps` 需要与价格尺度无关,建议在归一化 \(x \in [0,1]\) 之后拟合(或把斜率换算成“每根 K 的百分比变化”)。
|
||||
|
||||
---
|
||||
|
||||
## 6. 成交量与突破确认(把“形态”变成“信号”)
|
||||
|
||||
### 6.1 成交量收缩(形态期)
|
||||
常见经验:收敛阶段成交量逐步下降。可做一个弱约束:
|
||||
- `mean(volume[first_third]) > mean(volume[last_third])`
|
||||
或
|
||||
- `volume` 的线性回归斜率为负(噪声大,建议弱约束)
|
||||
|
||||
### 6.2 向上突破(看涨确认)
|
||||
对称三角形/上升三角形的看涨信号可定义为:
|
||||
- `close[t] > upper_line[t] * (1 + break_tol)`
|
||||
- 且成交量:`volume[t] > SMA(volume, n)[t] * vol_k`
|
||||
- 可选:`close` 连续 1~2 根站在线上方(避免一根假突破)
|
||||
|
||||
建议:
|
||||
- `break_tol`:`0.2%~0.8%` 或用 `ATR` 的一部分(更稳)
|
||||
- `vol_k`:`1.2~2.0`
|
||||
|
||||
### 6.3 失败回撤(False Breakout)处理
|
||||
突破后若在 `m` 根内收回形态内部(`close < upper_line`),可标记为失败突破或降低评分。
|
||||
|
||||

|
||||
|
||||
---
|
||||
|
||||
## 7. 量度目标(可解释输出)
|
||||
|
||||
经典量度:
|
||||
- 形态初期高度:`H = upper(start) - lower(start)`
|
||||
- 向上突破目标:`target = breakout_price + H`
|
||||
- 向下跌破目标:`target = breakout_price - H`
|
||||
|
||||
注意:量度目标是“参考”,实际还需结合前高/筹码/均线等约束。
|
||||
|
||||
---
|
||||
|
||||
## 8. Python 实现骨架(pandas/numpy baseline,可直接跑)
|
||||
|
||||
> 说明:下面代码优先做到“可读、可调参、可画图验算”。性能优化(向量化/numba)可在验证有效后再做。
|
||||
|
||||
```python
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Literal, Optional, Tuple
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
|
||||
@dataclass
|
||||
class TriangleCandidate:
|
||||
start: int
|
||||
end: int
|
||||
kind: Literal["sym", "asc", "desc"]
|
||||
upper_coef: Tuple[float, float] # (a_u, b_u)
|
||||
lower_coef: Tuple[float, float] # (a_l, b_l)
|
||||
apex_x: float
|
||||
width_start: float
|
||||
width_end: float
|
||||
|
||||
|
||||
def _pivots_fractal(high: np.ndarray, low: np.ndarray, k: int = 3) -> Tuple[np.ndarray, np.ndarray]:
|
||||
"""
|
||||
返回 pivot_high_idx, pivot_low_idx
|
||||
需要右侧 k 根确认,所以最后 k 根无法成为 pivot。
|
||||
"""
|
||||
n = len(high)
|
||||
ph = []
|
||||
pl = []
|
||||
for i in range(k, n - k):
|
||||
win_h = high[i - k : i + k + 1]
|
||||
win_l = low[i - k : i + k + 1]
|
||||
if high[i] == np.max(win_h):
|
||||
ph.append(i)
|
||||
if low[i] == np.min(win_l):
|
||||
pl.append(i)
|
||||
return np.array(ph, dtype=int), np.array(pl, dtype=int)
|
||||
|
||||
|
||||
def _fit_line(x: np.ndarray, y: np.ndarray) -> Tuple[float, float]:
|
||||
"""y = a*x + b"""
|
||||
a, b = np.polyfit(x, y, deg=1)
|
||||
return float(a), float(b)
|
||||
|
||||
|
||||
def _line_y(a: float, b: float, x: np.ndarray) -> np.ndarray:
|
||||
return a * x + b
|
||||
|
||||
|
||||
def detect_triangles(
|
||||
df: pd.DataFrame,
|
||||
window: int = 120,
|
||||
pivot_k: int = 3,
|
||||
tol: float = 0.006,
|
||||
slope_eps: float = 0.0005,
|
||||
shrink_ratio: float = 0.6,
|
||||
) -> List[TriangleCandidate]:
|
||||
"""
|
||||
在最近窗口内寻找三角形候选(返回候选列表,通常取最近一个做信号)。
|
||||
|
||||
参数口径:
|
||||
- tol: 枢轴点到趋势线的相对误差容忍(0.6%)
|
||||
- slope_eps: 近似水平线斜率阈值(需结合 x 的尺度;此处 x 用 bar_index)
|
||||
- shrink_ratio: 末端宽度/初端宽度阈值,越小要求越严格
|
||||
"""
|
||||
high = df["high"].to_numpy(dtype=float)
|
||||
low = df["low"].to_numpy(dtype=float)
|
||||
close = df["close"].to_numpy(dtype=float)
|
||||
n = len(df)
|
||||
|
||||
ph_idx, pl_idx = _pivots_fractal(high, low, k=pivot_k)
|
||||
out: List[TriangleCandidate] = []
|
||||
|
||||
# 只做最近窗口的检测(也可扩展为滑动扫描全历史)
|
||||
end = n - 1
|
||||
start = max(0, end - window + 1)
|
||||
x = np.arange(n, dtype=float)
|
||||
|
||||
ph_in = ph_idx[(ph_idx >= start) & (ph_idx <= end)]
|
||||
pl_in = pl_idx[(pl_idx >= start) & (pl_idx <= end)]
|
||||
if len(ph_in) < 2 or len(pl_in) < 2:
|
||||
return out
|
||||
|
||||
# 拟合两条线
|
||||
a_u, b_u = _fit_line(x[ph_in], high[ph_in])
|
||||
a_l, b_l = _fit_line(x[pl_in], low[pl_in])
|
||||
|
||||
# apex(两线交点)
|
||||
denom = (a_u - a_l)
|
||||
if abs(denom) < 1e-12:
|
||||
return out
|
||||
apex_x = (b_l - b_u) / denom
|
||||
|
||||
upper_start = _line_y(a_u, b_u, np.array([start]))[0]
|
||||
lower_start = _line_y(a_l, b_l, np.array([start]))[0]
|
||||
upper_end = _line_y(a_u, b_u, np.array([end]))[0]
|
||||
lower_end = _line_y(a_l, b_l, np.array([end]))[0]
|
||||
width_start = upper_start - lower_start
|
||||
width_end = upper_end - lower_end
|
||||
if width_start <= 0 or width_end <= 0:
|
||||
return out
|
||||
if width_end / width_start > shrink_ratio:
|
||||
return out
|
||||
|
||||
# 触碰判定:pivot 点距离线足够近
|
||||
ph_dist = np.abs(high[ph_in] - _line_y(a_u, b_u, x[ph_in])) / np.maximum(_line_y(a_u, b_u, x[ph_in]), 1e-9)
|
||||
pl_dist = np.abs(low[pl_in] - _line_y(a_l, b_l, x[pl_in])) / np.maximum(_line_y(a_l, b_l, x[pl_in]), 1e-9)
|
||||
if (ph_dist <= tol).sum() < 2 or (pl_dist <= tol).sum() < 2:
|
||||
return out
|
||||
|
||||
# apex 合理性:在窗口右侧一定范围内(可调)
|
||||
if not (end < apex_x < end + 2 * window):
|
||||
return out
|
||||
|
||||
# 分类
|
||||
kind: Optional[str] = None
|
||||
if a_u < 0 and a_l > 0:
|
||||
kind = "sym"
|
||||
elif abs(a_u) <= slope_eps and a_l > 0:
|
||||
kind = "asc"
|
||||
elif a_u < 0 and abs(a_l) <= slope_eps:
|
||||
kind = "desc"
|
||||
|
||||
if kind is None:
|
||||
return out
|
||||
|
||||
out.append(
|
||||
TriangleCandidate(
|
||||
start=start,
|
||||
end=end,
|
||||
kind=kind, # type: ignore[arg-type]
|
||||
upper_coef=(a_u, b_u),
|
||||
lower_coef=(a_l, b_l),
|
||||
apex_x=float(apex_x),
|
||||
width_start=float(width_start),
|
||||
width_end=float(width_end),
|
||||
)
|
||||
)
|
||||
return out
|
||||
|
||||
|
||||
def breakout_signal(
|
||||
df: pd.DataFrame,
|
||||
tri: TriangleCandidate,
|
||||
break_tol: float = 0.003,
|
||||
vol_window: int = 20,
|
||||
vol_k: float = 1.5,
|
||||
) -> Literal["up", "down", "none"]:
|
||||
"""
|
||||
简化版突破信号:
|
||||
- up: close > upper*(1+break_tol) 且 volume 放大
|
||||
- down: close < lower*(1-break_tol) 且 volume 放大
|
||||
"""
|
||||
a_u, b_u = tri.upper_coef
|
||||
a_l, b_l = tri.lower_coef
|
||||
end = tri.end
|
||||
x = float(end)
|
||||
upper = a_u * x + b_u
|
||||
lower = a_l * x + b_l
|
||||
|
||||
close = float(df["close"].iloc[end])
|
||||
vol = float(df.get("volume", pd.Series([np.nan] * len(df))).iloc[end])
|
||||
vol_ma = float(df.get("volume", pd.Series([np.nan] * len(df))).rolling(vol_window).mean().iloc[end])
|
||||
|
||||
vol_ok = np.isfinite(vol) and np.isfinite(vol_ma) and (vol > vol_ma * vol_k)
|
||||
|
||||
if close > upper * (1 + break_tol) and vol_ok:
|
||||
return "up"
|
||||
if close < lower * (1 - break_tol) and vol_ok:
|
||||
return "down"
|
||||
return "none"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 9. 可视化验算(强烈建议做,否则很容易“识别对了但画线不对”)
|
||||
|
||||
```python
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
|
||||
def plot_triangle(df, tri):
|
||||
a_u, b_u = tri.upper_coef
|
||||
a_l, b_l = tri.lower_coef
|
||||
start, end = tri.start, tri.end
|
||||
x = np.arange(start, end + 1)
|
||||
upper = a_u * x + b_u
|
||||
lower = a_l * x + b_l
|
||||
|
||||
close = df["close"].to_numpy()
|
||||
plt.figure(figsize=(12, 5))
|
||||
plt.plot(np.arange(len(close)), close, linewidth=1, label="close")
|
||||
plt.plot(x, upper, linewidth=2, label="upper")
|
||||
plt.plot(x, lower, linewidth=2, label="lower")
|
||||
plt.axvline(end, color="gray", linestyle="--", linewidth=1)
|
||||
plt.title(f"triangle={tri.kind}, width_end/width_start={tri.width_end/tri.width_start:.2f}")
|
||||
plt.legend()
|
||||
plt.show()
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10. 工程化落地建议(给“评级表/打分维度”用)
|
||||
|
||||
### 10.1 输出结构(可解释)
|
||||
建议把形态识别输出做成结构化字段,方便评分与回测:
|
||||
- `kind`: sym/asc/desc
|
||||
- `touches_upper/touches_lower`
|
||||
- `shrink_ratio = width_end/width_start`
|
||||
- `apex_distance = apex_x - end`
|
||||
- `breakout`: none/up/down
|
||||
- `break_strength`: (close - upper)/ATR 或百分比
|
||||
- `volume_ratio = volume / MA(volume, n)`
|
||||
- `measured_target`: `breakout_price ± H`
|
||||
|
||||
### 10.2 常见坑
|
||||
- **没有 pivot**:k 取太大/窗口太短,会导致拐点不足。
|
||||
- **斜率尺度问题**:不同价格/周期导致斜率阈值不可比;建议用归一化横轴或用“每根百分比斜率”。
|
||||
- **尖刺离群点**:极端上影线会影响拟合;可用 RANSAC 或在 pivot 上做 winsorize。
|
||||
- **形态过度拟合**:条件太多会导致回测看起来很强但实盘稀疏;建议分层评分(硬条件 + 软条件加分)。
|
||||
|
||||
---
|
||||
|
||||
## 11. 下一步(如果要继续深化)
|
||||
- 增加全历史滑动扫描:输出每个窗口的候选并去重(同一形态合并)。
|
||||
- 引入 ATR 作为容差:`tol = c * ATR/price`,比固定百分比更稳。
|
||||
- 加入“前置趋势”判定:三角形属于延续还是反转(给评级体系加解释)。
|
||||
- 将“画线/形态”统一到可视化回放工具(方便人工抽检)。
|
||||
|
||||
187
docs/突破强度计算方法.md
Normal file
187
docs/突破强度计算方法.md
Normal file
@ -0,0 +1,187 @@
|
||||
# 突破强度计算方法
|
||||
|
||||
## 概述
|
||||
|
||||
突破强度是一个 **0~1** 的连续分数,用于衡量收敛三角形突破的有效性。
|
||||
分数越高,表示突破越强势、越可信。
|
||||
|
||||
---
|
||||
|
||||
## 计算公式
|
||||
|
||||
```python
|
||||
strength = price_score × 5 × (1 + convergence_bonus × 0.5) × (1 + vol_bonus × 0.5)
|
||||
```
|
||||
|
||||
最终结果限制在 `[0, 1]` 范围内。
|
||||
|
||||
---
|
||||
|
||||
## 三个影响因素
|
||||
|
||||
### 1. 价格突破幅度 (price_score)
|
||||
|
||||
衡量收盘价突破趋势线的程度。
|
||||
|
||||
```python
|
||||
# 向上突破
|
||||
price_up = max(0, (close - upper_line) / upper_line)
|
||||
|
||||
# 向下突破
|
||||
price_down = max(0, (lower_line - close) / lower_line)
|
||||
```
|
||||
|
||||
| 突破幅度 | price_score |
|
||||
|----------|-------------|
|
||||
| 未突破 | 0 |
|
||||
| 突破 1% | 0.01 |
|
||||
| 突破 5% | 0.05 |
|
||||
| 突破 10% | 0.10 |
|
||||
|
||||
---
|
||||
|
||||
### 2. 收敛加成 (convergence_bonus)
|
||||
|
||||
三角形收敛程度越高,突破越有效。
|
||||
|
||||
```python
|
||||
convergence_bonus = max(0, 1 - width_ratio)
|
||||
```
|
||||
|
||||
| width_ratio | 收敛程度 | convergence_bonus |
|
||||
|-------------|----------|-------------------|
|
||||
| 0.8 | 较弱 | 0.2 |
|
||||
| 0.5 | 中等 | 0.5 |
|
||||
| 0.2 | 很强 | 0.8 |
|
||||
| 0.1 | 极强 | 0.9 |
|
||||
|
||||
**width_ratio** = 三角形末端宽度 / 起始宽度
|
||||
|
||||
---
|
||||
|
||||
### 3. 成交量加成 (vol_bonus)
|
||||
|
||||
放量突破更可信。
|
||||
|
||||
```python
|
||||
vol_bonus = min(1, max(0, volume_ratio - 1))
|
||||
```
|
||||
|
||||
| volume_ratio | 成交量状态 | vol_bonus |
|
||||
|--------------|------------|-----------|
|
||||
| 0.8 | 缩量 | 0 |
|
||||
| 1.0 | 平量 | 0 |
|
||||
| 1.5 | 放量 50% | 0.5 |
|
||||
| 2.0 | 放量 100% | 1.0 (满分) |
|
||||
| 3.0 | 放量 200% | 1.0 (上限) |
|
||||
|
||||
**volume_ratio** = 当日成交量 / 近 N 日均量
|
||||
|
||||
---
|
||||
|
||||
## 加权系数说明
|
||||
|
||||
```python
|
||||
strength = price_score × 5 × (1 + convergence_bonus × 0.5) × (1 + vol_bonus × 0.5)
|
||||
```
|
||||
|
||||
| 系数 | 作用 |
|
||||
|------|------|
|
||||
| `× 5` | 放大价格突破分数,使 2% 突破 = 0.1 基础分 |
|
||||
| `× (1 + convergence_bonus × 0.5)` | 收敛加成最多增加 50% |
|
||||
| `× (1 + vol_bonus × 0.5)` | 成交量加成最多增加 50% |
|
||||
|
||||
---
|
||||
|
||||
## 计算示例
|
||||
|
||||
### 示例 1:强势突破
|
||||
|
||||
```
|
||||
输入:
|
||||
close = 10.5, upper_line = 10.0 (突破 5%)
|
||||
width_ratio = 0.2 (收敛很强)
|
||||
volume_ratio = 1.8 (放量 80%)
|
||||
|
||||
计算:
|
||||
price_up = (10.5 - 10.0) / 10.0 = 0.05
|
||||
convergence_bonus = 1 - 0.2 = 0.8
|
||||
vol_bonus = min(1, 1.8 - 1) = 0.8
|
||||
|
||||
strength_up = 0.05 × 5 × (1 + 0.8 × 0.5) × (1 + 0.8 × 0.5)
|
||||
= 0.25 × 1.4 × 1.4
|
||||
= 0.49
|
||||
|
||||
结果: 向上突破强度 = 0.49 (中度突破)
|
||||
```
|
||||
|
||||
### 示例 2:弱势突破
|
||||
|
||||
```
|
||||
输入:
|
||||
close = 10.1, upper_line = 10.0 (突破 1%)
|
||||
width_ratio = 0.7 (收敛较弱)
|
||||
volume_ratio = 0.9 (缩量)
|
||||
|
||||
计算:
|
||||
price_up = 0.01
|
||||
convergence_bonus = 0.3
|
||||
vol_bonus = 0
|
||||
|
||||
strength_up = 0.01 × 5 × (1 + 0.3 × 0.5) × (1 + 0)
|
||||
= 0.05 × 1.15 × 1.0
|
||||
= 0.0575
|
||||
|
||||
结果: 向上突破强度 = 0.06 (微弱突破)
|
||||
```
|
||||
|
||||
### 示例 3:极强突破
|
||||
|
||||
```
|
||||
输入:
|
||||
close = 11.0, upper_line = 10.0 (突破 10%)
|
||||
width_ratio = 0.15 (极度收敛)
|
||||
volume_ratio = 2.5 (放量 150%)
|
||||
|
||||
计算:
|
||||
price_up = 0.10
|
||||
convergence_bonus = 0.85
|
||||
vol_bonus = 1.0
|
||||
|
||||
strength_up = 0.10 × 5 × (1 + 0.85 × 0.5) × (1 + 1.0 × 0.5)
|
||||
= 0.50 × 1.425 × 1.5
|
||||
= 1.07 → 截断为 1.0
|
||||
|
||||
结果: 向上突破强度 = 1.0 (满分)
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 强度等级参考
|
||||
|
||||
| 强度范围 | 等级 | 含义 |
|
||||
|----------|------|------|
|
||||
| 0 ~ 0.1 | 无/微弱 | 未突破或假突破风险高 |
|
||||
| 0.1 ~ 0.3 | 轻度 | 有突破迹象,需观察确认 |
|
||||
| 0.3 ~ 0.6 | 中度 | 有效突破,可作为参考信号 |
|
||||
| 0.6 ~ 1.0 | 强势 | 高置信度突破,值得关注 |
|
||||
|
||||
---
|
||||
|
||||
## 代码位置
|
||||
|
||||
```
|
||||
src/converging_triangle.py
|
||||
├── calc_breakout_strength() # 突破强度计算函数
|
||||
└── detect_converging_triangle() # 调用位置
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 相关参数
|
||||
|
||||
| 参数 | 默认值 | 说明 |
|
||||
|------|--------|------|
|
||||
| `vol_window` | 20 | 计算成交量均值的窗口大小 |
|
||||
| `vol_k` | 1.3 | 成交量确认阈值(volume_confirmed 判断用) |
|
||||
| `break_tol` | 0.001 | 突破判定容差 |
|
||||
1838
outputs/converging_triangles/all_results.csv
Normal file
1838
outputs/converging_triangles/all_results.csv
Normal file
File diff suppressed because it is too large
Load Diff
71
outputs/converging_triangles/report.md
Normal file
71
outputs/converging_triangles/report.md
Normal file
@ -0,0 +1,71 @@
|
||||
# 收敛三角形突破强度选股简报
|
||||
|
||||
- 生成时间:2026-01-21 17:55
|
||||
- 数据范围:20240909 ~ 20260120
|
||||
- 记录数:1837
|
||||
- 突破方向统计:上破 258 / 下破 251 / 无突破 1328
|
||||
|
||||
## 筛选条件
|
||||
- 强度阈值:> 0.3
|
||||
- 每方向最多输出:20 只(按单只股票的最高强度去重)
|
||||
|
||||
## 综合突破强度候选
|
||||
| 排名 | 股票 | 日期 | 方向 | 综合强度 | 宽度比 | 放量确认 |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| 1 | SZ300027 华谊兄弟 | 20260120 | down | 1.0000 | 0.1490 | 否 |
|
||||
| 2 | SZ002802 洪汇新材 | 20251211 | down | 1.0000 | 0.5451 | 否 |
|
||||
| 3 | SH688550 瑞联新材 | 20251208 | down | 1.0000 | 0.0508 | 否 |
|
||||
| 4 | SZ002242 九阳股份 | 20251112 | up | 1.0000 | 0.1145 | 是 |
|
||||
| 5 | SH600395 盘江股份 | 20250916 | up | 1.0000 | 0.0011 | 否 |
|
||||
| 6 | SZ002493 荣盛石化 | 20250820 | up | 1.0000 | 0.2808 | 是 |
|
||||
| 7 | SZ002192 融捷股份 | 20250808 | up | 1.0000 | 0.0833 | 否 |
|
||||
| 8 | SH600846 同济科技 | 20241128 | up | 1.0000 | 0.6111 | 否 |
|
||||
| 9 | SH600588 用友网络 | 20241104 | up | 1.0000 | 0.1045 | 否 |
|
||||
| 10 | SH603237 五芳斋 | 20241104 | up | 1.0000 | 0.0482 | 否 |
|
||||
| 11 | SZ300379 *ST东通 | 20241018 | up | 1.0000 | 0.7060 | 是 |
|
||||
| 12 | SH603707 健友股份 | 20241008 | up | 1.0000 | 0.3252 | 是 |
|
||||
| 13 | SZ002092 中泰化学 | 20241008 | up | 1.0000 | 0.1271 | 是 |
|
||||
| 14 | SH601236 红塔证券 | 20240927 | up | 1.0000 | 0.0672 | 是 |
|
||||
| 15 | SZ002694 顾地科技 | 20251120 | up | 0.8276 | 0.0439 | 否 |
|
||||
| 16 | SZ002293 罗莱生活 | 20250925 | down | 0.7794 | 0.1341 | 否 |
|
||||
| 17 | SH600984 建设机械 | 20260120 | down | 0.7263 | 0.2516 | 否 |
|
||||
| 18 | SZ002966 苏州银行 | 20260116 | down | 0.6074 | 0.5513 | 是 |
|
||||
| 19 | SH600475 华光环能 | 20250519 | up | 0.5628 | 0.2505 | 是 |
|
||||
| 20 | SZ002544 普天科技 | 20250723 | down | 0.5161 | 0.0167 | 否 |
|
||||
|
||||
## 向上突破候选
|
||||
| 排名 | 股票 | 日期 | 强度 | 宽度比 | 触碰(上/下) | 放量确认 |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| 1 | SZ002242 九阳股份 | 20251112 | 1.0000 | 0.1145 | 8/5 | 是 |
|
||||
| 2 | SH600395 盘江股份 | 20250916 | 1.0000 | 0.0011 | 6/6 | 否 |
|
||||
| 3 | SZ002493 荣盛石化 | 20250820 | 1.0000 | 0.2808 | 5/5 | 是 |
|
||||
| 4 | SZ002192 融捷股份 | 20250808 | 1.0000 | 0.0833 | 4/3 | 否 |
|
||||
| 5 | SH600846 同济科技 | 20241128 | 1.0000 | 0.6111 | 8/6 | 否 |
|
||||
| 6 | SH600588 用友网络 | 20241104 | 1.0000 | 0.1045 | 3/5 | 否 |
|
||||
| 7 | SH603237 五芳斋 | 20241104 | 1.0000 | 0.0482 | 4/5 | 否 |
|
||||
| 8 | SZ300379 *ST东通 | 20241018 | 1.0000 | 0.7060 | 2/2 | 是 |
|
||||
| 9 | SH603707 健友股份 | 20241008 | 1.0000 | 0.3252 | 4/8 | 是 |
|
||||
| 10 | SZ002092 中泰化学 | 20241008 | 1.0000 | 0.1271 | 4/2 | 是 |
|
||||
| 11 | SH601236 红塔证券 | 20240927 | 1.0000 | 0.0672 | 4/7 | 是 |
|
||||
| 12 | SZ002694 顾地科技 | 20251120 | 0.8276 | 0.0439 | 3/3 | 否 |
|
||||
| 13 | SH600475 华光环能 | 20250519 | 0.5628 | 0.2505 | 7/6 | 是 |
|
||||
| 14 | SZ002544 普天科技 | 20250818 | 0.3096 | 0.0912 | 5/5 | 是 |
|
||||
|
||||
## 向下突破候选
|
||||
| 排名 | 股票 | 日期 | 强度 | 宽度比 | 触碰(上/下) | 放量确认 |
|
||||
| --- | --- | --- | --- | --- | --- | --- |
|
||||
| 1 | SZ300027 华谊兄弟 | 20260120 | 1.0000 | 0.1490 | 4/3 | 否 |
|
||||
| 2 | SZ002802 洪汇新材 | 20251211 | 1.0000 | 0.5451 | 3/6 | 否 |
|
||||
| 3 | SH688550 瑞联新材 | 20251208 | 1.0000 | 0.0508 | 6/2 | 否 |
|
||||
| 4 | SZ002293 罗莱生活 | 20250925 | 0.7794 | 0.1341 | 4/3 | 否 |
|
||||
| 5 | SH600984 建设机械 | 20260120 | 0.7263 | 0.2516 | 5/3 | 否 |
|
||||
| 6 | SZ002966 苏州银行 | 20260116 | 0.6074 | 0.5513 | 4/5 | 是 |
|
||||
| 7 | SZ002092 中泰化学 | 20251216 | 0.5789 | 0.2660 | 7/5 | 否 |
|
||||
| 8 | SZ002544 普天科技 | 20250723 | 0.5161 | 0.0167 | 4/4 | 否 |
|
||||
| 9 | SZ002694 顾地科技 | 20251212 | 0.5158 | 0.0057 | 3/3 | 是 |
|
||||
|
||||
## 说明
|
||||
- 强度由价格突破幅度、收敛程度与成交量放大综合计算。
|
||||
- 仅统计 `breakout_dir` 与方向一致的记录。
|
||||
- 每只股票仅保留强度最高的一条记录(同强度取最新日期)。
|
||||
- 综合强度 = max(向上强度, 向下强度)。
|
||||
160
outputs/converging_triangles/strong_breakout_down.csv
Normal file
160
outputs/converging_triangles/strong_breakout_down.csv
Normal file
@ -0,0 +1,160 @@
|
||||
stock_idx,date_idx,is_valid,breakout_strength_up,breakout_strength_down,upper_slope,lower_slope,width_ratio,touches_upper,touches_lower,apex_x,breakout_dir,volume_confirmed,false_breakout,window_start,window_end,stock_code,stock_name,date
|
||||
17,7679,True,0.0,0.37483103642716237,0.0024806395582355986,0.007099245622539821,0.2585968622587718,5,3,538.1687501560897,down,False,,0,399,SH600984,建设机械,20260113
|
||||
17,7680,True,0.0,0.46062032935201896,0.0024806395582356025,0.0070992456225398215,0.25721665699268725,5,3,537.1687501560895,down,False,,0,399,SH600984,建设机械,20260114
|
||||
17,7681,True,0.0,0.5024994081415073,0.0024806395582356,0.007099245622539821,0.25583130332783627,5,3,536.1687501560892,down,False,,0,399,SH600984,建设机械,20260115
|
||||
17,7682,True,0.0,0.46942397268695085,0.0024806395582355986,0.007099245622539822,0.25444077240379553,5,3,535.1687501560892,down,False,,0,399,SH600984,建设机械,20260116
|
||||
17,7685,True,0.0,0.5112024791180694,0.0024806395582356055,0.007099245622539826,0.2530450351440289,5,3,534.1687501560896,down,False,,0,399,SH600984,建设机械,20260119
|
||||
17,7686,True,0.0,0.7262847783537918,0.0024806395582356025,0.0070992456225398215,0.2516440622538554,5,3,533.168750156089,down,False,,0,399,SH600984,建设机械,20260120
|
||||
45,7618,True,0.0,0.8254971157258881,0.08553694922344997,0.13109544178153384,0.07547473300486615,6,2,431.5728452688142,down,True,,0,399,SH688550,瑞联新材,20251113
|
||||
45,7619,True,0.0,0.7793538104932136,0.08553694922344994,0.1310954417815337,0.07332753473829365,6,2,430.5728452688149,down,False,,0,399,SH688550,瑞联新材,20251114
|
||||
45,7622,True,0.0,0.91700126980981,0.08553694922345002,0.13109544178153376,0.07117033957228698,6,2,429.57284526881523,down,False,,0,399,SH688550,瑞联新材,20251117
|
||||
45,7623,True,0.0,1.0,0.08553694922344995,0.13109544178153373,0.06900307752878228,6,2,428.5728452688147,down,False,,0,399,SH688550,瑞联新材,20251118
|
||||
45,7624,True,0.0,1.0,0.08553694922345002,0.13109544178153373,0.06682567797506232,6,2,427.57284526881483,down,False,,0,399,SH688550,瑞联新材,20251119
|
||||
45,7625,True,0.0,1.0,0.08553694922344997,0.13109544178153382,0.06463806961607792,6,2,426.5728452688142,down,False,,0,399,SH688550,瑞联新材,20251120
|
||||
45,7626,True,0.0,1.0,0.08553694922344998,0.13109544178153368,0.062440180486680624,6,2,425.57284526881494,down,False,,0,399,SH688550,瑞联新材,20251121
|
||||
45,7629,True,0.0,1.0,0.08553694922344995,0.13109544178153365,0.06023193794370936,6,2,424.57284526881557,down,False,,0,399,SH688550,瑞联新材,20251124
|
||||
45,7630,True,0.0,1.0,0.08553694922344995,0.13109544178153362,0.05801326865800534,6,2,423.5728452688156,down,False,,0,399,SH688550,瑞联新材,20251125
|
||||
45,7631,True,0.0,1.0,0.08553694922344998,0.13109544178153373,0.05578409860628728,6,2,422.57284526881494,down,False,,0,399,SH688550,瑞联新材,20251126
|
||||
45,7632,True,0.0,1.0,0.08553694922344994,0.13109544178153368,0.05354435306292516,6,2,421.5728452688153,down,False,,0,399,SH688550,瑞联新材,20251127
|
||||
45,7633,True,0.0,1.0,0.08553694922345,0.1310954417815338,0.051293956591577115,6,2,420.5728452688146,down,False,,0,399,SH688550,瑞联新材,20251128
|
||||
45,7636,True,0.0,1.0,0.08553694922345004,0.13109544178153365,0.04903283303673852,6,2,419.57284526881523,down,False,,0,399,SH688550,瑞联新材,20251201
|
||||
45,7637,True,0.0,1.0,0.08553694922344998,0.13109544178153382,0.04676090551513049,6,2,418.5728452688143,down,False,,0,399,SH688550,瑞联新材,20251202
|
||||
45,7638,True,0.0,1.0,0.08553694922344997,0.13109544178153376,0.04447809640700932,6,2,417.5728452688156,down,False,,0,399,SH688550,瑞联新材,20251203
|
||||
45,7639,True,0.0,1.0,0.08553694922345004,0.13109544178153376,0.042184327347298115,6,2,416.5728452688151,down,False,,0,399,SH688550,瑞联新材,20251204
|
||||
45,7640,True,0.0,1.0,0.08553694922345001,0.13109544178153368,0.039879519216651456,6,2,415.572845268816,down,False,,0,399,SH688550,瑞联新材,20251205
|
||||
45,7643,True,0.0,1.0,0.08553694922344995,0.13026409500754932,0.050822784864033334,6,2,420.3640728384365,down,False,,0,399,SH688550,瑞联新材,20251208
|
||||
45,7651,True,0.0,0.44566758977900295,0.08553694922345,0.09833743866671998,0.669133789695836,6,4,1205.9254997154314,down,False,,0,399,SH688550,瑞联新材,20251216
|
||||
45,7652,True,0.0,0.3920115431088978,0.08553694922345007,0.09833743866672004,0.6688591949508647,6,4,1204.9254997154321,down,False,,0,399,SH688550,瑞联新材,20251217
|
||||
45,7653,True,0.0,0.47007724400951084,0.0855369492234499,0.09833743866671996,0.6685841440402148,6,4,1203.9254997154271,down,False,,0,399,SH688550,瑞联新材,20251218
|
||||
45,7654,True,0.0,0.498240668435339,0.08553694922344993,0.09833743866672005,0.6683086358262478,6,4,1202.9254997154196,down,False,,0,399,SH688550,瑞联新材,20251219
|
||||
45,7657,True,0.0,0.45662071287358996,0.08553694922344997,0.09833743866671994,0.6680326691675427,6,4,1201.925499715433,down,False,,0,399,SH688550,瑞联新材,20251222
|
||||
45,7658,True,0.0,0.4497391417639845,0.08553694922345001,0.09833743866672,0.6677562429188604,6,4,1200.925499715432,down,False,,0,399,SH688550,瑞联新材,20251223
|
||||
45,7659,True,0.0,0.4075172301339248,0.08553694922345001,0.09833743866672001,0.6674793559311604,6,4,1199.9254997154312,down,False,,0,399,SH688550,瑞联新材,20251224
|
||||
45,7660,True,0.0,0.41772702074516804,0.08553694922345004,0.09833743866672005,0.66720200705156,6,4,1198.9254997154296,down,False,,0,399,SH688550,瑞联新材,20251225
|
||||
45,7661,True,0.0,0.48300263072266264,0.08553694922344997,0.09833743866672004,0.6669241951233315,6,4,1197.9254997154242,down,False,,0,399,SH688550,瑞联新材,20251226
|
||||
62,7647,True,0.0,0.33666826022676544,0.0009709497316493708,0.005145476601964163,0.2686586268214772,7,5,545.5728537083637,down,False,,0,399,SZ002092,中泰化学,20251212
|
||||
62,7650,True,0.0,0.37189360453987075,0.0009709497316493689,0.005145476601964164,0.2673156634912315,7,5,544.572853708365,down,False,,0,399,SZ002092,中泰化学,20251215
|
||||
62,7651,True,0.0,0.5788753488552334,0.0009709497316493696,0.005145476601964165,0.2659677589159549,7,5,543.5728537083638,down,False,,0,399,SZ002092,中泰化学,20251216
|
||||
62,7652,True,0.0,0.4261462508417063,0.0009709497316493673,0.005145476601964166,0.26461488577446324,7,5,542.5728537083631,down,False,,0,399,SZ002092,中泰化学,20251217
|
||||
62,7653,True,0.0,0.40501217836481745,0.0009709497316493735,0.005145476601964166,0.26325701654377837,7,5,541.5728537083639,down,False,,0,399,SZ002092,中泰化学,20251218
|
||||
62,7654,True,0.0,0.31563475917210615,0.0009709497316493713,0.005145476601964158,0.2618941234972615,7,5,540.5728537083658,down,False,,0,399,SZ002092,中泰化学,20251219
|
||||
62,7657,True,0.0,0.322682380391004,0.0009709497316493679,0.0051454766019641595,0.2605261787027247,7,5,539.5728537083643,down,False,,0,399,SZ002092,中泰化学,20251222
|
||||
62,7658,True,0.0,0.3979152814509742,0.0009709497316493667,0.005145476601964169,0.2591531540205362,7,5,538.572853708363,down,False,,0,399,SZ002092,中泰化学,20251223
|
||||
66,7561,True,0.0,0.3131770230433738,0.0014386289682874224,0.012846847568607687,0.145193224071279,4,3,466.7721539367758,down,False,,0,399,SZ002293,罗莱生活,20250917
|
||||
66,7562,True,0.0,0.47149700708730674,0.0014386289682874144,0.012846847568607682,0.1433579774411316,4,3,465.7721539367757,down,False,,0,399,SZ002293,罗莱生活,20250918
|
||||
66,7563,True,0.0,0.4816610485568412,0.0014386289682874092,0.01284684756860767,0.14151483340743068,4,3,464.77215393677557,down,False,,0,399,SZ002293,罗莱生活,20250919
|
||||
66,7566,True,0.0,0.524931120152862,0.0014386289682874124,0.01284684756860767,0.13966374088429304,4,3,463.77215393677625,down,False,,0,399,SZ002293,罗莱生活,20250922
|
||||
66,7567,True,0.0,0.6088625420886448,0.0014386289682874255,0.012846847568607666,0.13780464834426684,4,3,462.7721539367763,down,False,,0,399,SZ002293,罗莱生活,20250923
|
||||
66,7568,True,0.0,0.5943063720960615,0.001438628968287409,0.012846847568607683,0.13593750381355896,4,3,461.7721539367758,down,False,,0,399,SZ002293,罗莱生活,20250924
|
||||
66,7569,True,0.0,0.779401691676584,0.001438628968287408,0.012846847568607706,0.1340622548671874,4,3,460.77215393677477,down,False,,0,399,SZ002293,罗莱生活,20250925
|
||||
66,7570,True,0.0,0.6719292718069839,0.0014386289682874146,0.012846847568607682,0.13217884862407864,4,3,459.77215393677574,down,False,,0,399,SZ002293,罗莱生活,20250926
|
||||
66,7573,True,0.0,0.5753218252708945,0.0014386289682874257,0.012846847568607685,0.1302872317420841,3,3,458.7721539367756,down,False,,0,399,SZ002293,罗莱生活,20250929
|
||||
66,7574,True,0.0,0.609866349376905,0.0014386289682874122,0.012846847568607675,0.12838735041295837,3,3,457.7721539367755,down,False,,0,399,SZ002293,罗莱生活,20250930
|
||||
66,7583,True,0.0,0.6772917875535255,0.0014386289682874083,0.012846847568607666,0.12647915035725327,3,3,456.77215393677585,down,False,,0,399,SZ002293,罗莱生活,20251009
|
||||
66,7584,True,0.0,0.592145183309513,0.0014386289682874081,0.012846847568607688,0.12456257681914173,3,3,455.7721539367751,down,False,,0,399,SZ002293,罗莱生活,20251010
|
||||
66,7587,True,0.0,0.7134221756240102,0.001438628968287416,0.012846847568607671,0.12263757456119434,3,3,454.7721539367764,down,False,,0,399,SZ002293,罗莱生活,20251013
|
||||
66,7588,True,0.0,0.41235335951789576,0.0014386289682874185,0.012846847568607702,0.12070408785904983,3,3,453.7721539367747,down,True,,0,399,SZ002293,罗莱生活,20251014
|
||||
71,7504,True,0.0,0.44747515924528075,-0.009884494321716465,0.036974787048240726,0.018271588346642564,4,4,406.42604946925445,down,False,,0,399,SZ002544,普天科技,20250722
|
||||
71,7505,True,0.0,0.5160691186050713,-0.00988449432171636,0.03693010257455164,0.01667525960025787,4,4,405.7662576839042,down,False,,0,399,SZ002544,普天科技,20250723
|
||||
74,7647,True,0.0,0.5157653000697646,-0.010131885937242601,0.0030262417102144214,0.00574461989078576,3,3,401.30534667679825,down,True,,0,399,SZ002694,顾地科技,20251212
|
||||
76,7531,True,0.0,0.30026450171817354,0.012823752285427727,0.02392182403474506,0.3816540235129477,3,5,645.2698249397514,down,True,,0,399,SZ002802,洪汇新材,20250818
|
||||
76,7588,True,0.0,0.8814552013450127,0.022507680798543157,0.026585597873910204,0.7137958393332062,4,6,1394.1097119986516,down,False,,0,399,SZ002802,洪汇新材,20251014
|
||||
76,7589,True,0.0,0.8667926167178047,0.022507680798543126,0.026585597873910193,0.713590396676247,4,6,1393.109711998646,down,False,,0,399,SZ002802,洪汇新材,20251015
|
||||
76,7590,True,0.0,0.916560743844301,0.022507680798543178,0.026585597873910193,0.7133846588663236,4,6,1392.1097119986591,down,False,,0,399,SZ002802,洪汇新材,20251016
|
||||
76,7591,True,0.0,0.930455307357907,0.022507680798543178,0.02658559787391018,0.7131786252669173,4,6,1391.1097119986648,down,False,,0,399,SZ002802,洪汇新材,20251017
|
||||
76,7594,True,0.0,0.8658783343829409,0.022507680798543202,0.026585597873910172,0.7129722952396867,4,6,1390.1097119986755,down,False,,0,399,SZ002802,洪汇新材,20251020
|
||||
76,7595,True,0.0,0.833546583051607,0.02250768079854327,0.02658559787391019,0.7127656681444482,4,6,1389.109711998685,down,False,,0,399,SZ002802,洪汇新材,20251021
|
||||
76,7596,True,0.0,0.79599324710783,0.022507680798543237,0.0265855978739102,0.7125587433391712,4,6,1388.1097119986746,down,False,,0,399,SZ002802,洪汇新材,20251022
|
||||
76,7597,True,0.0,0.7607075897821185,0.02250768079854313,0.02658559787391018,0.7123515201799784,4,6,1387.1097119986518,down,False,,0,399,SZ002802,洪汇新材,20251023
|
||||
76,7598,True,0.0,0.7885431976084618,0.02250768079854324,0.026585597873910165,0.7121439980211478,4,6,1386.1097119986862,down,False,,0,399,SZ002802,洪汇新材,20251024
|
||||
76,7601,True,0.0,0.8354567809361753,0.022507680798543216,0.026585597873910197,0.7119361762150554,4,6,1385.1097119986703,down,False,,0,399,SZ002802,洪汇新材,20251027
|
||||
76,7602,True,0.0,0.7793789207887103,0.02250768079854319,0.026585597873910186,0.7117280541122417,4,6,1384.1097119986648,down,False,,0,399,SZ002802,洪汇新材,20251028
|
||||
76,7603,True,0.0,0.8391272771953203,0.022507680798543178,0.02658559787391022,0.7115196310613505,4,6,1383.109711998651,down,False,,0,399,SZ002802,洪汇新材,20251029
|
||||
76,7604,True,0.0,0.9004385937969404,0.022507680798543178,0.026585597873910207,0.7113109064091517,4,6,1382.109711998655,down,False,,0,399,SZ002802,洪汇新材,20251030
|
||||
76,7605,True,0.0,0.8669575533381921,0.022507680798543185,0.026585597873910186,0.7111018795005137,4,6,1381.109711998663,down,False,,0,399,SZ002802,洪汇新材,20251031
|
||||
76,7608,True,0.0,1.0,0.022507680798543185,0.026585597873910207,0.7108925496784072,4,6,1380.1097119986587,down,True,,0,399,SZ002802,洪汇新材,20251103
|
||||
76,7609,True,0.0,1.0,0.022507680798543202,0.026585597873910207,0.7106829162839027,4,6,1379.1097119986616,down,True,,0,399,SZ002802,洪汇新材,20251104
|
||||
76,7610,True,0.0,0.7854414067604016,0.022507680798543175,0.02658559787391022,0.7104729786561501,4,6,1378.1097119986514,down,False,,0,399,SZ002802,洪汇新材,20251105
|
||||
76,7611,True,0.0,0.7547645314423088,0.022507680798543185,0.026585597873910165,0.7102627361323951,4,6,1377.1097119986705,down,False,,0,399,SZ002802,洪汇新材,20251106
|
||||
76,7612,True,0.0,0.7413314230970524,0.02250768079854315,0.026585597873910193,0.7100521880479319,4,6,1376.109711998653,down,False,,0,399,SZ002802,洪汇新材,20251107
|
||||
76,7615,True,0.0,0.7022493739250828,0.022507680798543196,0.0265855978739102,0.7098413337361495,4,6,1375.1097119986623,down,False,,0,399,SZ002802,洪汇新材,20251110
|
||||
76,7616,True,0.0,0.6980092562187568,0.022507680798543175,0.026585597873910165,0.7096301725284747,4,6,1374.109711998668,down,False,,0,399,SZ002802,洪汇新材,20251111
|
||||
76,7617,True,0.0,1.0,0.022507680798543185,0.031787831371767016,0.3871210570318078,4,6,651.0257932302756,down,True,,0,399,SZ002802,洪汇新材,20251112
|
||||
76,7618,True,0.0,0.9640995770778701,0.02250768079854319,0.02896763158573018,0.555250306888399,4,6,897.133839954959,down,False,,0,399,SZ002802,洪汇新材,20251113
|
||||
76,7619,True,0.0,1.0,0.02250768079854319,0.028967631585730177,0.5547540085975837,4,6,896.1338399549595,down,True,,0,399,SZ002802,洪汇新材,20251114
|
||||
76,7622,True,0.0,0.9373873273197865,0.0225076807985432,0.02896763158573017,0.5542566014261339,4,6,895.1338399549629,down,False,,0,399,SZ002802,洪汇新材,20251117
|
||||
76,7623,True,0.0,1.0,0.02250768079854318,0.028967631585730156,0.5537580816535269,4,6,894.1338399549611,down,False,,0,399,SZ002802,洪汇新材,20251118
|
||||
76,7624,True,0.0,1.0,0.022507680798543237,0.02896763158573016,0.553258445542587,4,6,893.1338399549661,down,True,,0,399,SZ002802,洪汇新材,20251119
|
||||
76,7625,True,0.0,1.0,0.022507680798543185,0.02896763158573018,0.5527576893393664,4,6,892.133839954959,down,True,,0,399,SZ002802,洪汇新材,20251120
|
||||
76,7626,True,0.0,1.0,0.022507680798543178,0.028967631585730166,0.5522558092730875,4,6,891.1338399549612,down,True,,0,399,SZ002802,洪汇新材,20251121
|
||||
76,7629,True,0.0,1.0,0.0225076807985432,0.02896763158573015,0.5517528015560137,4,6,890.1338399549649,down,False,,0,399,SZ002802,洪汇新材,20251124
|
||||
76,7630,True,0.0,1.0,0.022507680798543213,0.028967631585730163,0.5512486623833701,4,6,889.133839954963,down,False,,0,399,SZ002802,洪汇新材,20251125
|
||||
76,7631,True,0.0,1.0,0.022507680798543164,0.02896763158573018,0.550743387933247,4,6,888.1338399549577,down,False,,0,399,SZ002802,洪汇新材,20251126
|
||||
76,7632,True,0.0,1.0,0.022507680798543175,0.028967631585730166,0.5502369743665089,4,6,887.1338399549595,down,False,,0,399,SZ002802,洪汇新材,20251127
|
||||
76,7633,True,0.0,1.0,0.022507680798543216,0.02896763158573015,0.549729417826682,4,6,886.1338399549664,down,False,,0,399,SZ002802,洪汇新材,20251128
|
||||
76,7636,True,0.0,1.0,0.022507680798543206,0.02896763158573015,0.5492207144398623,4,6,885.1338399549644,down,False,,0,399,SZ002802,洪汇新材,20251201
|
||||
76,7637,True,0.0,1.0,0.022507680798543164,0.028967631585730163,0.5487108603146253,4,6,884.1338399549588,down,False,,0,399,SZ002802,洪汇新材,20251202
|
||||
76,7638,True,0.0,1.0,0.022507680798543157,0.028967631585730177,0.5481998515419247,4,6,883.133839954957,down,False,,0,399,SZ002802,洪汇新材,20251203
|
||||
76,7639,True,0.0,1.0,0.022507680798543244,0.028967631585730173,0.5476876841949856,4,6,882.1338399549652,down,False,,0,399,SZ002802,洪汇新材,20251204
|
||||
76,7640,True,0.0,1.0,0.022507680798543164,0.028967631585730177,0.5471743543291943,4,6,881.1338399549572,down,False,,0,399,SZ002802,洪汇新材,20251205
|
||||
76,7643,True,0.0,1.0,0.022507680798543206,0.028967631585730177,0.5466598579820343,4,6,880.1338399549616,down,False,,0,399,SZ002802,洪汇新材,20251208
|
||||
76,7644,True,0.0,1.0,0.022507680798543168,0.02896763158573015,0.5461441911729371,3,6,879.1338399549602,down,False,,0,399,SZ002802,洪汇新材,20251209
|
||||
76,7645,True,0.0,1.0,0.02250768079854322,0.028967631585730173,0.5456273499032178,3,6,878.1338399549632,down,False,,0,399,SZ002802,洪汇新材,20251210
|
||||
76,7646,True,0.0,1.0,0.022507680798543223,0.02896763158573017,0.5451093301559472,3,6,877.1338399549651,down,False,,0,399,SZ002802,洪汇新材,20251211
|
||||
79,7540,True,0.0,0.3049811587877833,0.005576126060913147,0.007257854803634511,0.6120102913242108,6,5,1028.3777921888416,down,True,,0,399,SZ002966,苏州银行,20250827
|
||||
79,7542,True,0.0,0.5199983964336468,0.005576126060913126,0.00721350288989616,0.6195219641545565,6,5,1048.680771055287,down,True,,0,399,SZ002966,苏州银行,20250829
|
||||
79,7545,True,0.0,0.3821084457942304,0.005576126060913139,0.006811788613578286,0.6983241909033441,6,5,1322.6118501008543,down,False,,0,399,SZ002966,苏州银行,20250901
|
||||
79,7640,True,0.0,0.3986090480367696,0.0055761260609131355,0.00865467022084684,0.48069656926505083,5,5,768.3369228570494,down,False,,0,399,SZ002966,苏州银行,20251205
|
||||
79,7643,True,0.0,0.4052911179581672,0.005576126060913132,0.008654670220846821,0.48001980862019505,5,5,767.3369228570511,down,False,,0,399,SZ002966,苏州银行,20251208
|
||||
79,7644,True,0.0,0.401201681075587,0.0055761260609131355,0.008654670220846837,0.4793412817531326,5,5,766.3369228570496,down,False,,0,399,SZ002966,苏州银行,20251209
|
||||
79,7645,True,0.0,0.5078368396984874,0.005576126060913144,0.00865467022084683,0.47866098174055294,5,5,765.3369228570498,down,False,,0,399,SZ002966,苏州银行,20251210
|
||||
79,7646,True,0.0,0.4533172515624295,0.005576126060913141,0.008654670220846823,0.47797890162291384,5,5,764.3369228570507,down,False,,0,399,SZ002966,苏州银行,20251211
|
||||
79,7647,True,0.0,0.4492034024484112,0.005576126060913138,0.008654670220846837,0.477295034404196,5,5,763.3369228570482,down,False,,0,399,SZ002966,苏州银行,20251212
|
||||
79,7650,True,0.0,0.4350645190842566,0.0055761260609131485,0.008654670220846835,0.4766093730516883,5,5,762.3369228570533,down,False,,0,399,SZ002966,苏州银行,20251215
|
||||
79,7651,True,0.0,0.4710676756423045,0.005576126060913141,0.008654670220846828,0.4759219104957061,5,5,761.3369228570491,down,False,,0,399,SZ002966,苏州银行,20251216
|
||||
79,7652,True,0.0,0.5645039764457491,0.005576126060913124,0.008886401746133536,0.440989257319531,4,5,713.760880670712,down,False,,0,399,SZ002966,苏州银行,20251217
|
||||
79,7653,True,0.0,0.5300767773457756,0.005576126060913123,0.008819372224955902,0.4500716539084972,4,5,725.5490698666583,down,True,,0,399,SZ002966,苏州银行,20251218
|
||||
79,7654,True,0.0,0.339794178992292,0.005576126060913132,0.00872055308072664,0.46399540899889047,4,5,744.3966090939207,down,True,,0,399,SZ002966,苏州银行,20251219
|
||||
79,7657,True,0.0,0.3548055611234849,0.005576126060913142,0.00872055308072667,0.46327438796590953,4,5,743.3966090939167,down,False,,0,399,SZ002966,苏州银行,20251222
|
||||
79,7658,True,0.0,0.32142406609744645,0.0055761260609131485,0.008748083093436486,0.45842716962233293,4,5,736.743015194755,down,False,,0,399,SZ002966,苏州银行,20251223
|
||||
79,7659,True,0.0,0.48404170469815455,0.00557612606091314,0.009395544641345344,0.362902454366535,4,5,626.2777226731813,down,False,,0,399,SZ002966,苏州银行,20251224
|
||||
79,7660,True,0.0,0.5057919450867131,0.005576126060913144,0.009395544641345357,0.3618835510496057,4,5,625.2777226731815,down,False,,0,399,SZ002966,苏州银行,20251225
|
||||
79,7661,True,0.0,0.512313530956235,0.005576126060913138,0.009395544641345361,0.36086138346973756,4,5,624.277722673181,down,False,,0,399,SZ002966,苏州银行,20251226
|
||||
79,7664,True,0.0,0.6038365358236052,0.005576126060913132,0.009395544641345377,0.3598359359151689,4,5,623.2777226731778,down,False,,0,399,SZ002966,苏州银行,20251229
|
||||
79,7673,True,0.0,0.3644009179846638,0.005576126060913132,0.008091593407860464,0.5548215513226741,4,5,896.269801886126,down,False,,0,399,SZ002966,苏州银行,20260107
|
||||
79,7674,True,0.0,0.5003213960545323,0.005576126060913147,0.008091593407860457,0.5543242951349457,4,5,895.2698018861342,down,True,,0,399,SZ002966,苏州银行,20260108
|
||||
79,7675,True,0.0,0.40050550393334006,0.005576126060913147,0.008091593407860472,0.5538259268528823,4,5,894.2698018861297,down,False,,0,399,SZ002966,苏州银行,20260109
|
||||
79,7678,True,0.0,0.3961404034355945,0.005576126060913131,0.008091593407860479,0.5533264427415802,4,5,893.2698018861247,down,False,,0,399,SZ002966,苏州银行,20260112
|
||||
79,7679,True,0.0,0.39600958201730846,0.005576126060913136,0.008091593407860477,0.5528258390493842,4,5,892.2698018861256,down,True,,0,399,SZ002966,苏州银行,20260113
|
||||
79,7680,True,0.0,0.4719317044989689,0.0055761260609131355,0.008091593407860476,0.5523241120078023,4,5,891.2698018861237,down,True,,0,399,SZ002966,苏州银行,20260114
|
||||
79,7681,True,0.0,0.4512128301695595,0.005576126060913131,0.008091593407860483,0.5518212578314119,4,5,890.2698018861217,down,False,,0,399,SZ002966,苏州银行,20260115
|
||||
79,7682,True,0.0,0.6074172935943191,0.005576126060913131,0.008091593407860472,0.5513172727177644,4,5,889.2698018861246,down,True,,0,399,SZ002966,苏州银行,20260116
|
||||
79,7685,True,0.0,0.5639264870387247,0.005576126060913139,0.00809159340786049,0.5508121528472794,4,5,888.26980188612,down,False,,0,399,SZ002966,苏州银行,20260119
|
||||
79,7686,True,0.0,0.5429769523216124,0.005576126060913151,0.008091593407860484,0.5503058943831733,4,5,887.2698018861257,down,False,,0,399,SZ002966,苏州银行,20260120
|
||||
81,7646,True,0.0,0.7136182739020752,-0.0029386403653002217,0.0028722781592210502,0.19374592604850488,3,3,494.88122031368994,down,True,,0,399,SZ300027,华谊兄弟,20251211
|
||||
81,7647,True,0.0,0.7206299119837777,-0.002938640365300225,0.00287227815922105,0.19211344025882474,3,3,493.8812203136895,down,False,,0,399,SZ300027,华谊兄弟,20251212
|
||||
81,7650,True,0.0,0.8703436101464992,-0.002938640365300228,0.00287227815922105,0.19047433021274238,3,3,492.881220313689,down,False,,0,399,SZ300027,华谊兄弟,20251215
|
||||
81,7651,True,0.0,0.9619941431501848,-0.0029386403653002247,0.002872278159221051,0.1888285555086974,3,3,491.88122031368937,down,False,,0,399,SZ300027,华谊兄弟,20251216
|
||||
81,7652,True,0.0,0.9135536239845717,-0.002938640365300223,0.002872278159221049,0.18717607541591177,3,3,490.8812203136901,down,False,,0,399,SZ300027,华谊兄弟,20251217
|
||||
81,7653,True,0.0,1.0,-0.0029386403653002225,0.0028722781592210524,0.18551684887102762,3,3,489.88122031368914,down,False,,0,399,SZ300027,华谊兄弟,20251218
|
||||
81,7654,True,0.0,1.0,-0.002938640365300223,0.002872278159221052,0.18385083447471604,3,3,488.8812203136893,down,False,,0,399,SZ300027,华谊兄弟,20251219
|
||||
81,7657,True,0.0,1.0,-0.002938640365300226,0.0028722781592210507,0.18217799048822234,3,3,487.88122031368954,down,False,,0,399,SZ300027,华谊兄弟,20251222
|
||||
81,7658,True,0.0,1.0,-0.002938640365300225,0.0028722781592210515,0.18049827482988334,3,3,486.88122031368925,down,False,,0,399,SZ300027,华谊兄弟,20251223
|
||||
81,7659,True,0.0,1.0,-0.002938640365300224,0.0028722781592210485,0.17881164507160502,3,3,485.88122031368977,down,False,,0,399,SZ300027,华谊兄弟,20251224
|
||||
81,7660,True,0.0,0.9868647651284065,-0.002938640365300222,0.002872278159221051,0.1771180584352794,3,3,484.8812203136896,down,False,,0,399,SZ300027,华谊兄弟,20251225
|
||||
81,7661,True,0.0,0.9665083598159075,-0.0029386403653002225,0.0028722781592210502,0.17541747178917821,3,3,483.8812203136899,down,False,,0,399,SZ300027,华谊兄弟,20251226
|
||||
81,7664,True,0.0,1.0,-0.002938640365300223,0.0028722781592210507,0.17370984164428455,3,3,482.88122031369005,down,False,,0,399,SZ300027,华谊兄弟,20251229
|
||||
81,7665,True,0.0,1.0,-0.0029386403653002277,0.0028722781592210533,0.171995124150587,3,3,481.88122031368937,down,True,,0,399,SZ300027,华谊兄弟,20251230
|
||||
81,7666,True,0.0,1.0,-0.00293864036530023,0.00287227815922105,0.17027327509333104,3,3,480.88122031368965,down,False,,0,399,SZ300027,华谊兄弟,20251231
|
||||
81,7671,True,0.0,0.9966540160576123,-0.0029386403653002256,0.002872278159221056,0.16854424988920946,4,3,479.88122031368925,down,False,,0,399,SZ300027,华谊兄弟,20260105
|
||||
81,7672,True,0.0,0.8929129472862346,-0.0029386403653002243,0.002872278159221051,0.16680800358252434,4,3,478.88122031368954,down,False,,0,399,SZ300027,华谊兄弟,20260106
|
||||
81,7673,True,0.0,0.9838962225177941,-0.002938640365300224,0.002872278159221052,0.16506449084128164,4,3,477.8812203136894,down,False,,0,399,SZ300027,华谊兄弟,20260107
|
||||
81,7674,True,0.0,0.8506356045457729,-0.0029386403653002264,0.0028722781592210515,0.16331366595325303,4,3,476.8812203136895,down,False,,0,399,SZ300027,华谊兄弟,20260108
|
||||
81,7675,True,0.0,0.7541880653008863,-0.002938640365300224,0.0028722781592210502,0.16155548282197701,4,3,475.88122031368977,down,False,,0,399,SZ300027,华谊兄弟,20260109
|
||||
81,7678,True,0.0,0.5303187049287906,-0.0029386403653002243,0.0028722781592210467,0.15978989496271348,4,3,474.8812203136896,down,True,,0,399,SZ300027,华谊兄弟,20260112
|
||||
81,7679,True,0.0,0.6534927053731813,-0.002938640365300225,0.0028722781592210537,0.15801685549834885,4,3,473.88122031368954,down,True,,0,399,SZ300027,华谊兄弟,20260113
|
||||
81,7680,True,0.0,0.6251700644538551,-0.0029386403653002308,0.002872278159221048,0.15623631715524647,4,3,472.88122031368954,down,True,,0,399,SZ300027,华谊兄弟,20260114
|
||||
81,7681,True,0.0,0.8328964180701995,-0.0029386403653002316,0.0028722781592210485,0.15444823225904344,4,3,471.88122031368954,down,False,,0,399,SZ300027,华谊兄弟,20260115
|
||||
81,7682,True,0.0,1.0,-0.002938640365300219,0.0028722781592210524,0.15265255273039763,4,3,470.8812203136894,down,False,,0,399,SZ300027,华谊兄弟,20260116
|
||||
81,7685,True,0.0,1.0,-0.002938640365300224,0.00287227815922105,0.15084923008067877,4,3,469.8812203136898,down,False,,0,399,SZ300027,华谊兄弟,20260119
|
||||
81,7686,True,0.0,1.0,-0.0029386403653002303,0.0028722781592210507,0.14903821540759898,4,3,468.88122031368954,down,False,,0,399,SZ300027,华谊兄弟,20260120
|
||||
|
217
outputs/converging_triangles/strong_breakout_up.csv
Normal file
217
outputs/converging_triangles/strong_breakout_up.csv
Normal file
@ -0,0 +1,217 @@
|
||||
stock_idx,date_idx,is_valid,breakout_strength_up,breakout_strength_down,upper_slope,lower_slope,width_ratio,touches_upper,touches_lower,apex_x,breakout_dir,volume_confirmed,false_breakout,window_start,window_end,stock_code,stock_name,date
|
||||
7,7513,True,0.67545492083845,0.0,-0.0064824070003627636,-0.0017516195268630443,0.13992019271533362,7,7,463.910437869332,up,False,,0,399,SH600395,盘江股份,20250731
|
||||
7,7514,True,0.5014037792263863,0.0,-0.00610081908712438,-0.0017516195268630348,0.19253184038261748,7,7,494.1371312883291,up,False,,0,399,SH600395,盘江股份,20250801
|
||||
7,7517,True,0.5866530624042235,0.0,-0.00610081908712439,-0.001751619526863032,0.19089442939004256,7,7,493.13713128832774,up,False,,0,399,SH600395,盘江股份,20250804
|
||||
7,7518,True,0.6425971936880107,0.0,-0.006100819087124379,-0.0017516195268630296,0.18925036410992296,7,7,492.1371312883294,up,False,,0,399,SH600395,盘江股份,20250805
|
||||
7,7519,True,0.7718276324260158,0.0,-0.00610081908712437,-0.0017516195268630394,0.18759960389604377,7,7,491.13713128832995,up,False,,0,399,SH600395,盘江股份,20250806
|
||||
7,7520,True,0.8283390955827922,0.0,-0.006100819087124363,-0.0017516195268630374,0.18594210777048306,7,7,490.1371312883302,up,False,,0,399,SH600395,盘江股份,20250807
|
||||
7,7521,True,0.7933761537617943,0.0,-0.006100819087124363,-0.0017516195268630285,0.1842778344202149,7,7,489.1371312883298,up,False,,0,399,SH600395,盘江股份,20250808
|
||||
7,7524,True,0.7605759655948915,0.0,-0.0061008190871243775,-0.0017516195268630346,0.18260674219367776,7,7,488.13713128832944,up,False,,0,399,SH600395,盘江股份,20250811
|
||||
7,7525,True,0.8150907029599076,0.0,-0.006100819087124376,-0.0017516195268630413,0.1809287890972997,7,7,487.13713128832984,up,False,,0,399,SH600395,盘江股份,20250812
|
||||
7,7526,True,0.7660677939643149,0.0,-0.006100819087124347,-0.001751619526863045,0.17924393279197945,7,7,486.13713128833126,up,False,,0,399,SH600395,盘江股份,20250813
|
||||
7,7527,True,0.6082883766066589,0.0,-0.006100819087124367,-0.0017516195268630352,0.17755213058951716,7,7,485.1371312883292,up,False,,0,399,SH600395,盘江股份,20250814
|
||||
7,7528,True,0.6952269660063294,0.0,-0.006100819087124357,-0.001751619526863038,0.17585333944903955,7,7,484.1371312883309,up,False,,0,399,SH600395,盘江股份,20250815
|
||||
7,7531,True,0.6402192413778414,0.0,-0.00610081908712436,-0.0017516195268630246,0.1741475159733007,7,7,483.1371312883291,up,False,,0,399,SH600395,盘江股份,20250818
|
||||
7,7532,True,0.5936328002843243,0.0,-0.006100819087124368,-0.0017516195268630482,0.17243461640504276,7,7,482.1371312883313,up,False,,0,399,SH600395,盘江股份,20250819
|
||||
7,7533,True,0.6810501936156625,0.0,-0.006100819087124385,-0.0017516195268630385,0.17071459662319868,7,7,481.1371312883302,up,False,,0,399,SH600395,盘江股份,20250820
|
||||
7,7534,True,0.7384864997795033,0.0,-0.006100819087124385,-0.0017516195268630394,0.16898741213914664,7,7,480.1371312883283,up,False,,0,399,SH600395,盘江股份,20250821
|
||||
7,7535,True,0.7026672943734948,0.0,-0.006100819087124371,-0.0017516195268630385,0.167253018092864,7,7,479.13713128833007,up,False,,0,399,SH600395,盘江股份,20250822
|
||||
7,7538,True,0.9682162519993583,0.0,-0.006100819087124362,-0.0017516195268630409,0.1655113692490228,7,7,478.13713128833126,up,False,,0,399,SH600395,盘江股份,20250825
|
||||
7,7539,True,0.945320131945306,0.0,-0.006100819087124365,-0.0017516195268630355,0.16376241999307573,7,7,477.137131288331,up,False,,0,399,SH600395,盘江股份,20250826
|
||||
7,7540,True,1.0,0.0,-0.006100819087124372,-0.0017516195268630404,0.16200612432727504,7,7,476.1371312883292,up,True,,0,399,SH600395,盘江股份,20250827
|
||||
7,7541,True,1.0,0.0,-0.006100819087124353,-0.0017516195268630404,0.16024243586664333,7,7,475.13713128833155,up,True,,0,399,SH600395,盘江股份,20250828
|
||||
7,7542,True,1.0,0.0,-0.006100819087124367,-0.0017516195268630415,0.15847130783485947,7,7,474.1371312883301,up,False,,0,399,SH600395,盘江股份,20250829
|
||||
7,7545,True,1.0,0.0,-0.006100819087124387,-0.0017516195268630374,0.15669269306016428,7,7,473.1371312883289,up,True,,0,399,SH600395,盘江股份,20250901
|
||||
7,7546,True,1.0,0.0,-0.006770994127622311,-0.001751619526863042,0.05101111595516812,6,7,420.4474959699849,up,True,,0,399,SH600395,盘江股份,20250902
|
||||
7,7547,True,1.0,0.0,-0.0067709941276223274,-0.0017516195268630368,0.048748642360347046,6,6,419.44749596998383,up,False,,0,399,SH600395,盘江股份,20250903
|
||||
7,7549,True,1.0,0.0,-0.006884994267086155,-0.0017516195268630285,0.02682451791230067,6,6,409.9979986590367,up,False,,0,399,SH600395,盘江股份,20250905
|
||||
7,7552,True,1.0,0.0,-0.006884994267086156,-0.0017516195268630387,0.024445104112531972,6,6,408.9979986590373,up,False,,0,399,SH600395,盘江股份,20250908
|
||||
7,7553,True,1.0,0.0,-0.006884994267086149,-0.0017516195268630365,0.022054026462411398,6,6,407.9979986590372,up,False,,0,399,SH600395,盘江股份,20250909
|
||||
7,7554,True,1.0,0.0,-0.0068849942670861615,-0.0017516195268630359,0.019651198987191292,6,6,406.99799865903753,up,False,,0,399,SH600395,盘江股份,20250910
|
||||
7,7555,True,1.0,0.0,-0.006884994267086157,-0.0017516195268630426,0.017236534865074465,6,6,405.99799865903674,up,False,,0,399,SH600395,盘江股份,20250911
|
||||
7,7556,True,1.0,0.0,-0.006884994267086161,-0.0017516195268630391,0.014809946416764898,6,6,404.9979986590373,up,False,,0,399,SH600395,盘江股份,20250912
|
||||
7,7559,True,1.0,0.0,-0.006884994267086135,-0.0017516195268630446,0.012371345094847467,6,6,403.99799865903867,up,False,,0,399,SH600395,盘江股份,20250915
|
||||
7,7560,True,1.0,0.0,-0.006941129476498381,-0.0017516195268630307,0.001126615502093559,6,6,399.4500265922705,up,False,,0,399,SH600395,盘江股份,20250916
|
||||
8,7436,True,0.37564020510004503,0.0,-0.006975218774545214,0.002369261228607383,0.25330946486824296,7,6,534.3579183437682,up,True,,0,399,SH600475,华光环能,20250515
|
||||
8,7440,True,0.5628308941109345,0.0,-0.006975218774545257,0.002369261228607388,0.2505042448859588,7,6,532.3579183437661,up,True,,0,399,SH600475,华光环能,20250519
|
||||
8,7441,True,0.34511406701154457,0.0,-0.006975218774545259,0.0023692612286073827,0.24909371588236337,7,6,531.3579183437662,up,False,,0,399,SH600475,华光环能,20250520
|
||||
8,7442,True,0.3156597148763882,0.0,-0.006975218774545253,0.0023692612286073823,0.24767786771993353,7,6,530.357918343767,up,False,,0,399,SH600475,华光环能,20250521
|
||||
10,7189,True,0.3495425827469817,0.0,-0.05646269747901303,-0.03331758596698719,0.05780193866680936,5,4,423.4778401426801,up,False,,0,399,SH600588,用友网络,20240910
|
||||
10,7190,True,0.4642979575956762,0.0,-0.05646269747901302,-0.03331758596698724,0.0555717671126885,5,4,422.4778401426807,up,False,,0,399,SH600588,用友网络,20240911
|
||||
10,7191,True,0.4646514371704794,0.0,-0.056462697479013065,-0.03331758596698722,0.053331012930763776,5,4,421.47784014267967,up,False,,0,399,SH600588,用友网络,20240912
|
||||
10,7192,True,0.4197405584119481,0.0,-0.056462697479013016,-0.03331758596698719,0.051079600616746434,5,4,420.47784014268035,up,False,,0,399,SH600588,用友网络,20240913
|
||||
10,7197,True,0.4380902497530603,0.0,-0.056462697479013065,-0.033317585966987216,0.048817453946351924,5,4,419.47784014268046,up,False,,0,399,SH600588,用友网络,20240918
|
||||
10,7198,True,0.720013372130211,0.0,-0.05646269747901298,-0.03331758596698722,0.04654449596671506,5,4,418.4778401426807,up,False,,0,399,SH600588,用友网络,20240919
|
||||
10,7199,True,0.7890238735090407,0.0,-0.056462697479013016,-0.033317585966987244,0.04426064898765742,5,4,417.4778401426806,up,False,,0,399,SH600588,用友网络,20240920
|
||||
10,7202,True,0.7942019662843189,0.0,-0.056462697479013065,-0.03331758596698722,0.041965834572835085,5,4,416.4778401426793,up,False,,0,399,SH600588,用友网络,20240923
|
||||
10,7203,True,1.0,0.0,-0.056462697479013065,-0.033317585966987244,0.03965997353077074,5,4,415.4778401426805,up,True,,0,399,SH600588,用友网络,20240924
|
||||
10,7204,True,1.0,0.0,-0.05646269747901294,-0.033317585966987244,0.03734298590571914,5,4,414.47784014268103,up,True,,0,399,SH600588,用友网络,20240925
|
||||
10,7205,True,1.0,0.0,-0.05646269747901304,-0.0333175859669872,0.03501479096844584,5,4,413.4778401426804,up,True,,0,399,SH600588,用友网络,20240926
|
||||
10,7206,True,1.0,0.0,-0.05646269747901302,-0.033317585966987244,0.032675307206851884,5,5,412.4778401426808,up,True,,0,399,SH600588,用友网络,20240927
|
||||
10,7209,True,1.0,0.0,-0.05646269747901308,-0.03331758596698723,0.030324452316443005,5,5,411.4778401426797,up,True,,0,399,SH600588,用友网络,20240930
|
||||
10,7217,True,1.0,0.0,-0.05646269747901303,-0.03331758596698722,0.02796214319070376,5,5,410.47784014268046,up,True,,0,399,SH600588,用友网络,20241008
|
||||
10,7218,True,1.0,0.0,-0.05646269747901299,-0.033317585966987265,0.02558829591127563,5,5,409.47784014268086,up,True,,0,399,SH600588,用友网络,20241009
|
||||
10,7219,True,1.0,0.0,-0.056462697479012995,-0.03331758596698723,0.023202825738038363,5,5,408.47784014268086,up,True,,0,399,SH600588,用友网络,20241010
|
||||
10,7220,True,1.0,0.0,-0.0564626974790131,-0.03331758596698721,0.020805647099019473,5,5,407.47784014267927,up,True,,0,399,SH600588,用友网络,20241011
|
||||
10,7223,True,1.0,0.0,-0.05646269747901304,-0.03331758596698725,0.018396673580175972,5,5,406.4778401426799,up,False,,0,399,SH600588,用友网络,20241014
|
||||
10,7224,True,1.0,0.0,-0.056462697479013065,-0.0333175859669872,0.015975817914982437,4,5,405.4778401426798,up,False,,0,399,SH600588,用友网络,20241015
|
||||
10,7225,True,1.0,0.0,-0.05646269747901303,-0.03331758596698725,0.013542991973919075,4,5,404.47784014268046,up,False,,0,399,SH600588,用友网络,20241016
|
||||
10,7226,True,1.0,0.0,-0.05362149584094403,-0.0333175859669872,0.12798154200289816,3,5,457.55912198973897,up,False,,0,399,SH600588,用友网络,20241017
|
||||
10,7227,True,1.0,0.0,-0.05362149584094404,-0.03331758596698726,0.12607156273406692,3,5,456.55912198973994,up,True,,0,399,SH600588,用友网络,20241018
|
||||
10,7230,True,1.0,0.0,-0.053621495840944076,-0.03331758596698722,0.12415319825603872,3,5,455.55912198973897,up,True,,0,399,SH600588,用友网络,20241021
|
||||
10,7231,True,1.0,0.0,-0.053621495840944056,-0.03331758596698723,0.1222263932281027,3,5,454.5591219897389,up,False,,0,399,SH600588,用友网络,20241022
|
||||
10,7232,True,1.0,0.0,-0.053621495840944056,-0.03331758596698724,0.12029109182148337,3,5,453.55912198973914,up,True,,0,399,SH600588,用友网络,20241023
|
||||
10,7233,True,1.0,0.0,-0.05362149584094408,-0.033317585966987175,0.11834723771395339,3,5,452.5591219897375,up,False,,0,399,SH600588,用友网络,20241024
|
||||
10,7234,True,1.0,0.0,-0.053621495840944035,-0.03331758596698726,0.11639477408438692,3,5,451.55912198973994,up,False,,0,399,SH600588,用友网络,20241025
|
||||
10,7237,True,1.0,0.0,-0.05362149584094399,-0.033317585966987244,0.11443364360718429,3,5,450.55912198973977,up,False,,0,399,SH600588,用友网络,20241028
|
||||
10,7238,True,1.0,0.0,-0.053621495840944014,-0.033317585966987216,0.11246378844670345,3,5,449.55912198973977,up,False,,0,399,SH600588,用友网络,20241029
|
||||
10,7239,True,1.0,0.0,-0.05362149584094402,-0.03331758596698724,0.11048515025155105,3,5,448.5591219897403,up,False,,0,399,SH600588,用友网络,20241030
|
||||
10,7240,True,1.0,0.0,-0.053621495840944035,-0.03331758596698726,0.10849767014882356,3,5,447.55912198973994,up,True,,0,399,SH600588,用友网络,20241031
|
||||
10,7241,True,1.0,0.0,-0.05362149584094402,-0.03331758596698721,0.10650128873827343,3,5,446.5591219897391,up,False,,0,399,SH600588,用友网络,20241101
|
||||
10,7244,True,1.0,0.0,-0.05362149584094406,-0.03331758596698724,0.1044959460863883,3,5,445.5591219897383,up,False,,0,399,SH600588,用友网络,20241104
|
||||
15,7234,True,0.3016274677379567,0.0,-0.011244246545276426,-0.008643940786697432,0.6886021064725732,8,7,1281.3188794575378,up,True,,0,399,SH600846,同济科技,20241025
|
||||
15,7237,True,0.3931221270268056,0.0,-0.011244246545276443,-0.008643940786697422,0.6883588874600859,8,7,1280.318879457527,up,False,,0,399,SH600846,同济科技,20241028
|
||||
15,7239,True,0.3550840232451056,0.0,-0.011244246545276455,-0.008643940786697425,0.6878713078466598,8,6,1278.318879457523,up,False,,0,399,SH600846,同济科技,20241030
|
||||
15,7240,True,0.6807522047295599,0.0,-0.011244246545276464,-0.008643940786697427,0.6876269454582424,8,6,1277.318879457519,up,True,,0,399,SH600846,同济科技,20241031
|
||||
15,7241,True,0.32412098239502585,0.0,-0.01124424654527643,-0.008643940786697429,0.687382200152376,8,6,1276.3188794575365,up,False,,0,399,SH600846,同济科技,20241101
|
||||
15,7244,True,0.3603237471754106,0.0,-0.011244246545276481,-0.008643940786697429,0.6871370710282872,8,6,1275.3188794575124,up,False,,0,399,SH600846,同济科技,20241104
|
||||
15,7245,True,0.5151128972048112,0.0,-0.011244246545276462,-0.008643940786697429,0.6868915571824105,8,6,1274.3188794575206,up,False,,0,399,SH600846,同济科技,20241105
|
||||
15,7246,True,0.7516468878024695,0.0,-0.011244246545276428,-0.008643940786697443,0.6866456577083186,8,6,1273.318879457545,up,True,,0,399,SH600846,同济科技,20241106
|
||||
15,7247,True,0.9866291073444275,0.0,-0.011244246545276407,-0.008643940786697425,0.6863993716967287,8,6,1272.3188794575444,up,True,,0,399,SH600846,同济科技,20241107
|
||||
15,7248,True,0.8093657164512214,0.0,-0.011244246545276448,-0.008643940786697448,0.6861526982355124,8,6,1271.3188794575378,up,True,,0,399,SH600846,同济科技,20241108
|
||||
15,7251,True,0.7510571182355403,0.0,-0.011244246545276452,-0.008643940786697429,0.6859056364096648,8,6,1270.318879457527,up,False,,0,399,SH600846,同济科技,20241111
|
||||
15,7252,True,0.6274273698035664,0.0,-0.011244246545276438,-0.008643940786697434,0.6856581853013013,8,6,1269.3188794575337,up,False,,0,399,SH600846,同济科技,20241112
|
||||
15,7253,True,1.0,0.0,-0.011244246545276476,-0.008643940786697427,0.6854103439896282,8,6,1268.318879457516,up,True,,0,399,SH600846,同济科技,20241113
|
||||
15,7254,True,0.8317803173045203,0.0,-0.011244246545276428,-0.008643940786697437,0.6851621115509721,8,6,1267.3188794575387,up,True,,0,399,SH600846,同济科技,20241114
|
||||
15,7255,True,0.6067116330103267,0.0,-0.01124424654527643,-0.00864394078669742,0.6849134870587067,8,6,1266.3188794575324,up,False,,0,399,SH600846,同济科技,20241115
|
||||
15,7258,True,0.5623503939643885,0.0,-0.011244246545276464,-0.008643940786697434,0.684664469583302,8,6,1265.318879457523,up,False,,0,399,SH600846,同济科技,20241118
|
||||
15,7259,True,0.6493881838749089,0.0,-0.01124424654527647,-0.00864394078669742,0.6844150581922815,8,6,1264.3188794575153,up,False,,0,399,SH600846,同济科技,20241119
|
||||
15,7260,True,0.775405496930939,0.0,-0.011244246545276435,-0.00864394078669742,0.6841652519502199,8,6,1263.3188794575317,up,False,,0,399,SH600846,同济科技,20241120
|
||||
15,7261,True,0.8396907373786369,0.0,-0.011244246545276433,-0.008643940786697444,0.6839150499187145,8,6,1262.3188794575383,up,False,,0,399,SH600846,同济科技,20241121
|
||||
15,7262,True,0.7181121380587856,0.0,-0.011244246545276443,-0.008643940786697437,0.6836644511563954,8,6,1261.3188794575349,up,False,,0,399,SH600846,同济科技,20241122
|
||||
15,7265,True,1.0,0.0,-0.011244246545276467,-0.00864394078669744,0.6834134547189034,8,6,1260.3188794575221,up,True,,0,399,SH600846,同济科技,20241125
|
||||
15,7266,True,0.85518789855043,0.0,-0.011244246545276445,-0.008643940786697411,0.683162059658887,8,6,1259.3188794575224,up,False,,0,399,SH600846,同济科技,20241126
|
||||
15,7267,True,0.896822898942957,0.0,-0.011244246545276426,-0.008643940786697418,0.6829102650259757,8,6,1258.318879457533,up,False,,0,399,SH600846,同济科技,20241127
|
||||
15,7268,True,1.0,0.0,-0.011847736126313041,-0.008643940786697432,0.6110898976039021,8,6,1025.944035759775,up,False,,0,399,SH600846,同济科技,20241128
|
||||
19,7192,True,1.0,0.0,-0.01394341851826544,-0.005088782679772442,0.08435904906489626,4,7,435.76032678804825,up,True,,0,399,SH601236,红塔证券,20240913
|
||||
19,7197,True,0.9314402673331492,0.0,-0.013943418518265418,-0.005088782679772455,0.08225296694443675,4,7,434.76032678805,up,True,,0,399,SH601236,红塔证券,20240918
|
||||
19,7198,True,0.9949835360639058,0.0,-0.013943418518265449,-0.005088782679772456,0.08013717401368962,4,7,433.76032678804864,up,False,,0,399,SH601236,红塔证券,20240919
|
||||
19,7199,True,0.9411948419183233,0.0,-0.013943418518265464,-0.005088782679772459,0.07801160295496003,4,7,432.7603267880479,up,False,,0,399,SH601236,红塔证券,20240920
|
||||
19,7202,True,0.9197292794585771,0.0,-0.013943418518265461,-0.005088782679772451,0.07587618582689012,4,7,431.76032678804876,up,False,,0,399,SH601236,红塔证券,20240923
|
||||
19,7203,True,1.0,0.0,-0.01394341851826546,-0.005088782679772451,0.07373085405721537,4,7,430.76032678804796,up,True,,0,399,SH601236,红塔证券,20240924
|
||||
19,7204,True,1.0,0.0,-0.013943418518265414,-0.005088782679772451,0.07157553843544934,4,7,429.7603267880493,up,True,,0,399,SH601236,红塔证券,20240925
|
||||
19,7205,True,1.0,0.0,-0.013943418518265454,-0.00508878267977244,0.06941016910540763,4,7,428.76032678804825,up,True,,0,399,SH601236,红塔证券,20240926
|
||||
19,7206,True,1.0,0.0,-0.013943418518265421,-0.0050887826797724545,0.06723467555769695,4,7,427.760326788049,up,True,,0,399,SH601236,红塔证券,20240927
|
||||
26,7217,True,1.0,0.0,-0.035537144423966494,-0.015461582575024448,0.09272658455977499,4,5,439.7792255451442,up,True,,0,399,SH603237,五芳斋,20241008
|
||||
26,7218,True,1.0,0.0,-0.035537144423966514,-0.015461582575024436,0.09065886265632091,4,5,438.7792255451441,up,True,,0,399,SH603237,五芳斋,20241009
|
||||
26,7219,True,1.0,0.0,-0.035537144423966494,-0.015461582575024432,0.08858169433886197,4,5,437.7792255451436,up,False,,0,399,SH603237,五芳斋,20241010
|
||||
26,7220,True,1.0,0.0,-0.0355371444239665,-0.015461582575024417,0.08649501472509565,4,5,436.77922554514305,up,False,,0,399,SH603237,五芳斋,20241011
|
||||
26,7223,True,1.0,0.0,-0.03553714442396656,-0.015461582575024446,0.08439875833716932,4,5,435.7792255451434,up,False,,0,399,SH603237,五芳斋,20241014
|
||||
26,7224,True,1.0,0.0,-0.0355371444239665,-0.015461582575024434,0.08229285909482584,4,5,434.779225545144,up,False,,0,399,SH603237,五芳斋,20241015
|
||||
26,7225,True,1.0,0.0,-0.03553714442396645,-0.015461582575024424,0.08017725030846337,4,5,433.7792255451444,up,False,,0,399,SH603237,五芳斋,20241016
|
||||
26,7226,True,1.0,0.0,-0.03553714442396651,-0.015461582575024438,0.07805186467209628,4,5,432.7792255451443,up,False,,0,399,SH603237,五芳斋,20241017
|
||||
26,7227,True,1.0,0.0,-0.035537144423966494,-0.015461582575024448,0.07591663425621831,4,5,431.7792255451439,up,False,,0,399,SH603237,五芳斋,20241018
|
||||
26,7230,True,1.0,0.0,-0.03553714442396647,-0.01546158257502444,0.07377149050056545,4,5,430.7792255451446,up,False,,0,399,SH603237,五芳斋,20241021
|
||||
26,7231,True,1.0,0.0,-0.03553714442396652,-0.015461582575024429,0.07161636420676694,4,5,429.7792255451432,up,False,,0,399,SH603237,五芳斋,20241022
|
||||
26,7232,True,1.0,0.0,-0.035537144423966514,-0.015461582575024434,0.069451185530928,4,5,428.77922554514333,up,False,,0,399,SH603237,五芳斋,20241023
|
||||
26,7233,True,1.0,0.0,-0.03553714442396647,-0.015461582575024432,0.06727588397606044,4,5,427.7792255451441,up,False,,0,399,SH603237,五芳斋,20241024
|
||||
26,7234,True,1.0,0.0,-0.03553714442396652,-0.015461582575024432,0.06509038838443898,4,5,426.77922554514345,up,False,,0,399,SH603237,五芳斋,20241025
|
||||
26,7237,True,1.0,0.0,-0.0355371444239665,-0.015461582575024436,0.0628946269298536,4,5,425.77922554514385,up,False,,0,399,SH603237,五芳斋,20241028
|
||||
26,7238,True,1.0,0.0,-0.035239875330401585,-0.015461582575024401,0.06757660631606102,4,5,427.9171915920934,up,False,,0,399,SH603237,五芳斋,20241029
|
||||
26,7239,True,1.0,0.0,-0.03523987533040165,-0.015461582575024436,0.0653925214114257,4,5,426.91719159209157,up,False,,0,399,SH603237,五芳斋,20241030
|
||||
26,7240,True,1.0,0.0,-0.035689432557662014,-0.015461582575024436,0.052723201132233354,4,5,421.20740260598075,up,False,,0,399,SH603237,五芳斋,20241031
|
||||
26,7241,True,1.0,0.0,-0.03568943255766204,-0.01546158257502444,0.05046889339516289,4,5,420.2074026059795,up,False,,0,399,SH603237,五芳斋,20241101
|
||||
26,7244,True,1.0,0.0,-0.03568943255766202,-0.015461582575024424,0.04820383056301299,4,5,419.2074026059794,up,False,,0,399,SH603237,五芳斋,20241104
|
||||
31,7209,True,1.0,0.0,-0.016512059367761185,-0.0029534986161510917,0.32637011830059476,4,8,592.3133917299208,up,True,,0,399,SH603707,健友股份,20240930
|
||||
31,7217,True,1.0,0.0,-0.016512059367761157,-0.0029534986161511103,0.3252309087188055,4,8,591.3133917299212,up,True,,0,399,SH603707,健友股份,20241008
|
||||
31,7218,True,0.7835693695084364,0.0,-0.015369589765832078,-0.002953498616151125,0.36257106053855814,5,8,625.952126266987,up,True,,0,399,SH603707,健友股份,20241009
|
||||
31,7219,True,0.6836003777454753,0.0,-0.015369589765832088,-0.0029534986161511125,0.3615510961082117,5,8,624.9521262669864,up,True,,0,399,SH603707,健友股份,20241010
|
||||
31,7223,True,0.4967766086969954,0.0,-0.012395201386512907,-0.002953498616151112,0.4723571584856211,5,8,756.1933349741594,up,True,,0,399,SH603707,健友股份,20241014
|
||||
31,7225,True,0.520728908407892,0.0,-0.0123141530628176,-0.0029534986161511246,0.46431541771767765,5,8,744.8412987733045,up,False,,0,399,SH603707,健友股份,20241016
|
||||
62,7209,True,1.0,0.0,-0.014496016681320958,-0.008795821910708039,0.115273801749127,4,2,450.98698420916384,up,True,,0,399,SZ002092,中泰化学,20240930
|
||||
62,7217,True,1.0,0.0,-0.014405906485954348,-0.008795821910708034,0.1271407707735231,4,2,457.11838362938744,up,True,,0,399,SZ002092,中泰化学,20241008
|
||||
62,7218,True,0.8196211352221012,0.0,-0.014405906485954354,-0.008795821910708039,0.12522710261070671,4,2,456.118383629387,up,True,,0,399,SZ002092,中泰化学,20241009
|
||||
62,7219,True,0.605332578529142,0.0,-0.014383791186405155,-0.008795821910708029,0.1267307548940409,4,2,456.90375818924775,up,True,,0,399,SZ002092,中泰化学,20241010
|
||||
62,7220,True,0.4127918088917221,0.0,-0.014361248218845074,-0.008795821910708034,0.12832281395954842,4,2,457.7382618127667,up,True,,0,399,SZ002092,中泰化学,20241011
|
||||
62,7223,True,0.5522845815946182,0.0,-0.014331843233762213,-0.008795821910708029,0.1310098247410537,4,2,459.15363758986564,up,False,,0,399,SZ002092,中泰化学,20241014
|
||||
62,7224,True,0.5929605297642843,0.0,-0.014360672264414254,-0.008795821910708039,0.12458787786471003,4,2,455.78532660339016,up,False,,0,399,SZ002092,中泰化学,20241015
|
||||
62,7225,True,0.8917667410947838,0.0,-0.014489010804724365,-0.008795821910708034,0.10242953156612827,4,2,444.5333419850546,up,False,,0,399,SZ002092,中泰化学,20241016
|
||||
64,7483,True,0.400776777418149,0.0,-0.08595126480191613,-0.012801850499569868,0.13870889732657127,5,3,463.25800738160746,up,False,,0,399,SZ002192,融捷股份,20250701
|
||||
64,7484,True,0.8528651824968043,0.0,-0.08595126480191609,-0.012801850499569882,0.13684567140312737,5,3,462.258007381608,up,True,,0,399,SZ002192,融捷股份,20250702
|
||||
64,7485,True,1.0,0.0,-0.08595126480191612,-0.01280185049956988,0.13497436659153847,5,3,461.25800738160774,up,True,,0,399,SZ002192,融捷股份,20250703
|
||||
64,7486,True,0.6441361042136763,0.0,-0.08595126480191609,-0.01280185049956993,0.13309493023294142,4,3,460.2580073816076,up,False,,0,399,SZ002192,融捷股份,20250704
|
||||
64,7489,True,0.5488242470836775,0.0,-0.08595126480191613,-0.012801850499569938,0.13120730920982612,4,3,459.2580073816071,up,False,,0,399,SZ002192,融捷股份,20250707
|
||||
64,7490,True,0.8760498315009965,0.0,-0.08595126480191614,-0.012801850499569929,0.12931144994103963,4,3,458.2580073816073,up,False,,0,399,SZ002192,融捷股份,20250708
|
||||
64,7491,True,0.8535171797437157,0.0,-0.08595126480191609,-0.012801850499569896,0.12740729837670864,4,3,457.2580073816076,up,False,,0,399,SZ002192,融捷股份,20250709
|
||||
64,7492,True,0.9046405855276776,0.0,-0.08595126480191619,-0.012801850499569877,0.12549479999310442,4,3,456.2580073816072,up,False,,0,399,SZ002192,融捷股份,20250710
|
||||
64,7493,True,0.9507433325362438,0.0,-0.08595126480191621,-0.012801850499569955,0.1235738997874457,4,3,455.2580073816069,up,False,,0,399,SZ002192,融捷股份,20250711
|
||||
64,7496,True,1.0,0.0,-0.08595126480191607,-0.01280185049956989,0.12164454227261871,4,3,454.25800738160746,up,True,,0,399,SZ002192,融捷股份,20250714
|
||||
64,7497,True,1.0,0.0,-0.0859512648019162,-0.01280185049956984,0.11970667147183192,4,3,453.2580073816072,up,True,,0,399,SZ002192,融捷股份,20250715
|
||||
64,7498,True,1.0,0.0,-0.08595126480191616,-0.012801850499569854,0.11776023091321135,4,3,452.2580073816069,up,False,,0,399,SZ002192,融捷股份,20250716
|
||||
64,7499,True,1.0,0.0,-0.08595126480191619,-0.012801850499569896,0.11580516362430987,4,3,451.2580073816071,up,True,,0,399,SZ002192,融捷股份,20250717
|
||||
64,7500,True,1.0,0.0,-0.08595126480191607,-0.012801850499569898,0.11384141212654748,4,3,450.2580073816076,up,True,,0,399,SZ002192,融捷股份,20250718
|
||||
64,7503,True,1.0,0.0,-0.08595126480191613,-0.012801850499569958,0.11186891842957686,4,3,449.2580073816073,up,False,,0,399,SZ002192,融捷股份,20250721
|
||||
64,7504,True,1.0,0.0,-0.08595126480191614,-0.012801850499569892,0.10988762402558305,4,3,448.25800738160706,up,True,,0,399,SZ002192,融捷股份,20250722
|
||||
64,7505,True,1.0,0.0,-0.08595126480191617,-0.012801850499569894,0.1078974698834905,4,3,447.25800738160694,up,True,,0,399,SZ002192,融捷股份,20250723
|
||||
64,7506,True,1.0,0.0,-0.08595126480191619,-0.01280185049956985,0.10589839644310392,4,3,446.258007381607,up,True,,0,399,SZ002192,融捷股份,20250724
|
||||
64,7507,True,1.0,0.0,-0.08595126480191621,-0.012801850499569887,0.10389034360916403,4,3,445.2580073816069,up,True,,0,399,SZ002192,融捷股份,20250725
|
||||
64,7510,True,1.0,0.0,-0.08595126480191609,-0.012801850499569912,0.10187325074533099,4,3,444.25800738160757,up,False,,0,399,SZ002192,融捷股份,20250728
|
||||
64,7511,True,1.0,0.0,-0.0859512648019161,-0.01280185049956992,0.0998470566680707,4,3,443.2580073816074,up,False,,0,399,SZ002192,融捷股份,20250729
|
||||
64,7512,True,1.0,0.0,-0.08595126480191617,-0.012801850499569938,0.09781169964047973,4,3,442.25800738160694,up,False,,0,399,SZ002192,融捷股份,20250730
|
||||
64,7513,True,1.0,0.0,-0.08595126480191598,-0.012801850499569932,0.09576711736601441,4,3,441.25800738160814,up,False,,0,399,SZ002192,融捷股份,20250731
|
||||
64,7514,True,1.0,0.0,-0.08595126480191617,-0.01280185049956987,0.0937132469821169,4,3,440.2580073816072,up,False,,0,399,SZ002192,融捷股份,20250801
|
||||
64,7517,True,1.0,0.0,-0.0859512648019163,-0.0128018504995699,0.09165002505380083,4,3,439.25800738160694,up,False,,0,399,SZ002192,融捷股份,20250804
|
||||
64,7518,True,1.0,0.0,-0.08595126480191623,-0.012801850499569885,0.08957738756710058,4,3,438.25800738160746,up,False,,0,399,SZ002192,融捷股份,20250805
|
||||
64,7519,True,1.0,0.0,-0.08595126480191609,-0.01280185049956987,0.08749526992245224,4,3,437.25800738160734,up,False,,0,399,SZ002192,融捷股份,20250806
|
||||
64,7520,True,1.0,0.0,-0.0859512648019161,-0.01280185049956993,0.08540360692799055,4,3,436.2580073816072,up,False,,0,399,SZ002192,融捷股份,20250807
|
||||
64,7521,True,1.0,0.0,-0.08595126480191616,-0.012801850499569903,0.08330233279274014,4,3,435.2580073816076,up,False,,0,399,SZ002192,融捷股份,20250808
|
||||
65,7261,True,0.3431981853858018,0.0,-0.018312301342076214,-0.015522699700520918,0.6925151752667238,4,6,1297.6250140022605,up,False,,0,399,SZ002242,九阳股份,20241121
|
||||
65,7596,True,0.41348555528091463,0.0,-0.010701462581972621,7.155005218963802e-05,0.09682104242194266,7,4,441.7729140523254,up,False,,0,399,SZ002242,九阳股份,20251022
|
||||
65,7597,True,0.7681302693326046,0.0,-0.011477935157876581,7.155005218965274e-05,0.03724114787576562,6,4,414.4339977966914,up,False,,0,399,SZ002242,九阳股份,20251023
|
||||
65,7598,True,0.8182074692306863,0.0,-0.011662669946934991,7.155005218964054e-05,0.02129331676239766,6,4,407.6808780748196,up,False,,0,399,SZ002242,九阳股份,20251024
|
||||
65,7605,True,0.3580946899768398,0.0,-0.010083722094593978,7.155005218964089e-05,0.1299688669121848,8,4,458.60427842842216,up,False,,0,399,SZ002242,九阳股份,20251031
|
||||
65,7608,True,0.3594279700548148,0.0,-0.010083722094593984,7.155005218963692e-05,0.12806759287673175,8,4,457.60427842842176,up,False,,0,399,SZ002242,九阳股份,20251103
|
||||
65,7609,True,0.3832016165124314,0.0,-0.010083722094593961,7.155005218962884e-05,0.12615799095595331,8,4,456.6042784284225,up,False,,0,399,SZ002242,九阳股份,20251104
|
||||
65,7610,True,0.4426446481482097,0.0,-0.010083722094593982,7.155005218963424e-05,0.12424000631353908,8,4,455.6042784284226,up,False,,0,399,SZ002242,九阳股份,20251105
|
||||
65,7611,True,0.3910418600312175,0.0,-0.010083722094593989,7.15500521896351e-05,0.12231358363068398,8,4,454.60427842842154,up,False,,0,399,SZ002242,九阳股份,20251106
|
||||
65,7612,True,0.39094930248851834,0.0,-0.010083722094593949,7.15500521896381e-05,0.12037866710077638,8,4,453.60427842842296,up,False,,0,399,SZ002242,九阳股份,20251107
|
||||
65,7615,True,0.5787433681752375,0.0,-0.010083722094593984,7.155005218965644e-05,0.11843520042398144,8,5,452.60427842842154,up,False,,0,399,SZ002242,九阳股份,20251110
|
||||
65,7616,True,0.6897200488337273,0.0,-0.010083722094593978,7.15500521896546e-05,0.11648312680181916,8,5,451.60427842842194,up,True,,0,399,SZ002242,九阳股份,20251111
|
||||
65,7617,True,1.0,0.0,-0.010083722094593984,7.155005218964386e-05,0.11452238893159641,8,5,450.60427842842114,up,True,,0,399,SZ002242,九阳股份,20251112
|
||||
70,7217,True,0.460075429160228,0.0,-0.015468525023707293,-0.011237497014775156,0.5540877712907588,3,5,894.794926694351,up,True,,0,399,SZ002493,荣盛石化,20241008
|
||||
70,7513,True,0.44591486768436966,0.0,-0.009322417964396647,-0.0021906582121421947,0.29847698593486677,5,5,568.7625238235657,up,True,,0,399,SZ002493,荣盛石化,20250731
|
||||
70,7514,True,0.3893773037979847,0.0,-0.009322417964396644,-0.002190658212142225,0.29724139361478896,5,5,567.7625238235669,up,False,,0,399,SZ002493,荣盛石化,20250801
|
||||
70,7517,True,0.3114664223883132,0.0,-0.009322417964396677,-0.0021906582121422246,0.29600144111820115,5,5,566.7625238235638,up,False,,0,399,SZ002493,荣盛石化,20250804
|
||||
70,7518,True,0.3502142198580363,0.0,-0.009322417964396661,-0.0021906582121422164,0.2947571053249387,5,5,565.7625238235661,up,False,,0,399,SZ002493,荣盛石化,20250805
|
||||
70,7519,True,0.44461891585648866,0.0,-0.009322417964396684,-0.0021906582121422064,0.2935083629510615,5,5,564.7625238235642,up,False,,0,399,SZ002493,荣盛石化,20250806
|
||||
70,7520,True,0.37394928137288297,0.0,-0.00932241796439667,-0.0021906582121422177,0.2922551905474447,5,5,563.762523823564,up,False,,0,399,SZ002493,荣盛石化,20250807
|
||||
70,7521,True,0.41347565149191096,0.0,-0.009322417964396647,-0.0021906582121422125,0.29099756449828457,5,5,562.7625238235654,up,False,,0,399,SZ002493,荣盛石化,20250808
|
||||
70,7524,True,0.4762470778329295,0.0,-0.009322417964396642,-0.0021906582121422303,0.2897354610196188,5,5,561.7625238235654,up,False,,0,399,SZ002493,荣盛石化,20250811
|
||||
70,7525,True,0.4606764401532056,0.0,-0.00932241796439666,-0.00219065821214221,0.2884688561578356,5,5,560.762523823565,up,False,,0,399,SZ002493,荣盛石化,20250812
|
||||
70,7526,True,0.4294264921849654,0.0,-0.009322417964396675,-0.0021906582121422368,0.28719772578815417,5,5,559.7625238235655,up,False,,0,399,SZ002493,荣盛石化,20250813
|
||||
70,7527,True,0.358183435529262,0.0,-0.009322417964396711,-0.0021906582121422108,0.2859220456130825,5,5,558.7625238235613,up,False,,0,399,SZ002493,荣盛石化,20250814
|
||||
70,7528,True,0.5168566086390701,0.0,-0.00932241796439668,-0.0021906582121422285,0.2846417911609016,5,5,557.7625238235644,up,False,,0,399,SZ002493,荣盛石化,20250815
|
||||
70,7531,True,0.4774428349541203,0.0,-0.009322417964396654,-0.002190658212142203,0.2833569377840512,5,5,556.7625238235653,up,False,,0,399,SZ002493,荣盛石化,20250818
|
||||
70,7532,True,0.4281225527826842,0.0,-0.009322417964396625,-0.0021906582121422103,0.2820674606575908,5,5,555.7625238235674,up,False,,0,399,SZ002493,荣盛石化,20250819
|
||||
70,7533,True,1.0,0.0,-0.009322417964396658,-0.0021906582121422186,0.28077333477757344,5,5,554.7625238235655,up,True,,0,399,SZ002493,荣盛石化,20250820
|
||||
71,7528,True,0.30185837215949424,0.0,-0.012437305574206178,0.028726251186547494,0.09326148825122058,5,5,440.03866035255237,up,False,,0,399,SZ002544,普天科技,20250815
|
||||
71,7531,True,0.3095932879087683,0.0,-0.012437305574206201,0.02872625118654746,0.091196206549102,5,5,439.0386603525524,up,True,,0,399,SZ002544,普天科技,20250818
|
||||
74,7625,True,0.8275872051885943,0.0,-0.01013188593724261,0.0030262417102144136,0.04386559343792921,3,3,417.30534667679854,up,False,,0,399,SZ002694,顾地科技,20251120
|
||||
74,7626,True,0.5137556220551136,0.0,-0.010131885937242588,0.0030262417102144144,0.041568879225166736,3,3,416.3053466767991,up,True,,0,399,SZ002694,顾地科技,20251121
|
||||
74,7629,True,0.6410294544132464,0.0,-0.010131885937242603,0.0030262417102144166,0.03926110464811279,3,3,415.3053466767985,up,False,,0,399,SZ002694,顾地科技,20251124
|
||||
74,7630,True,0.6437793808935197,0.0,-0.01013188593724261,0.0030262417102144123,0.036942189618272066,3,3,414.3053466767982,up,False,,0,399,SZ002694,顾地科技,20251125
|
||||
74,7631,True,0.5041312277234224,0.0,-0.010131885937242612,0.0030262417102144175,0.03461205327204498,3,3,413.30534667679837,up,False,,0,399,SZ002694,顾地科技,20251126
|
||||
74,7632,True,0.4498899036079477,0.0,-0.010131885937242605,0.0030262417102144205,0.032270613961328264,3,3,412.3053466767986,up,False,,0,399,SZ002694,顾地科技,20251127
|
||||
74,7633,True,0.6869283936123264,0.0,-0.010131885937242619,0.0030262417102144223,0.029917789243978716,3,3,411.305346676798,up,False,,0,399,SZ002694,顾地科技,20251128
|
||||
74,7636,True,0.5633102142048387,0.0,-0.010131885937242617,0.0030262417102144236,0.027553495874143227,3,3,410.3053466767981,up,False,,0,399,SZ002694,顾地科技,20251201
|
||||
74,7637,True,0.6008717830276857,0.0,-0.01013188593724261,0.0030262417102144184,0.025177649792431898,3,3,409.30534667679837,up,False,,0,399,SZ002694,顾地科技,20251202
|
||||
74,7638,True,0.5859707877090077,0.0,-0.010131885937242601,0.0030262417102144283,0.022790166115958615,3,3,408.3053466767983,up,False,,0,399,SZ002694,顾地科技,20251203
|
||||
74,7639,True,0.46080681155562436,0.0,-0.010131885937242594,0.003026241710214422,0.020390959128235686,3,3,407.30534667679837,up,False,,0,399,SZ002694,顾地科技,20251204
|
||||
74,7640,True,0.533768011648808,0.0,-0.010131885937242615,0.003026241710214418,0.017979942268911066,3,3,406.3053466767988,up,False,,0,399,SZ002694,顾地科技,20251205
|
||||
74,7643,True,0.4652699700217194,0.0,-0.0101318859372426,0.0030262417102144227,0.01555702812335826,3,3,405.30534667679837,up,False,,0,399,SZ002694,顾地科技,20251208
|
||||
74,7644,True,0.35626755233257457,0.0,-0.0101318859372426,0.003026241710214419,0.013122128412117347,3,3,404.3053466767986,up,False,,0,399,SZ002694,顾地科技,20251209
|
||||
88,7223,True,1.0,0.0,-0.05245162933490928,-0.046350009966096714,0.7666721647756475,2,2,1710.0402942338542,up,True,,0,399,SZ300379,*ST东通,20241014
|
||||
88,7224,True,1.0,0.0,-0.05245162933490931,-0.04635000996609673,0.766535639126713,2,2,1709.0402942338496,up,True,,0,399,SZ300379,*ST东通,20241015
|
||||
88,7225,True,1.0,0.0,-0.05406482522827499,-0.04635000996609667,0.7064551203222318,2,2,1359.2470099904065,up,False,,0,399,SZ300379,*ST东通,20241016
|
||||
88,7226,True,1.0,0.0,-0.054064825228275035,-0.04635000996609673,0.7062389999276952,2,2,1358.2470099904092,up,False,,0,399,SZ300379,*ST东通,20241017
|
||||
88,7227,True,1.0,0.0,-0.05406482522827503,-0.0463500099660967,0.7060225610643845,2,2,1357.2470099904026,up,True,,0,399,SZ300379,*ST东通,20241018
|
||||
|
271
scripts/archive/run_sym_triangle_json.py
Normal file
271
scripts/archive/run_sym_triangle_json.py
Normal file
@ -0,0 +1,271 @@
|
||||
import json
|
||||
import os
|
||||
import sys
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
# 让脚本能找到 src/ 下的模块
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..", "src"))
|
||||
|
||||
from sym_triangle import detect_sym_triangle, line_y, pivots_fractal, fit_line, fit_boundary_line
|
||||
|
||||
# ============================================================================
|
||||
# 【可调参数区】- 在这里修改参数,然后重新运行脚本
|
||||
# ============================================================================
|
||||
|
||||
# --- 窗口大小 ---
|
||||
# 从最新数据点往前取多少个点作为分析窗口
|
||||
# 例如:400 表示分析最近 400 个交易日
|
||||
WINDOW = 400
|
||||
|
||||
# --- 数据源 ---
|
||||
# OHLCV 文件目录(包含 open/high/low/close/volume.json)
|
||||
DATA_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "data")
|
||||
|
||||
# --- 枢轴点检测 ---
|
||||
# pivot_k: 左右窗口大小,越大找到的枢轴点越少(更明显的峰/谷)
|
||||
# 建议范围:5~30
|
||||
PIVOT_K = 20
|
||||
|
||||
# --- 边界线拟合 ---
|
||||
# boundary_n_segments: 把窗口分成几段,每段取一个极值点来拟合
|
||||
# 越大 → 拟合用的点越多 → 线越"平均"
|
||||
# 越小 → 拟合用的点越少 → 线越贴近极端值
|
||||
# 建议范围:2~5
|
||||
BOUNDARY_N_SEGMENTS = 2
|
||||
# boundary_source:
|
||||
# - "pivots": 只用枢轴点来拟合(更稳定)
|
||||
# - "full": 用全量 high/low 来分段取极值(更贴边)
|
||||
BOUNDARY_SOURCE = "full"
|
||||
|
||||
# --- 斜率约束 ---
|
||||
# upper_slope_max: 上沿斜率的最大值
|
||||
# - 设为 0:严格要求上沿下降(标准对称三角形)
|
||||
# - 设为正数(如 0.05):允许上沿略微上升(放宽条件)
|
||||
# - 设为负数(如 -0.01):要求上沿必须明显下降
|
||||
UPPER_SLOPE_MAX = 0.10
|
||||
# lower_slope_min: 下沿斜率的最小值
|
||||
# - 设为 0:严格要求下沿上升
|
||||
# - 设为负数(如 -0.1):允许下沿略微下降(放宽条件)
|
||||
LOWER_SLOPE_MIN = -0.10
|
||||
|
||||
# --- 触碰判定 ---
|
||||
# touch_tol: 枢轴点"触碰"线的容差(百分比)
|
||||
# 越大 → 判定越宽松
|
||||
TOUCH_TOL = 0.10
|
||||
# touch_loss_max: 平均相对偏差上限(损失函数)
|
||||
TOUCH_LOSS_MAX = 0.10
|
||||
|
||||
# 是否打印调试信息(包含 loss_upper/loss_lower)
|
||||
PRINT_DEBUG = True
|
||||
|
||||
# --- 收敛要求 ---
|
||||
# shrink_ratio: 三角形末端宽度 / 起始宽度 的最大值
|
||||
# 越小 → 要求收敛越明显
|
||||
SHRINK_RATIO = 0.8
|
||||
|
||||
# --- 突破判定 ---
|
||||
BREAK_TOL = 0.001
|
||||
VOL_WINDOW = 20
|
||||
VOL_K = 1.3
|
||||
FALSE_BREAK_M = 5
|
||||
|
||||
# ============================================================================
|
||||
|
||||
|
||||
def load_series_from_json(json_path: str, name: str) -> pd.DataFrame:
|
||||
"""从单个 JSON 中读取 labels/values,返回 date + 指标列。"""
|
||||
with open(json_path, "r", encoding="utf-8") as f:
|
||||
raw = json.load(f)
|
||||
|
||||
data = raw.get("data", {})
|
||||
labels = data.get("labels", [])
|
||||
values = data.get("values", [])
|
||||
|
||||
if not labels or not values or len(labels) != len(values):
|
||||
raise ValueError(f"{name}.json 中未找到等长的 labels / values")
|
||||
|
||||
df = pd.DataFrame({"date": labels, name: values})
|
||||
df["date"] = pd.to_numeric(df["date"], errors="coerce")
|
||||
df[name] = pd.to_numeric(df[name], errors="coerce")
|
||||
return df.dropna(subset=["date", name]).reset_index(drop=True)
|
||||
|
||||
|
||||
def load_ohlcv_from_dir(data_dir: str) -> pd.DataFrame:
|
||||
"""从目录读取 open/high/low/close/volume.json,并按 date 对齐。"""
|
||||
open_df = load_series_from_json(os.path.join(data_dir, "open.json"), "open")
|
||||
high_df = load_series_from_json(os.path.join(data_dir, "high.json"), "high")
|
||||
low_df = load_series_from_json(os.path.join(data_dir, "low.json"), "low")
|
||||
close_df = load_series_from_json(os.path.join(data_dir, "close.json"), "close")
|
||||
volume_df = load_series_from_json(os.path.join(data_dir, "volume.json"), "volume")
|
||||
|
||||
df = open_df.merge(high_df, on="date", how="inner")
|
||||
df = df.merge(low_df, on="date", how="inner")
|
||||
df = df.merge(close_df, on="date", how="inner")
|
||||
df = df.merge(volume_df, on="date", how="inner")
|
||||
|
||||
return df.sort_values("date").reset_index(drop=True)
|
||||
|
||||
|
||||
def plot_sym_triangle(df: pd.DataFrame, res, out_path: str) -> None:
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
close = df["close"].to_numpy(dtype=float)
|
||||
x = np.arange(len(df), dtype=float)
|
||||
dates = df["date"].to_numpy()
|
||||
a_u, b_u = res.upper_coef
|
||||
a_l, b_l = res.lower_coef
|
||||
|
||||
start, end = res.start, res.end
|
||||
xw = np.arange(start, end + 1, dtype=float)
|
||||
upper = line_y(a_u, b_u, xw)
|
||||
lower = line_y(a_l, b_l, xw)
|
||||
|
||||
plt.figure(figsize=(12, 5))
|
||||
plt.plot(x, close, linewidth=1.2, label="close")
|
||||
plt.plot(xw, upper, linewidth=2, label="upper")
|
||||
plt.plot(xw, lower, linewidth=2, label="lower")
|
||||
plt.axvline(end, color="gray", linestyle="--", linewidth=1)
|
||||
start_date = dates[start] if len(dates) > 0 else start
|
||||
end_date = dates[end] if len(dates) > 0 else end
|
||||
plt.title(
|
||||
"sym_triangle: "
|
||||
f"range={start_date}-{end_date}, "
|
||||
f"slope=({a_u:.4f},{a_l:.4f}), "
|
||||
f"width_ratio={res.width_ratio:.2f}, "
|
||||
f"touches=({res.touches_upper},{res.touches_lower})"
|
||||
)
|
||||
# 稀疏显示日期标签(防止拥挤)
|
||||
if len(dates) > 0:
|
||||
step = max(1, len(dates) // 8)
|
||||
idx = np.arange(0, len(dates), step)
|
||||
plt.xticks(idx, dates[idx], rotation=45, ha="right")
|
||||
plt.legend()
|
||||
plt.tight_layout()
|
||||
plt.savefig(out_path, dpi=150)
|
||||
plt.show()
|
||||
|
||||
|
||||
def debug_latest_window(
|
||||
df: pd.DataFrame,
|
||||
window: int,
|
||||
pivot_k: int,
|
||||
touch_tol: float,
|
||||
shrink_ratio: float,
|
||||
boundary_fit: bool = True,
|
||||
boundary_n_segments: int = 3,
|
||||
boundary_source: str = "pivots",
|
||||
lower_slope_min: float = 0.0,
|
||||
) -> None:
|
||||
"""打印最近窗口的关键诊断指标,定位未识别的原因。"""
|
||||
n = len(df)
|
||||
end = n - 1
|
||||
start = max(0, end - window + 1)
|
||||
high = df["high"].to_numpy(dtype=float)
|
||||
low = df["low"].to_numpy(dtype=float)
|
||||
x_all = np.arange(n, dtype=float)
|
||||
|
||||
ph_idx, pl_idx = pivots_fractal(high, low, k=pivot_k)
|
||||
ph_in = ph_idx[(ph_idx >= start) & (ph_idx <= end)]
|
||||
pl_in = pl_idx[(pl_idx >= start) & (pl_idx <= end)]
|
||||
|
||||
print(f"window=[{start},{end}], len={window}, pivots_high={len(ph_in)}, pivots_low={len(pl_in)}")
|
||||
if len(ph_in) < 2 or len(pl_in) < 2:
|
||||
print("诊断:枢轴点不足(high/low pivots < 2)。")
|
||||
return
|
||||
|
||||
if boundary_fit:
|
||||
if boundary_source == "full":
|
||||
x_upper = x_all[start : end + 1]
|
||||
y_upper = high[start : end + 1]
|
||||
x_lower = x_all[start : end + 1]
|
||||
y_lower = low[start : end + 1]
|
||||
else:
|
||||
x_upper = x_all[ph_in]
|
||||
y_upper = high[ph_in]
|
||||
x_lower = x_all[pl_in]
|
||||
y_lower = low[pl_in]
|
||||
a_u, b_u = fit_boundary_line(x_upper, y_upper, mode="upper", n_segments=boundary_n_segments)
|
||||
a_l, b_l = fit_boundary_line(x_lower, y_lower, mode="lower", n_segments=boundary_n_segments)
|
||||
else:
|
||||
a_u, b_u = fit_line(x_all[ph_in], high[ph_in])
|
||||
a_l, b_l = fit_line(x_all[pl_in], low[pl_in])
|
||||
upper_start = float(line_y(a_u, b_u, np.array([start]))[0])
|
||||
lower_start = float(line_y(a_l, b_l, np.array([start]))[0])
|
||||
upper_end = float(line_y(a_u, b_u, np.array([end]))[0])
|
||||
lower_end = float(line_y(a_l, b_l, np.array([end]))[0])
|
||||
width_start = upper_start - lower_start
|
||||
width_end = upper_end - lower_end
|
||||
width_ratio = width_end / width_start if width_start > 0 else float("inf")
|
||||
|
||||
ph_dist = np.abs(high[ph_in] - line_y(a_u, b_u, x_all[ph_in])) / np.maximum(
|
||||
line_y(a_u, b_u, x_all[ph_in]), 1e-9
|
||||
)
|
||||
pl_dist = np.abs(low[pl_in] - line_y(a_l, b_l, x_all[pl_in])) / np.maximum(
|
||||
line_y(a_l, b_l, x_all[pl_in]), 1e-9
|
||||
)
|
||||
touches_upper = int((ph_dist <= touch_tol).sum())
|
||||
touches_lower = int((pl_dist <= touch_tol).sum())
|
||||
loss_upper = float(np.mean(ph_dist)) if len(ph_dist) else float("inf")
|
||||
loss_lower = float(np.mean(pl_dist)) if len(pl_dist) else float("inf")
|
||||
|
||||
print(
|
||||
f"a_u={a_u:.6f}, a_l={a_l:.6f} "
|
||||
f"(need a_u<=upper_slope_max, a_l>={lower_slope_min})"
|
||||
)
|
||||
print(f"width_ratio={width_ratio:.3f} (need <= {shrink_ratio})")
|
||||
print(
|
||||
f"touches_upper={touches_upper}, touches_lower={touches_lower} "
|
||||
f"(loss_upper={loss_upper:.4f}, loss_lower={loss_lower:.4f}, "
|
||||
f"need <= {TOUCH_LOSS_MAX})"
|
||||
)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
df = load_ohlcv_from_dir(DATA_DIR)
|
||||
|
||||
# 只分析“最近一个窗口”(从最新点往过去)
|
||||
res = detect_sym_triangle(
|
||||
df,
|
||||
window=WINDOW,
|
||||
pivot_k=PIVOT_K,
|
||||
touch_tol=TOUCH_TOL,
|
||||
touch_loss_max=TOUCH_LOSS_MAX,
|
||||
shrink_ratio=SHRINK_RATIO,
|
||||
break_tol=BREAK_TOL,
|
||||
vol_window=VOL_WINDOW,
|
||||
vol_k=VOL_K,
|
||||
false_break_m=FALSE_BREAK_M,
|
||||
upper_slope_max=UPPER_SLOPE_MAX,
|
||||
lower_slope_min=LOWER_SLOPE_MIN,
|
||||
boundary_fit=True,
|
||||
boundary_n_segments=BOUNDARY_N_SEGMENTS,
|
||||
boundary_source=BOUNDARY_SOURCE,
|
||||
)
|
||||
if PRINT_DEBUG:
|
||||
debug_latest_window(
|
||||
df,
|
||||
window=WINDOW,
|
||||
pivot_k=PIVOT_K,
|
||||
touch_tol=TOUCH_TOL,
|
||||
shrink_ratio=SHRINK_RATIO,
|
||||
boundary_fit=True,
|
||||
boundary_n_segments=BOUNDARY_N_SEGMENTS,
|
||||
boundary_source=BOUNDARY_SOURCE,
|
||||
lower_slope_min=LOWER_SLOPE_MIN,
|
||||
)
|
||||
if res is None:
|
||||
print("未识别到对称三角形")
|
||||
return
|
||||
|
||||
print(res)
|
||||
outputs_dir = os.path.join(os.path.dirname(__file__), "..", "..", "outputs")
|
||||
os.makedirs(outputs_dir, exist_ok=True)
|
||||
out_path = os.path.join(outputs_dir, "sym_triangle_result.png")
|
||||
plot_sym_triangle(df, res, out_path)
|
||||
print(f"图已保存:{out_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
279
scripts/archive/run_sym_triangle_pkl.py
Normal file
279
scripts/archive/run_sym_triangle_pkl.py
Normal file
@ -0,0 +1,279 @@
|
||||
"""
|
||||
批量识别对称三角形 - 从 pkl 文件读取 OHLCV 数据
|
||||
每个 pkl 文件包含 108 个股票 × N 个交易日的矩阵
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import pickle
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
# 让脚本能找到 src/ 下的模块
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), "..", "..", "src"))
|
||||
|
||||
from sym_triangle import detect_sym_triangle, line_y, pivots_fractal, fit_boundary_line
|
||||
|
||||
# ============================================================================
|
||||
# 【可调参数区】
|
||||
# ============================================================================
|
||||
|
||||
# --- 数据源 ---
|
||||
DATA_DIR = os.path.join(os.path.dirname(__file__), "..", "..", "data")
|
||||
|
||||
# --- 窗口大小 ---
|
||||
WINDOW = 400
|
||||
|
||||
# --- 枢轴点检测 ---
|
||||
PIVOT_K = 20
|
||||
|
||||
# --- 边界线拟合 ---
|
||||
BOUNDARY_N_SEGMENTS = 2
|
||||
BOUNDARY_SOURCE = "full"
|
||||
|
||||
# --- 斜率约束 ---
|
||||
UPPER_SLOPE_MAX = 0.10
|
||||
LOWER_SLOPE_MIN = -0.10
|
||||
|
||||
# --- 触碰判定 ---
|
||||
TOUCH_TOL = 0.10
|
||||
TOUCH_LOSS_MAX = 0.10
|
||||
|
||||
# --- 收敛要求 ---
|
||||
SHRINK_RATIO = 0.8
|
||||
|
||||
# --- 突破判定 ---
|
||||
BREAK_TOL = 0.001
|
||||
VOL_WINDOW = 20
|
||||
VOL_K = 1.3
|
||||
FALSE_BREAK_M = 5
|
||||
|
||||
# --- 输出控制 ---
|
||||
PRINT_DEBUG = False # 批量时关闭调试输出
|
||||
SAVE_ALL_CHARTS = False # True=保存所有股票图,False=只保存识别到的
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# pkl 数据加载
|
||||
# ============================================================================
|
||||
|
||||
class FakeModule:
|
||||
"""空壳模块,绕过 model 依赖"""
|
||||
ndarray = np.ndarray
|
||||
|
||||
|
||||
def load_pkl(pkl_path: str) -> dict:
|
||||
"""加载 pkl 文件,返回字典 {mtx, dtes, tkrs, tkrs_name, ...}"""
|
||||
# 注入空壳模块
|
||||
sys.modules['model'] = FakeModule()
|
||||
sys.modules['model.index_info'] = FakeModule()
|
||||
|
||||
with open(pkl_path, 'rb') as f:
|
||||
data = pickle.load(f)
|
||||
return data
|
||||
|
||||
|
||||
def load_ohlcv_from_pkl(data_dir: str) -> tuple:
|
||||
"""
|
||||
从 pkl 文件加载 OHLCV 数据
|
||||
|
||||
Returns:
|
||||
open_mtx, high_mtx, low_mtx, close_mtx, volume_mtx: shape=(n_stocks, n_days)
|
||||
dates: shape=(n_days,) 真实日期 (如 20050104)
|
||||
tkrs: shape=(n_stocks,) 股票代码
|
||||
tkrs_name: shape=(n_stocks,) 股票名称
|
||||
"""
|
||||
open_data = load_pkl(os.path.join(data_dir, "open.pkl"))
|
||||
high_data = load_pkl(os.path.join(data_dir, "high.pkl"))
|
||||
low_data = load_pkl(os.path.join(data_dir, "low.pkl"))
|
||||
close_data = load_pkl(os.path.join(data_dir, "close.pkl"))
|
||||
volume_data = load_pkl(os.path.join(data_dir, "volume.pkl"))
|
||||
|
||||
# 使用 close 的元数据
|
||||
dates = close_data["dtes"]
|
||||
tkrs = close_data["tkrs"]
|
||||
tkrs_name = close_data["tkrs_name"]
|
||||
|
||||
return (
|
||||
open_data["mtx"],
|
||||
high_data["mtx"],
|
||||
low_data["mtx"],
|
||||
close_data["mtx"],
|
||||
volume_data["mtx"],
|
||||
dates,
|
||||
tkrs,
|
||||
tkrs_name,
|
||||
)
|
||||
|
||||
|
||||
def get_stock_df(
|
||||
stock_idx: int,
|
||||
open_mtx: np.ndarray,
|
||||
high_mtx: np.ndarray,
|
||||
low_mtx: np.ndarray,
|
||||
close_mtx: np.ndarray,
|
||||
volume_mtx: np.ndarray,
|
||||
dates: np.ndarray,
|
||||
) -> pd.DataFrame:
|
||||
"""提取单个股票的 DataFrame"""
|
||||
df = pd.DataFrame({
|
||||
"date": dates,
|
||||
"open": open_mtx[stock_idx, :],
|
||||
"high": high_mtx[stock_idx, :],
|
||||
"low": low_mtx[stock_idx, :],
|
||||
"close": close_mtx[stock_idx, :],
|
||||
"volume": volume_mtx[stock_idx, :],
|
||||
})
|
||||
# 过滤掉 NaN/0 值
|
||||
df = df.replace(0, np.nan).dropna().reset_index(drop=True)
|
||||
return df
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 绘图
|
||||
# ============================================================================
|
||||
|
||||
def plot_sym_triangle(df: pd.DataFrame, res, stock_id: str, out_path: str) -> None:
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
close = df["close"].to_numpy(dtype=float)
|
||||
x = np.arange(len(df), dtype=float)
|
||||
dates = df["date"].to_numpy()
|
||||
a_u, b_u = res.upper_coef
|
||||
a_l, b_l = res.lower_coef
|
||||
|
||||
start, end = res.start, res.end
|
||||
xw = np.arange(start, end + 1, dtype=float)
|
||||
upper = line_y(a_u, b_u, xw)
|
||||
lower = line_y(a_l, b_l, xw)
|
||||
|
||||
plt.figure(figsize=(12, 5))
|
||||
plt.plot(x, close, linewidth=1.2, label="close")
|
||||
plt.plot(xw, upper, linewidth=2, label="upper")
|
||||
plt.plot(xw, lower, linewidth=2, label="lower")
|
||||
plt.axvline(end, color="gray", linestyle="--", linewidth=1)
|
||||
|
||||
start_date = dates[start] if len(dates) > start else start
|
||||
end_date = dates[end] if len(dates) > end else end
|
||||
|
||||
plt.title(
|
||||
f"[{stock_id}] sym_triangle: "
|
||||
f"range={start_date}-{end_date}, "
|
||||
f"slope=({a_u:.4f},{a_l:.4f}), "
|
||||
f"width_ratio={res.width_ratio:.2f}, "
|
||||
f"touches=({res.touches_upper},{res.touches_lower})"
|
||||
)
|
||||
|
||||
if len(dates) > 0:
|
||||
step = max(1, len(dates) // 8)
|
||||
idx = np.arange(0, len(dates), step)
|
||||
plt.xticks(idx, dates[idx], rotation=45, ha="right")
|
||||
|
||||
plt.legend()
|
||||
plt.tight_layout()
|
||||
plt.savefig(out_path, dpi=150)
|
||||
plt.close()
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 主流程
|
||||
# ============================================================================
|
||||
|
||||
def main() -> None:
|
||||
print("=" * 60)
|
||||
print("Symmetric Triangle Batch Detection - from pkl files")
|
||||
print("=" * 60)
|
||||
|
||||
# 1. 加载数据
|
||||
print("\n[1] Loading OHLCV pkl files...")
|
||||
open_mtx, high_mtx, low_mtx, close_mtx, volume_mtx, dates, tkrs, tkrs_name = load_ohlcv_from_pkl(DATA_DIR)
|
||||
n_stocks, n_days = close_mtx.shape
|
||||
print(f" Stocks: {n_stocks}")
|
||||
print(f" Days: {n_days}")
|
||||
print(f" Date range: {dates[0]} ~ {dates[-1]}")
|
||||
|
||||
# 2. 准备输出目录
|
||||
outputs_dir = os.path.join(os.path.dirname(__file__), "..", "..", "outputs", "sym_triangles")
|
||||
os.makedirs(outputs_dir, exist_ok=True)
|
||||
|
||||
# 3. 遍历所有股票
|
||||
print(f"\n[2] Scanning {n_stocks} stocks...")
|
||||
detected = []
|
||||
|
||||
for i in range(n_stocks):
|
||||
stock_code = tkrs[i]
|
||||
stock_name = tkrs_name[i]
|
||||
stock_id = f"{stock_code}" # 使用真实股票代码
|
||||
|
||||
# 提取单只股票数据
|
||||
df = get_stock_df(i, open_mtx, high_mtx, low_mtx, close_mtx, volume_mtx, dates)
|
||||
|
||||
if len(df) < WINDOW:
|
||||
if PRINT_DEBUG:
|
||||
print(f" [{stock_id}] 跳过: 数据不足 ({len(df)} < {WINDOW})")
|
||||
continue
|
||||
|
||||
# 运行检测
|
||||
res = detect_sym_triangle(
|
||||
df,
|
||||
window=WINDOW,
|
||||
pivot_k=PIVOT_K,
|
||||
touch_tol=TOUCH_TOL,
|
||||
touch_loss_max=TOUCH_LOSS_MAX,
|
||||
shrink_ratio=SHRINK_RATIO,
|
||||
break_tol=BREAK_TOL,
|
||||
vol_window=VOL_WINDOW,
|
||||
vol_k=VOL_K,
|
||||
false_break_m=FALSE_BREAK_M,
|
||||
upper_slope_max=UPPER_SLOPE_MAX,
|
||||
lower_slope_min=LOWER_SLOPE_MIN,
|
||||
boundary_fit=True,
|
||||
boundary_n_segments=BOUNDARY_N_SEGMENTS,
|
||||
boundary_source=BOUNDARY_SOURCE,
|
||||
)
|
||||
|
||||
if res is not None:
|
||||
detected.append((i, stock_id, res, df))
|
||||
out_path = os.path.join(outputs_dir, f"{stock_id}.png")
|
||||
plot_sym_triangle(df, res, stock_id, out_path)
|
||||
print(f" [OK] {stock_id} -> {out_path}")
|
||||
elif PRINT_DEBUG:
|
||||
print(f" [--] {stock_id} not detected")
|
||||
|
||||
# 4. 汇总结果
|
||||
print("\n" + "=" * 60)
|
||||
print(f"Scan completed! {len(detected)}/{n_stocks} stocks have symmetric triangles")
|
||||
print("=" * 60)
|
||||
|
||||
if detected:
|
||||
print("\nDetected stocks:")
|
||||
for i, stock_id, res, _ in detected:
|
||||
name = tkrs_name[i]
|
||||
print(f" - {stock_id} ({name}): slope=({res.upper_coef[0]:.4f},{res.lower_coef[0]:.4f}), "
|
||||
f"width_ratio={res.width_ratio:.2f}, breakout={res.breakout}")
|
||||
print(f"\nCharts saved to: {outputs_dir}")
|
||||
|
||||
# 5. Save summary CSV
|
||||
if detected:
|
||||
summary_path = os.path.join(outputs_dir, "summary.csv")
|
||||
summary_data = []
|
||||
for i, stock_id, res, _ in detected:
|
||||
summary_data.append({
|
||||
"stock_idx": i,
|
||||
"stock_code": stock_id,
|
||||
"stock_name": tkrs_name[i],
|
||||
"start_date": dates[res.start],
|
||||
"end_date": dates[res.end],
|
||||
"upper_slope": res.upper_coef[0],
|
||||
"lower_slope": res.lower_coef[0],
|
||||
"width_ratio": res.width_ratio,
|
||||
"touches_upper": res.touches_upper,
|
||||
"touches_lower": res.touches_lower,
|
||||
"breakout": res.breakout,
|
||||
})
|
||||
pd.DataFrame(summary_data).to_csv(summary_path, index=False, encoding="utf-8-sig")
|
||||
print(f"Summary saved: {summary_path}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
273
scripts/report_converging_triangles.py
Normal file
273
scripts/report_converging_triangles.py
Normal file
@ -0,0 +1,273 @@
|
||||
"""
|
||||
生成收敛三角形突破强度选股简报(Markdown)
|
||||
默认读取 outputs/converging_triangles/all_results.csv
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import csv
|
||||
import os
|
||||
from datetime import datetime
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
|
||||
def parse_bool(value: str) -> Optional[bool]:
|
||||
if value is None or value == "":
|
||||
return None
|
||||
if value.lower() in ("true", "1", "yes"):
|
||||
return True
|
||||
if value.lower() in ("false", "0", "no"):
|
||||
return False
|
||||
return None
|
||||
|
||||
|
||||
def to_int(value: str, default: int = 0) -> int:
|
||||
try:
|
||||
return int(float(value))
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
|
||||
|
||||
def to_float(value: str, default: float = 0.0) -> float:
|
||||
try:
|
||||
return float(value)
|
||||
except (TypeError, ValueError):
|
||||
return default
|
||||
|
||||
|
||||
def load_rows(csv_path: str) -> List[Dict[str, Any]]:
|
||||
rows: List[Dict[str, Any]] = []
|
||||
with open(csv_path, newline="", encoding="utf-8-sig") as f:
|
||||
reader = csv.DictReader(f)
|
||||
for row in reader:
|
||||
rows.append({
|
||||
"stock_idx": to_int(row.get("stock_idx", "")),
|
||||
"date_idx": to_int(row.get("date_idx", "")),
|
||||
"is_valid": parse_bool(row.get("is_valid", "")),
|
||||
"breakout_strength_up": to_float(row.get("breakout_strength_up", "")),
|
||||
"breakout_strength_down": to_float(row.get("breakout_strength_down", "")),
|
||||
"upper_slope": to_float(row.get("upper_slope", "")),
|
||||
"lower_slope": to_float(row.get("lower_slope", "")),
|
||||
"width_ratio": to_float(row.get("width_ratio", "")),
|
||||
"touches_upper": to_int(row.get("touches_upper", "")),
|
||||
"touches_lower": to_int(row.get("touches_lower", "")),
|
||||
"apex_x": to_float(row.get("apex_x", "")),
|
||||
"breakout_dir": (row.get("breakout_dir") or "").strip(),
|
||||
"volume_confirmed": parse_bool(row.get("volume_confirmed", "")),
|
||||
"false_breakout": parse_bool(row.get("false_breakout", "")),
|
||||
"window_start": to_int(row.get("window_start", "")),
|
||||
"window_end": to_int(row.get("window_end", "")),
|
||||
"stock_code": (row.get("stock_code") or "").strip(),
|
||||
"stock_name": (row.get("stock_name") or "").strip(),
|
||||
"date": to_int(row.get("date", "")),
|
||||
})
|
||||
return rows
|
||||
|
||||
|
||||
def best_by_stock(
|
||||
rows: List[Dict[str, Any]],
|
||||
strength_key: str,
|
||||
threshold: float,
|
||||
breakout_dir: str,
|
||||
) -> List[Dict[str, Any]]:
|
||||
filtered = [
|
||||
r for r in rows
|
||||
if r.get(strength_key, 0.0) > threshold and r.get("breakout_dir") == breakout_dir
|
||||
]
|
||||
best_map: Dict[str, Dict[str, Any]] = {}
|
||||
for r in filtered:
|
||||
key = r.get("stock_code") or f"IDX{r.get('stock_idx', '')}"
|
||||
prev = best_map.get(key)
|
||||
if prev is None:
|
||||
best_map[key] = r
|
||||
continue
|
||||
if r[strength_key] > prev[strength_key]:
|
||||
best_map[key] = r
|
||||
elif r[strength_key] == prev[strength_key] and r.get("date", 0) > prev.get("date", 0):
|
||||
best_map[key] = r
|
||||
return sorted(
|
||||
best_map.values(),
|
||||
key=lambda x: (x.get(strength_key, 0.0), x.get("date", 0)),
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
|
||||
def best_combined_by_stock(
|
||||
rows: List[Dict[str, Any]],
|
||||
threshold: float,
|
||||
) -> List[Dict[str, Any]]:
|
||||
filtered: List[Dict[str, Any]] = []
|
||||
for r in rows:
|
||||
if r.get("breakout_dir") not in ("up", "down"):
|
||||
continue
|
||||
strength_up = r.get("breakout_strength_up", 0.0)
|
||||
strength_down = r.get("breakout_strength_down", 0.0)
|
||||
combined = max(strength_up, strength_down)
|
||||
if combined <= threshold:
|
||||
continue
|
||||
r = dict(r)
|
||||
r["combined_strength"] = combined
|
||||
r["combined_dir"] = "up" if strength_up >= strength_down else "down"
|
||||
filtered.append(r)
|
||||
|
||||
best_map: Dict[str, Dict[str, Any]] = {}
|
||||
for r in filtered:
|
||||
key = r.get("stock_code") or f"IDX{r.get('stock_idx', '')}"
|
||||
prev = best_map.get(key)
|
||||
if prev is None:
|
||||
best_map[key] = r
|
||||
continue
|
||||
if r["combined_strength"] > prev["combined_strength"]:
|
||||
best_map[key] = r
|
||||
elif r["combined_strength"] == prev["combined_strength"] and r.get("date", 0) > prev.get("date", 0):
|
||||
best_map[key] = r
|
||||
|
||||
return sorted(
|
||||
best_map.values(),
|
||||
key=lambda x: (x.get("combined_strength", 0.0), x.get("date", 0)),
|
||||
reverse=True,
|
||||
)
|
||||
|
||||
|
||||
def count_by_dir(rows: List[Dict[str, Any]]) -> Dict[str, int]:
|
||||
counts = {"up": 0, "down": 0, "none": 0}
|
||||
for r in rows:
|
||||
dir_name = r.get("breakout_dir") or "none"
|
||||
if dir_name not in counts:
|
||||
counts[dir_name] = 0
|
||||
counts[dir_name] += 1
|
||||
return counts
|
||||
|
||||
|
||||
def fmt_bool(value: Optional[bool]) -> str:
|
||||
if value is True:
|
||||
return "是"
|
||||
if value is False:
|
||||
return "否"
|
||||
return "-"
|
||||
|
||||
|
||||
def build_report(
|
||||
rows: List[Dict[str, Any]],
|
||||
threshold: float,
|
||||
top_n: int,
|
||||
output_path: str,
|
||||
) -> None:
|
||||
total = len(rows)
|
||||
if total == 0:
|
||||
content = "# 收敛三角形突破强度选股简报\n\n数据为空。\n"
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
f.write(content)
|
||||
return
|
||||
|
||||
dates = [r.get("date", 0) for r in rows if r.get("date", 0) > 0]
|
||||
date_min = min(dates) if dates else 0
|
||||
date_max = max(dates) if dates else 0
|
||||
counts = count_by_dir(rows)
|
||||
|
||||
up_picks = best_by_stock(rows, "breakout_strength_up", threshold, "up")[:top_n]
|
||||
down_picks = best_by_stock(rows, "breakout_strength_down", threshold, "down")[:top_n]
|
||||
combined_picks = best_combined_by_stock(rows, threshold)[:top_n]
|
||||
|
||||
now_str = datetime.now().strftime("%Y-%m-%d %H:%M")
|
||||
|
||||
lines: List[str] = []
|
||||
lines.append("# 收敛三角形突破强度选股简报")
|
||||
lines.append("")
|
||||
lines.append(f"- 生成时间:{now_str}")
|
||||
if date_min and date_max:
|
||||
lines.append(f"- 数据范围:{date_min} ~ {date_max}")
|
||||
lines.append(f"- 记录数:{total}")
|
||||
lines.append(f"- 突破方向统计:上破 {counts.get('up', 0)} / 下破 {counts.get('down', 0)} / 无突破 {counts.get('none', 0)}")
|
||||
lines.append("")
|
||||
lines.append("## 筛选条件")
|
||||
lines.append(f"- 强度阈值:> {threshold}")
|
||||
lines.append(f"- 每方向最多输出:{top_n} 只(按单只股票的最高强度去重)")
|
||||
lines.append("")
|
||||
lines.append("## 综合突破强度候选")
|
||||
if combined_picks:
|
||||
lines.append("| 排名 | 股票 | 日期 | 方向 | 综合强度 | 宽度比 | 放量确认 |")
|
||||
lines.append("| --- | --- | --- | --- | --- | --- | --- |")
|
||||
for i, r in enumerate(combined_picks, start=1):
|
||||
stock = f"{r.get('stock_code', '')} {r.get('stock_name', '')}".strip()
|
||||
lines.append(
|
||||
f"| {i} | {stock} | {r.get('date', '')} | "
|
||||
f"{r.get('combined_dir', '')} | {r.get('combined_strength', 0.0):.4f} | "
|
||||
f"{r.get('width_ratio', 0.0):.4f} | {fmt_bool(r.get('volume_confirmed'))} |"
|
||||
)
|
||||
else:
|
||||
lines.append("无满足条件的综合突破记录。")
|
||||
lines.append("")
|
||||
lines.append("## 向上突破候选")
|
||||
if up_picks:
|
||||
lines.append("| 排名 | 股票 | 日期 | 强度 | 宽度比 | 触碰(上/下) | 放量确认 |")
|
||||
lines.append("| --- | --- | --- | --- | --- | --- | --- |")
|
||||
for i, r in enumerate(up_picks, start=1):
|
||||
stock = f"{r.get('stock_code', '')} {r.get('stock_name', '')}".strip()
|
||||
lines.append(
|
||||
f"| {i} | {stock} | {r.get('date', '')} | "
|
||||
f"{r.get('breakout_strength_up', 0.0):.4f} | "
|
||||
f"{r.get('width_ratio', 0.0):.4f} | "
|
||||
f"{r.get('touches_upper', 0)}/{r.get('touches_lower', 0)} | "
|
||||
f"{fmt_bool(r.get('volume_confirmed'))} |"
|
||||
)
|
||||
else:
|
||||
lines.append("无满足条件的向上突破记录。")
|
||||
lines.append("")
|
||||
lines.append("## 向下突破候选")
|
||||
if down_picks:
|
||||
lines.append("| 排名 | 股票 | 日期 | 强度 | 宽度比 | 触碰(上/下) | 放量确认 |")
|
||||
lines.append("| --- | --- | --- | --- | --- | --- | --- |")
|
||||
for i, r in enumerate(down_picks, start=1):
|
||||
stock = f"{r.get('stock_code', '')} {r.get('stock_name', '')}".strip()
|
||||
lines.append(
|
||||
f"| {i} | {stock} | {r.get('date', '')} | "
|
||||
f"{r.get('breakout_strength_down', 0.0):.4f} | "
|
||||
f"{r.get('width_ratio', 0.0):.4f} | "
|
||||
f"{r.get('touches_upper', 0)}/{r.get('touches_lower', 0)} | "
|
||||
f"{fmt_bool(r.get('volume_confirmed'))} |"
|
||||
)
|
||||
else:
|
||||
lines.append("无满足条件的向下突破记录。")
|
||||
lines.append("")
|
||||
lines.append("## 说明")
|
||||
lines.append("- 强度由价格突破幅度、收敛程度与成交量放大综合计算。")
|
||||
lines.append("- 仅统计 `breakout_dir` 与方向一致的记录。")
|
||||
lines.append("- 每只股票仅保留强度最高的一条记录(同强度取最新日期)。")
|
||||
lines.append("- 综合强度 = max(向上强度, 向下强度)。")
|
||||
lines.append("")
|
||||
|
||||
with open(output_path, "w", encoding="utf-8") as f:
|
||||
f.write("\n".join(lines))
|
||||
|
||||
|
||||
def main() -> None:
|
||||
parser = argparse.ArgumentParser(description="生成收敛三角形突破强度选股简报")
|
||||
parser.add_argument(
|
||||
"--input",
|
||||
default=os.path.join("outputs", "converging_triangles", "all_results.csv"),
|
||||
help="输入 CSV 路径",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--output",
|
||||
default=os.path.join("outputs", "converging_triangles", "report.md"),
|
||||
help="输出 Markdown 路径",
|
||||
)
|
||||
parser.add_argument("--threshold", type=float, default=0.3, help="强度阈值")
|
||||
parser.add_argument("--top-n", type=int, default=20, help="每方向输出数量")
|
||||
args = parser.parse_args()
|
||||
|
||||
if not os.path.exists(args.input):
|
||||
raise FileNotFoundError(f"输入文件不存在: {args.input}")
|
||||
|
||||
rows = load_rows(args.input)
|
||||
output_dir = os.path.dirname(args.output)
|
||||
if output_dir:
|
||||
os.makedirs(output_dir, exist_ok=True)
|
||||
build_report(rows, args.threshold, args.top_n, args.output)
|
||||
print(f"Report saved to: {args.output}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
230
scripts/run_converging_triangle.py
Normal file
230
scripts/run_converging_triangle.py
Normal file
@ -0,0 +1,230 @@
|
||||
"""
|
||||
批量滚动检测收敛三角形 - 从 pkl 文件读取 OHLCV 数据
|
||||
每个股票的每个交易日都会计算,输出 DataFrame
|
||||
"""
|
||||
|
||||
import os
|
||||
import sys
|
||||
import pickle
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
# 让脚本能找到 src/ 下的模块
|
||||
sys.path.append(os.path.join(os.path.dirname(__file__), "..", "src"))
|
||||
|
||||
from converging_triangle import (
|
||||
ConvergingTriangleParams,
|
||||
ConvergingTriangleResult,
|
||||
detect_converging_triangle_batch,
|
||||
)
|
||||
|
||||
# ============================================================================
|
||||
# 【可调参数区】
|
||||
# ============================================================================
|
||||
|
||||
# --- 数据源 ---
|
||||
DATA_DIR = os.path.join(os.path.dirname(__file__), "..", "data")
|
||||
|
||||
# --- 检测参数 ---
|
||||
PARAMS = ConvergingTriangleParams(
|
||||
window=400,
|
||||
pivot_k=20,
|
||||
boundary_n_segments=2,
|
||||
boundary_source="full",
|
||||
upper_slope_max=0.10,
|
||||
lower_slope_min=-0.10,
|
||||
touch_tol=0.10,
|
||||
touch_loss_max=0.10,
|
||||
shrink_ratio=0.8,
|
||||
break_tol=0.001,
|
||||
vol_window=20,
|
||||
vol_k=1.3,
|
||||
false_break_m=5,
|
||||
)
|
||||
|
||||
# --- 计算范围 ---
|
||||
# 设为 None 表示计算全部历史;设为具体数字可以只算最近 N 天
|
||||
RECENT_DAYS = 500 # 默认只算最近 500 天,避免计算时间过长
|
||||
|
||||
# --- 输出控制 ---
|
||||
ONLY_VALID = True # True: 只输出识别到三角形的记录; False: 输出所有记录
|
||||
VERBOSE = True
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# pkl 数据加载
|
||||
# ============================================================================
|
||||
|
||||
class FakeModule:
|
||||
"""空壳模块,绕过 model 依赖"""
|
||||
ndarray = np.ndarray
|
||||
|
||||
|
||||
def load_pkl(pkl_path: str) -> dict:
|
||||
"""加载 pkl 文件,返回字典 {mtx, dtes, tkrs, tkrs_name, ...}"""
|
||||
sys.modules['model'] = FakeModule()
|
||||
sys.modules['model.index_info'] = FakeModule()
|
||||
|
||||
with open(pkl_path, 'rb') as f:
|
||||
data = pickle.load(f)
|
||||
return data
|
||||
|
||||
|
||||
def load_ohlcv_from_pkl(data_dir: str) -> tuple:
|
||||
"""
|
||||
从 pkl 文件加载 OHLCV 数据
|
||||
|
||||
Returns:
|
||||
open_mtx, high_mtx, low_mtx, close_mtx, volume_mtx: shape=(n_stocks, n_days)
|
||||
dates: shape=(n_days,) 真实日期 (如 20050104)
|
||||
tkrs: shape=(n_stocks,) 股票代码
|
||||
tkrs_name: shape=(n_stocks,) 股票名称
|
||||
"""
|
||||
open_data = load_pkl(os.path.join(data_dir, "open.pkl"))
|
||||
high_data = load_pkl(os.path.join(data_dir, "high.pkl"))
|
||||
low_data = load_pkl(os.path.join(data_dir, "low.pkl"))
|
||||
close_data = load_pkl(os.path.join(data_dir, "close.pkl"))
|
||||
volume_data = load_pkl(os.path.join(data_dir, "volume.pkl"))
|
||||
|
||||
# 使用 close 的元数据
|
||||
dates = close_data["dtes"]
|
||||
tkrs = close_data["tkrs"]
|
||||
tkrs_name = close_data["tkrs_name"]
|
||||
|
||||
return (
|
||||
open_data["mtx"],
|
||||
high_data["mtx"],
|
||||
low_data["mtx"],
|
||||
close_data["mtx"],
|
||||
volume_data["mtx"],
|
||||
dates,
|
||||
tkrs,
|
||||
tkrs_name,
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 主流程
|
||||
# ============================================================================
|
||||
|
||||
def main() -> None:
|
||||
print("=" * 70)
|
||||
print("Converging Triangle Batch Detection")
|
||||
print("=" * 70)
|
||||
|
||||
# 1. 加载数据
|
||||
print("\n[1] Loading OHLCV pkl files...")
|
||||
open_mtx, high_mtx, low_mtx, close_mtx, volume_mtx, dates, tkrs, tkrs_name = load_ohlcv_from_pkl(DATA_DIR)
|
||||
n_stocks, n_days = close_mtx.shape
|
||||
print(f" Stocks: {n_stocks}")
|
||||
print(f" Days: {n_days}")
|
||||
print(f" Date range: {dates[0]} ~ {dates[-1]}")
|
||||
|
||||
# 2. 找有效数据范围(排除全 NaN 的列)
|
||||
any_valid = np.any(~np.isnan(close_mtx), axis=0)
|
||||
valid_day_idx = np.where(any_valid)[0]
|
||||
if len(valid_day_idx) == 0:
|
||||
print("No valid data found!")
|
||||
return
|
||||
last_valid_day = valid_day_idx[-1]
|
||||
print(f" Last valid day: {last_valid_day} (date: {dates[last_valid_day]})")
|
||||
|
||||
# 3. 计算范围
|
||||
start_day = PARAMS.window - 1
|
||||
end_day = last_valid_day # 使用最后有效日,而不是矩阵末尾
|
||||
|
||||
if RECENT_DAYS is not None:
|
||||
start_day = max(start_day, end_day - RECENT_DAYS + 1)
|
||||
|
||||
print(f"\n[2] Detection range: day {start_day} ~ {end_day}")
|
||||
print(f" Window size: {PARAMS.window}")
|
||||
print(f" Total points: {n_stocks} x {end_day - start_day + 1} = {n_stocks * (end_day - start_day + 1)}")
|
||||
|
||||
# 3. 批量检测
|
||||
print("\n[3] Running batch detection...")
|
||||
df = detect_converging_triangle_batch(
|
||||
open_mtx=open_mtx,
|
||||
high_mtx=high_mtx,
|
||||
low_mtx=low_mtx,
|
||||
close_mtx=close_mtx,
|
||||
volume_mtx=volume_mtx,
|
||||
params=PARAMS,
|
||||
start_day=start_day,
|
||||
end_day=end_day,
|
||||
only_valid=ONLY_VALID,
|
||||
verbose=VERBOSE,
|
||||
)
|
||||
|
||||
# 4. 添加股票代码、名称和真实日期
|
||||
if len(df) > 0:
|
||||
df["stock_code"] = df["stock_idx"].map(lambda x: tkrs[x] if x < len(tkrs) else "")
|
||||
df["stock_name"] = df["stock_idx"].map(lambda x: tkrs_name[x] if x < len(tkrs_name) else "")
|
||||
df["date"] = df["date_idx"].map(lambda x: dates[x] if x < len(dates) else 0)
|
||||
|
||||
# 5. 输出结果
|
||||
print("\n" + "=" * 70)
|
||||
print("Detection Results")
|
||||
print("=" * 70)
|
||||
|
||||
if ONLY_VALID:
|
||||
print(f"\nTotal valid triangles detected: {len(df)}")
|
||||
else:
|
||||
valid_count = df["is_valid"].sum()
|
||||
print(f"\nTotal records: {len(df)}")
|
||||
print(f"Valid triangles: {valid_count} ({valid_count/len(df)*100:.1f}%)")
|
||||
|
||||
# 按突破方向统计
|
||||
if len(df) > 0 and "breakout_dir" in df.columns:
|
||||
breakout_stats = df[df["is_valid"] == True]["breakout_dir"].value_counts()
|
||||
print(f"\nBreakout statistics:")
|
||||
for dir_name, count in breakout_stats.items():
|
||||
print(f" - {dir_name}: {count}")
|
||||
|
||||
# 突破强度统计
|
||||
if len(df) > 0 and "breakout_strength_up" in df.columns:
|
||||
valid_df = df[df["is_valid"] == True]
|
||||
if len(valid_df) > 0:
|
||||
print(f"\nBreakout strength (valid triangles):")
|
||||
print(f" - Up mean: {valid_df['breakout_strength_up'].mean():.4f}, max: {valid_df['breakout_strength_up'].max():.4f}")
|
||||
print(f" - Down mean: {valid_df['breakout_strength_down'].mean():.4f}, max: {valid_df['breakout_strength_down'].max():.4f}")
|
||||
|
||||
# 6. 保存结果
|
||||
outputs_dir = os.path.join(os.path.dirname(__file__), "..", "outputs", "converging_triangles")
|
||||
os.makedirs(outputs_dir, exist_ok=True)
|
||||
|
||||
# 保存完整 CSV (使用 utf-8-sig 支持中文)
|
||||
csv_path = os.path.join(outputs_dir, "all_results.csv")
|
||||
df.to_csv(csv_path, index=False, encoding="utf-8-sig")
|
||||
print(f"\nResults saved to: {csv_path}")
|
||||
|
||||
# 保存高强度突破记录 (strength > 0.3)
|
||||
if len(df) > 0:
|
||||
strong_up = df[(df["is_valid"] == True) & (df["breakout_strength_up"] > 0.3)]
|
||||
strong_down = df[(df["is_valid"] == True) & (df["breakout_strength_down"] > 0.3)]
|
||||
|
||||
if len(strong_up) > 0:
|
||||
strong_up_path = os.path.join(outputs_dir, "strong_breakout_up.csv")
|
||||
strong_up.to_csv(strong_up_path, index=False, encoding="utf-8-sig")
|
||||
print(f"Strong up breakouts ({len(strong_up)}): {strong_up_path}")
|
||||
|
||||
if len(strong_down) > 0:
|
||||
strong_down_path = os.path.join(outputs_dir, "strong_breakout_down.csv")
|
||||
strong_down.to_csv(strong_down_path, index=False, encoding="utf-8-sig")
|
||||
print(f"Strong down breakouts ({len(strong_down)}): {strong_down_path}")
|
||||
|
||||
# 7. 显示样本
|
||||
if len(df) > 0:
|
||||
print("\n" + "-" * 70)
|
||||
print("Sample results (first 10):")
|
||||
print("-" * 70)
|
||||
display_cols = [
|
||||
"stock_code", "date", "is_valid",
|
||||
"breakout_strength_up", "breakout_strength_down",
|
||||
"breakout_dir", "width_ratio"
|
||||
]
|
||||
display_cols = [c for c in display_cols if c in df.columns]
|
||||
print(df[display_cols].head(10).to_string(index=False))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
BIN
src/__pycache__/converging_triangle.cpython-313.pyc
Normal file
BIN
src/__pycache__/converging_triangle.cpython-313.pyc
Normal file
Binary file not shown.
BIN
src/__pycache__/sym_triangle.cpython-313.pyc
Normal file
BIN
src/__pycache__/sym_triangle.cpython-313.pyc
Normal file
Binary file not shown.
292
src/archive/sym_triangle.py
Normal file
292
src/archive/sym_triangle.py
Normal file
@ -0,0 +1,292 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass
|
||||
from typing import List, Literal, Optional, Tuple
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
|
||||
@dataclass
|
||||
class SymTriangleResult:
|
||||
start: int
|
||||
end: int
|
||||
upper_coef: Tuple[float, float] # (a_u, b_u)
|
||||
lower_coef: Tuple[float, float] # (a_l, b_l)
|
||||
width_start: float
|
||||
width_end: float
|
||||
width_ratio: float
|
||||
touches_upper: int
|
||||
touches_lower: int
|
||||
apex_x: float
|
||||
breakout: Literal["up", "down", "none"]
|
||||
breakout_idx: Optional[int]
|
||||
volume_confirmed: Optional[bool]
|
||||
false_breakout: Optional[bool]
|
||||
|
||||
|
||||
def pivots_fractal(
|
||||
high: np.ndarray, low: np.ndarray, k: int = 3
|
||||
) -> Tuple[np.ndarray, np.ndarray]:
|
||||
"""左右窗口分形:返回 pivot_high_idx, pivot_low_idx。"""
|
||||
n = len(high)
|
||||
ph: List[int] = []
|
||||
pl: List[int] = []
|
||||
for i in range(k, n - k):
|
||||
if high[i] == np.max(high[i - k : i + k + 1]):
|
||||
ph.append(i)
|
||||
if low[i] == np.min(low[i - k : i + k + 1]):
|
||||
pl.append(i)
|
||||
return np.array(ph, dtype=int), np.array(pl, dtype=int)
|
||||
|
||||
|
||||
def fit_line(x: np.ndarray, y: np.ndarray) -> Tuple[float, float]:
|
||||
"""拟合 y = a*x + b。"""
|
||||
a, b = np.polyfit(x, y, deg=1)
|
||||
return float(a), float(b)
|
||||
|
||||
|
||||
def fit_boundary_line(
|
||||
x: np.ndarray, y: np.ndarray, mode: str = "upper", n_segments: int = 3
|
||||
) -> Tuple[float, float]:
|
||||
"""
|
||||
边界线拟合(分段取极值):把时间轴分成 n_segments 段,每段取一个极值点。
|
||||
- mode="upper": 每段取最高点(让线连接各段峰值)
|
||||
- mode="lower": 每段取最低点(让线连接各段谷底)
|
||||
这样可以确保拟合的点在时间上分散,更接近手动画线。
|
||||
"""
|
||||
if len(x) < 2:
|
||||
return fit_line(x, y)
|
||||
|
||||
n_segments = min(n_segments, len(x))
|
||||
if n_segments < 2:
|
||||
n_segments = 2
|
||||
|
||||
# 按 x 排序
|
||||
sort_idx = np.argsort(x)
|
||||
x_sorted = x[sort_idx]
|
||||
y_sorted = y[sort_idx]
|
||||
|
||||
# 分段取极值
|
||||
segment_size = len(x_sorted) // n_segments
|
||||
boundary_x = []
|
||||
boundary_y = []
|
||||
|
||||
for i in range(n_segments):
|
||||
start = i * segment_size
|
||||
if i == n_segments - 1:
|
||||
end = len(x_sorted) # 最后一段包含剩余所有点
|
||||
else:
|
||||
end = (i + 1) * segment_size
|
||||
|
||||
if start >= end:
|
||||
continue
|
||||
|
||||
seg_x = x_sorted[start:end]
|
||||
seg_y = y_sorted[start:end]
|
||||
|
||||
if mode == "upper":
|
||||
idx = np.argmax(seg_y)
|
||||
else:
|
||||
idx = np.argmin(seg_y)
|
||||
|
||||
boundary_x.append(seg_x[idx])
|
||||
boundary_y.append(seg_y[idx])
|
||||
|
||||
if len(boundary_x) < 2:
|
||||
return fit_line(x, y)
|
||||
|
||||
return fit_line(np.array(boundary_x), np.array(boundary_y))
|
||||
|
||||
|
||||
def line_y(a: float, b: float, x: np.ndarray) -> np.ndarray:
|
||||
return a * x + b
|
||||
|
||||
|
||||
def detect_sym_triangle(
|
||||
df: pd.DataFrame,
|
||||
window: int = 120,
|
||||
pivot_k: int = 3,
|
||||
touch_tol: float = 0.006,
|
||||
touch_loss_max: float = 0.015,
|
||||
shrink_ratio: float = 0.6,
|
||||
break_tol: float = 0.003,
|
||||
vol_window: int = 20,
|
||||
vol_k: float = 1.5,
|
||||
false_break_m: int = 5,
|
||||
upper_slope_max: float = 0.0,
|
||||
lower_slope_min: float = 0.0,
|
||||
boundary_fit: bool = True,
|
||||
boundary_n_segments: int = 3,
|
||||
boundary_source: str = "pivots",
|
||||
) -> Optional[SymTriangleResult]:
|
||||
"""
|
||||
只检测“最近一个窗口”是否存在对称三角形,并给出突破/确认/假突破信息。
|
||||
- 实时:false_breakout 只能返回 None(因为需要未来 m 根确认)
|
||||
- 回测:如果 df 包含突破后的 m 根数据,才会输出 false_breakout True/False
|
||||
"""
|
||||
required = {"open", "high", "low", "close"}
|
||||
if not required.issubset(df.columns):
|
||||
raise ValueError(f"df must contain columns: {sorted(required)}")
|
||||
|
||||
n = len(df)
|
||||
if n < max(window, 2 * pivot_k + 5):
|
||||
return None
|
||||
|
||||
high = df["high"].to_numpy(dtype=float)
|
||||
low = df["low"].to_numpy(dtype=float)
|
||||
close = df["close"].to_numpy(dtype=float)
|
||||
has_volume = "volume" in df.columns and df["volume"].notna().any()
|
||||
volume = df["volume"].to_numpy(dtype=float) if has_volume else None
|
||||
|
||||
ph_idx, pl_idx = pivots_fractal(high, low, k=pivot_k)
|
||||
|
||||
end = n - 1
|
||||
start = max(0, end - window + 1)
|
||||
x_all = np.arange(n, dtype=float)
|
||||
|
||||
ph_in = ph_idx[(ph_idx >= start) & (ph_idx <= end)]
|
||||
pl_in = pl_idx[(pl_idx >= start) & (pl_idx <= end)]
|
||||
if len(ph_in) < 2 or len(pl_in) < 2:
|
||||
return None
|
||||
|
||||
# 拟合两条线(边界拟合:让线贴着峰/谷)
|
||||
if boundary_fit:
|
||||
if boundary_source == "full":
|
||||
x_upper = x_all[start : end + 1]
|
||||
y_upper = high[start : end + 1]
|
||||
x_lower = x_all[start : end + 1]
|
||||
y_lower = low[start : end + 1]
|
||||
else:
|
||||
x_upper = x_all[ph_in]
|
||||
y_upper = high[ph_in]
|
||||
x_lower = x_all[pl_in]
|
||||
y_lower = low[pl_in]
|
||||
a_u, b_u = fit_boundary_line(x_upper, y_upper, mode="upper", n_segments=boundary_n_segments)
|
||||
a_l, b_l = fit_boundary_line(x_lower, y_lower, mode="lower", n_segments=boundary_n_segments)
|
||||
else:
|
||||
a_u, b_u = fit_line(x_all[ph_in], high[ph_in])
|
||||
a_l, b_l = fit_line(x_all[pl_in], low[pl_in])
|
||||
|
||||
# 斜率:对称三角形硬条件(close-only 可放宽上/下沿斜率)
|
||||
if not (a_u <= upper_slope_max and a_l >= lower_slope_min):
|
||||
return None
|
||||
|
||||
# 宽度收敛
|
||||
upper_start = float(line_y(a_u, b_u, np.array([start]))[0])
|
||||
lower_start = float(line_y(a_l, b_l, np.array([start]))[0])
|
||||
upper_end = float(line_y(a_u, b_u, np.array([end]))[0])
|
||||
lower_end = float(line_y(a_l, b_l, np.array([end]))[0])
|
||||
width_start = upper_start - lower_start
|
||||
width_end = upper_end - lower_end
|
||||
if width_start <= 0 or width_end <= 0:
|
||||
return None
|
||||
width_ratio = width_end / width_start
|
||||
if width_ratio > shrink_ratio:
|
||||
return None
|
||||
|
||||
# 触碰程度(用 pivot 点贴线程度判定)
|
||||
ph_dist = np.abs(high[ph_in] - line_y(a_u, b_u, x_all[ph_in])) / np.maximum(
|
||||
line_y(a_u, b_u, x_all[ph_in]), 1e-9
|
||||
)
|
||||
pl_dist = np.abs(low[pl_in] - line_y(a_l, b_l, x_all[pl_in])) / np.maximum(
|
||||
line_y(a_l, b_l, x_all[pl_in]), 1e-9
|
||||
)
|
||||
touches_upper = int((ph_dist <= touch_tol).sum())
|
||||
touches_lower = int((pl_dist <= touch_tol).sum())
|
||||
# 用损失函数替代“>=2触碰”的硬条件
|
||||
loss_upper = float(np.mean(ph_dist)) if len(ph_dist) else float("inf")
|
||||
loss_lower = float(np.mean(pl_dist)) if len(pl_dist) else float("inf")
|
||||
if loss_upper > touch_loss_max or loss_lower > touch_loss_max:
|
||||
return None
|
||||
|
||||
# apex(两线交点)仅做合理性输出(可扩展为硬条件)
|
||||
denom = a_u - a_l
|
||||
apex_x = float((b_l - b_u) / denom) if abs(denom) > 1e-12 else float("inf")
|
||||
|
||||
# === 突破判定(用最后一根 close 做实时判断)===
|
||||
upper_last = upper_end
|
||||
lower_last = lower_end
|
||||
breakout: Literal["up", "down", "none"] = "none"
|
||||
breakout_idx: Optional[int] = None
|
||||
if close[end] > upper_last * (1 + break_tol):
|
||||
breakout = "up"
|
||||
breakout_idx = end
|
||||
elif close[end] < lower_last * (1 - break_tol):
|
||||
breakout = "down"
|
||||
breakout_idx = end
|
||||
|
||||
# 成交量确认(可选)
|
||||
volume_confirmed: Optional[bool] = None
|
||||
if breakout != "none" and has_volume and volume is not None:
|
||||
vol_ma = pd.Series(volume).rolling(vol_window).mean().to_numpy()
|
||||
if np.isfinite(vol_ma[end]) and vol_ma[end] > 0:
|
||||
volume_confirmed = bool(volume[end] > vol_ma[end] * vol_k)
|
||||
else:
|
||||
volume_confirmed = None
|
||||
|
||||
# 假突破(需要未来 m 根数据,实时无法得知)
|
||||
false_breakout: Optional[bool] = None
|
||||
if breakout != "none" and breakout_idx is not None:
|
||||
# 如果数据不足以观察未来 m 根,则返回 None
|
||||
if breakout_idx + false_break_m < n:
|
||||
false_breakout = False
|
||||
for t in range(breakout_idx + 1, breakout_idx + false_break_m + 1):
|
||||
upper_t = float(line_y(a_u, b_u, np.array([t]))[0])
|
||||
lower_t = float(line_y(a_l, b_l, np.array([t]))[0])
|
||||
if breakout == "up" and close[t] < upper_t:
|
||||
false_breakout = True
|
||||
break
|
||||
if breakout == "down" and close[t] > lower_t:
|
||||
false_breakout = True
|
||||
break
|
||||
else:
|
||||
false_breakout = None
|
||||
|
||||
return SymTriangleResult(
|
||||
start=start,
|
||||
end=end,
|
||||
upper_coef=(a_u, b_u),
|
||||
lower_coef=(a_l, b_l),
|
||||
width_start=float(width_start),
|
||||
width_end=float(width_end),
|
||||
width_ratio=float(width_ratio),
|
||||
touches_upper=touches_upper,
|
||||
touches_lower=touches_lower,
|
||||
apex_x=apex_x,
|
||||
breakout=breakout,
|
||||
breakout_idx=breakout_idx,
|
||||
volume_confirmed=volume_confirmed,
|
||||
false_breakout=false_breakout,
|
||||
)
|
||||
|
||||
|
||||
def plot_sym_triangle(df: pd.DataFrame, res: SymTriangleResult) -> None:
|
||||
"""简单可视化验算(需要 matplotlib)。"""
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
close = df["close"].to_numpy(dtype=float)
|
||||
x = np.arange(len(df), dtype=float)
|
||||
a_u, b_u = res.upper_coef
|
||||
a_l, b_l = res.lower_coef
|
||||
|
||||
start, end = res.start, res.end
|
||||
xw = np.arange(start, end + 1, dtype=float)
|
||||
upper = line_y(a_u, b_u, xw)
|
||||
lower = line_y(a_l, b_l, xw)
|
||||
|
||||
plt.figure(figsize=(12, 5))
|
||||
plt.plot(x, close, linewidth=1.2, label="close")
|
||||
plt.plot(xw, upper, linewidth=2, label="upper")
|
||||
plt.plot(xw, lower, linewidth=2, label="lower")
|
||||
plt.axvline(end, color="gray", linestyle="--", linewidth=1)
|
||||
plt.title(
|
||||
"sym_triangle: "
|
||||
f"width_ratio={res.width_ratio:.2f}, "
|
||||
f"touches=({res.touches_upper},{res.touches_lower}), "
|
||||
f"breakout={res.breakout}, "
|
||||
f"vol_ok={res.volume_confirmed}, "
|
||||
f"false={res.false_breakout}"
|
||||
)
|
||||
plt.legend()
|
||||
plt.show()
|
||||
509
src/converging_triangle.py
Normal file
509
src/converging_triangle.py
Normal file
@ -0,0 +1,509 @@
|
||||
"""
|
||||
收敛三角形检测算法 (Converging Triangle Detection)
|
||||
|
||||
支持:
|
||||
- 二维矩阵批量输入 (n_stocks, n_days)
|
||||
- 历史滚动计算 (每个交易日往过去看 window 天)
|
||||
- 突破强度分数 (0~1)
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field, asdict
|
||||
from typing import List, Literal, Optional, Tuple
|
||||
|
||||
import numpy as np
|
||||
import pandas as pd
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 参数对象
|
||||
# ============================================================================
|
||||
|
||||
@dataclass
|
||||
class ConvergingTriangleParams:
|
||||
"""收敛三角形检测参数"""
|
||||
|
||||
# 窗口设置
|
||||
window: int = 400
|
||||
|
||||
# 枢轴点检测
|
||||
pivot_k: int = 20
|
||||
|
||||
# 边界线拟合
|
||||
boundary_n_segments: int = 2
|
||||
boundary_source: str = "full" # "full" | "pivots"
|
||||
|
||||
# 斜率约束
|
||||
upper_slope_max: float = 0.10
|
||||
lower_slope_min: float = -0.10
|
||||
|
||||
# 触碰判定
|
||||
touch_tol: float = 0.10
|
||||
touch_loss_max: float = 0.10
|
||||
|
||||
# 收敛要求
|
||||
shrink_ratio: float = 0.8
|
||||
|
||||
# 突破判定
|
||||
break_tol: float = 0.001
|
||||
vol_window: int = 20
|
||||
vol_k: float = 1.3
|
||||
false_break_m: int = 5
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 返回对象
|
||||
# ============================================================================
|
||||
|
||||
@dataclass
|
||||
class ConvergingTriangleResult:
|
||||
"""收敛三角形检测结果 (单个股票单个日期)"""
|
||||
|
||||
# 基础标识
|
||||
stock_idx: int
|
||||
date_idx: int
|
||||
is_valid: bool # 是否识别到有效三角形
|
||||
|
||||
# 突破强度 (0~1 连续分数)
|
||||
breakout_strength_up: float = 0.0
|
||||
breakout_strength_down: float = 0.0
|
||||
|
||||
# 几何属性
|
||||
upper_slope: float = 0.0
|
||||
lower_slope: float = 0.0
|
||||
width_ratio: float = 0.0
|
||||
touches_upper: int = 0
|
||||
touches_lower: int = 0
|
||||
apex_x: float = 0.0
|
||||
|
||||
# 突破状态
|
||||
breakout_dir: str = "none" # "up" | "down" | "none"
|
||||
volume_confirmed: Optional[bool] = None
|
||||
false_breakout: Optional[bool] = None
|
||||
|
||||
# 窗口范围
|
||||
window_start: int = 0
|
||||
window_end: int = 0
|
||||
|
||||
def to_dict(self) -> dict:
|
||||
"""转换为字典"""
|
||||
return asdict(self)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 基础工具函数 (从 sym_triangle.py 复用)
|
||||
# ============================================================================
|
||||
|
||||
def pivots_fractal(
|
||||
high: np.ndarray, low: np.ndarray, k: int = 3
|
||||
) -> Tuple[np.ndarray, np.ndarray]:
|
||||
"""左右窗口分形:返回 pivot_high_idx, pivot_low_idx"""
|
||||
n = len(high)
|
||||
ph: List[int] = []
|
||||
pl: List[int] = []
|
||||
for i in range(k, n - k):
|
||||
if high[i] == np.max(high[i - k : i + k + 1]):
|
||||
ph.append(i)
|
||||
if low[i] == np.min(low[i - k : i + k + 1]):
|
||||
pl.append(i)
|
||||
return np.array(ph, dtype=int), np.array(pl, dtype=int)
|
||||
|
||||
|
||||
def fit_line(x: np.ndarray, y: np.ndarray) -> Tuple[float, float]:
|
||||
"""拟合 y = a*x + b"""
|
||||
if len(x) < 2:
|
||||
return 0.0, float(y[0]) if len(y) > 0 else 0.0
|
||||
a, b = np.polyfit(x, y, deg=1)
|
||||
return float(a), float(b)
|
||||
|
||||
|
||||
def fit_boundary_line(
|
||||
x: np.ndarray, y: np.ndarray, mode: str = "upper", n_segments: int = 3
|
||||
) -> Tuple[float, float]:
|
||||
"""
|
||||
边界线拟合(分段取极值)
|
||||
- mode="upper": 每段取最高点
|
||||
- mode="lower": 每段取最低点
|
||||
"""
|
||||
if len(x) < 2:
|
||||
return fit_line(x, y)
|
||||
|
||||
n_segments = min(n_segments, len(x))
|
||||
if n_segments < 2:
|
||||
n_segments = 2
|
||||
|
||||
sort_idx = np.argsort(x)
|
||||
x_sorted = x[sort_idx]
|
||||
y_sorted = y[sort_idx]
|
||||
|
||||
segment_size = len(x_sorted) // n_segments
|
||||
boundary_x = []
|
||||
boundary_y = []
|
||||
|
||||
for i in range(n_segments):
|
||||
start = i * segment_size
|
||||
if i == n_segments - 1:
|
||||
end = len(x_sorted)
|
||||
else:
|
||||
end = (i + 1) * segment_size
|
||||
|
||||
if start >= end:
|
||||
continue
|
||||
|
||||
seg_x = x_sorted[start:end]
|
||||
seg_y = y_sorted[start:end]
|
||||
|
||||
if mode == "upper":
|
||||
idx = np.argmax(seg_y)
|
||||
else:
|
||||
idx = np.argmin(seg_y)
|
||||
|
||||
boundary_x.append(seg_x[idx])
|
||||
boundary_y.append(seg_y[idx])
|
||||
|
||||
if len(boundary_x) < 2:
|
||||
return fit_line(x, y)
|
||||
|
||||
return fit_line(np.array(boundary_x), np.array(boundary_y))
|
||||
|
||||
|
||||
def line_y(a: float, b: float, x: np.ndarray) -> np.ndarray:
|
||||
"""计算线上的 y 值"""
|
||||
return a * x + b
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 突破强度计算
|
||||
# ============================================================================
|
||||
|
||||
def calc_breakout_strength(
|
||||
close: float,
|
||||
upper_line: float,
|
||||
lower_line: float,
|
||||
volume_ratio: float,
|
||||
width_ratio: float,
|
||||
) -> Tuple[float, float]:
|
||||
"""
|
||||
计算向上/向下突破强度 (0~1)
|
||||
|
||||
综合考虑:
|
||||
- 价格突破幅度 (close 相对于上/下沿的距离)
|
||||
- 成交量放大倍数
|
||||
- 收敛程度 (width_ratio 越小越强)
|
||||
|
||||
Returns:
|
||||
(strength_up, strength_down)
|
||||
"""
|
||||
# 价格突破分数
|
||||
price_up = max(0, (close - upper_line) / upper_line) if upper_line > 0 else 0
|
||||
price_down = max(0, (lower_line - close) / lower_line) if lower_line > 0 else 0
|
||||
|
||||
# 收敛加成 (越收敛, 突破越有效)
|
||||
convergence_bonus = max(0, 1 - width_ratio)
|
||||
|
||||
# 成交量加成 (放量2倍=满分)
|
||||
vol_bonus = min(1, max(0, volume_ratio - 1)) if volume_ratio > 0 else 0
|
||||
|
||||
# 加权合成
|
||||
# 基础分数 * 收敛加成 * 成交量加成
|
||||
strength_up = min(1.0, price_up * 5 * (1 + convergence_bonus * 0.5) * (1 + vol_bonus * 0.5))
|
||||
strength_down = min(1.0, price_down * 5 * (1 + convergence_bonus * 0.5) * (1 + vol_bonus * 0.5))
|
||||
|
||||
return strength_up, strength_down
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 单点检测函数
|
||||
# ============================================================================
|
||||
|
||||
def detect_converging_triangle(
|
||||
high: np.ndarray,
|
||||
low: np.ndarray,
|
||||
close: np.ndarray,
|
||||
volume: Optional[np.ndarray],
|
||||
params: ConvergingTriangleParams,
|
||||
stock_idx: int = 0,
|
||||
date_idx: int = 0,
|
||||
) -> ConvergingTriangleResult:
|
||||
"""
|
||||
检测单个窗口是否存在收敛三角形
|
||||
|
||||
Args:
|
||||
high, low, close: 窗口内的价格数据 (长度 = window)
|
||||
volume: 窗口内的成交量数据 (可选)
|
||||
params: 检测参数
|
||||
stock_idx: 股票索引 (用于结果标识)
|
||||
date_idx: 日期索引 (用于结果标识)
|
||||
|
||||
Returns:
|
||||
ConvergingTriangleResult
|
||||
"""
|
||||
n = len(close)
|
||||
window = params.window
|
||||
|
||||
# 创建默认无效结果
|
||||
invalid_result = ConvergingTriangleResult(
|
||||
stock_idx=stock_idx,
|
||||
date_idx=date_idx,
|
||||
is_valid=False,
|
||||
window_start=max(0, n - window),
|
||||
window_end=n - 1,
|
||||
)
|
||||
|
||||
# 数据长度检查
|
||||
if n < max(window, 2 * params.pivot_k + 5):
|
||||
return invalid_result
|
||||
|
||||
# 计算枢轴点
|
||||
ph_idx, pl_idx = pivots_fractal(high, low, k=params.pivot_k)
|
||||
|
||||
end = n - 1
|
||||
start = max(0, end - window + 1)
|
||||
x_all = np.arange(n, dtype=float)
|
||||
|
||||
# 筛选窗口内的枢轴点
|
||||
ph_in = ph_idx[(ph_idx >= start) & (ph_idx <= end)]
|
||||
pl_in = pl_idx[(pl_idx >= start) & (pl_idx <= end)]
|
||||
|
||||
if len(ph_in) < 2 or len(pl_in) < 2:
|
||||
return invalid_result
|
||||
|
||||
# 拟合边界线
|
||||
if params.boundary_source == "full":
|
||||
x_upper = x_all[start : end + 1]
|
||||
y_upper = high[start : end + 1]
|
||||
x_lower = x_all[start : end + 1]
|
||||
y_lower = low[start : end + 1]
|
||||
else:
|
||||
x_upper = x_all[ph_in]
|
||||
y_upper = high[ph_in]
|
||||
x_lower = x_all[pl_in]
|
||||
y_lower = low[pl_in]
|
||||
|
||||
a_u, b_u = fit_boundary_line(x_upper, y_upper, mode="upper", n_segments=params.boundary_n_segments)
|
||||
a_l, b_l = fit_boundary_line(x_lower, y_lower, mode="lower", n_segments=params.boundary_n_segments)
|
||||
|
||||
# 斜率检查
|
||||
if not (a_u <= params.upper_slope_max and a_l >= params.lower_slope_min):
|
||||
return invalid_result
|
||||
|
||||
# 宽度收敛检查
|
||||
upper_start = float(line_y(a_u, b_u, np.array([start]))[0])
|
||||
lower_start = float(line_y(a_l, b_l, np.array([start]))[0])
|
||||
upper_end = float(line_y(a_u, b_u, np.array([end]))[0])
|
||||
lower_end = float(line_y(a_l, b_l, np.array([end]))[0])
|
||||
|
||||
width_start = upper_start - lower_start
|
||||
width_end = upper_end - lower_end
|
||||
|
||||
if width_start <= 0 or width_end <= 0:
|
||||
return invalid_result
|
||||
|
||||
width_ratio = width_end / width_start
|
||||
if width_ratio > params.shrink_ratio:
|
||||
return invalid_result
|
||||
|
||||
# 触碰程度检查
|
||||
ph_dist = np.abs(high[ph_in] - line_y(a_u, b_u, x_all[ph_in])) / np.maximum(
|
||||
line_y(a_u, b_u, x_all[ph_in]), 1e-9
|
||||
)
|
||||
pl_dist = np.abs(low[pl_in] - line_y(a_l, b_l, x_all[pl_in])) / np.maximum(
|
||||
line_y(a_l, b_l, x_all[pl_in]), 1e-9
|
||||
)
|
||||
|
||||
touches_upper = int((ph_dist <= params.touch_tol).sum())
|
||||
touches_lower = int((pl_dist <= params.touch_tol).sum())
|
||||
|
||||
loss_upper = float(np.mean(ph_dist)) if len(ph_dist) else float("inf")
|
||||
loss_lower = float(np.mean(pl_dist)) if len(pl_dist) else float("inf")
|
||||
|
||||
if loss_upper > params.touch_loss_max or loss_lower > params.touch_loss_max:
|
||||
return invalid_result
|
||||
|
||||
# Apex 计算
|
||||
denom = a_u - a_l
|
||||
apex_x = float((b_l - b_u) / denom) if abs(denom) > 1e-12 else float("inf")
|
||||
|
||||
# 突破判定
|
||||
breakout_dir: Literal["up", "down", "none"] = "none"
|
||||
breakout_idx: Optional[int] = None
|
||||
|
||||
if close[end] > upper_end * (1 + params.break_tol):
|
||||
breakout_dir = "up"
|
||||
breakout_idx = end
|
||||
elif close[end] < lower_end * (1 - params.break_tol):
|
||||
breakout_dir = "down"
|
||||
breakout_idx = end
|
||||
|
||||
# 成交量确认
|
||||
volume_confirmed: Optional[bool] = None
|
||||
volume_ratio = 1.0
|
||||
|
||||
if volume is not None and len(volume) >= params.vol_window:
|
||||
vol_ma = np.mean(volume[-params.vol_window:])
|
||||
if vol_ma > 0:
|
||||
volume_ratio = volume[-1] / vol_ma
|
||||
if breakout_dir != "none":
|
||||
volume_confirmed = bool(volume[-1] > vol_ma * params.vol_k)
|
||||
|
||||
# 假突破检测 (历史回测模式,实时无法得知)
|
||||
false_breakout: Optional[bool] = None
|
||||
# 注意: 这里是基于历史数据,无法检测假突破
|
||||
# 假突破需要看"未来"数据,与当前设计不符
|
||||
|
||||
# 计算突破强度
|
||||
strength_up, strength_down = calc_breakout_strength(
|
||||
close=close[end],
|
||||
upper_line=upper_end,
|
||||
lower_line=lower_end,
|
||||
volume_ratio=volume_ratio,
|
||||
width_ratio=width_ratio,
|
||||
)
|
||||
|
||||
return ConvergingTriangleResult(
|
||||
stock_idx=stock_idx,
|
||||
date_idx=date_idx,
|
||||
is_valid=True,
|
||||
breakout_strength_up=strength_up,
|
||||
breakout_strength_down=strength_down,
|
||||
upper_slope=a_u,
|
||||
lower_slope=a_l,
|
||||
width_ratio=width_ratio,
|
||||
touches_upper=touches_upper,
|
||||
touches_lower=touches_lower,
|
||||
apex_x=apex_x,
|
||||
breakout_dir=breakout_dir,
|
||||
volume_confirmed=volume_confirmed,
|
||||
false_breakout=false_breakout,
|
||||
window_start=start,
|
||||
window_end=end,
|
||||
)
|
||||
|
||||
|
||||
# ============================================================================
|
||||
# 批量滚动检测函数
|
||||
# ============================================================================
|
||||
|
||||
def detect_converging_triangle_batch(
|
||||
open_mtx: np.ndarray,
|
||||
high_mtx: np.ndarray,
|
||||
low_mtx: np.ndarray,
|
||||
close_mtx: np.ndarray,
|
||||
volume_mtx: np.ndarray,
|
||||
params: ConvergingTriangleParams,
|
||||
start_day: Optional[int] = None,
|
||||
end_day: Optional[int] = None,
|
||||
only_valid: bool = False,
|
||||
verbose: bool = False,
|
||||
) -> pd.DataFrame:
|
||||
"""
|
||||
批量滚动检测收敛三角形
|
||||
|
||||
Args:
|
||||
open_mtx, high_mtx, low_mtx, close_mtx, volume_mtx:
|
||||
OHLCV 矩阵, shape=(n_stocks, n_days)
|
||||
params: 检测参数
|
||||
start_day: 从哪一天开始计算 (默认: window-1, 即第一个有足够历史的点)
|
||||
end_day: 到哪一天结束 (默认: 最后一天)
|
||||
only_valid: 是否只返回识别到三角形的记录
|
||||
verbose: 是否打印进度
|
||||
|
||||
Returns:
|
||||
DataFrame with columns:
|
||||
- stock_idx, date_idx
|
||||
- is_valid
|
||||
- breakout_strength_up, breakout_strength_down
|
||||
- upper_slope, lower_slope, width_ratio
|
||||
- touches_upper, touches_lower, apex_x
|
||||
- breakout_dir, volume_confirmed, false_breakout
|
||||
- window_start, window_end
|
||||
"""
|
||||
n_stocks, n_days = close_mtx.shape
|
||||
window = params.window
|
||||
|
||||
# 默认起止日
|
||||
if start_day is None:
|
||||
start_day = window - 1
|
||||
if end_day is None:
|
||||
end_day = n_days - 1
|
||||
|
||||
# 确保范围有效
|
||||
start_day = max(window - 1, start_day)
|
||||
end_day = min(n_days - 1, end_day)
|
||||
|
||||
results: List[dict] = []
|
||||
total = n_stocks * (end_day - start_day + 1)
|
||||
processed = 0
|
||||
|
||||
for stock_idx in range(n_stocks):
|
||||
# 提取该股票的全部数据,并过滤 NaN
|
||||
high_stock = high_mtx[stock_idx, :]
|
||||
low_stock = low_mtx[stock_idx, :]
|
||||
close_stock = close_mtx[stock_idx, :]
|
||||
volume_stock = volume_mtx[stock_idx, :] if volume_mtx is not None else None
|
||||
|
||||
# 找到有效数据的 mask(非 NaN)
|
||||
valid_mask = ~np.isnan(close_stock)
|
||||
valid_indices = np.where(valid_mask)[0]
|
||||
|
||||
if len(valid_indices) < window:
|
||||
# 该股票有效数据不足一个窗口
|
||||
for date_idx in range(start_day, end_day + 1):
|
||||
if not only_valid:
|
||||
results.append(ConvergingTriangleResult(
|
||||
stock_idx=stock_idx,
|
||||
date_idx=date_idx,
|
||||
is_valid=False,
|
||||
).to_dict())
|
||||
processed += 1
|
||||
continue
|
||||
|
||||
# 提取有效数据
|
||||
high_valid = high_stock[valid_mask]
|
||||
low_valid = low_stock[valid_mask]
|
||||
close_valid = close_stock[valid_mask]
|
||||
volume_valid = volume_stock[valid_mask] if volume_stock is not None else None
|
||||
|
||||
# 在有效数据上滚动
|
||||
n_valid = len(close_valid)
|
||||
for valid_end in range(window - 1, n_valid):
|
||||
# 原始数据中的 date_idx
|
||||
orig_date_idx = valid_indices[valid_end]
|
||||
|
||||
# 检查是否在指定范围内
|
||||
if orig_date_idx < start_day or orig_date_idx > end_day:
|
||||
continue
|
||||
|
||||
# 提取窗口
|
||||
valid_start = valid_end - window + 1
|
||||
high_win = high_valid[valid_start:valid_end + 1]
|
||||
low_win = low_valid[valid_start:valid_end + 1]
|
||||
close_win = close_valid[valid_start:valid_end + 1]
|
||||
volume_win = volume_valid[valid_start:valid_end + 1] if volume_valid is not None else None
|
||||
|
||||
# 检测
|
||||
result = detect_converging_triangle(
|
||||
high=high_win,
|
||||
low=low_win,
|
||||
close=close_win,
|
||||
volume=volume_win,
|
||||
params=params,
|
||||
stock_idx=stock_idx,
|
||||
date_idx=orig_date_idx,
|
||||
)
|
||||
|
||||
if only_valid and not result.is_valid:
|
||||
processed += 1
|
||||
continue
|
||||
|
||||
results.append(result.to_dict())
|
||||
processed += 1
|
||||
|
||||
if verbose and (stock_idx + 1) % 10 == 0:
|
||||
print(f" Progress: {stock_idx + 1}/{n_stocks} stocks, {processed}/{total} points")
|
||||
|
||||
if verbose:
|
||||
print(f" Completed: {processed} points processed")
|
||||
|
||||
return pd.DataFrame(results)
|
||||
Loading…
x
Reference in New Issue
Block a user