Files
stock/stock-html/docs/全景扫描算法梳理.md
T
freedakgmail 9c7d7abdd4 Initial commit
2026-07-17 18:49:35 +08:00

203 lines
10 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 全景扫描算法梳理
## 一、整体流程
全景扫描是对**全市场股票**做一次**技术信号全量检测**,结果写入 `stock_signal_scan` 表,供前端「全景扫描」页、策略建议、提醒、模拟交易等使用。
### 1.1 触发方式
| 方式 | 说明 |
|------|------|
| 用户点击 | 前端「全景扫描」按钮 → 后台执行 `full_signal_scan.py`(可选) |
| 定时任务 | crontab 配置,如 11:50、16:30 或凌晨 01:00`0 1 * * 1-5` |
| 管理员 | 管理后台「启动全景扫描」触发 |
### 1.2 入口与脚本
- **脚本**`full_signal_scan.py`
- **扫描日期**`scan_date = date.today()`,支持按日断点续扫
- **环境变量**`FORCE_RESCAN=1` 时先清空当日 `stock_signal_scan` 再扫
### 1.3 数据源
| 数据类型 | 来源 | 说明 |
|----------|------|------|
| 股票列表 | 表 `stock_realtime_price` | `SELECT code, name ORDER BY code`,全市场 |
| K 线 | 多级回退 | 见下文「K 线获取顺序」 |
| 结果存储 | 表 `stock_signal_scan` | 不写价格;现价由 `stock_realtime_price` 等更新 |
**K 线获取顺序**`get_kline_data()`):
1. **本地 DB**`get_kline_from_local_db_threaded(code, days)``stock_kline_daily`
2. **阿里云 K 线 API**:北交所跳过
3. **腾讯 K 线 API**:全市场(含北交所)
4. **麦蕊智数**`get_kline(period='d', days=120)`
5. **AKShare**`stock_zh_a_hist` 日 K
取数长度:**K_DAYS = 120** 日。
---
## 二、单只股票扫描流程
对每只待扫描股票(`pending = 全市场 - 当日已扫`):
```
1. get_kline_data(code, days=120) → DataFrame (date, open, high, low, close, volume)
2. 若 df 为空或 len(df) < 30 → 跳过,记失败
3. detect_all_signals(df, lookback=LOOKBACK) → 7 个信号 + 指标 + signal_status
4. 汇总 triggered_count、signal_status、indicators、latest_signals
5. 写入内存缓冲;满 BATCH_SAVE_SIZE 条后批量 INSERT/UPDATE stock_signal_scan
```
**并发与批参数**`full_signal_scan.py`):
- `WORKERS = 8`
- `BATCH_SIZE = 80`(任务批)
- `BATCH_SAVE_SIZE = 40`(每 40 条写一次库)
- `LOOKBACK = 5`(检测最近 5 天内的信号)
---
## 三、核心算法:detect_all_signals
位置:`services/signal_detector.py``detect_all_signals(df, lookback=5)`
### 3.1 输入输出
- **输入**`df` 需含列 `date, open, high, low, close, volume``lookback` 默认 5。
- **输出**
`signals``latest_signals``signal_summary``indicators`、**`signal_status`**(用于推荐与展示)。
### 3.2 内部步骤
1. **列类型**:必要列缺失则返回错误;对 `open/high/low/close/volume` 若非 float 则转成 float64。
2. **指标计算**`calc_all_indicators(df)`,得到 MACD、SKDJ、KDJ、EMA、MA 等。
3. **7 个信号检测**(均在 `lookback` 窗口内):
- `detect_main_rising_wave(df, lookback)`
- `detect_daily_bottom_divergence(df, lookback)`
- `detect_dragon_head(df, lookback)`
- `detect_true_dragon(df, lookback)`
- `detect_short_bottom_divergence(df, lookback)`
- `detect_rat_trading(df, lookback)`
- `detect_rebound(df, lookback)`
4. **汇总**
- 所有信号按 `(strength 降序, date)` 排序;
-`latest_date` 当天的信号为 `latest_signals`
- 统计 `signal_summary`
- 取最后一根 K 的指标 → `indicators`macd/skdj/kdj/ema/ma);
- **`_check_all_signal_status(df)`** → `signal_status`(每条为 7 个信号的 `triggered/description/readiness` 等)。
全景扫描**写库**用的是 `signal_status``triggered_count``signal_status``triggered==True` 的个数),推荐与策略建议也用同一套 `signal_status` + `compute_recommend`
---
## 四、七个信号的判定条件(与 _check_all_signal_status 一致)
| 序号 | 信号名 | 强度 | 判定逻辑概要(当前 K / 最近窗口) |
|------|--------|------|----------------------------------|
| 1 | **主升浪** | 85% | DIF>0 且 DEA>0,且前一根 DIF≤DEA、当前 DIF>DEA(零上金叉) |
| 2 | **日线底背离** | 80% | 20 日内:收盘价在窗口新低附近(≤1.01×min);当前是窗口最低点;DIF 高于前低且 DIF<0 |
| 3 | **龙抬头** | 75% | SKDJ 曾超跌(K<20 或 K<30)K 上穿 D;最近 3 根 K 的 K 值标准差<15(稳定) |
| 4 | **真龙** | 70% | 4 条件中≥3 且必须含「价格>MA20」:价格>MA20、MA5>MA20、MACD 翻红(柱>0)、放量(>10 日均量 1.2 倍) |
| 5 | **短底背离** | 65% | 10 日内:收盘在窗口新低附近;当前是窗口最低点;DIF 高于 10 日内 DIF 前低 |
| 6 | **老鼠仓** | 60% | 盘中跌幅 (low-open)/open < -3%;回收率 (close-low)/(high-low) > 60%;收盘较开盘跌幅 > -1%;成交量 > 10 日均量 1.3 倍 |
| 7 | **反弹** | 55% | EMA3 前根 ≤ EMA21,当前 EMA3 > EMA21(金叉) |
- **detect_*** 系列:在 `lookback` 天内逐日检测,若有满足条件的 K 线则生成一条信号(含 date/type/strength/description 等)。
- **\_check_all_signal_status**:只对**最后一根 K 线**判断 7 个信号是否「当前触发」,并给出描述与就绪度;全景扫描的「是否触发」以 `signal_status[].triggered` 为准。
---
## 五、结果落库与表结构
- **表名**`stock_signal_scan`
- **写入字段**`code, name, scan_date, triggered_count, signal_status, indicators, latest_signals`
- **唯一约束**`(code, scan_date)`,重复写入时 `ON CONFLICT DO UPDATE`
- **说明**:脚本不写 `stock_realtime_price`;列表页现价、涨跌幅等来自该表或实时接口。
---
## 六、与推荐算法的一致性
- **统一推荐函数**`services/stock_algorithms.compute_recommend(signal_status, indicators, triggered_count, is_holding)`
- 被「全景扫描结果列表」「策略建议」「提醒」「模拟交易自动买卖」共用。
- **体系最强战法**(suanfa.md)在推荐中的体现:
- 日线底背离 → 纳入关注
- 龙抬头 → 买入(核心买点)
- 真龙/主升浪 → 持有/加仓
- 不见主升浪 → 可出场(如 MACD 死叉且无主升浪 → 卖出)
即:**全景扫描只负责「全量信号检测 + 落库」;「买/卖/加仓/持有/关注/观望」由同一套 `compute_recommend` 基于 `signal_status` 计算,保证与策略建议、提醒、模拟交易一致。**
---
## 七、小结
| 项目 | 内容 |
|------|------|
| 入口 | `full_signal_scan.py`,按日断点续扫 |
| 股票池 | `stock_realtime_price` 全表 code+name |
| K 线 | 120 日,本地 DB → 阿里 → 腾讯 → 麦蕊 → AKShare |
| 核心函数 | `detect_all_signals(df, lookback=5)` → 7 信号 + `signal_status` |
| 7 信号 | 主升浪(85%)、日线底背离(80%)、龙抬头(75%)、真龙(70%)、短底背离(65%)、老鼠仓(60%)、反弹(55%) |
| 落库 | `stock_signal_scan`,不写价格 |
| 推荐 | 与策略/提醒/模拟交易共用 `compute_recommend(signal_status, ...)` |
如需调整「哪些算触发」,只需改 `signal_detector.py` 中对应 `detect_*``_check_all_signal_status`;如需调整买卖建议,只需改 `stock_algorithms.compute_recommend`
---
## 八、全景扫描结果的「每日推荐买入」是如何获取的?
### 8.1 数据来源
- **每日推荐**依赖的是**当日(或最近一次)全景扫描**的结果表 **`stock_signal_scan`**。
- 若当天没有跑全量扫描,接口会**自动回退**到「最近一次有数据的 `scan_date`」(见 8.2)。
### 8.2 接口与参数
- **接口**`GET /api/scan_results``routes/analysis.py``get_scan_results()`)。
- **关键参数**
- `date`:扫描日期,默认当天;若该日无数据且未显式传 `date`,则用 `MAX(scan_date)` 回退。
- `recommend_text`:推荐文案筛选,例如 **`买入`**、`加仓``持有``关注``观察``观望``卖出`
- `holding_codes`:当前用户持仓 code 列表(逗号分隔),用于区分「持仓 / 非持仓」下的推荐。
### 8.3 「推荐买入」的获取流程(recommend_text=买入)
1. **按日期取扫描数据**
`stock_signal_scan` 中取出 `scan_date` 当天的**全部**记录:
`code, signal_status, indicators, triggered_count`
2. **逐条计算推荐**
对每条记录调用统一推荐函数(`_compute_recommend``stock_algorithms.compute_recommend`):
```text
_compute_recommend(signal_status, indicators, triggered_count, is_holding)
```
- `is_holding = (code in holding_codes)`
返回 `(signal_type, display_text, reason, recommend_rate)`,其中 **display_text** 即「买入 / 加仓 / 持有 / 关注 / 观察 / 观望 / 卖出」。
3. **筛选「买入」**
只保留 **`display_text == recommend_text`**(例如 `display_text == '买入'`)的股票,得到「每日推荐买入」列表。
4. **排序与分页**
按 `triggered_count` 降序、其次 `code` 升序排序;再按 `page`、`per_page` 分页,取当前页的 code 列表。
5. **补全当前页展示数据**
用当前页的 code 再查 `stock_signal_scan` + `stock_realtime_price`,对每条再次调用 `_compute_recommend`,得到 `recommend_text`、`recommend_reason`、现价等,返回前端。
### 8.4 何时会得到「买入」?
由 **`compute_recommend`**(体系最强战法)决定,**非持仓**时出现「买入」的条件包括:
- **最佳买入**:日线底背离 + 龙抬头,且 MACD 未死叉 → `display_text = '买入'`(评分 95)。
- **龙抬头买入**:有龙抬头且 MACD 金叉或暂无数据 → `display_text = '买入'`(评分 80);若同时有主升浪且 MACD 正常 → 也是「买入」(评分 90)。
- 若 MACD 死叉则会被降级为「关注」等,不会出现在「推荐买入」筛选中。
因此:**「每日推荐买入」= 当日(或回退日)`stock_signal_scan` 中,在给定 `holding_codes` 下经 `compute_recommend` 计算得到 `display_text == '买入'` 的股票列表,经排序分页后返回。**
### 8.5 与策略建议的关系
- **策略建议**`GET /api/scan_strategy`)同样读 `stock_signal_scan` 当日数据,对每条记录调用同一个 `_compute_recommend`,再按 `display_text` 分成 4 档(立即买入、持仓加仓、关注、纳入关注)。
- 「立即买入」档即 **`display_text == '买入'`**,与全景扫描里筛选 `recommend_text=买入` 的列表**算法一致**,只是接口与展示形式不同(一为分档统计,一为分页列表)。