# AGENTS.md — AI 协作工程约定（必读）

> 本文档供 AI 助手在新会话/新账号中快速掌握项目上下文。架构与功能说明见同目录 `README.md`，本文只讲**操作约束、当前状态与踩坑经验**。

## 项目定位

A股交易决策看板：全市场扫描 → 回测 → 生成单文件 HTML 看板，部署在 Mac mini 上通过 launchd 定时运行与本地服务访问。

- 源码目录：`/Users/jugelizi/hftrading/mac-mini-deploy/trading_dashboard_source/`
- 成品看板：`ashare-dashboard.html`（浏览器打开或 http://localhost:8080/ashare-dashboard.html）
- 日志：`~/Library/Logs/hftrading/`（目录必须存在，缺失会导致定时任务启动失败）

## ⚠️ 最关键的架构事实：改前端的正确姿势

`generate_report_v6.py`（每晚 16:30 由 launchd 触发 `daily_scan_runner.sh` 调用）**不会用内嵌模板覆盖磁盘 HTML**。它的工作机制：

1. 读取磁盘上现有 `ashare-dashboard.html` 作为模板
2. 用正则只替换**数据段**（`const buySignals=`、`window._marketBreadth=` 等 `_DATA_SEGMENTS` 清单里的前缀）
3. 若所有数据段还原成功 → 保留磁盘版全部手工代码修改（日志打印"模板来源：磁盘现有 ashare-dashboard.html（保留手工修改）"）
4. 若任一数据段前缀在磁盘 HTML 中找不到 → **静默回退到生成器内嵌模板**，手工修改全部丢失

因此：

- **直接改 `ashare-dashboard.html` 是安全的、也是标准做法**（数据段之外的一切代码改动都会被保留）
- 改动时**绝不能重命名/删除数据段前缀**（如 `const buySignals=` 开头的整行结构），否则触发模板回退
- 生成器内嵌模板已严重滞后（信号追踪仍是旧 v2 逻辑），仅作最后兜底；修改前端后**无需**同步移植到 `generate_report_v6.py`，但要知道兜底模板不含最新逻辑
- 每次生成会留 `ashare-dashboard-v<时间戳>.html` 版本副本，可作恢复点

## 硬约束（违反即故障）

- 东财 WAF 拒绝 Python requests 的 TLS 指纹：东财接口必须走 `em_curl.py`（subprocess 调 curl），requests 直连会 RemoteDisconnected
- 东财 push2his K线接口会限流：连续失败触发熔断直连腾讯源；**收盘后运行若 K 线缺失当日数据，必须用腾讯日K校验补齐**（stock_scanner.py 已内置日期新鲜度校验）
- 雪球 API 需 `Referer: https://xueqiu.com/hq` 绕过 WAF
- 微博抓取需 `data/weibo_cookie.txt` 有效 Cookie；搜狗微信搜索限流需等 IP 解封
- Python 生成器脚本必须声明 `# -*- coding: utf-8 -*-`；runner 需 `export LANG=en_US.UTF-8`（launchd 无 locale，中文源码会 SyntaxError）
- 看板 JS 所有 `toFixed` 必须写成 `(x||0).toFixed(n)` 防数据缺失崩溃
- K线请求统一走 `fetchKlineSafe`（60秒缓存+并发去重+东财熔断+腾讯降级），禁止绕过

## 工程约定

- 前端改完必跑 `python3 validate_js.py`（Node.js 语法校验全部 script 块，目前 6 个，一个括号错误会静默杀死整页脚本）
- 信号追踪（v3）执行"先买入后卖出"：仅 A/B 级买入信号入库（localStorage 键 `signal-tracker-v3`），卖出信号只作为买入持仓的了结依据（exitBy='sell_signal'）
- **信号追踪的建仓/平仓逻辑只认 realtimeData 实时价**，禁止退回 scanData 内嵌滞后价——否则会复现"创建即止损"假记录（2026-09-07 修复的初始化时序漏洞）
- 用户数据存 `data/users/{用户名}.json`，前端 2 秒防抖 sendBeacon 同步；布局键 `ashare_mod_layout_v3_{用户名}`
- 模拟交易挂单撮合用独立 `paperTick()` 每 15 秒执行，不受标签页激活状态影响
- 市场全景板块数据双重去重：剥离Ⅰ~Ⅴ后缀按基础名去重 + 领涨股相同的父子板块保留涨幅最高者
- 事件日历走 `/api/econ-calendar`（ForexFactory 实时 + 静态降级），显示近3天+未来，已公布事件显示今值

## 当前运行状态（2026-09-08）

