10 KiB
全景扫描算法梳理
一、整体流程
全景扫描是对全市场股票做一次技术信号全量检测,结果写入 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()):
- 本地 DB:
get_kline_from_local_db_threaded(code, days)(stock_kline_daily) - 阿里云 K 线 API:北交所跳过
- 腾讯 K 线 API:全市场(含北交所)
- 麦蕊智数:
get_kline(period='d', days=120) - 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 = 8BATCH_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 内部步骤
- 列类型:必要列缺失则返回错误;对
open/high/low/close/volume若非 float 则转成 float64。 - 指标计算:
calc_all_indicators(df),得到 MACD、SKDJ、KDJ、EMA、MA 等。 - 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)
- 汇总:
- 所有信号按
(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=买入)
-
按日期取扫描数据
从stock_signal_scan中取出scan_date当天的全部记录:
code, signal_status, indicators, triggered_count。 -
逐条计算推荐
对每条记录调用统一推荐函数(_compute_recommend→stock_algorithms.compute_recommend):_compute_recommend(signal_status, indicators, triggered_count, is_holding)is_holding = (code in holding_codes)
返回(signal_type, display_text, reason, recommend_rate),其中 display_text 即「买入 / 加仓 / 持有 / 关注 / 观察 / 观望 / 卖出」。
-
筛选「买入」
只保留display_text == recommend_text(例如display_text == '买入')的股票,得到「每日推荐买入」列表。 -
排序与分页
按triggered_count降序、其次code升序排序;再按page、per_page分页,取当前页的 code 列表。 -
补全当前页展示数据
用当前页的 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=买入的列表算法一致,只是接口与展示形式不同(一为分档统计,一为分页列表)。