14 KiB
本应用全部算法总结
一、全景扫描与策略建议
1.1 全景扫描(全量信号扫描)
触发:用户点击「全景扫描」或定时任务(如凌晨 01:00,crontab 0 1 * * 1-5)。
数据源:
- 股票列表:从表
stock_realtime_price读取全部code, name(即全市场股票)。 - K 线:优先 麦蕊智数
get_kline(period='d', days=120),备用 AKShare 日 K。
算法流程:
- 脚本
full_signal_scan.py按日scan_date运行,支持断点续扫(已扫过的 code 跳过)。 - 每只股票:取约 120 日 K 线 → 调用
detect_all_signals(df, lookback=5)(services/signal_detector.py)检测 7 个信号。 - 7 个信号:主升浪(85%)、日线底背离(80%)、龙抬头(75%)、真龙(70%)、短底背离(65%)、老鼠仓(60%)、反弹(55%)。每个信号得到是否触发、描述等。
- 结果写入表
stock_signal_scan:code, name, scan_date, triggered_count, signal_status, indicators, latest_signals。
(注意:本脚本不写入价格;表stock_realtime_price由其他定时任务或接口更新。)
并发:WORKERS=3,BATCH_SIZE=30,多线程按批处理。
1.2 策略建议(分档买卖建议)
触发:用户点击「策略建议」。
接口:GET /api/scan_strategy,可选 date、holding_codes(逗号分隔)。
算法:
- 从
stock_signal_scan读取当日scan_date全部记录(signal_status, indicators, triggered_count)。 - 对每条记录调用 统一推荐函数
_compute_recommend(signal_status, indicators, triggered_count, is_holding)(与扫描结果、提醒共用):- 持仓:MACD 死叉且无主升浪 → 卖出;有主升浪 → 加仓;有真龙 → 持有;否则 → 观望。
- 非持仓:底背离+龙抬头 → 买入;主升浪 → 加仓;真龙 → 关注;MACD 死叉 → 卖出;仅底背离 → 关注;仅龙抬头 → 关注;其他触发 → 观察;无 → 观望。
- 按推荐文案分档:
- 档1 立即买入:
disp == '买入'(底背离+龙抬头)。 - 档2 持仓加仓:
disp in ('加仓','持有')。 - 档3 关注:
disp == '关注'且龙抬头触发、无底背离。 - 档4 纳入关注:
disp == '关注'且其余(如仅底背离)。
- 档1 立即买入:
- 返回 4 档的
action/condition/desc/count/stocks,前端展示。
与全景扫描结果:使用同一套 _compute_recommend,算法一致。
1.3 扫描结果列表(GET /api/scan_results)
触发:全景扫描完成后前端拉取或切换筛选/分页。
接口:GET /api/scan_results,参数:date, min_triggered, signal_type, holding_codes, recommend_text, page, per_page, sort。
算法:
- 从
stock_signal_scan按scan_date筛选,可选按triggered_count、信号类型过滤。 - 若有
recommend_text(如「买入」「关注」):全量读出当日扫描,逐条_compute_recommend,统计各推荐数量,筛出disp == recommend_text的 code,再分页。 - 否则:按
triggered_count等排序分页,LEFT JOINstock_realtime_price取price, change_pct。 - 对当前页每条记录再算一次
_compute_recommend(带入holding_codes),得到recommend_type/recommend_text等返回前端。 - 列表中的现价来自表
stock_realtime_price(与全量扫描脚本无直接关系,需另有时效性更新)。
二、检查信号与批量扫描关注
2.1 单只检查信号(技术信号详情)
触发:在「技术信号」里输入/选择股票并查询,或从扫描列表点击某只股票。
接口:GET /api/technical_signals/<stock_code>?lookback=5&days=120。
算法:
- 用
_get_kline_data(stock_code, days)取 K 线(优先麦蕊智数,备用 AKShare)。 - 调用
detect_all_signals(kline_df, lookback=5),得到 7 个信号的触发情况、指标、说明。 - 返回
signals, latest_signals, signal_summary, indicators, signal_status。
不读stock_signal_scan,不读stock_realtime_price,纯实时 K 线+本地计算。
2.2 批量扫描关注
触发:用户点击「批量扫描关注」。
接口:POST /api/batch_technical_signals,body:{ codes: [关注列表的 code], lookback: 3, days: 120 }。
算法:
- 对
codes中每只(最多 20 只):取 K 线 →detect_all_signals→ 得到signal_status, triggered_count, latest_signals, indicators。 - 结果仅用于当前页展示,不写入
stock_signal_scan,不写入stock_realtime_price。 - 即:批量扫描关注 = 多只股票各自走一遍「单只检查信号」逻辑,无持久化。
三、模拟交易及查看时的现价
3.1 模拟持仓与统计中的现价
数据来源:
- 表
sim_positions存有每只持仓的current_price(上次更新时的现价)。 - 列表/统计接口(如
GET /api/sim/positions、GET /api/sim/stats)直接读该字段,不在此处调实时接口。
现价何时更新:
- 手动刷新:在「分析 → 模拟交易」页的 当前持仓 区块,点击 「刷新现价」 按钮时,会调用
updateSimPrices():- 对每条持仓并行请求
GET /api/realtime_price/<stock_code>(12s 超时); - 拿到价格后写回前端展示,并
POST /api/sim/update_prices,将{ stock_code: price }写入sim_positions.current_price,并刷新统计。
- 对每条持仓并行请求
- 进入模拟交易子页(
loadSimData())时不会自动拉实时价,只读库中的current_price。 - 后端定时任务(如 scheduler)也可按配置更新
sim_positions.current_price(若已实现)。
结论:模拟交易「查看」时的现价 = 库中 sim_positions.current_price;只有用户点击「刷新现价」(或定时任务)时,才通过 实时 API 经 /api/realtime_price 再经 /api/sim/update_prices 写入。
四、提醒 tab 下的刷新
触发:进入提醒 tab 或用户点击刷新(含强制刷新)。
涉及股票:关注列表(searchHistory)+ 当前持仓(holdingStocks),合并去重得到 allStocks。
算法流程:
- 优先读缓存(未强制刷新时):
GET /api/alerts_cache,若缓存存在且为当日且版本匹配,则用缓存填充stockAlerts,并updateHoldingPricesFromAlerts(),对新加入的股票做增量分析;然后直接结束,不再请求信号接口。 - 主流程:
POST /api/signal_alerts,body:{ stocks: allStocks, holding_codes: this.holdingStocks }。- 后端从
stock_signal_scan取当日扫描结果(signal_status, indicators, triggered_count),从stock_realtime_price取price, change_pct。 - 对每只股票调用
_compute_recommend(..., is_holding),得到推荐类型、理由、推荐率等,并与价格一起返回。 - 前端用返回结果覆盖
stockAlerts,保存缓存,并updateHoldingPricesFromAlerts()(用提醒里的 price 回填持仓的 currentPrice)。
- 后端从
- 后台补齐实时价:
调用refreshAlertPricesFromRealtime()(不传参 = 刷新全部stockAlerts):- 每批 5 只,
GET /api/realtime_price/<code>(超时 12s),用返回价格覆盖对应 alert 的price及latest_data['收盘价']; - 更新后
saveAlertsCache、updateHoldingPricesFromAlerts()。
即:提醒列表的最终展示价来自实时接口,不是表里旧值。
- 每批 5 只,
小结:
- 信号与推荐:
stock_signal_scan+_compute_recommend,价格初值来自stock_realtime_price。 - 最终现价:实时 API(
/api/realtime_price→ 麦蕊智数等)通过refreshAlertPricesFromRealtime()覆盖。
五、交易 tab 下的刷新价格
触发:用户在交易 tab 点击「刷新价格」。
涉及股票:仅 当前持仓(holdingPositions 中 quantity > 0 的 code)。
算法(与提醒共用一套逻辑):
- 取持仓 code 列表
holdingCodes。 - 设置
priceRefreshing = true,调用refreshAlertPricesFromRealtime(holdingCodes, 12000):- 若持仓在
stockAlerts中存在:按与提醒相同的逻辑,每批 5 只请求GET /api/realtime_price/<code>,更新 alert 的price,并updateHoldingPricesFromAlerts(),从而更新holdingPositions[code].currentPrice及统计。 - 若某持仓不在
stockAlerts中:仍对该 code 单独请求GET /api/realtime_price/<code>,直接写holdingPositions[code].currentPrice,并触发calculateTradeStats()。
- 若持仓在
- 结束后
checkStopLoss(),priceRefreshing = false。
接口统一:
- 提醒与交易刷新现价均使用 同一接口
GET /api/realtime_price/<stock_code>,后端为get_realtime_price(stock_code)(优先麦蕊智数,备用 AKShare),不读stock_realtime_price表。
附录:关键数据流一览
| 场景 | 股票范围 | 信号/推荐来源 | 现价来源(最终展示) |
|---|---|---|---|
| 全景扫描 | 全市场(stock_realtime_price 表) | detect_all_signals,写入 stock_signal_scan | 扫描不写价格;列表用 stock_realtime_price |
| 策略建议 | 当日 stock_signal_scan 全量 | _compute_recommend | 不展示单股现价 |
| 扫描结果列表 | 按筛选/分页 | stock_signal_scan + _compute_recommend | stock_realtime_price 表 |
| 单只/批量检查信号 | 用户选定/关注列表 | 实时 K 线 + detect_all_signals | 不涉及现价 |
| 模拟交易查看 | 模拟持仓 | sim_positions.current_price | 刷新时:/api/realtime_price → update_prices |
| 提醒刷新 | 关注+持仓 | stock_signal_scan + _compute_recommend;初价 stock_realtime_price | refreshAlertPricesFromRealtime → /api/realtime_price |
| 交易刷新价格 | 持仓 | 无信号重算 | refreshAlertPricesFromRealtime(holdingCodes) → /api/realtime_price |
统一推荐逻辑:_compute_recommend(signal_status, indicators, triggered_count, is_holding) 用于:策略建议、扫描结果列表的推荐列、提醒的推荐与理由。信号类型键与 signal_detector 一致(如真龙为 true_dragon)。
算法与逻辑是否一致?
一致的部分
-
信号检测
- 全景扫描(full_signal_scan)、单只检查信号、批量扫描关注,均使用 同一套
detect_all_signals(services/signal_detector.py),7 个信号定义与判定一致。 - 唯一区别:全景扫描写库(
stock_signal_scan),单只/批量不写库。
- 全景扫描(full_signal_scan)、单只检查信号、批量扫描关注,均使用 同一套
-
推荐逻辑
- 策略建议、扫描结果列表的推荐列、提醒的买卖/观望结论,均使用 同一函数
_compute_recommend(routes/analysis.py),同一只股票在相同持仓状态下会得到相同推荐(买入/加仓/持有/关注/观察/观望/卖出)。 - 策略建议的 4 档(立即买入、持仓加仓、关注、纳入关注)即按该推荐结果分组,无第二套规则。
- 策略建议、扫描结果列表的推荐列、提醒的买卖/观望结论,均使用 同一函数
-
现价刷新(提醒与交易)
- 提醒 tab 的「用实时价覆盖」与交易 tab 的「刷新价格」共用 同一方法
refreshAlertPricesFromRealtime(codesOnly?, timeoutMs),同一接口GET /api/realtime_price/<code>,同一后端get_realtime_price(麦蕊智数优先,AKShare 备用)。 - 逻辑一致:按 code 列表分批请求、写回 alert/持仓、更新缓存与统计。
- 提醒 tab 的「用实时价覆盖」与交易 tab 的「刷新价格」共用 同一方法
-
持仓状态
- 提醒、策略建议、扫描结果列表都使用同一套
holding_codes(前端传holdingStocks),_compute_recommend的is_holding与真实持仓一致,故「持有/加仓/卖出」等与是否持仓一致。
- 提醒、策略建议、扫描结果列表都使用同一套
需注意的差异(非矛盾)
-
数据来源与时效
- 全景扫描 / 策略建议 / 扫描结果 / 提醒(初值):依赖 当日
stock_signal_scan(及stock_realtime_price)。若今日未跑全量扫描,则无当日信号,提醒会显示「今日尚未扫描此股」等。 - 单只/批量检查信号:不读库,用当前 K 线实时算,与库内扫描结果可能不同(日期或数据源不同)。
- 设计如此:全量扫描是「当日快照」,单只/批量是「实时计算」,二者用途不同,不要求数值完全一致。
- 全景扫描 / 策略建议 / 扫描结果 / 提醒(初值):依赖 当日
-
现价来源
- 列表/表内展示(扫描结果、提醒初值):来自
stock_realtime_price表(由定时或其它任务更新)。 - 用户主动刷新后(提醒、交易、模拟持仓):来自 实时 API(
/api/realtime_price)。 - 即:先表后实时,两段一致(同一实时接口),只是数据源阶段不同。
- 列表/表内展示(扫描结果、提醒初值):来自
-
模拟交易现价
- 模拟持仓的
current_price仅在使用「刷新」或定时更新时从实时接口写入;查看时只读库,不自动调实时接口。与「真实交易 tab」的持仓现价逻辑相同(都是刷新时才拉实时价)。
- 模拟持仓的
结论
- 信号检测:全应用共用
detect_all_signals,一致。 - 推荐与分档:策略/扫描结果/提醒共用
_compute_recommend,一致。 - 现价刷新:提醒与交易共用
refreshAlertPricesFromRealtime与/api/realtime_price,一致。 - 差异仅在于:谁写库、谁读库、何时用表价/何时用实时价,属设计上的分工,不是算法或逻辑不一致。