Initial commit

This commit is contained in:
freedakgmail
2026-07-17 18:49:07 +08:00
commit 9c7d7abdd4
100 changed files with 41337 additions and 0 deletions
+202
View File
@@ -0,0 +1,202 @@
# 全景扫描算法梳理
## 一、整体流程
全景扫描是对**全市场股票**做一次**技术信号全量检测**,结果写入 `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=买入` 的列表**算法一致**,只是接口与展示形式不同(一为分档统计,一为分页列表)。