Files
auv/AGENTS.md
T

93 lines
12 KiB
Markdown
Executable File
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.
# A股走势追踪应用 - 技术上下文
## Dependencies
- **recharts** - K线图表库(已内置)
- **lucide-react** - 图标库(已内置)
- **sonner** - Toast通知组件(已内置)
## Architecture
### 路由结构
- `/` - 首页:搜索股票、创建集合、管理股票
- `/stock/$code` - 股票详情页:K线图、实时行情、涨幅显示
- `/share/$code` - 分享页面:通过短链查看公开的股票集合
### 数据库表
- `stock_collections` - 股票集合(名称、描述、user_id、last_accessed_at)
- `collection_stocks` - 集合中的股票记录(股票代码、名称、添加时间、添加价格)
- `share_links` - 分享短链(短链码、关联集合ID)
### 用户管理
- 每个匿名用户生成永久 userid 存储到 localStorage
- 集合通过 user_id 隔离,RLS 策略基于 user_id 控制访问
- 3个月未访问的集合自动删除(通过 Edge Function 定时清理)
### 数据源
- Edge Function `stock-quote` 代理腾讯财经API获取A股实时行情
- Edge Function `stock-history` 双数据源获取历史K线:主源腾讯 fqkline(前复权),腾讯返回数据少于2条时降级新浪 getKLineData(北交所920开头新股腾讯常只返回1条,新浪能返回完整90天)
- Edge Function `stock-search` 代理腾讯智能搜索API(smartbox.gtimg.cn)支持名称/代码/拼音模糊搜索
- Edge Function `stock-fund-flow` 获取近30日资金流向数据,主数据源为东方财富妙想MX API(mkapi2.dfcfs.com,每日150次调用限制,需配置 `MX_APIKEY` 环境变量),回退方案为东方财富 push2his.eastmoney.com
- **MX API 数据特性**:返回数据按日期降序(最新在前),Edge Function 内必须 `sort` 为正序后再计算累计值;只返回主力净流入和成交额,不提供散户/中单/小单数据
- **日期格式标准化**:MX API 返回日期格式可能不统一(`2026/07/04`、`07-04`),Edge Function 内统一为 `YYYY-MM-DD` 以匹配 K 线数据
- **数值解析**:MX API `rawTable` 字段可能是字符串(如 `"1.432亿元"`),需用 `parseAmount` 函数解析
- **JWT 鉴权**:`stock-fund-flow` 必须部署为 `-j false`(匿名可访问),否则前端调用被拒绝
- 回退方案东方财富API字段(索引0-18):日期,主力净流入,超大单流入/流出,大单流入/流出,中单流入/流出,小单流入/流出,主力净流入%,超大单流入/流出%,大单流入/流出%,中单流入/流出%,小单流入/流出%
- 腾讯K线API字段:[日期, 开, 收, 高, 低, 成交量, 成交额](第7个字段是成交额)
- ❌ 东方财富K线API(kline/get)不稳定,可能返回 data:null,已弃用改用腾讯
- 前端通过 `/sb-api/functions/v1/stock-quote?code={6位代码}` 调用实时行情
- 前端通过 `/sb-api/functions/v1/stock-history?code={6位代码}&days={天数}` 调用历史数据
- 前端通过 `/sb-api/functions/v1/stock-search?keyword={关键词}` 调用搜索(腾讯返回UTF-8+\u转义,类型字段GP-A/GP-A-KCB/GP-A-BJB区分主板/科创板/北交所)
- 前端通过 `/sb-api/functions/v1/stock-fund-flow?code={6位代码}&name={股票名称}&days={天数}` 调用资金流向数据(`name` 参数用于MX API查询)
- 市场标识:688/60开头为SH(上海主板/科创板),920/8/4开头为BJ(北交所),其他为SZ(深圳主板/创业板)
## What Didn't Work
- ❌ 新浪财经API直接在前端调用 → 跨域限制 → 改用Edge Function代理
- ❌ 腾讯财经历史API直接在前端调用 → 跨域限制 → 改用Edge Function代理
- ❌ 百度股市通API → 返回格式不稳定 → 改用腾讯+新浪双数据源
- ❌ Python mootdx库 → 沙箱环境无法安装C++依赖 → 放弃Python方案
## Patterns / Constraints
- RLS策略设置为匿名可读写,支持分享链接公开访问
- 股票代码格式:6位数字(如600519)
- 分享短链生成:随机8位字母数字组合
- 集合卡片股票列表格式:名称/代码(左)+ 添加价/涨跌%/最新价(右三列)
- 添加股票时 `added_price` 保存为当日实时价格;若无历史数据则显示当前价和 0.00%
- 分享链接区域点击自动复制到剪贴板,通过 Sonner Toaster 显示提示;复制失败时降级为 textarea 方案或提示手动复制
- 生成分享链接时复用已有短链,避免每次生成新链接
- 全页面适配移动端:响应式字体、间距、布局断点(sm/md/lg)
- K线图历史数据获取失败时直接报错,禁止使用模拟数据降级(避免"刷新数据变化"问题)
- K线天数逻辑:日K一次拉取近365天(供拖动回看),默认视口只展示最近90个自然日(3个月),向左拖动查看更早数据;分钟K一次拉取320根,铺满展示
- K线图基于 TradingView Lightweight Charts v5 实现(蜡烛/折线 + MA/MACD/RSI 副图);拖动/缩放查看数据,切换指标或显示方式保留当前视口(仅在数据集变化时重置)
- K线图时间格式自定义:时间轴刻度 年→`YYYY`、月→`YYYY-MM`、日→`MM-DD`,十字光标→`YYYY-MM-DD`(`tickMarkFormatter` + `localization.timeFormatter`);格式化必须用 UTC 取值(`getUTCFullYear` 等),因为时间戳按"北京时间墙钟视作 UTC"存储,用本地时区方法会错位一天
- 板块标记:688开头=科创(红)、300/301开头=创业(紫)、920/8/4开头=北交(橙),主板不显示标签;标记位置:搜索候选、详情页标题、集合卡片股票列表、分享页股票卡片标题
- 详情页右上角外部跳转按钮:①"东方财富" `https://wap.eastmoney.com/quote/stock/{market}.{code}.html?appfenxiang=1`,market映射 688→6/60→1/其他→0;②"金十数据" `https://search.jin10.com/?keyword={股票名称URL编码}`(按名称搜索金十资讯)
- 详情页资金流向模块:展示近30日资金流向分析,包含:
- **资金流向概览**:主力总净流入、大单流入资金(≈主买资金)、日均主力流入、主力流入/流出天数统计
- **主力资金流向**:柱状图展示每日主力净额
- **累计主力资金趋势**:折线图展示累计主力资金的走势变化
- 每日行情明细表:独立于资金流向模块,默认显示7日数据,支持切换7日/21日;表格列包含日期、涨幅、主力净流入、成交额,颜色遵循A股惯例(红涨绿跌)
- 每日行情明细表数据截取:使用 `chartData.slice(-dailyTableDays).reverse()` 获取最近N个交易日的倒序数据(最新日期在前)
- 每日行情明细表资金流向匹配:K线 `dateObj` 转 `YYYY-MM-DD` 时必须用本地时区(`getFullYear/getMonth/getDate`),禁止用 `toISOString().split('T')[0]`(UTC 偏移会导致中国时区日期错位一天,资金流向数据匹配失败);匹配字段为 `mainForceNet`(主力净额=超大单+大单净流入)和 `turnover`(成交额)
- 成交额备用计算:当资金流向API获取失败时,使用 `chartData` 的 `volume × close` 近似计算成交额(单位:元)
- 资金流向数据获取失败时,前端通过 `fundFlowError` 状态显示错误信息,便于排查问题
- ❌ 东方财富 API(push2his.eastmoney.com)在 Edge Function 环境被拒绝访问(peer closed connection),主力净流入数据无法获取,显示为 "-"
- ✅ 替代方案:使用腾讯实时行情API的外盘(索引7)和内盘(索引8)数据计算净主动买入额 = (外盘 - 内盘) × 当前价 × 100(单位:元)
## 每日 AI 分析(backend FastAPI,15:10 自动触发)
- 报告结构约定:正文标题(#)后第一行必须是引用块 `> 今日定调:<80字内核心结论+关键数字>`;保存时由 `_extract_summary()` 正则提取进 `summary` 字段,前端顶部高亮展示,缺失时退回正文截断
- 环比数据:`collect_ai_analysis` 每次运行先采集当日盘面快照(指数+涨跌统计 JSON)写入 `daily_market_stats` 表(UNIQUE trade_date,upsert),再读上一交易日快照格式化为 prompt 中的"环比数据段";首跑无昨日数据时 AI 须如实标注"暂无昨日基准"
- 连板梯队:`get_market_dashboard` 返回 `limitLadder` 字段(完整版,2连板以上全量+首板前8),涨停原因/封单金额来自涨停池按 thscode 匹配;看板 events 里的天梯仍只取每层前2(保持 UI 精简),两处用途不同不要合并
- tokens 口径:`ai_reports.tokens_used` 只记"当次生成"消耗(约7万/份);同日重新生成走 UPSERT,`generation_count` 自增、`updated_at` 刷新、`created_at` 保留首次生成时间;表有 UNIQUE(trade_date, report_type),禁止改回 INSERT OR REPLACE 之外还要注意别用 lastrowid(UPSERT 更新时不可靠,须回查 id)
- 旧库补列用 init_db 里的 try/except ALTER TABLE 轻量迁移(CREATE TABLE IF NOT EXISTS 不会更新旧表结构)
- 补充数据源(services/market_extra.py):①财经快讯 get_news=新浪7x24 zhibo.sina.cn(feed.list.rich_text);②板块主力资金流=东财 push2 的 clist 接口(f62 主力净额),**必须用 push2delay.eastmoney.com 镜像**——push2 对部分客户端 TLS 指纹拦截(peer closed),查询串保持字面量 `+` 号;③两融=datacenter-web 的 RPTA_RZRQ_LSHJ(T+1 披露),两融余额=RZYE+RQYE
- AI 报告重要消息面规则:必须基于 get_news 快讯,5-8条,格式【宏观/政策/行业/公司/海外】新闻——影响解读,禁止编造;板块资金面规则:必须引用 sectorFundFlow 的行业净流入/流出TOP3+概念TOP3
- 截断续写:报告长导致 finish_reason=length 时,拼接已有内容并向 messages 追加"继续"指令让模型续写(最多3次),truncated 仅在续写后仍截断时为 true;call_llm 读超时 300s(续写携带全部上下文)
- 快照入库防护:_capture_market_snapshot 校验 indices 非空且涨跌统计不全 0,fuyao 失败时跳过入库,防止空快照污染环比
- 调试注意:独立脚本跑 backend 代码必须显式 `load_dotenv("/path/to/repo/.env")`——fuyao_apikey 在仓库根目录 .env(uvicorn 靠 --env-file 参数加载),backend/.env 只有 MX keys;且 stdin 脚本里 load_dotenv() 无参调用会因 frame 断言报错,须显式传路径
- 第三档呈现:报告页按 `## ` 二级标题拆分为多张卡片渲染(`splitReport`),首卡含 # 标题+定调引用块;表格单元格数字按 A股惯例红涨绿跌(`colorizeChildren` 只给带 +/- 号的数字着色);标题栏显示"总第 N 期"(`issue_number`,latest 接口用 COUNT(trade_date<=) 子查询计算)
- 海外指数/国内期货:`fetch_global_markets` 走 push2delay 的 ulist.np(海外 secid `100.NDX` 等)+ clist(期货 fs `m:8/113/142/114/115`,只取名称含"主连/主力合约"且排除"次主连",按成交额降序取前12);接入 dashboard `globalMarkets` 字段
- ⚠️ openteam 网关对 LLM 单请求有约 120s 硬超时(超时返回 502 或空 SSE),GLM reasoning 长报告一次生成必死。解法:`call_llm` 全流式(SSE)+ `collect_ai_analysis` 分三段生成(REPORT_PARTS,每段约1200-1600字,各重试3次带退避,失败占位不阻塞);工具轮拿到数据后立即 break 进分段(再问一轮只会空转120s);`thinking:{type:disabled}` 参数网关返回400不可用
- 工具结果必须瘦身:`get_active_core_stocks` 只保留出现次数前25只+题材前5(全量145KB会撑爆上下文);get_market_dashboard 全量约13KB可接受
- glm-5.3-flash 空返回特征:SSE 200 但只有 reasoning_content 无 content/tool_calls/finish_reason(可能流满120s被掐),按空轮次处理重试即可