Files
stock/stock-html/docs/应用算法总结.md
freedakgmail 9c7d7abdd4 Initial commit
2026-07-17 18:49:35 +08:00

14 KiB
Raw Permalink Blame History

本应用全部算法总结

一、全景扫描与策略建议

1.1 全景扫描(全量信号扫描)

触发:用户点击「全景扫描」或定时任务(如凌晨 01:00,crontab 0 1 * * 1-5)。

数据源

  • 股票列表:从表 stock_realtime_price 读取全部 code, name(即全市场股票)。
  • K 线:优先 麦蕊智数 get_kline(period='d', days=120),备用 AKShare 日 K。

算法流程

  1. 脚本 full_signal_scan.py 按日 scan_date 运行,支持断点续扫(已扫过的 code 跳过)。
  2. 每只股票:取约 120 日 K 线 → 调用 detect_all_signals(df, lookback=5)services/signal_detector.py)检测 7 个信号。
  3. 7 个信号:主升浪(85%)、日线底背离(80%)、龙抬头(75%)、真龙(70%)、短底背离(65%)、老鼠仓(60%)、反弹(55%)。每个信号得到是否触发、描述等。
  4. 结果写入表 stock_signal_scancode, name, scan_date, triggered_count, signal_status, indicators, latest_signals
    注意:本脚本不写入价格;表 stock_realtime_price 由其他定时任务或接口更新。)

并发WORKERS=3BATCH_SIZE=30,多线程按批处理。


1.2 策略建议(分档买卖建议)

触发:用户点击「策略建议」。

接口GET /api/scan_strategy,可选 dateholding_codes(逗号分隔)。

算法

  1. stock_signal_scan 读取当日 scan_date 全部记录(signal_status, indicators, triggered_count)。
  2. 对每条记录调用 统一推荐函数 _compute_recommend(signal_status, indicators, triggered_count, is_holding)(与扫描结果、提醒共用):
    • 持仓:MACD 死叉且无主升浪 → 卖出;有主升浪 → 加仓;有真龙 → 持有;否则 → 观望。
    • 非持仓:底背离+龙抬头 → 买入;主升浪 → 加仓;真龙 → 关注;MACD 死叉 → 卖出;仅底背离 → 关注;仅龙抬头 → 关注;其他触发 → 观察;无 → 观望。
  3. 按推荐文案分档:
    • 档1 立即买入disp == '买入'(底背离+龙抬头)。
    • 档2 持仓加仓disp in ('加仓','持有')
    • 档3 关注disp == '关注' 且龙抬头触发、无底背离。
    • 档4 纳入关注disp == '关注' 且其余(如仅底背离)。
  4. 返回 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

算法

  1. stock_signal_scanscan_date 筛选,可选按 triggered_count、信号类型过滤。
  2. 若有 recommend_text(如「买入」「关注」):全量读出当日扫描,逐条 _compute_recommend,统计各推荐数量,筛出 disp == recommend_text 的 code,再分页。
  3. 否则:按 triggered_count 等排序分页,LEFT JOIN stock_realtime_priceprice, change_pct
  4. 对当前页每条记录再算一次 _compute_recommend(带入 holding_codes),得到 recommend_type/recommend_text 等返回前端。
  5. 列表中的现价来自表 stock_realtime_price(与全量扫描脚本无直接关系,需另有时效性更新)。

二、检查信号与批量扫描关注

2.1 单只检查信号(技术信号详情)

触发:在「技术信号」里输入/选择股票并查询,或从扫描列表点击某只股票。

接口GET /api/technical_signals/<stock_code>?lookback=5&days=120

算法

  1. _get_kline_data(stock_code, days) 取 K 线(优先麦蕊智数,备用 AKShare)。
  2. 调用 detect_all_signals(kline_df, lookback=5),得到 7 个信号的触发情况、指标、说明。
  3. 返回 signals, latest_signals, signal_summary, indicators, signal_status
    不读 stock_signal_scan不读 stock_realtime_price,纯实时 K 线+本地计算。

2.2 批量扫描关注

触发:用户点击「批量扫描关注」。

接口POST /api/batch_technical_signalsbody{ codes: [关注列表的 code], lookback: 3, days: 120 }

算法

  1. codes 中每只(最多 20 只):取 K 线 → detect_all_signals → 得到 signal_status, triggered_count, latest_signals, indicators
  2. 结果仅用于当前页展示,不写入 stock_signal_scan不写入 stock_realtime_price
  3. 即:批量扫描关注 = 多只股票各自走一遍「单只检查信号」逻辑,无持久化。

三、模拟交易及查看时的现价

3.1 模拟持仓与统计中的现价

