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

208 lines
14 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 本应用全部算法总结
## 一、全景扫描与策略建议
### 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_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`(逗号分隔)。
**算法**
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_scan`** 按 `scan_date` 筛选,可选按 `triggered_count`、信号类型过滤。
2. 若有 **`recommend_text`**(如「买入」「关注」):全量读出当日扫描,逐条 `_compute_recommend`,统计各推荐数量,筛出 `disp == recommend_text` 的 code,再分页。
3. 否则:按 `triggered_count` 等排序分页,LEFT JOIN **`stock_realtime_price`** 取 `price, 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_signals`body`{ 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/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`
**算法流程**
1. **优先读缓存**(未强制刷新时):`GET /api/alerts_cache`,若缓存存在且为当日且版本匹配,则用缓存填充 `stockAlerts`,并 `updateHoldingPricesFromAlerts()`,对新加入的股票做增量分析;然后**直接结束**,不再请求信号接口。
2. **主流程**
**`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)。
3. **后台补齐实时价**
调用 **`refreshAlertPricesFromRealtime()`**(不传参 = 刷新全部 `stockAlerts`):
- 每批 5 只,**`GET /api/realtime_price/<code>`**(超时 12s),用返回价格覆盖对应 alert 的 `price``latest_data['收盘价']`
- 更新后 **`saveAlertsCache`**、**`updateHoldingPricesFromAlerts()`**。
即:提醒列表的**最终展示价**来自**实时接口**,不是表里旧值。
**小结**
- 信号与推荐:**`stock_signal_scan`** + **`_compute_recommend`**,价格初值来自 **`stock_realtime_price`**。
- 最终现价:**实时 API**`/api/realtime_price` → 麦蕊智数等)通过 **`refreshAlertPricesFromRealtime()`** 覆盖。
---
## 五、交易 tab 下的刷新价格
**触发**:用户在交易 tab 点击「刷新价格」。
**涉及股票**:仅 **当前持仓**`holdingPositions``quantity > 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_signals`**`services/signal_detector.py`),7 个信号定义与判定一致。
- 唯一区别:全景扫描写库(`stock_signal_scan`),单只/批量不写库。
2. **推荐逻辑**
- 策略建议、扫描结果列表的推荐列、提醒的买卖/观望结论,均使用 **同一函数** **`_compute_recommend`**`routes/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_recommend`** 的 `is_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`**,一致。
- **差异**仅在于:谁写库、谁读库、何时用表价/何时用实时价,属设计上的分工,不是算法或逻辑不一致。