Initial commit

This commit is contained in:
freedakgmail
2026-07-17 18:49:07 +08:00
commit 9c7d7abdd4
100 changed files with 41337 additions and 0 deletions
+207
View File
@@ -0,0 +1,207 @@
# 本应用全部算法总结
## 一、全景扫描与策略建议
### 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`**,一致。
- **差异**仅在于:谁写库、谁读库、何时用表价/何时用实时价,属设计上的分工,不是算法或逻辑不一致。