数据来源

  • sim_positions 存有每只持仓的 current_price(上次更新时的现价)。
  • 列表/统计接口(如 GET /api/sim/positionsGET /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

算法流程

  1. 优先读缓存(未强制刷新时):GET /api/alerts_cache,若缓存存在且为当日且版本匹配,则用缓存填充 stockAlerts,并 updateHoldingPricesFromAlerts(),对新加入的股票做增量分析;然后直接结束,不再请求信号接口。
  2. 主流程
    POST /api/signal_alertsbody{ stocks: allStocks, holding_codes: this.holdingStocks }
    • 后端从 stock_signal_scan 取当日扫描结果(signal_status, indicators, triggered_count),从 stock_realtime_priceprice, change_pct
    • 对每只股票调用 _compute_recommend(..., is_holding),得到推荐类型、理由、推荐率等,并与价格一起返回。
    • 前端用返回结果覆盖 stockAlerts,保存缓存,并 updateHoldingPricesFromAlerts()(用提醒里的 price 回填持仓的 currentPrice)。
  3. 后台补齐实时价
    调用 refreshAlertPricesFromRealtime()(不传参 = 刷新全部 stockAlerts):
    • 每批 5 只,GET /api/realtime_price/<code>(超时 12s),用返回价格覆盖对应 alert 的 pricelatest_data['收盘价']
    • 更新后 saveAlertsCacheupdateHoldingPricesFromAlerts()
      即:提醒列表的最终展示价来自实时接口,不是表里旧值。

小结

  • 信号与推荐:stock_signal_scan + _compute_recommend,价格初值来自 stock_realtime_price
  • 最终现价:实时 API/api/realtime_price → 麦蕊智数等)通过 refreshAlertPricesFromRealtime() 覆盖。

五、交易 tab 下的刷新价格

触发:用户在交易 tab 点击「刷新价格」。

涉及股票:仅 当前持仓holdingPositionsquantity > 0 的 code)。

算法(与提醒共用一套逻辑):

  1. 取持仓 code 列表 holdingCodes
  2. 设置 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()
  3. 结束后 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)。


算法与逻辑是否一致?

一致的部分

  1. 信号检测

    • 全景扫描(full_signal_scan)、单只检查信号、批量扫描关注,均使用 同一套 detect_all_signalsservices/signal_detector.py),7 个信号定义与判定一致。
    • 唯一区别:全景扫描写库(stock_signal_scan),单只/批量不写库。
  2. 推荐逻辑

    • 策略建议、扫描结果列表的推荐列、提醒的买卖/观望结论,均使用 同一函数 _compute_recommendroutes/analysis.py),同一只股票在相同持仓状态下会得到相同推荐(买入/加仓/持有/关注/观察/观望/卖出)。
    • 策略建议的 4 档(立即买入、持仓加仓、关注、纳入关注)即按该推荐结果分组,无第二套规则。
  3. 现价刷新(提醒与交易)

    • 提醒 tab 的「用实时价覆盖」与交易 tab 的「刷新价格」共用 同一方法 refreshAlertPricesFromRealtime(codesOnly?, timeoutMs),同一接口 GET /api/realtime_price/<code>,同一后端 get_realtime_price(麦蕊智数优先,AKShare 备用)。
    • 逻辑一致:按 code 列表分批请求、写回 alert/持仓、更新缓存与统计。
  4. 持仓状态

    • 提醒、策略建议、扫描结果列表都使用同一套 holding_codes(前端传 holdingStocks),_compute_recommendis_holding 与真实持仓一致,故「持有/加仓/卖出」等与是否持仓一致。

需注意的差异(非矛盾)

  1. 数据来源与时效

    • 全景扫描 / 策略建议 / 扫描结果 / 提醒(初值):依赖 当日 stock_signal_scan(及 stock_realtime_price)。若今日未跑全量扫描,则无当日信号,提醒会显示「今日尚未扫描此股」等。
    • 单只/批量检查信号:不读库,用当前 K 线实时算,与库内扫描结果可能不同(日期或数据源不同)。
    • 设计如此:全量扫描是「当日快照」,单只/批量是「实时计算」,二者用途不同,不要求数值完全一致。
  2. 现价来源

    • 列表/表内展示(扫描结果、提醒初值):来自 stock_realtime_price 表(由定时或其它任务更新)。
    • 用户主动刷新后(提醒、交易、模拟持仓):来自 实时 API/api/realtime_price)。
    • 即:先表后实时,两段一致(同一实时接口),只是数据源阶段不同。
  3. 模拟交易现价

    • 模拟持仓的 current_price 仅在使用「刷新」或定时更新时从实时接口写入;查看时只读库,不自动调实时接口。与「真实交易 tab」的持仓现价逻辑相同(都是刷新时才拉实时价)。

结论

  • 信号检测:全应用共用 detect_all_signals,一致。
  • 推荐与分档:策略/扫描结果/提醒共用 _compute_recommend,一致。
  • 现价刷新:提醒与交易共用 refreshAlertPricesFromRealtime/api/realtime_price,一致。
  • 差异仅在于:谁写库、谁读库、何时用表价/何时用实时价,属设计上的分工,不是算法或逻辑不一致。