- launchd 任务（`~/Library/LaunchAgents/com.hftrading.*`）：daily-scan（工作日16:30）、serve（8080端口看板）、funnel-health（公网链路自检，失败自动 tailscale 重建）
- 2026-09-07 完成信号追踪"全止损"修复：后端K线新鲜度校验+腾讯降级、回测价格交叉校准（kline_data.json 校准35只滞后股）、前端 tracker v3+实时价时序修复，当晚扫描已验证生效
- 2026-09-08 前端优化：①导航栏补齐"量化学习"入口（原 layer4 无导航链接）②全量修复不安全 toFixed 调用（bt_bb_pos/entry_price 除零/pct/amtYi 等 ~20 处，符合硬约束）③卖出信号与操盘面板添加空状态提示 ④清理 `test_tracker_fix.js` 临时文件
- 2026-09-08 新增智能助理「栗子」：右下角悬浮球全页面唤醒（导航栏"🤖 智能助理"入口已按要求删除，悬浮球保留）；对话面板前端在 `ashare-dashboard.html` 尾部（`window.AIChat`），后端 `/api/chat` 代理在 `serve_dashboard.py`（OpenAI 兼容接口）。**已接入 DeepSeek 官方**（`llm_config.json` 已配好 base_url=api.deepseek.com + model=deepseek-chat + api_key）；配置来源：文件优先，环境变量 `LLM_BASE_URL / LLM_API_KEY / LLM_MODEL` 覆盖。**密钥属最高敏感凭证，只能由属主本人生成/写入（文件或环境变量），AI 不得经手代写**
- 栗子已具备上下文多轮记忆：前端维护 `HISTORY`，当前问题单独发送（`HISTORY.slice(0,-1)`），避免与后端"问题"重复，模型可正确识别"我/你/它"指代。**2026-09-08 升级：对话持久化到 localStorage（键 `ai-chat-history-v1`，上限40条），刷新页面不丢；头部新增 🗑 清空按钮（`AIChat.clear()`）；后端历史窗口 8→16 条**
- 栗子已升级为**全能问答助手**（2026-09-08）：①系统提示词从"A股看板助理"重写为"全能型智能助理，金融是特长而非边界"——通用问题（编程/写作/百科/闲聊）不再强行往股票上扯；②联网检索改为**按需触发**（`_needs_web()` 正则匹配时效性/行情关键词，编程/百科类问题不搜，避免搜索噪音）；③`_web_query()` 把口语问题清洗成搜索关键词（去疑问词/填充词/时间诱饵词——"今天"会命中黄历站，时间词转"最新"后缀）；④**金融类查询优先接东财实时快讯**（`_fetch_em_flashes()`，newsapi.eastmoney.com 直连可用无需 key，当日50条带时间戳，按关键词过滤），Bing 搜索页仅作补充；⑤`max_tokens` 1200→2500、`temperature` 0.4→0.6；⑥快捷提问混入通用问题、欢迎语/输入框占位符改为全能定位
- 前端 `buildContext()` 把 `window._marketBreadth（字段取 up/down/flat/total/limit_up/limit_down，勿再用 up_count/up_ratio/total_amount 等不存在的字段）/ scanData / buySignals(entry_price,target_price,holding_period) / sellSignals(signal)` 摘成上下文，注入头标注"仅当问题与A股盘面相关时使用"
- 遗留：`generate_report_v6.py` 内嵌模板滞后（见上）；个股类问题（如"浪潮信息涨了吗"）东财快讯无命中时退化为 Bing 企业官网页+看板数据，模型可优雅处理
- **2026-09-11 移除智能问答机器人「栗子」**：因用户要求删除，前端全部栗子相关代码已从前端 `ashare-dashboard.html` 移除（右下角悬浮球 `#aiFab`、对话面板 `#aiPanel`、`window.AIChat` 整块 HTML/CSS/JS，含 buildContext/localStorage 记忆/快捷提问）。删除后通过 `validate_js.py`（脚本块 6→5 全 PASSED）与浏览器实测。**保留**：后端 `/api/chat`（serve_dashboard.py 接口仍可复用，前端已无入口调用）；「AI深度分析」模块（`.ai-verdict` 等为个股分析样式，与栗子无关，未删）。删除净收益：HTML 减小约19KB。加载优化现状：gzip 传输已生效（1.4MB→307KB，浏览器自动发 Accept-Encoding:gzip 即压缩），首屏 requestIdleCallback 分层渲染 + 网络请求错峰已完善。

