208 lines
14 KiB
Markdown
208 lines
14 KiB
Markdown
# 本应用全部算法总结
|
||
|
||
## 一、全景扫描与策略建议
|
||
|
||
### 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`**,一致。
|
||
- **差异**仅在于:谁写库、谁读库、何时用表价/何时用实时价,属设计上的分工,不是算法或逻辑不一致。
|