203 lines
10 KiB
Markdown
203 lines
10 KiB
Markdown
# 全景扫描算法梳理
|
||
|
||
## 一、整体流程
|
||
|
||
全景扫描是对**全市场股票**做一次**技术信号全量检测**,结果写入 `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=买入` 的列表**算法一致**,只是接口与展示形式不同(一为分档统计,一为分页列表)。
|