- **2026-10-08 新增个股策略回测（浏览器本地回测引擎）**：`ashare-dashboard.html` 个股详情弹窗新增「个股策略回测」区块（紫色左边框），Top50 详情（`showDetail`，K线图区块后拼接）与全市场详情（`showDetailEx`，K线图区块后独立调用）两个入口均已接入。核心函数在 `showDetailEx` 之后：`btStockSectionHtml()`（渲染区块+控件，写 `window._btStockCtx`）、`runStockBacktest()`（取数+调度）、`backtestKline()`（回测引擎）、`renderBtResult()`/`renderBtEquityChart()`（结果渲染）。规则口径：**信号收盘确认、次日开盘成交、往返成本0.2%、满仓单笔**；6种策略（MA/MACD/KDJ/RSI/BOLL/多指标共振≥2）× 止损线（无/-5%/-8%/-12%，盘中触发按止损价、跳空按开盘价）；指标复用页面现成的 `calcMACDSeries/calcKDJSeries/calcRSISeries/calcBollSeries`，K线复用 `fetchKlineSafe`（含腾讯降级）+Top50 内嵌 `klineData`，指标预热期30根。输出：策略vs买入持有对比徽章、六指标网格、ECharts 资金曲线双线图（`#btEquityChart`）、交易明细表（含离场原因：信号卖出/止损/期末持仓）。`validate_js.py` 5块全 PASSED，浏览器实测两个入口（科德教育300192、贵州茅台600519）渲染正常、Console 无相关报错。注意：此为浏览器本地即时回测，与后端 `backtest_engine.py`（次日/3日/5日胜率口径）是两套并行功能，互不影响。

- **2026-10-08 新增类通达信公式编辑器（自定义策略）**：回测区块策略下拉新增「自定义公式(类通达信)」选项（value=`custom`），选中展开 `#btCustomPanel` 编辑面板（`btToggleCustom()`）。公式语法：`X:=expr;` 隐藏变量 / `X:expr;` 输出线 / `{…}` 注释 / 标识符大小写不敏感 / `AND OR && ||` 逻辑，**必须含 `BUY:` 与 `SELL:` 两条输出行**（值>0 即当日收盘确认信号），N/M 周期参数须常数（可 `N:=14;` 先定义）。引擎实现（`ashare-dashboard.html` 中 `let _tdxCtx` 起至 `tdxRun` 止）：词法 `_tdxTok`（注释替换为空白保留行号）→ 递归下降解析 `tdxCompile`（AST）→ 解释执行 `tdxRun`（全序列向量化，NaN 传染标记预热期，`start` 取 BUY/SELL 最后一个 NaN 之后，有效区间<10根报"预热期过长"）。内置函数 18 个：MA/EMA/SMA/REF/HHV/LLV/SUM/STD/ROC/COUNT/EVERY/EXIST/BARSLAST/CROSS/ATR/MAX/MIN/ABS/IF（`TDX_FUNCS` 表，`s` 数组声明哪些参数必须为常数标量）。错误提示带行号（`{line,msg}` 抛给 `runStockBacktest` 渲染到 `#btFormulaErr`）。5 个模板 `TDX_TPL`（ma/macd/kdj/boll/rsi）+ 保存/载入/删除（localStorage 键 `bt-custom-strats-v1`，`saveBtStrat/loadBtStrat/delBtStrat/populateBtSaved`）。**KDJ 模板教训**：SELL 原为 `CROSS(D,K) AND J>95`，真实数据（000785 阴跌股320根）死叉日 J 值从未>75 → 卖出信号恒 0 只能靠止损离场，已改为纯死叉 `CROSS(D,K)`（低位金叉买入+高位死叉卖出存在逻辑矛盾：低位买入后指标难回高位）。验证：Node 隔离测试 16/16 通过（5模板模拟+真实320根日K信号/交易全正常、8个语法错误用例行号准确、无信号不崩溃）。

## 排查经验

- 看板 JS 报错：用"无用 script 块提取 + validate_js.py 校验全部 6 个块"定位
- 扫描数据价格异常（如信号集体秒止损）：先查 `daily-scan.log` 的 `[价格校准]` 行和 K 线最后日期是否为当日
- `get_analytics_summary` 类聚合函数中 max() 误把列表替换为 int 导致后续 append 崩溃——收集用 append，汇总时再取 max
- 联网资讯源可用性（Mac mini 实测）：Google News RSS/Google 被墙（No route to host）、百度新闻弹安全验证码（wappass）、Bing News RSS 被 302 重定向回 cn.bing.com 首页拿不到条目；**Bing 可用的是解析 `cn.bing.com/search?q=` 搜索结果页 HTML 的 `<li class="b_algo">`**（标题+摘要+链接），但口语化查询（含"今天/最近/有什么"）会命中黄历站/亚马逊等垃圾，必须先经 `_web_query()` 清洗
- **东财实时快讯接口 `newsapi.eastmoney.com/kuaixun/v1/getlist_101_ajaxResult_50_1_.html` 直连可用**（urllib + Referer: kuaixun.eastmoney.com 即可，不走 em_curl），返回 `var ajaxResult={...}` JS 变量需正则抠 JSON，`LivesList` 为当日50条快讯（title/showtime）——金融类联网问题的首选源，远优于 Bing
