quant-buddy-skill
查询A股、港股、美股股票及指数的最新收盘价、开盘价、涨跌幅、成交额、成交量、换手率、PE、PB、市值等实时行情与估值数据;支持查询 A 股股票所属行业。 查询最近N个交易日的价格序列、日涨跌幅序列、窗口最高价、最低价、振幅等短期统计。 查询单个资产当前盘中或最近完整交易日的分钟频 OHLCVA 序列(开高低收、成交量、成交额)。 查询上市公司最近报告期的营业收入、净利润、归母净利润、ROE、总资产、资产负债率等财务指标(A股及部分港/美股字段,以工具返回为准)。 查询单只股票的预计算及千维动态指标画像,按估值、财务分析、资金流向、波动率、宏观胜率背景、资
Install
npx skills add https://github.com/pseudo-longinus/quant-buddy-skills/tree/main/skills/quant-buddy-skill
claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install pseudo-longinus-quant-buddy-skills@llmmart
git clone https://github.com/pseudo-longinus/quant-buddy-skills.git
The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole pseudo-longinus/quant-buddy-skills collection as a plugin from our marketplace. Git is the plain clone.
Skill manifest
观照量化投研
首屏优先:先读本文件前部的「平台工具参数速查」「硬规则」和「场景路由」。简单行情、窗口序列、最近报告期、固定区间收益、K 线图等高频任务命中 Fast Path 时,无需继续整本通读。
平台工具参数速查(高频踩坑,先看这一段)
下表是 LLM 最容易写错的三个 schema。任何调用前先核对,不要凭"看起来合理"猜参数名。
| 工具 | ✅ 正确参数 | ❌ 模型常见错误(已被 call.py 自动归一化或拦截,但仍应避免) |
|---|---|---|
confirmDataMulti |
{"data_desc": "市盈率 TTM,股息率"} — 逗号分隔字符串 |
{"queries": [...]} / {"query": "..."} / {"names": [...]} |
runMultiFormulaBatchStream 公式中引用 session 中间变量 |
必须用双引号包裹:排序値 = "A股股息率〔估値数据〕" * "条件合并" |
裸变量名相乘:"A股股息率…" * 条件合并 ← 平台直接报错 |
readData |
{"ids": ["69fe…<24位hex>"], "mode": "last_column_full"} — 必须是 runMultiFormulaBatchStream 返回的 data_id 字段(hex) |
传中文变量名 {"ids": ["Top10股息"]} / 用错参数名 {"index_title": "..."} / {"variable_names": [...]} / 传 expression_id 而非 data_id(两者相邻易混,传错会返回 "error": "IndexInfo {id}") |
口径转换(confirmDataMulti 查询词):用户写 PE(TTM) / 归母净利润 等英文或缩写时,查询词应使用中文规范名(如 市盈率 TTM / 归母净利润),而不是把用户原文照抄进 data_desc。详细规则见 workflows/global-rules.md#指标口径精确匹配。
已有文件转活页:先交 QBV 静态托管
用户提供 JPG/PNG、HTML、PDF 等已有文件并要求转活页或公开分享时,优先进入 QBV workflows/existing-file-static-first.md:先 file_prepare 保存原件、生成原始承载页和持久发布参数,保留 file_publish_dir 执行静态发布,验收后在下一次工具调用前先向用户发送链接,file_confirm_delivery确认实际发出的消息,再自动继续已授权的研究、纠错、补指标和QBS接入。复合需求也不能先研究再发布。此顺序高于资产映射、查数、公式验证、图表分类器及“先计算再Handoff”;首版不要求计算胶囊,不先改写原报告。复用真实 task_id/turn_id 和同一个 page_id,不能另建用户可见任务。QBS查询/接入失败只影响增强,保留线上成功版本;未查到不等于平台不支持。纯文件分析、明确不发布不触发;真实公开授权、不可读文件、转换/首次托管问题须如实说明。不得将快照标为实时或已核验。
硬规则(违反必失败)
工具名与 unknown-tool 红线(最高优先级):
- 公式执行唯一可调用工具名:
runMultiFormulaBatchStream。 - 禁止调用或重试旧名/错名:
runMultiFormulaBatch/runMultiFormula/run_multi_formula。 - 任何工具返回
未知工具/Unknown tool/tool not found后,同名工具 0 次重试,也不得尝试名称变体。 - 若 workflow 已声明唯一正确原生工具,只允许切换到该工具 1 次;仍失败则立即输出受控失败答复。
- 若上一步结果已足够回答用户问题,必须直接收敛回答,禁止继续升级工具链。
- 公式执行唯一可调用工具名:
认证后验与 session 初始化:
- 不要在普通查数题第一步读取
config.json,也不要检查.session.json、output/.session*.json或任何本地 session 文件。 - 只要本轮准备调用平台原生工具,先直接调用原生
newSession;不得用 Bash / Glob / Read / ls 做 session 存在性探测。 - quant-buddy-view 上游继承例外:若当前任务由 quant-buddy-view 编排,且上游已经通过
trace_context.py begin创建 task_id,不得再生成第二个 task_id。优先由 QBV 的scripts/qbs_bridge.py调用本技能;bridge 会传{"task_mode":"inherit","task_id":"<上游 task_id>","task_source":"quant-buddy-view","user_query":"<用户原始问题>"}并用QBS_SESSION_KEY=<task_id>隔离并发 session。此例外只用于跨 Skill 会话绑定。 - 继承 task_id 时必须使用显式
task_mode=inherit,不要通过qbv_等字符串前缀猜测来源。继承 session 会锁定 task_id;后续参数若传入不同值,必须按TASK_ID_CONTEXT_MISMATCH停止,不能静默拆链。 - quant-buddy-skill 独立使用时保持原行为:不传
task_mode/task_id,由newSession自动生成新的 UUID 并上报 session begin。 - 工具实际返回
api_key 为空/code: 1/ 401/402 时才进入认证引导并停止当前查数任务。 - 同一对话追问可复用当前 session;新问题必须新建 session。
- 所有业务 HTTP/SSE 请求统一携带
x-skill-name: quant-buddy-skill与当前x-task-id,用于跨 Skill Trace 聚合;quant-buddy-view 上游任务不得切换 task_id。
- 不要在普通查数题第一步读取
原生工具优先,禁止脚本包装:
- 平台已有原生工具时,必须直接调用原生工具:
fast_query、fast_query_minute、fast_query_minute_range、confirmDataMulti、selectByComposition、runMultiFormulaBatchStream、resumeJob、readData、renderKLine、renderChart等。 - 禁止用 Bash / shell / Python /
scripts/call.py/run_skill_script包装已有原生平台工具。唯一编排例外是 quant-buddy-view 的qbs_bridge.py,它只负责继承 task_id 和隔离 session,不改写业务参数或结果。 - 只有平台明确不存在等价原生工具,且 workflow 明确允许脚本兜底时,才可使用本地脚本。
- 许可例外(csv 解析):当
fast_query返回mode:"csv"+csv_url(数据点 > 500 的正常交付)时,调用python scripts/fetch_fastquery_csv.py "<csv_url>"下载并解析该 csv 属于许可路径——这是消费工具返回的 OSS 产物(平台无等价原生解析工具),不算"包装原生工具"。历史分钟长表使用scripts/fetch_minute_range_csv.py @output/minute-manifest.json --output output/minute-data.json,不能套用日频宽表解析器。但仍禁止用裸curl/ 自写临时脚本替代该脚本。 - 涉及资产时仍需先用
grep presets/assets_db/{类型}.yaml搜索本地资产库,禁止整文件读取;命中多条先澄清,未命中再交给服务端兜底解析。 - 英文代码无市场后缀时必须先 grep 对应资产库确认 ticker 格式。
- 平台已有原生工具时,必须直接调用原生工具:
工具失败熔断:同类错误不得重复
- 同一工具、同一参数结构、同一错误类型出现第 1 次后,只能按 workflow 声明的备用路径切换;无备用路径则受控失败。
- 禁止无新信息地重复调用失败工具;禁止尝试名称变体;禁止读更多文档代替执行;禁止用 shell/Python 包装绕过失败工具。
runMultiFormulaBatchStream/resumeJob只有最终completed且全部结果成功时才返回validation_receipt_file;failed、部分失败、deferred均不生成收据。QBV 编排必须以该收据作为进度完成证据。长结果可传output_mode:"summary":completed 保留data_id/expression_id/status;deferred 额外完整保留status/task_id/trace_id/job_id/stream_url/_deferred。deferred 缺task_id/trace_id时返回DEFERRED_CONTINUATION_MISSING,禁止重提原批次。
任何 workflow 失败退出时必须输出受控失败答复:禁止以空白或纯过程日志结束对话。失败答复必须包含:
- ①用户的原始问题(一句话复述)
- ②失败卡在哪一步(工具名 + 错误摘要)
- ③给用户的一句话说明("当前无法获取…,原因:…")
- 可选④:用户可采取的下一步(如"稍后重试"或"换用完整链路")
先读 workflow 再操作:按下方「场景路由」表加载对应 workflow,不要自行猜测参数格式。
配置/认证错误立即停止,不得在普通查数流程中转为认证收集:
- 工具返回 API Key 缺失错误(含
api_key 为空消息 /code: 1):立即停止查数,输出新用户引导消息(格式见「前置条件」章节模板),禁止继续执行查数;等待用户粘贴 Key 后再执行配置向导。 - 其他工具报错(网络、服务端错误等):直接报告"内部工具异常",不做认证相关引导。
- 工具返回 API Key 缺失错误(含
最终答案首句必须是数据结论:回答用户时,第一句话必须直接给出数据结论(如资产名+数值、表格、或"符合条件的共N只"),绝对禁止以"已成功获取""数据已获取""根据返回结果""让我来"等过程性陈述开头。违反此规则 = 必须删除过程话术后重新输出。
- 禁止原样粘贴工具 JSON:工具返回
code:0/success:true后,最终答复必须把data.results等业务字段转写成人类可读结论(一句话、短表格或名单)。除非用户明确要求"给我原始 JSON / 调试输出",否则不得把完整工具响应原样发给用户。 - 隐藏运行态字段:最终答案默认忽略
code、success、task_id、_quota、skill_latest_version、skill_update_available、skill_update_enforced、skill_self_update、auto_upgrade*、version_check等运行态/升级字段;这些字段只供 Agent 判断流程,不是给普通用户看的答案内容。 - 版本心跳不打断业务回答:若业务
data已成功返回,即使响应体带版本心跳,也必须先回答用户问题;只有工具明确返回业务失败或SKILL_VERSION_MISMATCH时才进入自愈/排错流程。
- 禁止原样粘贴工具 JSON:工具返回
用户条件冻结,不得改写:执行前必须逐字核对用户原始条件,以下改写行为均属违规(一旦发现必须回退并重新确认):
- 百分比↔小数互转(如"股息率>3%"禁止改写为
>0.03) - 相对时间改为年份区间(如"过去10年"禁止改写为"2015-2025")
- 资产宇宙替换(如"普通股票"禁止改写为"万得全A成分股"或"非ST股")
- 事件口径扩大(如"年报/半年报"禁止扩大为全部业绩披露类型)
- 卡片附加条件继承:命中知识卡片后,若卡片含用户未明确提出的"首次/非ST/封板/流动性门槛"等附加条件,必须先删除再执行,禁止默默继承进最终答案
- 百分比↔小数互转(如"股息率>3%"禁止改写为
任务含糊时先反问,禁止猜测开干:若用户的指令有 2 种以上合理解读(如"批量确认X"不清楚是确认指数本身还是全部成分股、"分析一下Y"不清楚要哪个维度),第一步必须向用户提问澄清,不得凭推测选择一种解读自行执行。反问应简洁列出各种可能(例:"您的意思是 ① … 还是 ② …?"),等用户确认后再继续。唯一例外:用户语义明确无歧义(如"给我贵州茅台今日收盘价"),无需反问。
⚠️ 模糊词处理规则(先判断是否真歧义,再决定反问还是默认口径直行):
下列词在量化语义中存在多种定义,必须正确处理:
- 技术分析类:支撑位 / 阻力位 / 压力位 / 颈线位 / 关键位 / 关键点位 / 突破位
- 走势判断类:趋势 / 趋势预测 / 后市判断 / 还能不能涨 / 会不会跌 / 短期看法 / 中线看法
- 盘面定性类:异动 / 主力 / 主力流向 / 庄家动向 / 强势 / 弱势 / 抗跌 / 抗跌性
- 健康度类:基本面好不好 / 估值贵不贵 / 财务健康 / 业绩怎么样 / 基本面
判定流程(按顺序匹配,命中即停):
综合分析请求 → 用默认口径直行,禁止反问阻塞。 判定:用户在一句话里列出 ≥2 个分析维度(如"基本面 + 技术指标 + 趋势"、"估值 + 财务 + 走势"),或明确说"全面分析 / 综合看一下 / 给一份报告"。 做法:直接走
stockProfile(综合画像)+ 常规技术指标 + 默认趋势口径,报告首句告知用户使用的口径(例:"本次按以下默认口径输出:基本面=综合画像(估值+财务+资金流+波动率),技术=MACD/KDJ/RSI/布林带,趋势=MA20/MA60 排列方向。如需调整口径请告诉我。")。孤立的单点定义性请求 → 必须反问。 判定:触发词单独出现,且其定义直接决定结论数值(如"贵州茅台支撑位是多少"、"宁德时代趋势怎么样",无任何其他维度上下文)。 做法:反问 ① 口径定义(如"支撑位"=近N日最低/布林下轨/均线密集区);② 时间窗口;③ 输出格式。
触发词只是修饰语 → 直行。 判定:用户主体诉求清晰,触发词只是顺带描述(如"找最近基本面改善的股票"主体是筛选,"基本面改善"已隐含"营收/净利同比上升"等可执行口径)。 做法:用最常见的可执行口径执行,并在结果中标注口径。
实测教训(T-036):用户问"贵州茅台支撑位",模型擅自用"60日最低价附近"作为定义直接执行——这是孤立单点请求,应走流程 2 反问。 反例(不要再犯):用户问"分析东方财富的基本面、技术指标、趋势预测",模型反问 4 个问题阻塞——这是综合分析请求,应走流程 1 默认口径直行。
工具返回
SKILL_VERSION_MISMATCH时必须自愈,不得继续执行原任务:当任何工具调用的输出中出现"error": "SKILL_VERSION_MISMATCH"时,说明当前对话上下文中的工具签名/参数格式已过时。此时必须执行以下自愈流程(每步不得省略):立即停止当前任务,不得再调用任何平台工具;
调用
newSession(新建 session,获取新 task_id);强制重读
SKILL.md(本文件)+ 当前场景对应的 workflow 文档 + 涉及的 tools/*.md;以新读取的工具签名和参数格式重新执行用户原始任务;
禁止询问用户"是否继续"或解释版本升级过程——直接静默完成自愈并返回结果。
CHANGELOG / skill-changelog 仅作为审计,不作为规则源:
CHANGELOG.md、skill-changelog/**是按时间叠加的变更记录,包含已被后续版本反转或废弃的旧口径。任何「执行顺序、字段名、协议块语义、工具签名、参数格式」相关的判断,必须以SKILL.md+workflows/**+tools/**+references/troubleshooting.md为唯一权威;CHANGELOG 描述与上述文件冲突时,以上述文件为准。CHANGELOG 仅可用于:① 排查问题时回看「哪一版动过什么」;② 升级成功后做 5 条以内的版本上下文摘要。禁止:把 CHANGELOG 某条历史叙述当作当前执行规则、依据 CHANGELOG 推断现行参数格式、或在 CHANGELOG 与 SKILL.md 冲突时偏向 CHANGELOG。判断工具成败看返回 body 的
code/success,不看 HTTP 状态码:HTTP 200 不代表业务成功——body 里出现"code": -1/"success": false即为业务错误,必须按失败处理(读error/message再决定重试/改参/走排查表),禁止「HTTP 通了就当成功」继续往下走。另:call.py返回"error": "INVALID_TOOL_NAME"表示工具名写错或缺失(工具名必须排在命令最前、且为已注册工具名),属可立即修正的本地错误。详见references/troubleshooting.md顶部「成败判定通则」。图表请求与已登记的高频稳定榜单必须实际执行活页路由,不得只靠模型判断:凡用户要求任何图表 artifact(包括“放在一张图里”“画成一张图”“同图比较”“绘制成图表”),或命中
workflows/live-page-routing.md已登记的确定性 durable 场景(当前包括“低 PE + 高 ROE + 选股/排名 + TopN”),输出 QBS 第一条回答前必须实际执行python scripts/live_page_routing.py route ...并保留 route JSON。不得把所有 TopN/选股都视为 durable;只有路由合同明确列出的窄场景才触发。命中“单资产 + 2~4 个 fast_query 标准历史字段 + 明确同图”时,必须在取数和静态渲染前读取并执行workflows/visual-page-fast-path.md;不得读取quant-standard.md或render-kline.md,不得先生成静态图。其他图表与 durable 场景按各自 workflow 的路由检查点执行。不得因为用户没说“活页/网页”、已经生成 PNG、已经读过规则、或模型自行判断应为none/create而跳过命令;必须保留 route JSON 作为本轮 Trace 证据。create|existing_page且 QBS 已有排名、对比、回测、热力图等结构化 artifact 时,优先把最小业务字段写入skill/output下的请求 JSON,并执行一次python scripts/live_page_routing.py prepare-validated-page @output/...json;该命令原子完成 computation capsule、Handoff 和幂等 Job,成功后禁止再手工执行handoff或prepare。无结构化 artifact 时才使用通用 handoff → prepare。随后使用宿主真实提供的内部子 Agent 委派工具(优先spawn_agent),只等待即时成功回执,绝不在首答前等待 QBV 完成;不得用create_thread/fork_thread代替内部子 Agent,也不得只口头声称已启动。若宿主没有内部委派工具,必须执行python scripts/live_page_routing.py mark-delegation-unavailable --qbv-job-id <ID>把 Job 置为DELEGATION_UNAVAILABLE,不得遗留 queued Job,也不得声称页面正在生成。source_skill_id有真实值就记录,缺失则标记unavailable,不得阻断 Handoff。none|suggest按分类结果继续 QBS。页面 direct/fork/unmatched、本人原位更新、他人复制和权限判断全部由 QBV 执行。路由或委派失败必须记录 Job 失败终态,但不得阻断 QBS 正常答案。只有用户明确只要 PNG/本地图片/表格或不要网页时不创建页面;弱“看看走势”仍保持 QBS。QBS→QBV 只复用本轮已经算完的部分,不把 QBV 改成 QBS 专用渲染器:
create|existing_page在 Handoff 前优先运行scripts/qbv_computation_capsule.py build @capsule-input.json,生成qbs_computation_capsule_v1。胶囊必须同时包含用户核心问题/主图意图、资产规范化结果、可复现查询或公式合同及 fingerprint、结果快照或 artifact SHA256、字段映射、结论与验证收据;禁止只交 PNG 或一句总结。 同一业务 role 对应多个已物化结果时可传data_ids;构建器按原顺序展开为role__01、role__02…,保留原始 ID 字符串并同步required_roles,禁止 Agent 手工改写或复制 ID。QBV 的 thin adapter 判定covered时不得重复识别资产或重算相同 role,partial时只补missing_roles,unusable时无损回退原 QBV→QBS bridge;direct/fork/unmatched、ownership、构建、运行时注册、发布和验收仍完全归 QBV。用户直接使用 QBV 时不依赖胶囊,原 SOP 不变。已跑通的公式执行合同必须原样交给 QBV,禁止二次改写:
runMultiFormulaBatchStream成功后,以 Validation Receipt 中的qbs_formula_runtime_contract_v1为唯一执行合同,保留formulas的条数、顺序、完整指标名、引号、begin_date、include_description、use_minute_data、force_reusable_array、reads和 fingerprint。prepare-validated-page必须把该合同写入 computation capsule;不得把平台已确认的"A股市盈率(PE, TTM)〔估值数据〕"/"A股净资产收益率ROE"缩写成PE(TTM)/ROE后交给 QBV,也不得把多条已验证公式合并成一条新公式。显式合同与 Receipt 不一致、fingerprint 不一致或输出左值不完整时必须失败关闭,不得猜测修复。准备交接 JSON 时,Receipt 已含原始公式就不要在validated_roles[].formula手抄第二份,也不要在每个 role 重复同一 Receipt;优先在顶层validation_receipts传一次 Receipt 对象或 Receipt 文件路径字符串,也可以让prepare-validated-page按同任务全部data_id自动发现,避免引号转义失败和无效重试。
Fast Path / Leaf workflow 顶部硬闸门(每次进入 leaf 都生效)
修复 T-001 / T-011 / T-024 等 leaf 没把
newSession当成首条强制步骤、跳过直接调平台工具的问题。
无论路由进入 fast-snapshot / fast-window / fast-report-period / render-kline / 任何 leaf workflow:
- 当前 Skill Session 尚未建立时,调用任何平台原生工具前必须先调用
newSession;newSession同时登记本 Session 的首个 Turn。 - 同一 Session 收到新的用户消息(包括追问)时,必须先调用
beginTurn,参数中的user_query必须是本轮原话;同一 Turn 内连续调用多个工具时复用当前上下文,不重复beginTurn。- 正常 Agent 调用
newSession/beginTurn时必须同时生成可选agent_intent:用简短文字写清本轮要解决的对象、动作、约束和期望产物,推荐 20~160 字。追问中的“那它呢/和上一个比/继续”等指代必须结合已有上下文展开,但不得覆盖用户原话。 agent_intent示例:首问分析贵州茅台的盈利质量、估值水平与主要风险。;追问“那和五粮液比呢?” →延续上一轮贵州茅台分析,对比五粮液的盈利能力、估值水平与主要风险。;带约束问题“只要近三年,做成一张表” →整理目标公司近三年的核心指标并输出单表对比,不扩展到其他期间。- 禁止直接复制
user_query充当 Intent;禁止记录 Chain of Thought、内部推理步骤或未经数据分析的结论。老客户端、自动测试和无法生成 Intent 的旁路允许省略,服务端按null处理,不得阻断业务。
- 正常 Agent 调用
- 只有真正开始新的独立 Skill Session 才重新
newSession。不允许用“已读 SKILL.md / leaf workflow”跳过首个newSession,也不允许用重复newSession代替追问的beginTurn。- 新版
newSession的单次版本检查可能同时返回可选 companion(当前为quant-buddy-view):QBS 自身需要升级时必须先只升级 QBS 并 reload;QBS 已最新时才可安装、更新或补齐当前 Agent 的 companion 注册。companion 失败属于旁路错误,不得阻断当前 QBS 数据业务;只要返回reload_required=true,最终回复必须明确提示用户重新加载 Agent,禁止忽略该提示后声称活页能力已在当前会话生效。 - 建议在
newSession同时传agent_model:填入当前 Agent 的真实模型标识(例如gpt-4o/claude-sonnet-4/gemini-2.5-pro);拿不准就留空,禁止猜测。
- 新版
- 未建立 Session 直接调用平台工具仍会返回
MISSING_NEW_SESSION。若新问题未先beginTurn,或显式turn_id/user_query与当前 Turn 漂移,客户端必须取消不安全的 Turn 关联、保留本轮真实user_query并继续业务工具;仅输出追踪诊断,禁止因 Turn 记录失败停止回答用户,也禁止把调用错误挂到其他 Session。
最小充分原则(任何动作前自检)
默认走最窄路径;只在收到"明确不够用"的证据后,才扩大范围。
每次准备读文件、调工具、扩大读取范围前,回答三个问题:
- 这一步要解决的具体问题是什么? — 必须能用一句话写成"为了 X,所以做 Y",其中 X 是已经发生的需求,不能是"可能会需要 X"、"以防万一"、"先准备着"。
- 有没有更窄的选项能完成同样的 X? — 更下游的输出 / 更精简的文件 / 更少的字段 / 不调用这个工具直接构造。
- 当前选择如果失败,下一步是什么? — 如果答不上来,说明还没想清楚就在动手。
任一回答含糊 → 不做这一步。
扩大范围的唯一合法触发:上一步工具明确返回了"缺数据 / 字段不存在 / 失败",且失败原因可以追溯。不允许用"为了更全面"、"为了更准确"、"为了避免遗漏"作为理由。
这条原则覆盖:要不要多读一个文档;readData 读哪个变量;要不要为某个字段调 confirmDataMulti;公式自己写还是查现成数据集;以及所有未来出现的同类决策。
工具层面落地:调用 confirmDataMulti / readData / runMultiFormulaBatchStream 或加载额外文档前,必须在心里完成工具清单自检;不要为执行清单而搜索、加载或读取 recipes/tool-call-checklist.md。无论该文件是否已在上下文中,只在心里完成以下三条最小自检即可(这三条已是清单的浓缩版,不需要再去查原文):
- 这次调用是否直接服务于用户当前问题?
- 是否有更窄的输出或更少的字段可读?
- 如果调用失败,下一步是否明确且只改一个维度?
顶层原则管"要不要做",清单管"具体怎么做"。
Skill 包根目录
本 SKILL.md 所在目录即为 skill 根目录(SKILL_ROOT),下文所有相对路径均以此为基准。
宿主已将命令工作目录(cwd)固定为本 Skill 根目录时,禁止再次执行 cd,直接使用相对路径运行;仅在人工终端或宿主没有设置 cwd 时,才先切换到本目录。
SKILL_ROOT/
├── config.json ← API Key 配置(按需读取;非每题必读)
├── SKILL.md ← 本文件(入口 + 路由)
│
├── workflows/ ← 业务流程编排(路由目标)
│ ├── fast-snapshot.md Fast Path:最新时点行情/估值(≤1000资产,标量/CSV)
│ ├── fast-window.md Fast Path:最近N日序列/窗口统计(≤2500日)
│ ├── fast-report-period.md Fast Path:最近报告期财务(≤1000资产)
│ ├── quick-lookup.md 快速查数路由器 + 共享基础规则
│ ├── quick-snapshot.md 最新时点行情/估值快照(字段齐即停)
│ ├── quick-window.md 最近N日短窗序列/窗口统计
│ ├── quick-report-period.md 最近报告期财务指标
│ ├── period-return-compare.md 固定区间累计涨跌幅对比
│ ├── stock-profile.md 单股预计算指标画像
│ ├── external-fact-verification.md 上市/退市/更名/换代码及知识冲突核验
│ ├── composition-select.md 已物化维度组合选股(selectByComposition 快路径)
│ ├── global-rules-lite.md 精简全局规则(quick-window/period-return-compare 专用)
│ ├── quant-standard.md 选股/回测/因子/图表标准流程
│ ├── live-page-routing.md QBS→QBV 非阻塞活页路由、Handoff 与 Job 合同
│ ├── visual-page-fast-path.md 单资产标准历史字段同图的取数复用快路径
│ ├── event-study.md 事件研究(给定或可识别事件后的窗口表现)
│ ├── regime-segmentation.md 阈值区间/连续阶段识别与区间统计
│ └── render-kline.md K线图渲染与交付
│
├── recipes/ ← 公式模板 & 工具用法(被 workflow 引用)
│ ├── ma-crossover-backtest.md 均线金叉策略
│ ├── value-pe-strategy.md PE估值选股
│ ├── upload-custom-data.md 上传自有数据
│ ├── render-chart.md 渲染图表
│ ├── download-data.md 下载数据
│ └── industry-aggregation.md 行业聚合排名
│
├── references/ ← 参考文档
│ ├── environment.md 环境依赖
│ ├── troubleshooting.md 故障排查
│ └── ru-billing.md RU 计费
│
├── tools/ ← API 工具完整参数文档(默认不读;workflow 标注「必读」或报错时再查)
│ │ ⚠️ 下表列出所有可用工具的**实际调用名**,调用时必须使用此名,不得变体
│ ├── fast_query.md → 工具名 `fast_query` 快速合并查询(行情/估值/财务,≤1000资产,支持CSV)
│ ├── fast_query_minute_range.md → 历史单资产跨日1分钟CSV全列(日期/自然日offset)
│ ├── fast_query_minute.md → 工具名 `fast_query_minute` 单资产当前盘中/最近完整日分钟 OHLCVA 序列
│ ├── confirm_data_multi.md → 工具名 `confirmDataMulti` 批量确认数据项存在性与维度(写公式前必查)
│ ├── run_multi_formula.md → 工具名 `runMultiFormulaBatchStream` 执行公式批次(选股/回测/因子计算)
│ ├── read_data.md → 工具名 `readData` 读取公式计算结果(需传 data_id,非 expression_id)
│ ├── render_kline.md → 工具名 `renderKLine` 渲染 K 线图(直接传 ticker,无需提前跑公式)
│ ├── stock_profile.md → 工具名 `stockProfile` 单股预计算指标画像(估值/财务/资金/波动/走势)
│ ├── select_by_composition.md → 工具名 `selectByComposition` 已物化维度组合选股/筛选(不走公式引擎)
│ ├── dimension_indicators.md → 工具名 `listDimensionIndicators` 按维度列出指标目录(细分+综合)
│ │ → 工具名 `getIndicatorFormulas` 按指标名取该指标的完整公式组
│ │ ⚠️ 二者非平台原生工具,走 `python scripts/call.py <工具名>`(硬规则 #2 许可路径)
│ ├── render_chart.md → 工具名 `renderChart` 渲染折线/柱状/面积图(需先有 data_id)
│ ├── get_card_formulas.md → 工具名 `getCardFormulas` 按卡片名拉取完整公式组(量化场景使用)
│ ├── scan_dimensions.md → 工具名 `scanDimensions` 九维度 IC 扫描(单股多维度预测力分析)
│ ├── search_similar_cases.md → 工具名 `searchSimilarCases` 向量检索相似案例(设计策略前的 fallback 查找)
│ ├── search_functions.md → 工具名 `searchFunctions` 检索平台函数名称与调用格式
│ ├── download_data.md → 工具名 `downloadData` 按 data_id 下载一维时序到 CSV/JSON
│ ├── upload_data.md → 工具名 `uploadData` 上传自有因子 CSV,上传后可在公式中引用
│ ├── refresh_snapshot_time.md → 工具名 `refreshSnapshotTime` 强制刷新分钟数据截止时间(盘中实时场景)
│ ├── resume_job.md → 工具名 `resumeJob` 续传 deferred 后台任务(配合 research_24h 使用)
│ └── formula_package.md → 脚本 `scripts/formula_package.py` 注册公式组为「任务包」→ 凭 package_id+signature 无 key 取数(对外只读/前端页面接入)
│
├── presets/ ← 已验证的常用数据(按需加载)
│ ├── cases_index.yaml 106 张案例卡片目录(量化标准场景必读,快速查数无需)
│ ├── assets.yaml 常用资产(99 行精选,可一次读完)
│ ├── assets_db/ 全量资产字典(按类型分文件,⚠️ 仅 grep 检索,禁止 read_file 整文件;不含指数成分股映射)
│ │ ├── stock_a.yaml A 股 5316 条(SH/SZ,含场内 ETF)
│ │ ├── stock_hk.yaml 港股 2858 条(HK 前缀;行情优先,财务以 fast_query 返回为准)
│ │ ├── stock_us.yaml 美股及境外ETF 1069 条(.N/.O/.A/.BAT;行情优先,财务以 fast_query 返回为准)
│ │ ├── index.yaml 指数 513 条
│ │ └── future.yaml 期货 239 条
│ ├── functions.yaml 常用函数(170 条)
│ ├── data_catalog.yaml 常用精选数据集(高频 index_title)
│ ├── index_info_catalog/ 系统支持数据名全量索引(2566 条,按业务分类分 YAML,grep 检索)
│ ├── dimensions.yaml 已物化指标候选的本地快照,用于 selectByComposition 的常见快速映射
│ │ ⚠️ 细分指标、快照未命中项和实时可选状态,用 `listDimensionIndicators` 在线确认;公式口径用 `getIndicatorFormulas`
│ ├── sectors.yaml 行业板块(742 条,10 个分类)
│ └── themes.yaml 题材板块
│
├── scripts/ ← 执行脚本
│ ├── call.py 工具统一入口(所有命令通过它调用)
│ ├── executor.py call.py 的底层(禁止直接调用)
│ ├── formula_package.py 公式任务包客户端(register/query/list/revoke/refresh,取数走 SSE)
│ ├── quant_api.py Python SDK(供其他脚本 import)
│ ├── auth/ 认证脚本
│ └── eval/ 评测脚本
│
└── output/ ← 输出目录(自动创建)
├── .session.<key>.json 当前 session task_id(按 QBS_SESSION_KEY 派生,多会话隔离)
├── ic_data/ IC 扫描结果
└── *.png / *.csv 图表和数据文件
全局 429 处理(所有路径均适用):
| error.code | 处理 |
|---|---|
RATE_LIMIT_EXCEEDED / CONCURRENT_LIMIT |
读 retryAfter 秒后静默重试,不向用户暴露 |
WINDOW_QUOTA_EXCEEDED |
立即停止,读 references/troubleshooting.md 配额限流段,输出提示 |
DAILY_QUOTA_EXCEEDED / DAILY_SCAN_EXCEEDED |
立即停止,输出:⚠️ 今日额度已满,次日 00:00 重置。 |
SERVICE_OVERLOADED(503) |
retryAfter 秒后静默重试 1 次,仍失败则告知"系统繁忙,请稍后重试" |
⛔ 执行顺序(路由前必读,所有场景必须遵守)
已有文件转活页先执行上方静态托管优先例外,不进入下表 QBS leaf;首次静态交付后的数据增强才按本节加载查询规则。
无论匹配到哪个 leaf workflow,执行顺序固定为:
① read_skill_file(global-rules 版本,见下表) → ② read_skill_file(leaf workflow) → ③ 执行
步骤 ① 全局规则文件选择(按目标 leaf workflow 确定):
| 目标 leaf workflow | 步骤 ① 读取的文件 |
|---|---|
fast-snapshot.md |
无(Fast Path,跳过步骤 ①,直接执行) |
fast-window.md |
无(Fast Path,跳过步骤 ①,直接执行) |
fast-report-period.md |
无(Fast Path,跳过步骤 ①,直接执行) |
quick-window.md |
workflows/global-rules-lite.md |
period-return-compare.md |
workflows/global-rules-lite.md |
| 其他所有 workflow | workflows/global-rules.md |
- 步骤 ① 是硬前置条件。确定目标 leaf 后,先按上表选择并读取对应 global-rules 版本,再读 leaf workflow,最后执行。
- Fast Path(fast-*.md)直接从步骤 ② 开始,无需步骤 ①。
场景路由
先识别用户意图,确定目标 leaf workflow;然后按上方执行顺序加载:
| 场景 | 触发词 | 目标 leaf workflow |
|---|---|---|
| 单资产历史/跨日分钟 | 用户明确历史交易日或跨日区间的原始1分钟/分时CSV | 确认唯一资产 → tools/fast_query_minute_range.md → fast_query_minute_range;不传fields/format/remove_nan |
| 单资产日内分钟 / 分时序列 | 明确要求分钟、分时、1分钟、每分钟、日内 OHLCV、逐分钟开高低收/成交量;不含历史日期、区间或多资产 | 先按资产库规则确认唯一资产 → 直接调用 fast_query_minute → 成功即停 |
| 最新时点行情 / 估值 / 基础信息(快照) | 最新价、今日收盘、最新涨跌幅、当前换手率、最新PE/PB/市值、所属行业… | Fast Path 条件满足 → 只读 fast-snapshot.md;不满足/无法查询 → global-rules.md → quick-snapshot.md |
| 最近N日序列 / 窗口统计 | 最近5日、最近20日、近N个交易日、窗口最高/最低/振幅…(仅单资产、最近N日) | Fast Path 条件满足 → 只读 fast-window.md;不满足/无法查询 → global-rules-lite.md → quick-window.md |
| 最近报告期财务 | 营收、净利润、归母净利润、ROE、总资产、总负债、资产负债率… | Fast Path 条件满足 → 只读 fast-report-period.md;不满足/无法查询 → global-rules.md → quick-report-period.md |
| 单股指标画像 / 个股综合分析 | 分析一下XX个股、看一下XX这只股票、个股画像、指标概览、估值财务资金走势综合看一下、基本面和估值怎么样… | global-rules.md → stock-profile.md |
| 最新上市/退市/更名/换代码或资产状态冲突 | 现在上市了吗、最新代码、是否退市;或本地资产库命中但平台返回 ASSET_NOT_FOUND |
global-rules.md → external-fact-verification.md;外部事实与 Quant Buddy 数据状态必须分开判断 |
| 单资产标准历史字段同图 | 一个资产 + 2~4 个价格/成交/估值标准历史字段 + “放在一张图里/画成图/同图比较” | 只读 visual-page-fast-path.md;先 route,再一次 fast_query,一次命令准备 capsule + Handoff + Job;禁止进入 quant-standard.md、render-kline.md 和静态渲染 |
| 申万一级行业近N日涨跌幅排名图 | 申万一级行业/行业板块 + 最近N日/最近一个月 + 涨跌幅 + 排名图/柱状图/可视化 | 只读 industry-ranking-fast.md;固定一条行业聚合公式,只读 indexinfo_id,直接准备已物化 QBV Job;禁止进入 global-rules.md、quant-standard.md、行业 recipe 和 renderChart |
| K线图(可视化) | 明确出现 K线/K 线/蜡烛图/OHLC/开高低收;普通“股价、成交量、PE 放在一张图”不属于 K 线 | global-rules.md → render-kline.md;输出首答前必须实际运行 live_page_routing.py route;明确“只要 PNG/不要网页”时 route 为 none |
| 固定区间累计涨跌幅 | 从A到B、某年某月至某年某月、区间收益、累计涨跌幅、区间表现、多资产区间对比 | global-rules-lite.md → period-return-compare.md |
| 数据下载 / 导出本地 CSV | 下载成CSV、导出到本地、保存到本地、下载历史数据 | global-rules.md → recipes/download-data.md;单资产单字段时序优先 runMultiFormulaBatchStream → downloadData → write_skill_file,禁止 Bash 兜底 |
| 已物化指标选股 / 维度分或细分指标 TopN / 推荐股票 | 分数最高、综合分最高、维度分,或由已物化细分 score/screen 指标组成的推荐/选出/筛选 TopN | global-rules.md → composition-select.md(newSession → 本地快照匹配或在线目录确认 → selectByComposition) |
| 高频稳定因子榜单:低 PE + 高 ROE TopN | 同时出现低PE/低市盈率、高ROE/高净资产收益率、选股/筛选/排名、TopN/前N;即使没说图表或活页 | global-rules.md → quant-standard.md 的“高频默认口径”;验证 TopN 后必须实际 route,create 时用三个已物化 data_id 一次 prepare-validated-page,QBS 先答、QBV 后台补链接;明确“只要表格/不要网页”则 none |
| 维度指标库查询 / 指标口径与公式 | 平台有哪些维度、XX 维度下有哪些指标、XX 指标怎么算的/口径是什么/公式是什么、想按现成指标改口径 | tools/dimension_indicators.md(用 scripts/call.py 调 listDimensionIndicators → getIndicatorFormulas,非平台原生工具;要拿改过的公式跑数再转 quant-standard.md) |
| 量化选股 / 回测 / 因子 / 图表 / 上传下载 | 选股、回测、均线、PE选股、因子、净值、上传CSV、下载数据、画图、多个指标放进同一张图…;或目录无匹配维度、需要临时构造指标/历史曲线/自定义公式 | global-rules.md → quant-standard.md;任何图表 artifact 在首答前必须实际运行 live_page_routing.py route,命中 create 后非阻塞交接 QBV |
| 直接运行用户给定的公式链文件 | 「运行/跑一遍/执行这个文件里的全部公式」「公式链文件」「formula chain」「按这个 md/json 跑」 | global-rules.md → run-formula-chain.md |
| 事件研究 | 复盘、历次、涨价、降息、加息、事件窗口、随后表现、超预期、不及预期、政策后表现…(给定事件或需先识别事件日) | global-rules.md → event-study.md |
| 阈值区间统计 / 连续阶段 | 历次、每次、平均、回撤超过、从高点下跌超过、熊市区间、连续阶段、regime | global-rules.md → regime-segmentation.md |
| 对外发布公式组 / 做取数页面 / 注册任务包 | 注册公式包、package_id、签名取数、做个能直接打开的页面/看板、前端实时取数、对外只读接口、第三方接入 | tools/formula_package.md + recipes/formula-package.md(用 scripts/formula_package.py,非平台原生工具) |
上传、下载、画图不是独立场景——它们是 workflow 内的子步骤,workflow 文档会在需要时指引你读对应的
recipes/。
路由硬排除(优先于触发词匹配)
以下规则在触发词匹配之前检查,命中即强制改道,不得被触发词覆盖:
| 用户意图特征 | 禁止进入 | 强制导向 | 判断依据 |
|---|---|---|---|
| 盘中/实时/当前/现在/今天/今日/当日 + 查询日内行情(涨幅排名、涨停、日内跌幅等) | quick-snapshot quick-window |
quant-standard.md(优先匹配分钟频卡片) |
需要分钟频卡片的专用公式;use_minute_data: true 已是全局默认 |
| 盘中/实时/当前/今天/今日/当日 + 全市场/板块 + TopN/排名/阈值名单/选股/筛选/信号 | quick-snapshot quick-window |
quant-standard.md → 优先命中"实时横截面 TopN 排名"或"盘中阈值筛选_名单查询"微流程 |
这类高频短题有专用封闭微流程 |
| 给出明确起止日期,只问区间累计涨跌幅/收益 | event-study quick-window quant-standard |
period-return-compare.md |
本质是固定区间收益比较,不是因果窗口分析,也不是复杂量化流程 |
| 行业/板块聚合排名(如"申万行业涨幅前5") | quick-window quick-snapshot |
quant-standard.md |
需要横截面聚合,不是单资产序列 |
| 阈值触发型离散事件识别(如"跌幅超过X%的次数",问每次后表现) | — | event-study.md(阈值触发模式) |
需先识别阈值事件日,再做窗口分析 |
| 由阈值条件定义连续区间(如"历次熊市""回撤超30%的阶段") | event-study |
regime-segmentation.md |
研究的是连续阶段而非离散事件后的窗口 |
| "创近N日新高/新低"(不含"首次"修饰词) | 不得加"昨日未满足"条件 | 按当前状态判断(state check),公式只比较当前值与昨日的N日极值 | 只有用户明确出现"首次突破/首次跌破""新晋""今日第一次"时,才允许追加首次触发条件;详见 quant-standard.md |
判断口诀:
- 有明确起止日 + 只问区间数值 →
period-return-compare(固定区间收益比较) - 有事件 + 问"随后N天/月表现" →
event-study(因果窗口) - 有阈值条件 + 问"每次发生后表现" →
event-study(阈值触发模式) - 有阈值条件 + 问"连续阶段/区间内表现" →
regime-segmentation(连续阶段统计)
若用户请求满足以下任一模式,应优先判定为【快速查数任务】,按以下路由直接跳转,不得先进入其他 workflow:
Fast Path 条件(同时满足以下 2 点才可走 Fast Path;否则走完整链路):
- 所有目标字段属于 fast_query whitelist(价格/估值/财务/衍生/资金流向·南北向持股/商品现货·库存,以及 A 股所属行业基础信息字段,详见
tools/fast_query.md),不涉及自定义公式/选股/排名 - 非全市场横截面查询(不是"全市场排名/前N只/行业筛选"等场景)
资产数量不再限制 Fast Path 路由(服务端支持 ≤1000 个资产)。超过 500 数据点时服务端自动返回 CSV 格式(OSS 下载链接),详见
tools/fast_query.md限流与 CSV 模式段落。
快速查数路由(按优先级依次判断,首个匹配即停):
用户明确要求一个资产的 2~4 个标准历史字段放在同一张图中,且未明确只要 PNG → 只读
workflows/visual-page-fast-path.md;这是页面主图快路径,不得继续读取quant-standard.md/render-kline.md。 0a. 用户明确要求申万一级行业最近 N 个交易日涨跌幅排名图/柱状图/可视化,且未明确只要 PNG → 只读workflows/industry-ranking-fast.md;不得读取global-rules.md、quant-standard.md或行业 recipe,不得调用renderChart。 分钟覆盖前置检查:先确认市场/资产类型并按 分钟支持范围 判断。A股/美股股票、国内期货起于2026-05-13,港股股票起于2026-05-20,国内指数起于2026-08-13;美国/香港指数及期货暂不支持。历史窗口早于边界必须先提示:全窗口越界不查;部分覆盖明确缺失范围,不静默改日期或宣称完整覆盖。此限制不套用于日频行情。用户明确要单资产历史/跨日分钟序列或分钟CSV →
tools/fast_query_minute_range.md;只支持历史日期,完整返回所有列;窗口/offset互斥,不得偷换成日频或当日分钟。用户明确要单资产完整分钟/分时/逐分钟序列或分钟 OHLCVA,且未指定历史日期、日期区间、分钟聚合或多个资产 → 先按资产库规则确认唯一资产,调用
fast_query_minute;按索引配对返回的dates[]与fields.<name>[],保留data_scope/trade_date/timezone语义,成功即停。只问最新标量仍走 snapshot;历史/区间改走 fast_query_minute_range;多资产不能塞进任一单资产工具。连续期货只按所选 trade_date 解读 contract_info,不将 inferred 说成已实时核验;附加 warnings 不影响成功行情。用户是开放式单股综合指标概览(如“分析一下XX个股”“看一下XX这只股票”“个股画像”“指标概览”“估值财务资金走势综合看一下”),且不是只问单字段/明确窗口/IC 预测力 →
workflows/global-rules.md→workflows/stock-profile.md时间锚点是"最近 N 日窗口/序列",或用户明确给出起止日期要求返回区间序列(如"从X日到X日每日的…走势/序列/数据"),或用户只说"最近走势/看走势"但未明确要图片/K线 → Fast Path 条件满足时读
workflows/fast-window.md,不满足则workflows/global-rules-lite.md→workflows/quick-window.md;未给 N 时默认按最近 20 个交易日时间锚点是"最近报告期"且字段属于财务类 → Fast Path 条件满足时读
workflows/fast-report-period.md,不满足则workflows/global-rules.md→workflows/quick-report-period.md用户明确要“K线 / K 线 / 蜡烛图 / OHLC / 开高低收” → 直接加载
workflows/render-kline.md;普通多指标同图已由步骤 0 截止其余(明确是最近完成交易日或当日的行情/估值/多资产对比,且不含 排名/筛选/全市场 语义)→ Fast Path 条件满足时读
workflows/fast-snapshot.md,不满足则workflows/global-rules.md→workflows/quick-snapshot.md说明:含"今天/今日/当日/当前/现在/实时/盘中"但仅查单资产行情字段,属于日内刷新场景,
fast_query snapshot已自动启用盘中刷新(等效use_minute_data: true),应直接走 Fast Path;上方"路由硬排除"已拦截"今天 + 全市场/板块 + 排名/筛选",此处无需重复排除。
上述路由不需要先读
workflows/quick-lookup.md。
关键红线速查(即使未读 global-rules.md 也必须遵守)
以下 4 条规则从 global-rules.md 摘录,优先级最高,对所有场景生效:
- 事件定义冻结:事件类型/范围必须逐字匹配用户原始措辞。用户说"年报/半年报"就只查年报和半年报,不得扩大到业绩预告/快报/季报;用户说"国务院或住建部"就只纳入该层级,不得扩大到央行/银保监会/地方政府。若认为用户定义可能遗漏,在回答末尾建议扩大,不得擅自扩大。
- evidence-only 回答:最终答案只输出本轮工具结果直接支持的数值、日期、排名、口径说明。未经工具验证,禁止默认输出宏观归因、政策归因、方向性判断("通常""往往""偏正面")。
- 去过程化交付:禁止「已成功获取」「让我来」「按照流程」「Step 1/2/3」「根据 workflow」等过程性话术;禁止泄露
_working/路径、checkpoint 名称、workflow 文件名。查到即答,不展示内部过程。 - 条件口径冻结:用户条件必须原样执行,禁止任何改写(百分比↔小数、相对时间→年份区间、资产宇宙替换、卡片附加条件继承)。详见硬规则第 8 条。
- 时效性事实核验:涉及最新上市/退市/更名/换代码,或本地资产库命中但平台返回
ASSET_NOT_FOUND时,必须读external-fact-verification.md。ASSET_NOT_FOUND只表示当前接口未识别资产,不得推导为公司未上市。
触发词参考:
- 分析一下XX个股 / 看一下XX这只股票 / 个股画像 / 指标概览 / 估值财务资金走势综合看一下 →
stock-profile - 最近交易日收盘 / 最新已披露PE / 最新市值(非盘中、非筛选) →
quick-snapshot - 最近5日 / 最近20个交易日 / 近N日序列 / 窗口最高最低 →
quick-window - 营收 / 净利润 / ROE / 总资产 / 总负债 / 资产负债率 →
quick-report-period
禁止:
- 优先调用
scanDimensions、renderKLine(除非用户明确要看图) - 先做分析性扩写,再补充结构化数值
- 在读取对应 leaf workflow 之前直接调用
runMultiFormulaBatchStream/renderKLine/scanDimensions/stockProfile/ 输出“无法联网”或“无法获取实时数据” - 资产已唯一命中
presets/assets_db/future.yaml时,静态输出“平台不支持期货/期权”或“期货无法查询”;应先按行情/窗口序列工具链尝试,失败后只按工具返回说明当前品种或字段暂不可得 - 把卡片附加条件(首次/非ST/封板/流动性门槛等)默默继承进最终答案
- 以
description、samples、预览行、截断大表作为名单题的完整结果直接收尾(必须提取完整名单或明确声明不完整)
leaf workflow 最终回答合同优先:leaf workflow 中的"最终回答合同"优先负责收紧该场景的输出格式;若 leaf workflow 已满足停止条件,必须直接按该合同输出,不得再解释内部过程。
执行权授权规则
规则层级(从高到低):
- SKILL.md:路由 + 全局门禁(硬规则 10 条、路由硬排除)
- global-rules.md:所有 leaf 必须遵守的全局合同(执行合同、证据分级、简答模式、不补精度、方法限制说明、参数规范、数值精度、终答一致性检查)
- leaf workflow:当前任务的具体执行流程(checkpoint、模板、停止条件、格式化)
冲突解决:
- leaf workflow 中的具体规则(如 readData 模式选择)优先于 global-rules 的一般规则
- 但 leaf workflow 不得放宽 global-rules 的红线(如证据分级门槛、不补精度原则)
- 不得从其他 leaf workflow 借用模板、fallback 或回答格式
quick-lookup.md 的定位:
- 仅作为快查子流程的路由入口和规则参考总表
- 各 leaf workflow 已自包含所有执行规则,执行时无需回到 quick-lookup.md
- quick-lookup.md 不定义任何 leaf 独有规则
全局执行规则
全局合同详见
workflows/global-rules.md,进入任何 leaf workflow 时自动生效。 leaf workflow 可在其内部添加更严格的约束,但不得豁免或放宽 global-rules 中的规则。
平台数据覆盖范围
| ✅ 支持 | ⚠️ 有条件支持 | ❌ 不支持(短期内不会支持) |
|---|---|---|
| A股个股(沪深主板/创业板/科创板/北交所) | ETF / LOF / 场外基金(先 grep 本地资产库,能唯一命中则正常执行;未命中才告知不支持);期货行情/窗口序列(先 grep presets/assets_db/future.yaml,唯一命中后按工具返回尝试行情字段;不承诺估值/财务/K线图) |
期权 |
| 港股个股(HK + 代码,如 HK0001) | 台股 / 韩股 / 日股 / 德股等其他境外市场 | |
| 美股个股(NASDAQ: 代码.N;NYSE: 代码.O;AMEX: 代码.A) | ||
| 主要宽基指数(沪深300、中证500、万得全A等) |
港股 / 美股数据范围限制:
- 行情价格类(收盘价、开盘价、最高价、最低价、涨跌幅、成交量、成交额):A / HK / US 均支持。
- 所属行业基础信息:仅 A 股股票支持
所属行业,使用fast_query(snapshot/value);资产须为type=stock且market_id=1/2。返
Files (quant-buddy-skills)
-
presets
-
assets_db
-
future.yaml 12.3 KB
# ============================================================ # 商品期货 / 金融期货 # 来源:MongoDB tkrsInfo 集合(type=future) # 格式:future | name | ticker | 备注 | short_names # ============================================================ future | AD.CME | AD.CME | | future | BPH25E.CME | BP.CME | | future | CBOT2年期美国国债 | TU.CBT | | 2年期美国国债 future | CBOT30年期美国国债 | US.CBT | | 30年期美国国债 future | CBOT5年期美国国债 | FV.CBT | | 5年期美国国债 future | CBOT小麦 | W.CBT | | 小麦 future | CBOT燕麦 | O.CBT | | 燕麦 future | CBOT稻谷 | RR.CBT | | 稻谷 future | CBOT豆油 | BO.CBT | | 豆油,美豆油 future | CBOT豆粕 | SM.CBT | | 美豆粕 future | CBOT超国债 | UL.CBT | | future | CBOT迷你小麦 | YW.CBT | | 迷你小麦 future | CBOT迷你玉米 | YC.CBT | | 迷你玉米 future | CBT-10年美国国债 | TY.CBT | | 10年美国国债,美债期货 future | CBT-大豆 | S.CBT | | 大豆,美豆 future | CBT-玉米 | C.CBT | | 玉米,美玉米 future | CME日元兑美元 | J1.CME | | future | CME活牛 | LC.CME | | 活牛 future | CME瘦肉猪 | LH.CME | | 瘦肉猪 future | CME饲牛 | FC.CME | | 饲牛 future | CMX-白银 | SI.CMX | | 白银,comex银,comex白银,COMEX银,COMEX白银,CMX银 future | CMX-铜 | HG.CMX | | 铜,COMEX铜,comex铜,CMX铜 future | CMX-黄金 | GC.CMX | | 黄金,comex黄金,comex金,COMEX黄金,COMEX金 future | COMEX上海黄金(美元) | SGU.CMX | | 上海黄金(美元) future | COMEX迷你白银 | QI.CMX | | future | ICE NYH(RBOB)汽油 | N.IPE | | NYH(RBOB)汽油 future | ICE WTI原油 | T.IPE | | WTI原油,WTI原油期货 future | ICE冷冻浓缩橙汁 | OJ.NYB | | 冷冻浓缩橙汁 future | ICE取暖油 | O.IPE | | 取暖油 future | ICE可可 | CC.NYB | | 可可 future | ICE咖啡 | KC.NYB | | 咖啡 future | ICE白糖期货 | W.IPE | | future | IPE-布伦特原油 | B.IPE | | 布伦特原油 future | IPE-柴油 | G.IPE | | 柴油 future | LME-铅 | PB.LME | | 铅 future | LME-铜 | CA.LME | | 铜,LME铜,伦敦铜 future | LME-铝 | AH.LME | | 铝 future | LME-锌 | ZS.LME | | 锌 future | LME锡 | SN.LME | | 锡 future | LME镍 | NI.LME | | 镍 future | NYB-11号糖 | SB.NYB | | 11号糖 future | NYB-棉花 | CT.NYB | | 棉花,2号棉,美棉 future | NYM-取暖油 | HO.NYM | | 取暖油 future | NYM-天然气 | NG.NYM | | 天然气 future | NYM-汽油 | RB.NYM | | 汽油 future | NYM-轻质原油 | CL.NYM | | 轻质原油,WTI原油 future | NYMEX迷你轻质原油 | QM.NYM | | 迷你轻质原油 future | NYMEX钯 | PA.NYM | | future | NYMEX铂 | PL.NYM | | 铂 future | 上证50主连 | IH.CFE | | IH,ih future | 上证50次主连 | IH_S.CFE | | IH,ih future | 中国10年期国债主连 | T.CFE | 美容护理 | 10年期国债期货,国债,10年期国债 future | 中国10年期国债次主连 | T_S.CFE | 美容护理 | 10年期国债期货,国债,10年期国债 future | 中国2年期国债主连 | TS.CFE | | 2年期国债期货,2年期国债 future | 中国2年期国债次主连 | TS_S.CFE | | 2年期国债期货,2年期国债 future | 中国30年期国债主连 | TL.CFE | | 30年期国债期货,30年期国债 future | 中国30年期国债次主连 | TL_S.CFE | | 30年期国债期货,30年期国债 future | 中国5年期国债主连 | TF.CFE | | 5年期国债期货,5年期国债,lpr5年 future | 中国5年期国债次主连 | TF_S.CFE | | 5年期国债期货,5年期国债,lpr5年 future | 中证1000主连 | IM.CFE | | future | 中证1000次主连 | IM_S.CFE | | future | 中证500主连 | IC.CFE | | future | 中证500次主连 | IC_S.CFE | | future | 微型标普500 | MES.CME | | future | 微型罗素 | M2K.CME | | future | 微型道琼斯工业平均指数 | MYM.CBT | | future | 微型黄金 | MGC.CMX | | future | 标普500期货小型 | ES.CME | | 小型标普指数 future | 欧元兑美元 | EC.CME | | future | 欧元区STOXX50期货 | FESX.EUX | | future | 沪20号胶主连 | NR.INE | | 20号胶 future | 沪20号胶次主连 | NR_S.INE | | 20号胶 future | 沪丁二烯橡胶主连 | BR.SHF | | 丁二烯橡胶 future | 沪丁二烯橡胶次主连 | BR_S.SHF | | 丁二烯橡胶 future | 沪不锈钢主连 | SS.SHF | | 不锈钢 future | 沪不锈钢次主连 | SS_S.SHF | | 不锈钢 future | 沪低硫燃油主连 | LU.INE | | 低硫燃料油 future | 沪低硫燃油次主连 | LU_S.INE | | 低硫燃料油 future | 沪原油TAS次主连 | SCTAS_S.INE | | future | 沪原油主连 | SC.INE | | 原油,INE原油,原油主连,SC.SHF future | 沪原油次主连 | SC_S.INE | | 原油,INE原油 future | 沪国际铜主连 | BC.INE | | 国际铜 future | 沪国际铜次主连 | BC_S.INE | | 国际铜 future | 沪欧线集运主连 | EC.INE | | 集运指数(欧线) future | 沪欧线集运次主连 | EC_S.INE | | 集运指数(欧线) future | 沪氧化铝主连 | AO.SHF | | 氧化铝 future | 沪氧化铝次主连 | AO_S.SHF | | 氧化铝 future | 沪沥青主连 | BU.SHF | | 沥青 future | 沪沥青次主连 | BU_S.SHF | | 沥青 future | 沪深300主连 | IF.CFE | | future | 沪深300次主连 | IF_S.CFE | | future | 沪热卷主连 | HC.SHF | | 热轧卷板 future | 沪热卷次主连 | HC_S.SHF | | 热轧卷板 future | 沪燃油主连 | FU.SHF | | 燃油 future | 沪燃油次主连 | FU_S.SHF | | 燃油 future | 沪纸浆主连 | SP.SHF | | 纸浆 future | 沪纸浆次主连 | SP_S.SHF | | 纸浆 future | 沪线材主连 | WR.SHF | | 线材 future | 沪线材次主连 | WR_S.SHF | | 线材 future | 沪胶版纸主连 | OP.SHF | | future | 沪胶版纸次主连 | OP_S.SHF | | future | 沪螺纹钢主连 | RB.SHF | | 螺纹钢 future | 沪螺纹钢次主连 | RB_S.SHF | | 螺纹钢 future | 沪金主连 | AU.SHF | | 黄金,沪金 future | 沪金次主连 | AU_S.SHF | | 黄金,沪金 future | 沪铅主连 | PB.SHF | | 铅,沪铅 future | 沪铅次主连 | PB_S.SHF | | 铅,沪铅 future | 沪铜主连 | CU.SHF | | 铜,沪铜 future | 沪铜次主连 | CU_S.SHF | | 铜,沪铜 future | 沪铝主连 | AL.SHF | | 铝,沪铝 future | 沪铝次主连 | AL_S.SHF | | 铝,沪铝 future | 沪银主连 | AG.SHF | | 白银,沪银 future | 沪银次主连 | AG_S.SHF | | 白银,沪银 future | 沪铸造铝主连 | AD.SHF | | future | 沪铸造铝次主连 | AD_S.SHF | | future | 沪锌主连 | ZN.SHF | | 锌,沪锌 future | 沪锌次主连 | ZN_S.SHF | | 锌,沪锌 future | 沪锡主连 | SN.SHF | | 锡,沪锡 future | 沪锡次主连 | SN_S.SHF | | 锡,沪锡 future | 沪镍主连 | NI.SHF | | 镍,沪镍 future | 沪镍次主连 | NI_S.SHF | | 镍,沪镍 future | 沪香蕉主连 | RU.SHF | | 橡胶 future | 沪香蕉次主连 | RU_S.SHF | | 橡胶 future | 粤多晶硅主力合约 | PS.GFE | | future | 粤工业硅主力合约 | SI.GFE | | 硅 future | 粤碳酸锂主力合约 | LC.GFE | | 碳酸锂 future | 粤钯主力合约 | PD.GFE | | future | 粤铂主力合约 | PT.GFE | | future | 纳斯达克100小型期货 | NQ.CME | | future | 纳斯达克100微型迷你期货 | MNQ.CME | | future | 美国天然气 | HH.NYM | | 美国天然气 future | 连PVC主连 | V.DCE | | PVC future | 连PVC次主连 | V_S.DCE | | PVC future | 连乙二醇主连 | EG.DCE | | 乙二醇 future | 连乙二醇次主连 | EG_S.DCE | | 乙二醇 future | 连乙烯主连 | L.DCE | | 塑料 future | 连乙烯次主连 | L_S.DCE | | 塑料 future | 连原木主连 | LG.DCE | | future | 连原木次主连 | LG_S.DCE | | future | 连大豆次主连 | S_S.DCE | | future | 连棕油主连 | P.DCE | | 棕榈油 future | 连棕油次主连 | P_S.DCE | | 棕榈油 future | 连液化气主连 | PG.DCE | | LPG future | 连液化气次主连 | PG_S.DCE | | LPG future | 连焦炭主连 | J.DCE | | 焦炭 future | 连焦炭次主连 | J_S.DCE | | 焦炭 future | 连焦煤主连 | JM.DCE | | 焦煤 future | 连焦煤次主连 | JM_S.DCE | | 焦煤 future | 连玉米主连 | C.DCE | | 玉米 future | 连玉米次主连 | C_S.DCE | | 玉米 future | 连玉米淀粉主连 | CS.DCE | | 玉米淀粉 future | 连玉米淀粉次主连 | CS_S.DCE | | 玉米淀粉 future | 连生猪主连 | LH.DCE | | 生猪 future | 连生猪次主连 | LH_S.DCE | | 生猪 future | 连粳米主连 | RR.DCE | | 粳米 future | 连粳米次主连 | RR_S.DCE | | 粳米 future | 连纤维板主连 | FB.DCE | | 纤维板 future | 连纤维板次主连 | FB_S.DCE | | 纤维板 future | 连纯苯主连 | BZ.DCE | | future | 连纯苯次主连 | BZ_S.DCE | | future | 连聚丙烯主连 | PP.DCE | | 聚丙烯 future | 连聚丙烯次主连 | PP_S.DCE | | 聚丙烯 future | 连胶合板主连 | BB.DCE | | 胶合板 future | 连胶合板次主连 | BB_S.DCE | | 胶合板 future | 连苯乙烯主连 | EB.DCE | | 苯乙烯 future | 连苯乙烯次主连 | EB_S.DCE | | 苯乙烯 future | 连豆一主连 | A.DCE | | 豆一 future | 连豆一次主连 | A_S.DCE | | 豆一 future | 连豆二主连 | B.DCE | | 豆二 future | 连豆二次主连 | B_S.DCE | | 豆二 future | 连豆油主连 | Y.DCE | | 豆油 future | 连豆油次主连 | Y_S.DCE | | 豆油 future | 连豆粕主连 | M.DCE | | 豆粕 future | 连豆粕次主连 | M_S.DCE | | 豆粕 future | 连铁矿石主连 | I.DCE | | 铁矿石 future | 连铁矿石次主连 | I_S.DCE | | 铁矿石 future | 连鸡蛋主连 | JD.DCE | | 鸡蛋 future | 连鸡蛋次主连 | JD_S.DCE | | 鸡蛋 future | 迷你罗素 | RTY.CME | | future | 郑PTA主连 | TA.CZC | | PTA future | 郑PTA次主连 | TA_S.CZC | | PTA future | 郑丙烯主连 | PL.CZC | | future | 郑丙烯次主连 | PL_S.CZC | | future | 郑动力煤主连(退) | ZC.CZC | | future | 郑动力煤次主连 | TC_S.CZC | | 动力煤 future | 郑对二甲苯主连 | PX.CZC | | 对二甲苯 future | 郑对二甲苯次主连 | PX_S.CZC | | 对二甲苯 future | 郑尿素主连 | UR.CZC | | 尿素 future | 郑尿素次主连 | UR_S.CZC | | 尿素 future | 郑强麦主连(退) | WH.CZC | | future | 郑强麦次主连 | WS_S.CZC | | 强麦 future | 郑强麦次主连(退) | WH_S.CZC | | future | 郑早籼稻主连(退) | RI.CZC | | future | 郑早籼稻次主连 | ER_S.CZC | | 早籼稻 future | 郑早籼稻次主连(退) | RI_S.CZC | | future | 郑晚籼稻次主连 | LR_S.CZC | | 晚籼稻 future | 郑普麦主连(退) | PM.CZC | | 普麦 future | 郑普麦次主连(退) | PM_S.CZC | | 普麦 future | 郑棉纱主连 | CY.CZC | | 棉纱 future | 郑棉纱次主连 | CY_S.CZC | | 棉纱 future | 郑棉花主连 | CF.CZC | | 棉花,郑棉,棉花主连 future | 郑棉花次主连 | CF_S.CZC | | 棉花,郑棉 future | 郑油菜籽主连 | RS.CZC | | 菜籽 future | 郑油菜籽次主连 | RS_S.CZC | | 菜籽 future | 郑烧碱主连 | SH.CZC | | 烧碱 future | 郑烧碱次主连 | SH_S.CZC | | 烧碱 future | 郑玻璃主连 | FG.CZC | | 玻璃 future | 郑玻璃次主连 | FG_S.CZC | | 玻璃 future | 郑瓶片主连 | PR.CZC | | future | 郑瓶片次主连 | PR_S.CZC | | future | 郑甲醇主连 | MA.CZC | | 甲醇 future | 郑甲醇次主连 | MA_S.CZC | | 甲醇 future | 郑甲醇次主连 | ME_S.CZC | | future | 郑白糖主连 | SR.CZC | | 白糖,内糖,郑糖 future | 郑白糖次主连 | SR_S.CZC | | 白糖,内糖,郑糖 future | 郑短纤主连 | PF.CZC | | 短纤 future | 郑短纤次主连 | PF_S.CZC | | 短纤 future | 郑硅铁主连 | SF.CZC | | 硅铁 future | 郑硅铁次主连 | SF_S.CZC | | 硅铁 future | 郑硬麦次主连 | WT_S.CZC | | 硬麦 future | 郑粳稻主连(退) | JR.CZC | | 粳稻 future | 郑粳稻次主连(退) | JR_S.CZC | | 粳稻 future | 郑红枣主连 | CJ.CZC | | 红枣 future | 郑红枣次主连 | CJ_S.CZC | | 红枣 future | 郑纯碱主连 | SA.CZC | | 纯碱 future | 郑纯碱次主连 | SA_S.CZC | | 纯碱 future | 郑绿豆次主连 | GN_S.CZC | | future | 郑花生主连 | PK.CZC | | 花生 future | 郑花生次主连 | PK_S.CZC | | 花生 future | 郑苹果主连 | AP.CZC | | 苹果 future | 郑苹果次主连 | AP_S.CZC | | 苹果 future | 郑菜油主连 | OI.CZC | | 菜油 future | 郑菜油次主连 | OI_S.CZC | | 菜油 future | 郑菜油次主连 | RO_S.CZC | | future | 郑菜粕主连 | RM.CZC | | 菜粕 future | 郑菜粕次主连 | RM_S.CZC | | 菜粕 future | 郑锰硅主连 | SM.CZC | | 锰硅 future | 郑锰硅次主连 | SM_S.CZC | | 锰硅 future | 长期欧元债券期货 | FGBL.EUX | | -
index.yaml 27.7 KB
# ============================================================ # 指数资产(A股宽基/行业/主题指数 + 海外指数) # 来源:MongoDB tkrsInfo 集合(type=index) # 格式:index | name(公式中使用此名) | ticker | 备注 | short_names # ============================================================ index | 300红利低波 | 930740.CSI | | index | 300银行全收益 | H00951.CSI | | index | A股指数 | SH000002 | | index | CBOE波动率VIX | VIX.GI | | index | CS中药生产 | CI005153.WI | | index | CS人工智 | 930713.CSI | | index | CS化学制药 | CI005152.WI | | index | CS生物医药Ⅱ | CI005154.WI | | index | EUR | EURCNY | | index | GBP | GBPCNY | | index | HKD | HKDCNY | | index | IT服务II(申万) | 801103.SI | | IT服务Ⅱ(申万) index | JPY | JPYCNY | | index | KRW | KRWCNY | | index | MSCI中国A50互联互通(在岸人民币) | 746059.MI | | index | MSCI亚太地区 | 302000.MI | | index | MSCI欧洲 | 990500.MI | | index | MSCI欧澳远东 | 990300.MI | | index | MSCI美国 | 984000.MI | | index | MYR | MYRCNY | | index | PB-ROE指数(长江) | 861302.CJ | | index | USD | USDCNY | | index | 一般零售II(申万) | 801203.SI | | 一般零售(申万) index | 万得300除金融 | 881007.WI | | index | 万得300除银行 | 881006.WI | | index | 万得A50 | 888002.WI | | index | 万得A500 | 888003.WI | | index | 万得主板 | 881033.WI | | index | 万得全A | 881001.WI | | A股,全A,wind全A index | 万得全A(除金融、石油石化) | 881003.WI | | index | 万得全A可持续ESG | 881101.WI | | index | 万得双创 | 881034.WI | | index | 万得小市值指数 | 8841425.WI | | index | 万得小盘股指数 | 8841656.WI | | index | 万得蓝筹280指数 | 881002.WI | | index | 上证180 | SH000010 | | index | 上证380 | SH000009 | | index | 上证50 | SH000016 | | 50 index | 上证指数 | SH000001 | | index | 专业工程II(申万) | 801724.SI | | 专业工程(申万) index | 专业服务II(申万) | 801218.SI | | 专业服务(申万) index | 专业零售II(申万) | 801204.SI | | 专业连锁Ⅱ(申万) index | 专用设备II(申万) | 801074.SI | | 专用设备(申万) index | 专精特新指数 | 8841451.WI | | index | 个护用品II(申万) | 801981.SI | | 个护用品(申万) index | 中价股指数 | 801842.SI | | index | 中小100 | SZ399005 | | index | 中小综指 | SZ399101 | | index | 中市净率指数 | 801832.SI | | index | 中市盈率指数 | 801822.SI | | index | 中盘价值 | SZ399375 | | index | 中盘低估值(长江) | 861131.CJ | | index | 中盘低波(长江) | 861531.CJ | | index | 中盘动量(长江) | 861631.CJ | | index | 中盘反转(长江) | 861731.CJ | | index | 中盘成长 | SZ399374 | | index | 中盘成长指数(长江) | 861231.CJ | | index | 中盘指数 | 801812.SI | | index | 中盘指数(长江) | 861003.CJ | | index | 中盘高估值(长江) | 861132.CJ | | index | 中盘高波(长江) | 861532.CJ | | index | 中盘高盈利质量(长江) | 861331.CJ | | index | 中药II(申万) | 801155.SI | | 中药Ⅱ(申万) index | 中证100 | SH000903 | | index | 中证1000 | SH000852 | | 1000 index | 中证2000 | 932000.CSI | | index | 中证500 | SH000905 | | 500 index | 中证800 | SH000906 | | index | 中证A500 | SH000510 | | index | 中证信息 | SH000935 | | index | 中证全指 | 000985.CSI | | index | 中证全指信息(TMT) | SH000993 | | index | 中证全指公用 | 000995.CSI | | index | 中证全指媒体指数 | H30174.CSI | | index | 中证全指房地产 | 931775.CSI | | index | 中证全指房地产全收益指数 | 931775CNY01.CSI | | index | 中证全指材料 | SH000987 | | index | 中证全指消费 | 000990.CSI | | index | 中证全指消费者 | H30173.CSI | | index | 中证全指能源 | SH000986 | | index | 中证全指资本品 | H30169.CSI | | index | 中证全指车汽车汽配 | H30164.CSI | | index | 中证全指运输指数 | H30171.CSI | | index | 中证全指酒店休闲 | H30204.CSI | | index | 中证全指金融 | SH000992 | | index | 中证全指零售指数 | H30175.CSI | | index | 中证农业 | 000949.CSI | | index | 中证医药 | SH000933 | | index | 中证新能源汽车 | SZ399976 | | index | 中证智能汽车主题 | 930721.CSI | | index | 中证白酒 | SZ399997 | | index | 中证红利 | 000922.CSI | | index | 中证红利低波动指数 | H30269.CSI | | index | 中证通信 | 000936.CSI | | index | 乘用车II(申万) | 801095.SI | | 乘用车(申万) index | 亏损股指数 | 801851.SI | | index | 互联网电商II(申万) | 801206.SI | | 互联网电商(申万) index | 交通运输I(申万) | 801170.SI | | 交运,交通运输(申万) index | 人工智能 | 931071.CSI | | index | 以太坊兑美元 | ETHUSD | | index | 休闲服务I(申万) | 801210.SI | | 社会服务,社会服务(申万) index | 休闲食品II(申万) | 801128.SI | | 休闲食品(申万) index | 传媒I(申万) | 801760.SI | | 传媒,传媒(申万) index | 低价股指数 | 8841135.WI | | index | 低价股指数 | 801843.SI | | index | 低估值成长指数(长江) | 861202.CJ | | index | 低估值指数(长江) | 861101.CJ | | index | 低市净率指数 | 801833.SI | | index | 低市盈率指数 | 801823.SI | | index | 低波指数(长江) | 861501.CJ | | index | 保险II(申万) | 801194.SI | | 保险Ⅱ(申万) index | 信息技术与硬件红利(长江) | 861410.CJ | | index | 信息技术(万得) | 882008.WI | | index | 储能指数 | 884790.WI | | index | 元件II(申万) | 801083.SI | | 元件(申万) index | 先进制造指数 | 8841448.WI | | index | 光伏设备II(申万) | 801735.SI | | 光伏设备(申万) index | 光学光电子II(申万) | 801084.SI | | 光学光电子(申万) index | 全A精选指数 | 8841341.WI | | index | 公用事业I(申万) | 801160.SI | | 公用,公共事业(申万) index | 公用事业及服务红利(长江) | 861413.CJ | | index | 公用事业(万得) | 882010.WI | | index | 其他交运设备II(申万) | 801881.SI | | 摩托车及其他(申万) index | 其他建材II(申万) | 801713.SI | | 装修建材(申万) index | 其他电子II(申万) | 801082.SI | | 其他电子Ⅱ(申万) index | 养老产业指数 | 884142.WI | | index | 军工电子II(申万) | 801745.SI | | 军工电子Ⅱ(申万) index | 农产品加工II(申万) | 801012.SI | | 农产品加工(申万) index | 农化制品II(申万) | 801038.SI | | 农化制品(申万) index | 农商行II(申万) | 801785.SI | | 农商行Ⅱ(申万) index | 农林牧渔I(申万) | 801010.SI | | 农林牧渔,农林牧渔(申万) index | 冶钢原料II(申万) | 801043.SI | | 冶钢原料(申万) index | 出版II(申万) | 801769.SI | | 出版(申万) index | 创业大盘 | SZ399293 | | index | 创业小盘 | SZ399020 | | index | 创业成长 | SZ399667 | | index | 创业板50 | SZ399673 | | index | 创业板指 | SZ399006 | | index | 创业板综 | SZ399102 | | index | 创业蓝筹 | SZ399295 | | index | 创成长 | SZ399296 | | index | 动物保健II(申万) | 801018.SI | | 动物保健Ⅱ(申万) index | 动量指数(长江) | 861601.CJ | | index | 包装印刷II(申万) | 801141.SI | | 包装印刷(申万) index | 化妆品II(申万) | 801982.SI | | 化妆品(申万) index | 化学制品II(申万) | 801034.SI | | 化学制品(申万) index | 化学制药II(申万) | 801151.SI | | 化学制药(申万) index | 化学原料II(申万) | 801033.SI | | 化学原料(申万) index | 化学纤维II(申万) | 801032.SI | | 化学纤维(申万) index | 化工I(申万) | 801030.SI | | 化工,化工(申万) index | 医疗保健(万得) | 882006.WI | | index | 医疗器械II(申万) | 801153.SI | | 医疗器械(申万) index | 医疗器械(中信) | CI005322.WI | | index | 医疗服务II(申万) | 801156.SI | | 医疗服务(申万) index | 医疗研发外包(申万) | 851563.SI | | index | 医疗红利(长江) | 861408.CJ | | index | 医药商业II(申万) | 801154.SI | | 医药商业(申万) index | 医药流通(中信) | CI005321.WI | | index | 医药生物I(申万) | 801150.SI | | 医药,医药生物(申万) index | 半导体II(申万) | 801081.SI | | 半导体(申万) index | 半导体指数 | 8841349.WI | | index | 南华20号胶指数 | NH0046.NHF | | index | 南华LPG指数 | NH0052.NHF | | index | 南华PTA指数 | NH0011.NHF | | index | 南华PVC指数 | NH0019.NHF | | index | 南华不锈钢指数 | NH0050.NHF | | index | 南华乙二醇指数 | NH0043.NHF | | index | 南华低硫燃料油指数 | NH0053.NHF | | index | 南华农产品指数 | NH0300.NHF | | index | 南华动力煤指数 | NH0030.NHF | | index | 南华原油指数 | NH0041.NHF | | index | 南华商品指数 | NH0100.NHF | | index | 南华国际铜 | NH0055.NHF | | index | 南华塑料指数 | NH0004.NHF | | index | 南华大豆指数 | NH0001.NHF | | index | 南华尿素指数 | NH0045.NHF | | index | 南华工业品指数 | NH0200.NHF | | index | 南华有色金属指数 | NH0700.NHF | | index | 南华棉一指数 | NH0006.NHF | | index | 南华棉纱指数 | NH0039.NHF | | index | 南华棕榈油指数 | NH0021.NHF | | index | 南华橡胶指数 | NH0014.NHF | | index | 南华沥青指数 | NH0031.NHF | | index | 南华沪燃油指数 | NH0010.NHF | | index | 南华沪铜指数 | NH0012.NHF | | index | 南华沪铝指数 | NH0013.NHF | | index | 南华沪锌指数 | NH0009.NHF | | index | 南华油菜籽指数 | NH0027.NHF | | index | 南华热轧卷板指数 | NH0038.NHF | | index | 南华焦炭指数 | NH0022.NHF | | index | 南华焦煤指数 | NH0029.NHF | | index | 南华玉米指数 | NH0003.NHF | | index | 南华玉米淀粉指数 | NHCS.NHF | | index | 南华玻璃指数 | NH0026.NHF | | index | 南华生猪指数 | NH0056.NHF | | index | 南华甲醇指数 | NH0024.NHF | | index | 南华白银指数 | NH0025.NHF | | index | 南华短纤指数 | NH0054.NHF | | index | 南华硅铁指数 | NHSF.NHF | | index | 南华粳米指数 | NH0048.NHF | | index | 南华红枣指数 | NH0044.NHF | | index | 南华纤维板指数 | NH0036.NHF | | index | 南华纯碱指数 | NH0051.NHF | | index | 南华纸浆指数 | NH0042.NHF | | index | 南华线材指数 | NH0017.NHF | | index | 南华聚丙烯指数 | NH0037.NHF | | index | 南华能化指数 | NH0500.NHF | | index | 南华花生指数 | NH0057.NHF | | index | 南华苯乙烯指数 | NH0049.NHF | | index | 南华苹果指数 | NH0040.NHF | | index | 南华菜籽油指数 | NH0020.NHF | | index | 南华菜籽粕指数 | NH0028.NHF | | index | 南华螺纹钢指数 | NH0016.NHF | | index | 南华豆二指数 | NH0047.NHF | | index | 南华豆油指数 | NH0002.NHF | | index | 南华豆粕指数 | NH0015.NHF | | index | 南华贵金属指数 | NH0600.NHF | | index | 南华郑糖指数 | NH0007.NHF | | index | 南华金属指数 | NH0400.NHF | | index | 南华铁矿石指数 | NH0032.NHF | | index | 南华铅指数 | NH0023.NHF | | index | 南华锡指数 | NHSN.NHF | | index | 南华锰硅指数 | NHSM.NHF | | index | 南华镍指数 | NHNI.NHF | | index | 南华鸡蛋指数 | NH0033.NHF | | index | 南华黄金指数 | NH0008.NHF | | index | 南华黑色指数 | NH0800.NHF | | index | 原材料红利(长江) | 861404.CJ | | index | 厨卫电器II(申万) | 801114.SI | | 厨卫电器(申万) index | 双循环指数 | 8841423.WI | | index | 反转指数(长江) | 861701.CJ | | index | 可选消费红利(长江) | 861406.CJ | | index | 可选消费(万得) | 882004.WI | | index | 周期(风格.中信) | CI005918.WI | | 周期风格 index | 商业贸易I(申万) | 801200.SI | | 商贸零售,商贸零售(申万) index | 商用车II(申万) | 801096.SI | | 商用车(申万) index | 国有大型银行II(申万) | 801782.SI | | 国有大型银行Ⅱ(申万) index | 国证1000 | SZ399311 | | index | 国证2000 | SZ399303 | | index | 国证价值 | SZ399371 | | index | 国证成长 | SZ399370 | | index | 国防军工I(申万) | 801740.SI | | 军工,国防军工(申万) index | 地面兵装II(申万) | 801743.SI | | 地面兵装Ⅱ(申万) index | 城商行II(申万) | 801784.SI | | 城商行Ⅱ(申万) index | 基础建设II(申万) | 801723.SI | | 基础建设(申万) index | 塑料II(申万) | 801036.SI | | 塑料(申万) index | 多元金融II(申万) | 801191.SI | | 多元金融(申万) index | 大消费指数 | 8841163.WI | | index | 大盘价值 | SZ399373 | | index | 大盘低估值(长江) | 861121.CJ | | index | 大盘低波(长江) | 861521.CJ | | index | 大盘动量(长江) | 861621.CJ | | index | 大盘反转(长江) | 861721.CJ | | index | 大盘成长 | SZ399372 | | index | 大盘成长指数(长江) | 861221.CJ | | index | 大盘指数 | 801811.SI | | index | 大盘指数(长江) | 861002.CJ | | index | 大盘红利(长江) | 861421.CJ | | index | 大盘股指数 | 8841430.WI | | index | 大盘高估值(长江) | 861122.CJ | | index | 大盘高波(长江) | 861522.CJ | | index | 大盘高盈利质量(长江) | 861321.CJ | | index | 宁组合 | 8841447.WI | | index | 家用电器 | 930697.CSI | | index | 家用电器I(申万) | 801110.SI | | 家电,家用电器(申万) index | 家用轻工II(申万) | 801142.SI | | 家居用品(申万) index | 家用轻工(长江) | 002068.CJ | | index | 家电零部件II(申万) | 801116.SI | | 家电零部件Ⅱ(申万) index | 富时中国A50 | 830009.XI | | A50 index | 小家电II(申万) | 801113.SI | | 小家电(申万) index | 小盘价值 | SZ399377 | | index | 小盘低估值(长江) | 861141.CJ | | index | 小盘低波(长江) | 861541.CJ | | index | 小盘动量(长江) | 861641.CJ | | index | 小盘反转(长江) | 861741.CJ | | index | 小盘成长 | SZ399376 | | index | 小盘成长指数(长江) | 861241.CJ | | index | 小盘指数 | 801813.SI | | index | 小盘指数(长江) | 861004.CJ | | index | 小盘高估值(长江) | 861142.CJ | | index | 小盘高波(长江) | 861542.CJ | | index | 小盘高盈利质量(长江) | 861341.CJ | | index | 工业红利(长江) | 861405.CJ | | index | 工业金属II(申万) | 801055.SI | | 工业金属(申万) index | 工业(万得) | 882003.WI | | index | 工程咨询服务II(申万) | 801726.SI | | 工程咨询服务Ⅱ(申万) index | 工程机械II(申万) | 801077.SI | | 工程机械(申万) index | 巨潮中盘 | SZ399315 | | index | 巨潮大盘 | SZ399314 | | index | 巨潮小盘 | SZ399316 | | index | 广告营销II(申万) | 801765.SI | | 广告营销(申万) index | 建筑材料I(申万) | 801710.SI | | 建材,建筑材料(申万) index | 建筑装饰I(申万) | 801720.SI | | 建筑装饰,建筑装饰(申万) index | 影视院线II(申万) | 801766.SI | | 影视院线(申万) index | 微利股指数 | 801852.SI | | index | 微盘股指数 | 8841431.WI | | index | 必选消费红利(长江) | 861407.CJ | | index | 恒生中国(香港上市)100 | HSML100.HI | | index | 恒生中国企业指数 | HSCEI.HI | | 恒生国企 index | 恒生可持续发展企业指数 | HSSUS.HI | | index | 恒生指数 | HSI.HI | | 港股,恒指 index | 恒生港股通 | HSHKI.HI | | index | 恒生港股通中国内地银行 | HSSCBI.HI | | index | 恒生港股通中小型股 | HSHKMS.HI | | index | 恒生港股通小型股 | HSHKS.HI | | index | 恒生科技 | HSTECH.HI | | 恒科,恒生科技指数 index | 恒生等权重 | HSEWI.HI | | index | 恒生综指 | HSCI.HI | | index | 恒生金融类 | HSF.HI | | index | 恒生非必需性消费业 | HSCICD.HI | | index | 恒生香港35 | HSHK35.HI | | index | 恒生香港中资企业指数 | HSCCI.HI | | index | 成长(风格.中信) | CI005920.WI | | 成长风格 index | 成长指数(长江) | 861201.CJ | | index | 房地产I(申万) | 801180.SI | | 地产,房地产(申万) index | 房地产及服务红利(长江) | 861414.CJ | | index | 房地产开发II(申万) | 801181.SI | | 房地产开发(申万) index | 房地产指数 | 886057.WI | | index | 房地产服务II(申万) | 801183.SI | | 房地产服务(申万) index | 房地产(万得) | 882011.WI | | index | 房屋建设II(申万) | 801721.SI | | 房屋建设Ⅱ(申万) index | 教育II(申万) | 801994.SI | | 教育(申万) index | 数字媒体II(申万) | 801767.SI | | 数字媒体(申万) index | 文娱用品II(申万) | 801145.SI | | 文娱用品(申万) index | 新基建指数 | 8841309.WI | | index | 新材料指数 | 884057.WI | | index | 新股指数 | 801863.SI | | index | 新能源指数 | 884035.WI | | index | 旅游及景区II(申万) | 801993.SI | | 旅游及景区(申万) index | 日常消费(万得) | 882005.WI | | index | 日经225 | N225.GI | | 日经 index | 普钢II(申万) | 801044.SI | | 普钢(申万) index | 智能体指数 | 8841904.WI | | index | 智选创新价值 | 950333.CSI | | index | 有色金属I(申万) | 801050.SI | | 有色,有色金属(申万) index | 服装家纺II(申万) | 801132.SI | | 服装家纺(申万) index | 机构调研指数 | 8841244.WI | | index | 机械设备I(申万) | 801890.SI | | 机械,机械设备(申万) index | 材料(万得) | 882002.WI | | index | 标普500 | SPX.GI | | index | 核心资产指数 | 8841318.WI | | index | 橡胶II(申万) | 801037.SI | | 橡胶(申万) index | 欧元兑瑞士法郎 | EURCHF | | index | 欧元兑美元 | EURUSD | | index | 欧洲斯托克银行欧元价格指数 | SX7E.Z | | index | 比特币兑美元 | BTCUSD | | index | 水泥制造II(申万) | 801711.SI | | 水泥(申万) index | 汽车I(申万) | 801880.SI | | 汽车,汽车(申万) index | 汽车服务II(申万) | 801092.SI | | 汽车服务(申万) index | 汽车零部件II(申万) | 801093.SI | | 汽车零部件(申万) index | 沪深300 | SH000300 | | 300 index | 油服工程II(申万) | 801962.SI | | 油服工程(申万) index | 消费(风格.中信) | CI005919.WI | | 消费风格 index | 消费电子产业指数 | 8841278.WI | | index | 深创100 | SZ399088 | | index | 深证100 | SZ399330 | | index | 深证A指 | SZ399107 | | index | 深证成指 | SZ399001 | | index | 深证综指 | SZ399106 | | index | 渔业II(申万) | 801015.SI | | 渔业(申万) index | 游戏II(申万) | 801764.SI | | 游戏Ⅱ(申万) index | 潜在分红(长江) | 861402.CJ | | index | 澳元兑美元 | AUDUSD | | index | 炼化及贸易II(申万) | 801963.SI | | 炼化及贸易(申万) index | 焦炭II(申万) | 801952.SI | | 焦炭Ⅱ(申万) index | 煤炭I(申万) | 801950.SI | | 煤炭,煤炭(申万) index | 煤炭开采II(申万) | 801951.SI | | 煤炭开采(申万) index | 照明设备II(申万) | 801115.SI | | 照明设备Ⅱ(申万) index | 燃气II(申万) | 801163.SI | | 燃气Ⅱ(申万) index | 物流II(申万) | 801178.SI | | 物流(申万) index | 特钢II(申万) | 801045.SI | | 特钢Ⅱ(申万) index | 环保I(申万) | 801970.SI | | 环保,环保(申万) index | 环保设备II(申万) | 801972.SI | | 环保设备Ⅱ(申万) index | 环境治理II(申万) | 801971.SI | | 环境治理(申万) index | 玻璃制造II(申万) | 801712.SI | | 玻璃玻纤(申万) index | 生物制品II(申万) | 801152.SI | | 生物制品(申万) index | 申万A股 | 801003.SI | | index | 电信服务(万得) | 882009.WI | | index | 电力II(申万) | 801161.SI | | 电力(申万) index | 电子I(申万) | 801080.SI | | 电子,电子(申万) index | 电子制造II(申万) | 801085.SI | | 消费电子(申万) index | 电子化学品II(申万) | 801086.SI | | 电子化学品Ⅱ(申万) index | 电机II(申万) | 801731.SI | | 电机Ⅱ(申万) index | 电气设备I(申万) | 801730.SI | | 新能源,电气设备(申万) index | 电气设备(中信) | CI005808.WI | | index | 电池II(申万) | 801737.SI | | 电池(申万) index | 电源设备II(申万) | 801733.SI | | 其他电源设备Ⅱ(申万) index | 电源设备(中信) | CI005809.WI | | index | 电网设备II(申万) | 801738.SI | | 电网设备(申万) index | 电视广播II(申万) | 801995.SI | | 电视广播Ⅱ(申万) index | 畜禽养殖II(申万) | 801017.SI | | 养殖业(申万) index | 白色家电II(申万) | 801111.SI | | 白色家电(申万) index | 白酒II(申万) | 801125.SI | | 白酒Ⅱ(申万) index | 盈利质量成长(长江) | 861203.CJ | | index | 石油石化I(申万) | 801960.SI | | 石油石化,石油石化(申万) index | 碳中和指数 | 8841429.WI | | index | 种植业II(申万) | 801016.SI | | 种植业(申万) index | 科创100 | SH000698 | | index | 科创200 | SH000699 | | index | 科创50 | SH000688 | | index | 科创创业50 | 931643.CSI | | index | 科创成长 | SH000690 | | index | 科创综指 | SH000680 | | index | 科技龙头指数 | 8841234.WI | | index | 稀有金属II(申万) | 801054.SI | | 小金属(申万) index | 稳定(风格.中信) | CI005921.WI | | 稳定风格 index | 红利指数 | SH000015 | | index | 红利指数(长江) | 861401.CJ | | 红利 index | 纳斯达克100指数 | NDX.GI | | index | 纳斯达克指数 | IXIC.GI | | index | 纺织制造II(申万) | 801131.SI | | 纺织制造(申万) index | 纺织服装I(申万) | 801130.SI | | 纺服,纺织服饰(申万) index | 绩优股指数 | 801853.SI | | index | 综合II(申万) | 801231.SI | | 综合Ⅱ(申万) index | 综合I(申万) | 801230.SI | | 综合,综合(申万) index | 美元兑俄罗斯卢布 | USDRUB | | index | 美元兑加元 | USDCAD | | index | 美元兑土耳其里拉 | USDTRY | | index | 美元兑墨西哥比索 | USDMXN | | index | 美元兑新加坡元 | USDSGD | | index | 美元兑新台币 | USDTWD | | index | 美元兑日元 | USDJPY | | index | 美元兑港币 | USDHKD | | index | 美元兑瑞士法郎 | USDCHF | | index | 美元兑离岸人民币 | USDCNH | | index | 美元兑阿根廷比索 | USDARS | | index | 美元兑马来西亚林吉特 | USDMYR | | index | 美元指数DXY | DXY.M | | 美元指数 index | 美容护理I(申万) | 801980.SI | | 美容护理,美容护理(申万) index | 股份制银行II(申万) | 801783.SI | | 股份制银行Ⅱ(申万) index | 股权激励指数 | 8841248.WI | | index | 能源红利(长江) | 861403.CJ | | index | 能源金属II(申万) | 801056.SI | | 能源金属(申万) index | 能源(万得) | 882001.WI | | index | 自主可控指数 | 884169.WI | | index | 自动化设备II(申万) | 801078.SI | | 自动化设备(申万) index | 航天装备II(申万) | 801741.SI | | 航天装备Ⅱ(申万) index | 航空机场II(申万) | 801991.SI | | 航空机场(申万) index | 航空装备II(申万) | 801742.SI | | 航空装备Ⅱ(申万) index | 航运港口II(申万) | 801992.SI | | 航运港口(申万) index | 船舶制造II(申万) | 801744.SI | | 航海装备Ⅱ(申万) index | 英镑兑美元 | GBPUSD | | index | 茅指数 | 8841415.WI | | index | 行业中性PB-ROE(长江) | 861312.CJ | | index | 行业中性中盘(长江) | 861013.CJ | | index | 行业中性低估值(长江) | 861111.CJ | | index | 行业中性低波(长江) | 861511.CJ | | index | 行业中性动量(长江) | 861611.CJ | | index | 行业中性反转(长江) | 861711.CJ | | index | 行业中性大盘(长江) | 861012.CJ | | index | 行业中性小盘(长江) | 861014.CJ | | index | 行业中性成长(长江) | 861211.CJ | | index | 行业中性红利(长江) | 861411.CJ | | index | 行业中性高波(长江) | 861512.CJ | | index | 行业中性高盈利质量(长江) | 861311.CJ | | index | 行业龙头指数 | 884254.WI | | index | 装修装饰II(申万) | 801722.SI | | 装修装饰Ⅱ(申万) index | 视听器材II(申万) | 801112.SI | | 黑色家电(申万) index | 计算机I(申万) | 801750.SI | | 计算机,计算机(申万) index | 计算机设备II(申万) | 801101.SI | | 计算机设备(申万) index | 计算机(中信) | CI005027.WI | | index | 证券II(申万) | 801193.SI | | 证券Ⅱ(申万) index | 调味发酵品II(申万) | 801129.SI | | 调味发酵品Ⅱ(申万) index | 贸易II(申万) | 801202.SI | | 贸易Ⅱ(申万) index | 费城半导体指数 | SOX.GI | | index | 资源股 | 8841249.WI | | index | 超大盘指数(长江) | 861001.CJ | | index | 超小盘指数(长江) | 861005.CJ | | index | 软件开发II(申万) | 801104.SI | | 软件开发(申万) index | 轻工制造I(申万) | 801140.SI | | 轻工,轻工制造(申万) index | 运输设备II(申万) | 801076.SI | | 轨交设备Ⅱ(申万) index | 通信I(申万) | 801770.SI | | 通信,通信(申万) index | 通信设备II(申万) | 801102.SI | | 通信设备(申万) index | 通信运营II(申万) | 801223.SI | | 通信服务(申万) index | 通用机械II(申万) | 801072.SI | | 通用设备(申万) index | 通讯业务红利(长江) | 861412.CJ | | index | 速冻食品(中信) | CI005516.WI | | index | 造纸II(申万) | 801143.SI | | 造纸(申万) index | 道琼斯工业指数 | DJI.GI | | index | 酒店餐饮II(申万) | 801219.SI | | 酒店餐饮(申万) index | 金属非金属新材料II(申万) | 801051.SI | | 金属新材料(申万) index | 金融(风格.中信) | CI005917.WI | | 金融风格 index | 金融红利(长江) | 861409.CJ | | index | 金融(万得) | 882007.WI | | index | 钢铁I(申万) | 801040.SI | | 钢铁,钢铁(申万) index | 钴(万得) | 8841083.WI | | index | 铁路公路II(申万) | 801179.SI | | 铁路公路(申万) index | 银指数 | 8841450.WI | | index | 银行I(申万) | 801780.SI | | 银行,银行(申万) index | 长江全A | 860001.CJ | | index | 非白酒II(申万) | 801126.SI | | 非白酒(申万) index | 非金属材料II(申万) | 801039.SI | | 非金属材料Ⅱ(申万) index | 非银行金融(中信) | CI005022.WI | | index | 非银金融I(申万) | 801790.SI | | 非银,非银金融(申万) index | 韩国KOSDAQ | KOSDAQ.GI | | index | 韩国综合指数 | KS11.GI | | index | 韩国综合股价200 | KOSPI200.GI | | index | 预增指数 | 8841090.WI | | index | 风电设备II(申万) | 801736.SI | | 风电设备(申万) index | 食品加工II(申万) | 801124.SI | | 食品加工(申万) index | 食品饮料I(申万) | 801120.SI | | 食品饮料,食品饮料(申万) index | 饮料乳品II(申万) | 801127.SI | | 饮料乳品(申万) index | 饰品II(申万) | 801133.SI | | 饰品(申万) index | 饲料II(申万) | 801014.SI | | 饲料(申万) index | 高价股指数 | 8841134.WI | | index | 高价股指数 | 801841.SI | | index | 高估值指数(长江) | 861102.CJ | | index | 高市净率指数 | 801831.SI | | index | 高市盈率指数 | 801821.SI | | index | 高波指数(长江) | 861502.CJ | | index | 高盈利质量指数(长江) | 861301.CJ | | index | 高端装备制造指数 | 884092.WI | | index | 黄金II(申万) | 801053.SI | | 贵金属(申万) -
stock_a.yaml 244.6 KB
# ============================================================ # A 股个股(上交所 SH + 深交所 SZ) # 来源:MongoDB tkrsInfo 集合(type=stock,ticker 以 SH/SZ 开头) # 格式:stock | name | ticker | 行业 | short_names # ⚠️ ETF 也在此文件中(如 SH510300 沪深300ETF),行业字段可能为空 # ============================================================ stock | 180治理ETF交银 | SH510010 | | stock | 上证50ETF华夏 | SH510050 | | stock | 上证指数ETF富国 | SH510210 | | stock | 沪深300ETF华泰柏瑞 | SH510300 | | stock | 中证500ETF南方 | SH510500 | | stock | 红利ETF华泰柏瑞 | SH510880 | | stock | 五年国债ETF国泰 | SH511010 | | stock | 城投债ETF海富通 | SH511220 | | stock | 十年国债ETF国泰 | SH511260 | | stock | 证券保险ETF易方达 | SH512070 | | stock | 中证1000ETF南方 | SH512100 | | stock | 创新药ETF | SH512170 | | stock | 地产ETF | SH512200 | | stock | 生物医药ETF国泰 | SH512290 | | stock | 有色ETF南方 | SH512400 | | stock | 半导体ETF国联安 | SH512480 | | stock | 军工ETF | SH512660 | | stock | 银行ETF华宝 | SH512800 | | stock | 红利低波ETF华泰柏瑞 | SH512890 | | stock | 港股科技ETF易方达 | SH513010 | | stock | 中概互联ETF易方达 | SH513050 | | stock | 纳指100ETF国泰 | SH513100 | | stock | 恒生科技ETF华夏 | SH513180 | | stock | 恒生ETF | SH513210 | | stock | 中韩半导体ETF华泰柏瑞 | SH513310 | | stock | 通信ETF华夏 | SH515050 | | stock | 人工智能ETF华夏 | SH515070 | | stock | 红利低波100ETF景顺 | SH515100 | | stock | 钢铁ETF国泰 | SH515210 | | stock | 煤炭ETF国泰 | SH515220 | | stock | 消费50ETF富国 | SH515650 | | stock | 通信ETF国泰 | SH515880 | | stock | 化工ETF华安 | SH516120 | | stock | 物流ETF富国 | SH516910 | | stock | 基建ETF银华 | SH516950 | | stock | 黄金股ETF国泰 | SH517400 | | stock | 黄金ETF华安 | SH518880 | | stock | 光伏龙头ETF广发 | SH560980 | | stock | 交运ETF国泰 | SH561320 | | stock | 石油ETF国泰 | SH561360 | | stock | 绿电ETF华夏 | SH562550 | | stock | 绿色电力ETF易方达 | SH562960 | | stock | A500ETF华泰柏瑞 | SH563360 | | stock | 科创50ETF华夏 | SH588000 | | stock | 科创100ETF国泰 | SH588120 | | stock | 科创100ETF银华 | SH588190 | | stock | 科创芯片ETF嘉实 | SH588200 | | stock | 科创100ETF鹏华 | SH588220 | | stock | UC科创半导体设备ETF华泰柏瑞 | SH588710 | | stock | 科创芯片ETF博时 | SH588990 | | stock | 科创芯片设计ETF易方达 | SH589030 | | stock | 科创创新药ETF国泰 | SH589720 | | stock | 浦发银行 | SH600000 | 银行 | stock | 白云机场 | SH600004 | 交通运输 | stock | 东风股份 | SH600006 | 汽车 | stock | 中国国贸 | SH600007 | 房地产 | stock | 首创环保 | SH600008 | 环保 | stock | 上海机场 | SH600009 | 交通运输 | stock | 包钢股份 | SH600010 | 钢铁 | stock | 华能国际 | SH600011 | 公用事业 | stock | 皖通高速 | SH600012 | 交通运输 | stock | 华夏银行 | SH600015 | 银行 | stock | 民生银行 | SH600016 | 银行 | stock | 日照港 | SH600017 | 交通运输 | stock | 上港集团 | SH600018 | 交通运输 | 上港 stock | 宝钢股份 | SH600019 | 钢铁 | 宝钢 stock | 中原高速 | SH600020 | 交通运输 | stock | 上海电力 | SH600021 | 公用事业 | stock | 山东钢铁 | SH600022 | 钢铁 | stock | 浙能电力 | SH600023 | 公用事业 | stock | 华能水电 | SH600025 | 公用事业 | stock | 中远海能 | SH600026 | 交通运输 | stock | 华电国际 | SH600027 | 公用事业 | stock | 中国石化 | SH600028 | 石油石化 | 中石化 stock | 南方航空 | SH600029 | 交通运输 | 南航 stock | 中信证券 | SH600030 | 非银金融 | 中信 stock | 三一重工 | SH600031 | 机械设备 | stock | 浙江新能 | SH600032 | 公用事业 | stock | 福建高速 | SH600033 | 交通运输 | stock | 楚天高速 | SH600035 | 交通运输 | stock | 招商银行 | SH600036 | 银行 | 招行 stock | 歌华有线 | SH600037 | 传媒 | stock | 中直股份 | SH600038 | 国防军工 | stock | 四川路桥 | SH600039 | 建筑装饰 | stock | 保利发展 | SH600048 | 房地产 | stock | 中国联通 | SH600050 | 通信 | 联通 stock | 宁波联合 | SH600051 | 综合 | stock | 东望时代 | SH600052 | 公用事业 | stock | *ST九鼎 | SH600053 | 非银金融 | stock | 黄山旅游 | SH600054 | 社会服务 | stock | 万东医疗 | SH600055 | 医药生物 | stock | 中国医药 | SH600056 | 医药生物 | stock | 厦门象屿 | SH600057 | 交通运输 | stock | 五矿发展 | SH600058 | 商贸零售 | stock | 古越龙山 | SH600059 | 食品饮料 | stock | 海信视像 | SH600060 | 家用电器 | stock | 国投资本 | SH600061 | 非银金融 | stock | 华润双鹤 | SH600062 | 医药生物 | stock | 皖维高新 | SH600063 | 基础化工 | stock | 南京高科 | SH600064 | 房地产 | stock | 宇通客车 | SH600066 | 汽车 | stock | 冠城新材 | SH600067 | 电力设备 | stock | 凤凰光学 | SH600071 | 电子 | stock | 中船科技 | SH600072 | 电力设备 | stock | 光明肉业 | SH600073 | 食品饮料 | stock | 新疆天业 | SH600075 | 基础化工 | stock | 康欣新材 | SH600076 | 轻工制造 | stock | 澄星股份 | SH600078 | 基础化工 | stock | ST人福 | SH600079 | 医药生物 | stock | ST金花 | SH600080 | 医药生物 | stock | 东风科技 | SH600081 | 汽车 | stock | ST海泰 | SH600082 | 商贸零售 | stock | *ST尼雅 | SH600084 | 食品饮料 | stock | 同仁堂 | SH600085 | 医药生物 | stock | 中视传媒 | SH600088 | 传媒 | stock | 特变电工 | SH600089 | 电力设备 | stock | 大名城 | SH600094 | 房地产 | stock | 湘财股份 | SH600095 | 非银金融 | stock | 云天化 | SH600096 | 基础化工 | stock | 开创国际 | SH600097 | 农林牧渔 | stock | 广州发展 | SH600098 | 公用事业 | stock | 林海股份 | SH600099 | 汽车 | stock | 同方股份 | SH600100 | 计算机 | stock | 明星电力 | SH600101 | 公用事业 | stock | 青山纸业 | SH600103 | 轻工制造 | stock | 上汽集团 | SH600104 | 汽车 | 上汽 stock | 永鼎股份 | SH600105 | 通信 | stock | 重庆路桥 | SH600106 | 交通运输 | stock | *ST尔雅 | SH600107 | 纺织服饰 | stock | 亚盛集团 | SH600108 | 农林牧渔 | 亚盛 stock | 国金证券 | SH600109 | 非银金融 | 国金 stock | 诺德股份 | SH600110 | 电力设备 | stock | 北方稀土 | SH600111 | 有色金属 | stock | 浙江东日 | SH600113 | 综合 | stock | 东睦股份 | SH600114 | 机械设备 | stock | 中国东航 | SH600115 | 交通运输 | 东航 stock | 三峡水利 | SH600116 | 公用事业 | stock | 西宁特钢 | SH600117 | 钢铁 | stock | 中国卫星 | SH600118 | 国防军工 | stock | *ST长投 | SH600119 | 交通运输 | stock | 浙江东方 | SH600120 | 非银金融 | stock | 郑州煤电 | SH600121 | 煤炭 | stock | 兰花科创 | SH600123 | 煤炭 | stock | 铁龙物流 | SH600125 | 交通运输 | stock | 杭钢股份 | SH600126 | 钢铁 | stock | 金健米业 | SH600127 | 农林牧渔 | stock | 苏豪弘业 | SH600128 | 商贸零售 | stock | 太极集团 | SH600129 | 医药生物 | stock | 波导股份 | SH600130 | 电子 | stock | 国网信通 | SH600131 | 计算机 | stock | 重庆啤酒 | SH600132 | 食品饮料 | stock | 东湖高新 | SH600133 | 环保 | stock | 乐凯胶片 | SH600135 | 基础化工 | stock | ST明诚 | SH600136 | 房地产 | stock | 浪莎股份 | SH600137 | 纺织服饰 | stock | 中青旅 | SH600138 | 社会服务 | stock | 兴发集团 | SH600141 | 基础化工 | 兴发 stock | 金发科技 | SH600143 | 基础化工 | stock | 长春一东 | SH600148 | 汽车 | stock | 廊坊发展 | SH600149 | 公用事业 | stock | 中国船舶 | SH600150 | 国防军工 | 船舶 stock | 航天机电 | SH600151 | 汽车 | stock | 维科技术 | SH600152 | 电力设备 | 维科 stock | 建发股份 | SH600153 | 交通运输 | stock | 华创云信 | SH600155 | 非银金融 | stock | 华升股份 | SH600156 | 纺织服饰 | stock | 永泰能源 | SH600157 | 公用事业 | stock | 中体产业 | SH600158 | 社会服务 | stock | 大龙地产 | SH600159 | 房地产 | stock | 巨化股份 | SH600160 | 基础化工 | stock | 天坛生物 | SH600161 | 医药生物 | stock | 香江控股 | SH600162 | 房地产 | stock | 中闽能源 | SH600163 | 公用事业 | stock | 宁科生物 | SH600165 | 基础化工 | ST宁科 stock | 福田汽车 | SH600166 | 汽车 | stock | 联美控股 | SH600167 | 公用事业 | stock | 武汉控股 | SH600168 | 环保 | stock | ST太重 | SH600169 | 机械设备 | stock | 上海建工 | SH600170 | 建筑装饰 | stock | 上海贝岭 | SH600171 | 电子 | stock | 黄河旋风 | SH600172 | 机械设备 | stock | 卧龙新能 | SH600173 | 房地产 | stock | 中国巨石 | SH600176 | 建筑材料 | 巨石 stock | 雅戈尔 | SH600177 | 纺织服饰 | stock | 东安动力 | SH600178 | 汽车 | stock | 安通控股 | SH600179 | 交通运输 | 安通 stock | *ST瑞茂 | SH600180 | 交通运输 | stock | S佳通 | SH600182 | 汽车 | stock | 生益科技 | SH600183 | 电子 | stock | 光电股份 | SH600184 | 国防军工 | stock | 珠免集团 | SH600185 | 商贸零售 | stock | 莲花控股 | SH600186 | 食品饮料 | stock | *ST国中 | SH600187 | 环保 | stock | 兖矿能源 | SH600188 | 煤炭 | stock | 泉阳泉 | SH600189 | 食品饮料 | stock | 华资实业 | SH600191 | 农林牧渔 | 华资 stock | 长城电工 | SH600192 | 电力设备 | stock | 退市创兴 | SH600193 | 建筑装饰 | stock | 中牧股份 | SH600195 | 农林牧渔 | stock | 复星医药 | SH600196 | 医药生物 | 复星 stock | 伊力特 | SH600197 | 食品饮料 | stock | 大唐电信 | SH600198 | 通信 | stock | 金种子酒 | SH600199 | 食品饮料 | stock | 生物股份 | SH600201 | 农林牧渔 | stock | 哈空调 | SH600202 | 机械设备 | stock | 福日电子 | SH600203 | 电子 | stock | 有研新材 | SH600206 | 电子 | stock | 安彩高科 | SH600207 | 电力设备 | stock | 衢州发展 | SH600208 | 房地产 | stock | 紫江企业 | SH600210 | 轻工制造 | stock | 西藏药业 | SH600211 | 医药生物 | stock | 绿能慧充 | SH600212 | 电力设备 | stock | 派斯林 | SH600215 | 机械设备 | stock | 浙江医药 | SH600216 | 医药生物 | stock | 中再资环 | SH600217 | 环保 | stock | 全柴动力 | SH600218 | 汽车 | stock | 南山铝业 | SH600219 | 有色金属 | stock | 海航控股 | SH600221 | 交通运输 | stock | 太龙药业 | SH600222 | 医药生物 | stock | 福瑞达 | SH600223 | 美容护理 | stock | 亨通股份 | SH600226 | 公用事业 | stock | 赤天化 | SH600227 | 基础化工 | stock | 返利科技 | SH600228 | 传媒 | stock | 城市传媒 | SH600229 | 传媒 | stock | 沧州大化 | SH600230 | 基础化工 | stock | 凌钢股份 | SH600231 | 钢铁 | stock | 金鹰股份 | SH600232 | 机械设备 | stock | 圆通速递 | SH600233 | 交通运输 | stock | 科新发展 | SH600234 | 建筑装饰 | stock | 民丰特纸 | SH600235 | 轻工制造 | stock | 桂冠电力 | SH600236 | 公用事业 | stock | 铜峰电子 | SH600237 | 电子 | stock | ST椰岛 | SH600238 | 食品饮料 | stock | ST云城 | SH600239 | 房地产 | stock | 时代万恒 | SH600241 | 电力设备 | stock | *ST海华 | SH600243 | 机械设备 | stock | 万通发展 | SH600246 | 房地产 | stock | 陕建股份 | SH600248 | 建筑装饰 | stock | 两面针 | SH600249 | 美容护理 | stock | 南京商旅 | SH600250 | 社会服务 | stock | 冠农股份 | SH600251 | 基础化工 | stock | 中恒集团 | SH600252 | 医药生物 | 中恒 stock | 鑫科材料 | SH600255 | 有色金属 | stock | 广汇能源 | SH600256 | 石油石化 | stock | 大湖股份 | SH600257 | 农林牧渔 | stock | 首旅酒店 | SH600258 | 社会服务 | stock | 中稀有色 | SH600259 | 有色金属 | stock | 阳光照明 | SH600261 | 家用电器 | stock | 北方股份 | SH600262 | 机械设备 | stock | *ST景谷 | SH600265 | 农林牧渔 | stock | 城建发展 | SH600266 | 房地产 | stock | 海正药业 | SH600267 | 医药生物 | stock | 国电南自 | SH600268 | 电力设备 | stock | 赣粤高速 | SH600269 | 交通运输 | stock | 航天信息 | SH600271 | 计算机 | stock | 开开实业 | SH600272 | 医药生物 | stock | 嘉化能源 | SH600273 | 基础化工 | stock | 恒瑞医药 | SH600276 | 医药生物 | stock | 东方创业 | SH600278 | 商贸零售 | stock | 重庆港 | SH600279 | 交通运输 | stock | 中央商场 | SH600280 | 商贸零售 | stock | 华阳新材 | SH600281 | 有色金属 | stock | 南钢股份 | SH600282 | 钢铁 | stock | 钱江水利 | SH600283 | 环保 | stock | 浦东建设 | SH600284 | 建筑装饰 | stock | 羚锐制药 | SH600285 | 医药生物 | stock | 苏豪时尚 | SH600287 | 商贸零售 | stock | 大恒科技 | SH600288 | 计算机 | stock | ST信通 | SH600289 | 通信 | stock | 电投水电 | SH600292 | 公用事业 | stock | 三峡新材 | SH600293 | 建筑材料 | stock | 鄂尔多斯 | SH600295 | 钢铁 | stock | 安琪酵母 | SH600298 | 食品饮料 | 安琪 stock | 安迪苏 | SH600299 | 基础化工 | stock | 维维股份 | SH600300 | 食品饮料 | stock | 华锡有色 | SH600301 | 有色金属 | stock | ST标准 | SH600302 | 机械设备 | stock | 曙光股份 | SH600303 | 汽车 | stock | 恒顺醋业 | SH600305 | 食品饮料 | stock | 酒钢宏兴 | SH600307 | 钢铁 | stock | 华泰股份 | SH600308 | 轻工制造 | stock | 万华化学 | SH600309 | 基础化工 | stock | 广西能源 | SH600310 | 公用事业 | stock | 平高电气 | SH600312 | 电力设备 | stock | 农发种业 | SH600313 | 农林牧渔 | stock | 上海家化 | SH600315 | 美容护理 | stock | 洪都航空 | SH600316 | 国防军工 | stock | 新力金融 | SH600318 | 非银金融 | stock | 亚星化学 | SH600319 | 基础化工 | stock | 振华重工 | SH600320 | 机械设备 | stock | 津投城开 | SH600322 | 房地产 | stock | 瀚蓝环境 | SH600323 | 环保 | stock | 华发股份 | SH600325 | 房地产 | stock | 西藏天路 | SH600326 | 建筑材料 | stock | 大东方 | SH600327 | 商贸零售 | stock | 中盐化工 | SH600328 | 基础化工 | stock | 达仁堂 | SH600329 | 医药生物 | stock | 天通股份 | SH600330 | 电子 | stock | 宏达股份 | SH600331 | 基础化工 | stock | 白云山 | SH600332 | 医药生物 | stock | 长春燃气 | SH600333 | 公用事业 | stock | 国机汽车 | SH600335 | 汽车 | stock | 澳柯玛 | SH600336 | 家用电器 | stock | ST美克 | SH600337 | 轻工制造 | stock | 西藏珠峰 | SH600338 | 有色金属 | stock | 中油工程 | SH600339 | 石油石化 | stock | *ST华幸 | SH600340 | 房地产 | stock | 航天动力 | SH600343 | 机械设备 | stock | 长江通信 | SH600345 | 通信 | stock | 恒力石化 | SH600346 | 石油石化 | stock | 华阳股份 | SH600348 | 煤炭 | stock | 山东高速 | SH600350 | 交通运输 | stock | 亚宝药业 | SH600351 | 医药生物 | stock | 浙江龙盛 | SH600352 | 基础化工 | stock | 旭光电子 | SH600353 | 电子 | stock | 敦煌种业 | SH600354 | 农林牧渔 | stock | *ST精伦 | SH600355 | 通信 | stock | 恒丰纸业 | SH600356 | 轻工制造 | stock | 国旅联合 | SH600358 | 传媒 | stock | 新农开发 | SH600359 | 农林牧渔 | stock | 华微电子 | SH600360 | 电子 | stock | 创新新材 | SH600361 | 有色金属 | stock | 江西铜业 | SH600362 | 有色金属 | stock | 联创光电 | SH600363 | 电子 | stock | ST通葡 | SH600365 | 商贸零售 | stock | 宁波韵升 | SH600366 | 有色金属 | stock | 红星发展 | SH600367 | 基础化工 | stock | 五洲交通 | SH600368 | 交通运输 | stock | 西南证券 | SH600369 | 非银金融 | stock | *ST三房 | SH600370 | 基础化工 | stock | 万向德农 | SH600371 | 农林牧渔 | stock | 中航机载 | SH600372 | 国防军工 | stock | 中文传媒 | SH600373 | 传媒 | stock | 汉马科技 | SH600375 | 汽车 | stock | 首开股份 | SH600376 | 房地产 | stock | 宁沪高速 | SH600377 | 交通运输 | stock | 昊华科技 | SH600378 | 基础化工 | stock | 宝光股份 | SH600379 | 电力设备 | stock | 健康元 | SH600380 | 医药生物 | stock | *ST春天 | SH600381 | 食品饮料 | stock | 广东明珠 | SH600382 | 钢铁 | stock | 金地集团 | SH600383 | 房地产 | 金地 stock | 北巴传媒 | SH600386 | 汽车 | stock | 紫金龙净 | SH600388 | 环保 | 龙净环保 stock | 江山股份 | SH600389 | 基础化工 | stock | 五矿资本 | SH600390 | 非银金融 | stock | 航发科技 | SH600391 | 国防军工 | stock | 盛和资源 | SH600392 | 有色金属 | stock | 盘江股份 | SH600395 | 煤炭 | stock | 华电辽能 | SH600396 | 公用事业 | stock | 江钨装备 | SH600397 | 机械设备 | stock | 海澜之家 | SH600398 | 纺织服饰 | stock | 抚顺特钢 | SH600399 | 钢铁 | stock | 红豆股份 | SH600400 | 纺织服饰 | stock | 大有能源 | SH600403 | 煤炭 | stock | 动力源 | SH600405 | 电力设备 | stock | 国电南瑞 | SH600406 | 电力设备 | stock | 安泰集团 | SH600408 | 煤炭 | 安泰 stock | 三友化工 | SH600409 | 基础化工 | stock | 华胜天成 | SH600410 | 计算机 | stock | 小商品城 | SH600415 | 商贸零售 | 小商品 stock | 湘电股份 | SH600416 | 电力设备 | stock | 江淮汽车 | SH600418 | 汽车 | stock | 天润乳业 | SH600419 | 食品饮料 | stock | 国药现代 | SH600420 | 医药生物 | stock | 退市华嵘 | SH600421 | 机械设备 | 华嵘,*ST华嵘 stock | 昆药集团 | SH600422 | 医药生物 | 昆药 stock | *ST柳化 | SH600423 | 基础化工 | stock | 青松建化 | SH600425 | 建筑材料 | stock | 华鲁恒升 | SH600426 | 基础化工 | stock | 中远海特 | SH600428 | 交通运输 | stock | 三元股份 | SH600429 | 食品饮料 | stock | 冠豪高新 | SH600433 | 轻工制造 | stock | 北方导航 | SH600435 | 国防军工 | stock | 片仔癀 | SH600436 | 医药生物 | stock | 通威股份 | SH600438 | 电力设备 | stock | ST瑞贝卡 | SH600439 | 纺织服饰 | stock | 国机通用 | SH600444 | 机械设备 | stock | 金证股份 | SH600446 | 计算机 | stock | 华纺股份 | SH600448 | 纺织服饰 | stock | 宁夏建材 | SH600449 | 建筑材料 | stock | 涪陵电力 | SH600452 | 公用事业 | stock | 博通股份 | SH600455 | 计算机 | stock | 宝钛股份 | SH600456 | 有色金属 | stock | 时代新材 | SH600458 | 综合 | stock | 贵研铂业 | SH600459 | 有色金属 | stock | 士兰微 | SH600460 | 电子 | stock | 洪城环境 | SH600461 | 环保 | stock | 空港股份 | SH600463 | 房地产 | stock | 好当家 | SH600467 | 农林牧渔 | stock | 百利电气 | SH600468 | 电力设备 | stock | 风神股份 | SH600469 | 汽车 | stock | 六国化工 | SH600470 | 基础化工 | stock | 华光环能 | SH600475 | 公用事业 | stock | *ST湘邮 | SH600476 | 计算机 | stock | 杭萧钢构 | SH600477 | 建筑装饰 | stock | 科力远 | SH600478 | 电力设备 | stock | 千金药业 | SH600479 | 医药生物 | stock | 凌云股份 | SH600480 | 汽车 | stock | 双良节能 | SH600481 | 电力设备 | stock | 中国动力 | SH600482 | 电力设备 | stock | 福能股份 | SH600483 | 公用事业 | stock | 扬农化工 | SH600486 | 基础化工 | stock | 亨通光电 | SH600487 | 通信 | stock | 津药药业 | SH600488 | 医药生物 | stock | 中金黄金 | SH600489 | 有色金属 | stock | 鹏欣资源 | SH600490 | 有色金属 | stock | ST龙元 | SH600491 | 建筑装饰 | stock | 凤竹纺织 | SH600493 | 纺织服饰 | stock | 晋西车轴 | SH600495 | 机械设备 | stock | 精工钢构 | SH600496 | 建筑装饰 | stock | 驰宏锌锗 | SH600497 | 有色金属 | stock | 烽火通信 | SH600498 | 通信 | stock | 科达制造 | SH600499 | 机械设备 | stock | 中化国际 | SH600500 | 基础化工 | stock | 航天晨光 | SH600501 | 机械设备 | stock | 安徽建工 | SH600502 | 建筑装饰 | stock | 华丽家族 | SH600503 | 房地产 | stock | 西昌电力 | SH600505 | 公用事业 | stock | 统一股份 | SH600506 | 石油石化 | stock | 方大特钢 | SH600507 | 钢铁 | stock | 上海能源 | SH600508 | 煤炭 | stock | 天富能源 | SH600509 | 公用事业 | stock | 黑牡丹 | SH600510 | 房地产 | stock | 国药股份 | SH600511 | 医药生物 | stock | 腾达建设 | SH600512 | 建筑装饰 | stock | 联环药业 | SH600513 | 医药生物 | stock | 海南机场 | SH600515 | 房地产 | stock | 方大炭素 | SH600516 | 有色金属 | stock | 国网英大 | SH600517 | 非银金融 | stock | 康美药业 | SH600518 | 医药生物 | stock | 贵州茅台 | SH600519 | 食品饮料 | 茅台 stock | 三佳科技 | SH600520 | 机械设备 | stock | 华海药业 | SH600521 | 医药生物 | stock | 中天科技 | SH600522 | 通信 | stock | 贵航股份 | SH600523 | 汽车 | stock | 长园集团 | SH600525 | 电力设备 | 长圆,ST长园 stock | 菲达环保 | SH600526 | 环保 | stock | 江南高纤 | SH600527 | 纺织服饰 | stock | 中铁工业 | SH600528 | 机械设备 | stock | 山东药玻 | SH600529 | 医药生物 | stock | ST交昂 | SH600530 | 食品饮料 | stock | 豫光金铅 | SH600531 | 有色金属 | stock | 栖霞建设 | SH600533 | 房地产 | stock | 天士力 | SH600535 | 医药生物 | stock | 中国软件 | SH600536 | 计算机 | stock | *ST亿晶 | SH600537 | 电力设备 | stock | 国发股份 | SH600538 | 医药生物 | stock | 狮头股份 | SH600539 | 商贸零售 | stock | 新赛股份 | SH600540 | 农林牧渔 | stock | *ST莫高 | SH600543 | 食品饮料 | stock | 卓郎智能 | SH600545 | 机械设备 | stock | 山煤国际 | SH600546 | 煤炭 | stock | 山东黄金 | SH600547 | 有色金属 | stock | 深高速 | SH600548 | 交通运输 | stock | 厦门钨业 | SH600549 | 有色金属 | stock | 保变电气 | SH600550 | 电力设备 | stock | 时代出版 | SH600551 | 传媒 | stock | 凯盛科技 | SH600552 | 电子 | stock | 天下秀 | SH600556 | 传媒 | stock | 康缘药业 | SH600557 | 医药生物 | stock | 大西洋 | SH600558 | 机械设备 | stock | 老白干酒 | SH600559 | 食品饮料 | stock | 金自天正 | SH600560 | 机械设备 | stock | 江西长运 | SH600561 | 交通运输 | stock | 国睿科技 | SH600562 | 国防军工 | stock | 法拉电子 | SH600563 | 电子 | stock | 济川药业 | SH600566 | 医药生物 | stock | 山鹰国际 | SH600567 | 轻工制造 | stock | ST中珠 | SH600568 | 医药生物 | stock | 安阳钢铁 | SH600569 | 钢铁 | stock | 恒生电子 | SH600570 | 计算机 | stock | 信雅达 | SH600571 | 计算机 | stock | 康恩贝 | SH600572 | 医药生物 | stock | 惠泉啤酒 | SH600573 | 食品饮料 | stock | 淮河能源 | SH600575 | 公用事业 | stock | 祥源文旅 | SH600576 | 社会服务 | stock | 精达股份 | SH600577 | 电力设备 | stock | 京能电力 | SH600578 | 公用事业 | stock | 中化装备 | SH600579 | 机械设备 | stock | 卧龙电驱 | SH600580 | 电力设备 | stock | *ST八钢 | SH600581 | 钢铁 | stock | 天地科技 | SH600582 | 机械设备 | stock | 海油工程 | SH600583 | 石油石化 | stock | 长电科技 | SH600584 | 电子 | stock | 海螺水泥 | SH600585 | 建筑材料 | stock | 金晶科技 | SH600586 | 建筑材料 | stock | 新华医疗 | SH600587 | 医药生物 | stock | 用友网络 | SH600588 | 计算机 | stock | 大位科技 | SH600589 | 计算机 | stock | 泰豪科技 | SH600590 | 电力设备 | stock | 龙溪股份 | SH600592 | 机械设备 | stock | 大连圣亚 | SH600593 | 社会服务 | stock | 益佰制药 | SH600594 | 医药生物 | stock | 中孚实业 | SH600595 | 有色金属 | stock | 新安股份 | SH600596 | 基础化工 | stock | 光明乳业 | SH600597 | 食品饮料 | stock | 北大荒 | SH600598 | 农林牧渔 | stock | 退市熊猫 | SH600599 | 非银金融 | *ST熊猫 stock | 青岛啤酒 | SH600600 | 食品饮料 | stock | 方正科技 | SH600601 | 电子 | stock | 云赛智联 | SH600602 | 计算机 | stock | 广汇物流 | SH600603 | 房地产 | stock | 市北高新 | SH600604 | 房地产 | stock | 汇通能源 | SH600605 | 商贸零售 | stock | 绿地控股 | SH600606 | 房地产 | stock | 退市沪科 | SH600608 | 商贸零售 | stock | 金杯汽车 | SH600609 | 汽车 | stock | 中毅达 | SH600610 | 基础化工 | stock | 大众交通 | SH600611 | 交通运输 | stock | 老凤祥 | SH600612 | 纺织服饰 | stock | 神奇制药 | SH600613 | 医药生物 | stock | 鑫源智造 | SH600615 | 有色金属 | stock | 金枫酒业 | SH600616 | 食品饮料 | stock | 国新能源 | SH600617 | 公用事业 | stock | 氯碱化工 | SH600618 | 基础化工 | stock | 海立股份 | SH600619 | 家用电器 | stock | 天宸股份 | SH600620 | 综合 | stock | 华鑫股份 | SH600621 | 非银金融 | stock | 光大嘉宝 | SH600622 | 房地产 | stock | 华谊集团 | SH600623 | 基础化工 | 华谊 stock | ST复华 | SH600624 | 医药生物 | stock | 申达股份 | SH600626 | 汽车 | stock | 新世界 | SH600628 | 商贸零售 | stock | 华建集团 | SH600629 | 建筑装饰 | 华建 stock | 龙头股份 | SH600630 | 纺织服饰 | stock | 浙数文化 | SH600633 | 传媒 | stock | 大众公用 | SH600635 | 公用事业 | stock | 退市国化 | SH600636 | 社会服务 | *ST国化 stock | 东方明珠 | SH600637 | 传媒 | stock | 新黄浦 | SH600638 | 房地产 | stock | 浦东金桥 | SH600639 | 房地产 | stock | 国脉文化 | SH600640 | 传媒 | stock | 先导基电 | SH600641 | 电子 | stock | 申能股份 | SH600642 | 公用事业 | stock | 爱建集团 | SH600643 | 非银金融 | 爱建 stock | 乐山电力 | SH600644 | 公用事业 | stock | 中源协和 | SH600645 | 医药生物 | stock | 外高桥 | SH600648 | 房地产 | stock | 城投控股 | SH600649 | 房地产 | stock | 锦江在线 | SH600650 | 交通运输 | stock | 飞乐音响 | SH600651 | 汽车 | stock | 申华控股 | SH600653 | 汽车 | stock | 中安科 | SH600654 | 计算机 | stock | 豫园股份 | SH600655 | 商贸零售 | stock | 信达地产 | SH600657 | 房地产 | stock | 电子城 | SH600658 | 房地产 | stock | 福耀玻璃 | SH600660 | 汽车 | stock | 昂立教育 | SH600661 | 社会服务 | stock | 外服控股 | SH600662 | 社会服务 | stock | 陆家嘴 | SH600663 | 房地产 | stock | 哈药股份 | SH600664 | 医药生物 | stock | 天地源 | SH600665 | 房地产 | stock | 奥瑞德 | SH600666 | 计算机 | stock | 太极实业 | SH600667 | 建筑装饰 | stock | 尖峰集团 | SH600668 | 建筑材料 | 尖峰 stock | 天目药业 | SH600671 | 医药生物 | stock | 东阳光 | SH600673 | 综合 | stock | 川投能源 | SH600674 | 公用事业 | stock | 中华企业 | SH600675 | 房地产 | stock | 久事动娱 | SH600676 | 汽车 | stock | ST金顶 | SH600678 | 建筑材料 | stock | 上海凤凰 | SH600679 | 汽车 | stock | 百川能源 | SH600681 | 公用事业 | stock | 南京新百 | SH600682 | 综合 | stock | 京投发展 | SH600683 | 房地产 | stock | 珠江股份 | SH600684 | 房地产 | stock | 中船防务 | SH600685 | 国防军工 | stock | 金龙汽车 | SH600686 | 汽车 | stock | 上海石化 | SH600688 | 石油石化 | stock | 上海三毛 | SH600689 | 综合 | stock | 海尔智家 | SH600690 | 家用电器 | stock | 潞化科技 | SH600691 | 基础化工 | stock | 亚通股份 | SH600692 | 房地产 | stock | 东百集团 | SH600693 | 商贸零售 | 东百 stock | 大商股份 | SH600694 | 商贸零售 | stock | 退市岩石 | SH600696 | 食品饮料 | *ST岩石 stock | 欧亚集团 | SH600697 | 商贸零售 | stock | 湖南天雁 | SH600698 | 汽车 | stock | 均胜电子 | SH600699 | 汽车 | stock | 舍得酒业 | SH600702 | 食品饮料 | stock | 三安光电 | SH600703 | 电子 | stock | 物产中大 | SH600704 | 交通运输 | stock | 曲江文旅 | SH600706 | 社会服务 | stock | 彩虹股份 | SH600707 | 电子 | stock | 光明地产 | SH600708 | 房地产 | stock | 苏美达 | SH600710 | 商贸零售 | stock | 盛屯矿业 | SH600711 | 有色金属 | stock | 南宁百货 | SH600712 | 商贸零售 | stock | 南京医药 | SH600713 | 医药生物 | stock | 金瑞矿业 | SH600714 | 基础化工 | stock | 文投控股 | SH600715 | 传媒 | stock | 凤凰股份 | SH600716 | 房地产 | stock | 天津港 | SH600717 | 交通运输 | stock | 东软集团 | SH600718 | 计算机 | 东软 stock | 大连热电 | SH600719 | 公用事业 | stock | 中交设计 | SH600720 | 建筑装饰 | stock | 百花医药 | SH600721 | 医药生物 | stock | 金牛化工 | SH600722 | 基础化工 | stock | 宁波富达 | SH600724 | 房地产 | stock | 云维股份 | SH600725 | 煤炭 | stock | 华电能源 | SH600726 | 公用事业 | stock | 鲁北化工 | SH600727 | 基础化工 | stock | 佳都科技 | SH600728 | 计算机 | stock | 重百集团 | SH600729 | 商贸零售 | stock | *ST高科 | SH600730 | 社会服务 | stock | 湖南海利 | SH600731 | 基础化工 | stock | 爱旭股份 | SH600732 | 电力设备 | stock | 北汽蓝谷 | SH600733 | 汽车 | stock | ST实达 | SH600734 | 通信 | *ST实达 stock | ST新华锦 | SH600735 | 纺织服饰 | stock | 苏州高新 | SH600736 | 房地产 | stock | 中粮糖业 | SH600737 | 农林牧渔 | stock | 丽尚国潮 | SH600738 | 商贸零售 | stock | 辽宁成大 | SH600739 | 商贸零售 | stock | 山西焦化 | SH600740 | 煤炭 | stock | 华域汽车 | SH600741 | 汽车 | stock | 富维股份 | SH600742 | 汽车 | stock | 华远控股 | SH600743 | 房地产 | stock | 华银电力 | SH600744 | 公用事业 | stock | *ST闻泰 | SH600745 | 电子 | stock | 江苏索普 | SH600746 | 基础化工 | stock | 上实发展 | SH600748 | 房地产 | stock | 西藏旅游 | SH600749 | 社会服务 | stock | 华润江中 | SH600750 | 医药生物 | stock | 海航科技 | SH600751 | 交通运输 | stock | ST海钦 | SH600753 | 交通运输 | stock | 锦江酒店 | SH600754 | 社会服务 | 锦江 stock | 厦门国贸 | SH600755 | 交通运输 | stock | 浪潮软件 | SH600756 | 计算机 | stock | 长江传媒 | SH600757 | 传媒 | stock | 辽宁能源 | SH600758 | 煤炭 | stock | ST洲际 | SH600759 | 石油石化 | stock | 中航沈飞 | SH600760 | 国防军工 | stock | 安徽合力 | SH600761 | 机械设备 | stock | 通策医疗 | SH600763 | 医药生物 | stock | 中国海防 | SH600764 | 国防军工 | 海防 stock | 中航重机 | SH600765 | 国防军工 | stock | 富邦新材 | SH600768 | 有色金属 | 宁波富邦 stock | 祥龙电业 | SH600769 | 环保 | stock | 综艺股份 | SH600770 | 综合 | stock | 广誉远 | SH600771 | 医药生物 | stock | 西藏城投 | SH600773 | 房地产 | stock | 汉商集团 | SH600774 | 医药生物 | 汉商 stock | 南京熊猫 | SH600775 | 通信 | stock | 东方通信 | SH600776 | 通信 | stock | 新潮能源 | SH600777 | 石油石化 | stock | 友好集团 | SH600778 | 商贸零售 | 友好 stock | 水井坊 | SH600779 | 食品饮料 | stock | 通宝能源 | SH600780 | 公用事业 | stock | 新钢股份 | SH600782 | 钢铁 | stock | 鲁信创投 | SH600783 | 机械设备 | stock | 鲁银投资 | SH600784 | 综合 | stock | 新华百货 | SH600785 | 商贸零售 | stock | 中储股份 | SH600787 | 交通运输 | stock | 鲁抗医药 | SH600789 | 医药生物 | stock | 轻纺城 | SH600790 | 商贸零售 | stock | 京能置业 | SH600791 | 房地产 | stock | 云煤能源 | SH600792 | 煤炭 | stock | 宜宾纸业 | SH600793 | 轻工制造 | stock | 保税科技 | SH600794 | 交通运输 | stock | 国电电力 | SH600795 | 公用事业 | stock | 钱江生化 | SH600796 | 环保 | stock | 浙大网新 | SH600797 | 计算机 | stock | 宁波海运 | SH600798 | 交通运输 | stock | 渤海化学 | SH600800 | 石油石化 | stock | 华新建材 | SH600801 | 建筑材料 | stock | 福建水泥 | SH600802 | 建筑材料 | stock | 新奥股份 | SH600803 | 公用事业 | stock | 悦达投资 | SH600805 | 综合 | stock | 济高发展 | SH600807 | 综合 | stock | 马钢股份 | SH600808 | 钢铁 | stock | 山西汾酒 | SH600809 | 食品饮料 | 汾酒 stock | 神马股份 | SH600810 | 基础化工 | stock | 华北制药 | SH600812 | 医药生物 | stock | 杭州解百 | SH600814 | 商贸零售 | stock | 厦工股份 | SH600815 | 机械设备 | stock | 建元信托 | SH600816 | 非银金融 | stock | 宇通重工 | SH600817 | 环保 | stock | ST中路 | SH600818 | 汽车 | stock | 耀皮玻璃 | SH600819 | 建筑材料 | stock | 隧道股份 | SH600820 | 建筑装饰 | stock | 金开新能 | SH600821 | 公用事业 | stock | 上海物贸 | SH600822 | 汽车 | stock | 益民集团 | SH600824 | 商贸零售 | 益民 stock | 新华传媒 | SH600825 | 传媒 | stock | 兰生股份 | SH600826 | 社会服务 | stock | 百联股份 | SH600827 | 商贸零售 | stock | 茂业商业 | SH600828 | 商贸零售 | stock | 人民同泰 | SH600829 | 医药生物 | stock | 香溢融通 | SH600830 | 非银金融 | stock | 广电网络 | SH600831 | 传媒 | stock | 第一医药 | SH600833 | 医药生物 | stock | 申通地铁 | SH600834 | 交通运输 | stock | 上海机电 | SH600835 | 机械设备 | stock | 上海九百 | SH600838 | 商贸零售 | stock | 四川长虹 | SH600839 | 家用电器 | stock | 动力新科 | SH600841 | 汽车 | stock | 上工申贝 | SH600843 | 机械设备 | stock | 金煤科技 | SH600844 | 基础化工 | stock | 宝信软件 | SH600845 | 计算机 | 宝信 stock | 同济科技 | SH600846 | 建筑装饰 | stock | 万里股份 | SH600847 | 电力设备 | stock | 上海临港 | SH600848 | 房地产 | stock | 电科数字 | SH600850 | 计算机 | stock | 海欣股份 | SH600851 | 医药生物 | stock | 龙建股份 | SH600853 | 建筑装饰 | stock | 春兰股份 | SH600854 | 家用电器 | stock | 航天长峰 | SH600855 | 国防军工 | stock | 宁波中百 | SH600857 | 商贸零售 | stock | 银座股份 | SH600858 | 商贸零售 | stock | 王府井 | SH600859 | 商贸零售 | stock | 京城股份 | SH600860 | 机械设备 | stock | 北京人力 | SH600861 | 社会服务 | stock | 中航高科 | SH600862 | 国防军工 | stock | 华能蒙电 | SH600863 | 公用事业 | stock | 哈投股份 | SH600864 | 非银金融 | stock | 百大集团 | SH600865 | 商贸零售 | 百大 stock | 星湖科技 | SH600866 | 食品饮料 | stock | 通化东宝 | SH600867 | 医药生物 | stock | 梅雁吉祥 | SH600868 | 公用事业 | stock | 远东股份 | SH600869 | 电力设备 | stock | 石化油服 | SH600871 | 石油石化 | stock | 中炬高新 | SH600872 | 食品饮料 | stock | 梅花生物 | SH600873 | 食品饮料 | stock | 创业环保 | SH600874 | 环保 | stock | 东方电气 | SH600875 | 电力设备 | stock | 凯盛新能 | SH600876 | 建筑材料 | stock | 电科芯片 | SH600877 | 电子 | stock | 航天电子 | SH600879 | 国防军工 | stock | 博瑞传播 | SH600880 | 社会服务 | stock | 亚泰集团 | SH600881 | 综合 | 亚泰 stock | 妙可蓝多 | SH600882 | 食品饮料 | stock | 博闻科技 | SH600883 | 农林牧渔 | stock | 杉杉股份 | SH600884 | 电力设备 | stock | 宏发股份 | SH600885 | 电力设备 | stock | 国投电力 | SH600886 | 公用事业 | stock | 伊利股份 | SH600887 | 食品饮料 | stock | 新疆众和 | SH600888 | 有色金属 | stock | *ST京化 | SH600889 | 机械设备 | stock | 大晟文化 | SH600892 | 传媒 | stock | 航发动力 | SH600893 | 国防军工 | stock | 广日股份 | SH600894 | 机械设备 | stock | 张江高科 | SH600895 | 房地产 | stock | 厦门空港 | SH600897 | 交通运输 | stock | 长江电力 | SH600900 | 公用事业 | stock | 江苏金租 | SH600901 | 非银金融 | stock | 贵州燃气 | SH600903 | 公用事业 | stock | 三峡能源 | SH600905 | 公用事业 | stock | 财达证券 | SH600906 | 非银金融 | stock | 无锡银行 | SH600908 | 银行 | stock | 华安证券 | SH600909 | 非银金融 | stock | 中国黄金 | SH600916 | 纺织服饰 | stock | 重庆燃气 | SH600917 | 公用事业 | stock | 中泰证券 | SH600918 | 非银金融 | stock | 江苏银行 | SH600919 | 银行 | stock | 苏能股份 | SH600925 | 煤炭 | stock | 杭州银行 | SH600926 | 银行 | stock | 永安期货 | SH600927 | 非银金融 | stock | 西安银行 | SH600928 | 银行 | stock | 雪天盐业 | SH600929 | 基础化工 | stock | 华电新能 | SH600930 | 公用事业 | stock | 爱柯迪 | SH600933 | 汽车 | stock | 华塑股份 | SH600935 | 基础化工 | stock | 北投科技 | SH600936 | 传媒 | stock | 中国海油 | SH600938 | 石油石化 | 中海油 stock | 重庆建工 | SH600939 | 建筑装饰 | stock | 中国移动 | SH600941 | 通信 | 移动 stock | 维远股份 | SH600955 | 基础化工 | stock | 新天绿能 | SH600956 | 公用事业 | stock | 东方证券 | SH600958 | 非银金融 | stock | 江苏有线 | SH600959 | 传媒 | stock | 渤海汽车 | SH600960 | 汽车 | stock | 株冶集团 | SH600961 | 有色金属 | 株冶 stock | 国投中鲁 | SH600962 | 农林牧渔 | stock | 岳阳林纸 | SH600963 | 轻工制造 | stock | *ST福成 | SH600965 | 农林牧渔 | stock | 博汇纸业 | SH600966 | 轻工制造 | stock | 内蒙一机 | SH600967 | 国防军工 | stock | 海油发展 | SH600968 | 石油石化 | stock | 郴电国际 | SH600969 | 公用事业 | stock | 中材国际 | SH600970 | 建筑装饰 | stock | 恒源煤电 | SH600971 | 煤炭 | stock | 宝胜股份 | SH600973 | 电力设备 | stock | 新五丰 | SH600975 | 农林牧渔 | stock | 健民集团 | SH600976 | 医药生物 | 健民 stock | 中国电影 | SH600977 | 传媒 | stock | 广安爱众 | SH600979 | 公用事业 | stock | 北矿科技 | SH600980 | 机械设备 | stock | 苏豪汇鸿 | SH600981 | 商贸零售 | 汇鸿 stock | 宁波能源 | SH600982 | 公用事业 | stock | 惠而浦 | SH600983 | 家用电器 | stock | 建设机械 | SH600984 | 机械设备 | stock | 淮北矿业 | SH600985 | 煤炭 | stock | 浙文互联 | SH600986 | 传媒 | stock | 航民股份 | SH600987 | 纺织服饰 | stock | 赤峰黄金 | SH600988 | 有色金属 | stock | 宝丰能源 | SH600989 | 基础化工 | stock | 四创电子 | SH600990 | 国防军工 | stock | 贵绳股份 | SH600992 | 机械设备 | stock | 马应龙 | SH600993 | 医药生物 | stock | 南网储能 | SH600995 | 公用事业 | stock | 贵广网络 | SH600996 | 传媒 | stock | 开滦股份 | SH600997 | 煤炭 | stock | 九州通 | SH600998 | 医药生物 | stock | 招商证券 | SH600999 | 非银金融 | stock | 唐山港 | SH601000 | 交通运输 | stock | 晋控煤业 | SH601001 | 煤炭 | stock | 晋亿实业 | SH601002 | 机械设备 | stock | 柳钢股份 | SH601003 | 钢铁 | stock | 重庆钢铁 | SH601005 | 钢铁 | stock | 大秦铁路 | SH601006 | 交通运输 | stock | 金陵饭店 | SH601007 | 社会服务 | stock | 连云港 | SH601008 | 交通运输 | stock | 南京银行 | SH601009 | 银行 | stock | ST文峰 | SH601010 | 商贸零售 | stock | 宝泰隆 | SH601011 | 煤炭 | stock | 隆基绿能 | SH601012 | 电力设备 | stock | 陕西黑猫 | SH601015 | 煤炭 | stock | 节能风电 | SH601016 | 公用事业 | stock | 宁波港 | SH601018 | 交通运输 | stock | 山东出版 | SH601019 | 传媒 | stock | 华钰矿业 | SH601020 | 有色金属 | stock | 春秋航空 | SH601021 | 交通运输 | stock | 宁波远洋 | SH601022 | 交通运输 | stock | 道生天合 | SH601026 | 基础化工 | stock | 永兴股份 | SH601033 | 环保 | stock | 一拖股份 | SH601038 | 机械设备 | stock | 赛轮轮胎 | SH601058 | 汽车 | stock | 信达证券 | SH601059 | 非银金融 | stock | 中信金属 | SH601061 | 商贸零售 | stock | 江盐集团 | SH601065 | 基础化工 | 江盐 stock | 中信建投 | SH601066 | 非银金融 | stock | 中铝国际 | SH601068 | 建筑装饰 | stock | 西部黄金 | SH601069 | 有色金属 | stock | 渝农商行 | SH601077 | 银行 | stock | 锦江航运 | SH601083 | 交通运输 | stock | 国芳集团 | SH601086 | 商贸零售 | 国芳 stock | 中国神华 | SH601088 | 煤炭 | 神华 stock | 福元医药 | SH601089 | 医药生物 | stock | 沈鼓集团 | SH601091 | | stock | 宏盛华源 | SH601096 | 建筑装饰 | stock | 中南传媒 | SH601098 | 传媒 | stock | 太平洋 | SH601099 | 非银金融 | stock | 恒立液压 | SH601100 | 机械设备 | stock | 昊华能源 | SH601101 | 煤炭 | stock | 中国一重 | SH601106 | 机械设备 | 一重 stock | 四川成渝 | SH601107 | 交通运输 | stock | 财通证券 | SH601108 | 非银金融 | stock | 中国国航 | SH601111 | 交通运输 | 国航 stock | 振石股份 | SH601112 | 建筑材料 | stock | 华鼎股份 | SH601113 | 基础化工 | stock | 三江购物 | SH601116 | 商贸零售 | stock | 中国化学 | SH601117 | 建筑装饰 | stock | 海南橡胶 | SH601118 | 农林牧渔 | stock | 宝地矿业 | SH601121 | 钢铁 | stock | 马矿股份 | SH601123 | | stock | 四方股份 | SH601126 | 电力设备 | stock | 赛力斯 | SH601127 | 汽车 | stock | 常熟银行 | SH601128 | 银行 | stock | 柏诚股份 | SH601133 | 建筑装饰 | stock | 首创证券 | SH601136 | 非银金融 | stock | 博威合金 | SH601137 | 有色金属 | stock | 工业富联 | SH601138 | 电子 | stock | 深圳燃气 | SH601139 | 公用事业 | stock | 新城控股 | SH601155 | 房地产 | stock | 东航物流 | SH601156 | 交通运输 | stock | 重庆水务 | SH601158 | 环保 | stock | 天风证券 | SH601162 | 非银金融 | 天风 stock | 三角轮胎 | SH601163 | 汽车 | stock | 兴业银行 | SH601166 | 银行 | stock | 西部矿业 | SH601168 | 有色金属 | stock | 北京银行 | SH601169 | 银行 | stock | 杭齿前进 | SH601177 | 机械设备 | stock | 中国西电 | SH601179 | 电力设备 | 西电 stock | 中国铁建 | SH601186 | 建筑装饰 | 铁建 stock | 厦门银行 | SH601187 | 银行 | stock | 龙江交通 | SH601188 | 交通运输 | stock | 东兴证券 | SH601198 | 非银金融 | stock | 江南水务 | SH601199 | 环保 | stock | 上海环境 | SH601200 | 环保 | stock | 东材科技 | SH601208 | 基础化工 | stock | 国泰海通 | SH601211 | 非银金融 | stock | 白银有色 | SH601212 | 有色金属 | stock | 君正集团 | SH601216 | 基础化工 | 君正 stock | 吉鑫科技 | SH601218 | 电力设备 | stock | 林洋能源 | SH601222 | 电力设备 | stock | 陕西煤业 | SH601225 | 煤炭 | stock | 华电科工 | SH601226 | 建筑装饰 | stock | 广州港 | SH601228 | 交通运输 | stock | 上海银行 | SH601229 | 银行 | stock | 环旭电子 | SH601231 | 电子 | stock | 桐昆股份 | SH601233 | 石油石化 | stock | 红塔证券 | SH601236 | 非银金融 | stock | 广汽集团 | SH601238 | 汽车 | 广汽 stock | 英利汽车 | SH601279 | 汽车 | stock | 农业银行 | SH601288 | 银行 | 农行 stock | 青岛港 | SH601298 | 交通运输 | stock | 骆驼股份 | SH601311 | 电力设备 | stock | 中国平安 | SH601318 | 非银金融 | 平安 stock | 中国人保 | SH601319 | 非银金融 | 人保 stock | 秦港股份 | SH601326 | 交通运输 | stock | 交通银行 | SH601328 | 银行 | 交行 stock | 绿色动力 | SH601330 | 环保 | stock | 广深铁路 | SH601333 | 交通运输 | stock | 新华保险 | SH601336 | 非银金融 | stock | 百隆东方 | SH601339 | 纺织服饰 | stock | 三六零 | SH601360 | 计算机 | stock | 利群股份 | SH601366 | 商贸零售 | stock | 绿城水务 | SH601368 | 环保 | stock | 陕鼓动力 | SH601369 | 机械设备 | stock | 中原证券 | SH601375 | 非银金融 | stock | 兴业证券 | SH601377 | 非银金融 | stock | 怡球资源 | SH601388 | 有色金属 | stock | 中国中铁 | SH601390 | 建筑装饰 | 中铁 stock | 工商银行 | SH601398 | 银行 | 工行 stock | 国机重装 | SH601399 | 机械设备 | stock | 国联民生 | SH601456 | 非银金融 | stock | 通用股份 | SH601500 | 汽车 | stock | 中新集团 | SH601512 | 房地产 | 中新 stock | 衢州东峰 | SH601515 | 轻工制造 | stock | 吉林高速 | SH601518 | 交通运输 | stock | 大智慧 | SH601519 | 计算机 | stock | 瑞丰银行 | SH601528 | 银行 | stock | 东吴证券 | SH601555 | 非银金融 | stock | 九牧王 | SH601566 | 纺织服饰 | stock | 三星电气 | SH601567 | 电力设备 | stock | 北元化工 | SH601568 | 基础化工 | 北元 stock | 长沙银行 | SH601577 | 银行 | stock | 会稽山 | SH601579 | 食品饮料 | stock | 北辰实业 | SH601588 | 房地产 | stock | 上海电影 | SH601595 | 传媒 | stock | 中国外运 | SH601598 | 交通运输 | 外运 stock | 浙文影业 | SH601599 | 纺织服饰 | stock | 中国铝业 | SH601600 | 有色金属 | stock | 中国太保 | SH601601 | 非银金融 | 太保 stock | 长城军工 | SH601606 | 国防军工 | stock | 上海医药 | SH601607 | 医药生物 | stock | 中信重工 | SH601608 | 机械设备 | stock | 金田股份 | SH601609 | 有色金属 | stock | 中国核建 | SH601611 | 建筑装饰 | 核建 stock | 明阳智能 | SH601615 | 电力设备 | stock | 广电电气 | SH601616 | 电力设备 | stock | 中国中冶 | SH601618 | 建筑装饰 | 中冶 stock | 嘉泽新能 | SH601619 | 公用事业 | stock | 中国人寿 | SH601628 | 非银金融 | 人寿 stock | 长城汽车 | SH601633 | 汽车 | stock | 旗滨集团 | SH601636 | 建筑材料 | 旗滨 stock | 邮储银行 | SH601658 | 银行 | stock | 齐鲁银行 | SH601665 | 银行 | stock | 平煤股份 | SH601666 | 煤炭 | stock | 中国建筑 | SH601668 | 建筑装饰 | stock | 中国电建 | SH601669 | 建筑装饰 | 电建 stock | 明泰铝业 | SH601677 | 有色金属 | stock | 滨化股份 | SH601678 | 基础化工 | stock | 友发集团 | SH601686 | 钢铁 | 友发 stock | 华泰证券 | SH601688 | 非银金融 | stock | 拓普集团 | SH601689 | 汽车 | 拓普 stock | 中银证券 | SH601696 | 非银金融 | stock | 中国卫通 | SH601698 | 国防军工 | 卫通 stock | 潞安环能 | SH601699 | 煤炭 | stock | 风范股份 | SH601700 | 电力设备 | stock | 华峰铝业 | SH601702 | 有色金属 | stock | 中创智领 | SH601717 | 机械设备 | stock | ST际华 | SH601718 | 纺织服饰 | 际华 stock | 上海电气 | SH601727 | 电力设备 | stock | 中国电信 | SH601728 | 通信 | 电信 stock | 中国中车 | SH601766 | 机械设备 | 中车 stock | 千里科技 | SH601777 | 汽车 | stock | 晶科科技 | SH601778 | 公用事业 | stock | 光大证券 | SH601788 | 非银金融 | stock | 宁波建工 | SH601789 | 建筑装饰 | stock | 蓝科高新 | SH601798 | 机械设备 | stock | 星宇股份 | SH601799 | 汽车 | stock | 中国交建 | SH601800 | 建筑装饰 | stock | 皖新传媒 | SH601801 | 传媒 | stock | 中海油服 | SH601808 | 石油石化 | stock | 新华文轩 | SH601811 | 传媒 | stock | 京沪高铁 | SH601816 | 交通运输 | stock | 光大银行 | SH601818 | 银行 | stock | 沪农商行 | SH601825 | 银行 | stock | 三峰环境 | SH601827 | 环保 | stock | 美凯龙 | SH601828 | 商贸零售 | stock | 成都银行 | SH601838 | 银行 | stock | 中国石油 | SH601857 | 石油石化 | 中石油 stock | 中国科传 | SH601858 | 传媒 | 科传 stock | 紫金银行 | SH601860 | 银行 | stock | 福莱特 | SH601865 | 电力设备 | stock | 中远海发 | SH601866 | 交通运输 | stock | 中国能建 | SH601868 | 建筑装饰 | 能建 stock | 长飞光纤 | SH601869 | 通信 | stock | 招商轮船 | SH601872 | 交通运输 | stock | 正泰电器 | SH601877 | 电力设备 | stock | 浙商证券 | SH601878 | 非银金融 | stock | 辽港股份 | SH601880 | 交通运输 | stock | 中国银河 | SH601881 | 非银金融 | 银河 stock | 海天精工 | SH601882 | 机械设备 | stock | 江河集团 | SH601886 | 建筑装饰 | 江河 stock | 中国中免 | SH601888 | 商贸零售 | 中免 stock | 亚星锚链 | SH601890 | 国防军工 | stock | 中煤能源 | SH601898 | 煤炭 | stock | 紫金矿业 | SH601899 | 有色金属 | stock | 南方传媒 | SH601900 | 传媒 | stock | 方正证券 | SH601901 | 非银金融 | stock | 京运通 | SH601908 | 公用事业 | stock | 浙商银行 | SH601916 | 银行 | stock | 新集能源 | SH601918 | 煤炭 | stock | 中远海控 | SH601919 | 交通运输 | stock | 浙版传媒 | SH601921 | 传媒 | stock | 凤凰传媒 | SH601928 | 传媒 | stock | 吉视传媒 | SH601929 | 传媒 | stock | 永辉超市 | SH601933 | 商贸零售 | stock | 建设银行 | SH601939 | 银行 | 建行 stock | 中国出版 | SH601949 | 传媒 | stock | 苏垦农发 | SH601952 | 农林牧渔 | stock | 东贝集团 | SH601956 | 家用电器 | 东贝 stock | 金钼股份 | SH601958 | 有色金属 | stock | 重庆银行 | SH601963 | 银行 | stock | 中国汽研 | SH601965 | 汽车 | 汽研 stock | 玲珑轮胎 | SH601966 | 汽车 | stock | 宝钢包装 | SH601968 | 轻工制造 | stock | 海南矿业 | SH601969 | 钢铁 | stock | 招商南油 | SH601975 | 交通运输 | stock | 中国核电 | SH601985 | 公用事业 | stock | 中国银行 | SH601988 | 银行 | 中行 stock | 南京证券 | SH601990 | 非银金融 | stock | 大唐发电 | SH601991 | 公用事业 | stock | 金隅集团 | SH601992 | 建筑材料 | 金隅 stock | 中金公司 | SH601995 | 非银金融 | stock | 丰林集团 | SH601996 | 轻工制造 | 丰林 stock | 贵阳银行 | SH601997 | 银行 | stock | 中信银行 | SH601998 | 银行 | stock | 出版传媒 | SH601999 | 传媒 | stock | 人民网 | SH603000 | 传媒 | stock | 奥康国际 | SH603001 | 纺织服饰 | stock | 宏昌电子 | SH603002 | 电子 | stock | 鼎龙科技 | SH603004 | 基础化工 | stock | 晶方科技 | SH603005 | 电子 | stock | 联明股份 | SH603006 | 汽车 | stock | 顺景科技 | SH603007 | 汽车 | stock | ST喜临门 | SH603008 | 轻工制造 | stock | 北特科技 | SH603009 | 汽车 | stock | 万盛股份 | SH603010 | 基础化工 | stock | 合锻智能 | SH603011 | 机械设备 | stock | 创力集团 | SH603012 | 机械设备 | 创力 stock | 亚普股份 | SH603013 | 汽车 | stock | 威高血净 | SH603014 | 医药生物 | stock | 弘讯科技 | SH603015 | 机械设备 | stock | 新宏泰 | SH603016 | 电力设备 | stock | 中衡设计 | SH603017 | 建筑装饰 | stock | 华设集团 | SH603018 | 建筑装饰 | 华设 stock | 中科曙光 | SH603019 | 计算机 | stock | 爱普股份 | SH603020 | 基础化工 | stock | *ST华鹏 | SH603021 | 轻工制造 | stock | 新通联 | SH603022 | 轻工制造 | stock | 威帝股份 | SH603023 | 汽车 | stock | 大豪科技 | SH603025 | 机械设备 | stock | 石大胜华 | SH603026 | 电力设备 | stock | 千禾味业 | SH603027 | 食品饮料 | stock | 赛福天 | SH603028 | 机械设备 | stock | 天鹅股份 | SH603029 | 机械设备 | stock | 全筑股份 | SH603030 | 建筑装饰 | stock | 安孚科技 | SH603031 | 电力设备 | stock | 德新科技 | SH603032 | 电力设备 | stock | 三维股份 | SH603033 | 基础化工 | stock | 常熟汽饰 | SH603035 | 汽车 | stock | 如通股份 | SH603036 | 机械设备 | stock | 凯众股份 | SH603037 | 汽车 | stock | 华立股份 | SH603038 | 建筑材料 | stock | 泛微网络 | SH603039 | 计算机 | stock | 新坐标 | SH603040 | 汽车 | stock | 美思德 | SH603041 | 基础化工 | stock | 华脉科技 | SH603042 | 通信 | stock | 广州酒家 | SH603043 | 食品饮料 | stock | 福达合金 | SH603045 | 有色金属 | stock | 浙江黎明 | SH603048 | 汽车 | stock | 中策橡胶 | SH603049 | 汽车 | stock | 科林电气 | SH603050 | 电力设备 | stock | 鹿山新材 | SH603051 | 基础化工 | stock | 可川科技 | SH603052 | 电子 | stock | 成都燃气 | SH603053 | 公用事业 | stock | 台华新材 | SH603055 | 纺织服饰 | stock | 德邦股份 | SH603056 | 交通运输 | stock | 紫燕食品 | SH603057 | 食品饮料 | stock | 永吉股份 | SH603058 | 轻工制造 | stock | 倍加洁 | SH603059 | 美容护理 | stock | 国检集团 | SH603060 | 社会服务 | 国检 stock | 金海通 | SH603061 | 电子 | stock | 麦加芯彩 | SH603062 | 基础化工 | stock | 禾望电气 | SH603063 | 电力设备 | stock | 宿迁联盛 | SH603065 | 基础化工 | stock | 音飞储存 | SH603066 | 交通运输 | stock | 振华股份 | SH603067 | 基础化工 | stock | 博通集成 | SH603068 | 电子 | stock | 海汽集团 | SH603069 | 交通运输 | 海汽 stock | 万控智造 | SH603070 | 电力设备 | stock | 物产环能 | SH603071 | 公用事业 | stock | 天和磁材 | SH603072 | 有色金属 | stock | 彩蝶实业 | SH603073 | 基础化工 | stock | 热威股份 | SH603075 | 家用电器 | stock | 乐惠国际 | SH603076 | 机械设备 | stock | 和邦生物 | SH603077 | 基础化工 | stock | 江化微 | SH603078 | 电子 | stock | 圣达生物 | SH603079 | 基础化工 | stock | 新疆火炬 | SH603080 | 公用事业 | stock | 大丰实业 | SH603081 | 机械设备 | stock | 北自科技 | SH603082 | 机械设备 | stock | 剑桥科技 | SH603083 | 通信 | stock | 天成自控 | SH603085 | 汽车 | stock | 先达股份 | SH603086 | 基础化工 | stock | 甘李药业 | SH603087 | 医药生物 | stock | 宁波精达 | SH603088 | 机械设备 | stock | 正裕工业 | SH603089 | 汽车 | stock | 宏盛股份 | SH603090 | 机械设备 | stock | 众鑫股份 | SH603091 | 轻工制造 | stock | 德力佳 | SH603092 | 电力设备 | stock | 南华期货 | SH603093 | 非银金融 | stock | 越剑智能 | SH603095 | 机械设备 | stock | 新经典 | SH603096 | 传媒 | stock | 江苏华辰 | SH603097 | 电力设备 | stock | 森特股份 | SH603098 | 建筑装饰 | stock | 长白山 | SH603099 | 社会服务 | stock | 川仪股份 | SH603100 | 机械设备 | stock | 汇嘉时代 | SH603101 | 商贸零售 | stock | 百合股份 | SH603102 | 食品饮料 | stock | 横店影视 | SH603103 | 传媒 | stock | 芯能科技 | SH603105 | 公用事业 | stock | 恒银科技 | SH603106 | 计算机 | stock | 上海汽配 | SH603107 | 汽车 | stock | 润达医疗 | SH603108 | 医药生物 | stock | 神驰机电 | SH603109 | 汽车 | stock | 东方材料 | SH603110 | 基础化工 | stock | 康尼机电 | SH603111 | 机械设备 | stock | 华翔股份 | SH603112 | 家用电器 | stock | 金能科技 | SH603113 | 基础化工 | stock | 海星股份 | SH603115 | 电子 | stock | 红蜻蜓 | SH603116 | 纺织服饰 | stock | 万林物流 | SH603117 | 交通运输 | stock | 共进股份 | SH603118 | 通信 | stock | 浙江荣泰 | SH603119 | 汽车 | stock | 肯特催化 | SH603120 | 基础化工 | stock | 华培动力 | SH603121 | 汽车 | stock | 合富中国 | SH603122 | 医药生物 | 合富 stock | 翠微股份 | SH603123 | 商贸零售 | stock | 江南新材 | SH603124 | 有色金属 | stock | 常青科技 | SH603125 | 基础化工 | stock | 中材节能 | SH603126 | 环保 | stock | 昭衍新药 | SH603127 | 医药生物 | stock | 华贸物流 | SH603128 | 交通运输 | stock | 春风动力 | SH603129 | 汽车 | stock | 云中马 | SH603130 | 纺织服饰 | stock | 上海沪工 | SH603131 | 机械设备 | stock | 金徽股份 | SH603132 | 有色金属 | stock | 中重科技 | SH603135 | 机械设备 | stock | 天目湖 | SH603136 | 社会服务 | stock | 恒尚节能 | SH603137 | 建筑装饰 | stock | 海量数据 | SH603138 | 计算机 | stock | 康惠股份 | SH603139 | 医药生物 | stock | 万朗磁塑 | SH603150 | 基础化工 | stock | 邦基科技 | SH603151 | 农林牧渔 | stock | 上海建科 | SH603153 | 建筑装饰 | stock | 新亚强 | SH603155 | 基础化工 | stock | 养元饮品 | SH603156 | 食品饮料 | stock | 腾龙股份 | SH603158 | 汽车 | stock | 上海亚虹 | SH603159 | 机械设备 | stock | 汇顶科技 | SH603160 | 电子 | stock | 科华控股 | SH603161 | 汽车 | stock | 海通发展 | SH603162 | 交通运输 | stock | 圣晖集成 | SH603163 | 建筑装饰 | stock | 荣晟环保 | SH603165 | 轻工制造 | stock | 福达股份 | SH603166 | 汽车 | stock | 渤海轮渡 | SH603167 | 交通运输 | stock | 莎普爱思 | SH603168 | 医药生物 | stock | 兰石重装 | SH603169 | 机械设备 | stock | 宝立食品 | SH603170 | 食品饮料 | stock | 税友股份 | SH603171 | 计算机 | stock | 万丰股份 | SH603172 | 基础化工 | stock | 福斯达 | SH603173 | 机械设备 | stock | 超颖电子 | SH603175 | 电子 | stock | 汇通集团 | SH603176 | 建筑装饰 | 汇通 stock | 德创环保 | SH603177 | 环保 | stock | 圣龙股份 | SH603178 | 汽车 | stock | 新泉股份 | SH603179 | 汽车 | stock | 金牌家居 | SH603180 | 轻工制造 | stock | 皇马科技 | SH603181 | 基础化工 | stock | 嘉华股份 | SH603182 | 农林牧渔 | stock | 建研院 | SH603183 | 社会服务 | stock | 弘元绿能 | SH603185 | 电力设备 | stock | 华正新材 | SH603186 | 电子 | stock | 海容冷链 | SH603187 | 机械设备 | stock | 亚邦股份 | SH603188 | 基础化工 | stock | *ST网达 | SH603189 | 计算机 | stock | 亚通精工 | SH603190 | 汽车 | stock | 望变电气 | SH603191 | 电力设备 | stock | 汇得科技 | SH603192 | 基础化工 | stock | 润本股份 | SH603193 | 美容护理 | stock | 中力股份 | SH603194 | 机械设备 | stock | 公牛集团 | SH603195 | 轻工制造 | 公牛 stock | 璞源材料 | SH603196 | 纺织服饰 | stock | 保隆科技 | SH603197 | 汽车 | stock | 迎驾贡酒 | SH603198 | 食品饮料 | stock | 九华旅游 | SH603199 | 社会服务 | stock | 上海洗霸 | SH603200 | 环保 | stock | 常润股份 | SH603201 | 汽车 | stock | 天有为 | SH603202 | 汽车 | stock | 快克智能 | SH603203 | 机械设备 | stock | 健尔康 | SH603205 | 医药生物 | stock | 嘉环科技 | SH603206 | 通信 | stock | 小方制药 | SH603207 | 医药生物 | stock | 江山欧派 | SH603208 | 轻工制造 | stock | 兴通股份 | SH603209 | 交通运输 | stock | 泰鸿万立 | SH603210 | 汽车 | stock | 晋拓股份 | SH603211 | 汽车 | stock | 赛伍技术 | SH603212 | 电力设备 | stock | 镇洋发展 | SH603213 | 基础化工 | stock | 爱婴室 | SH603214 | 商贸零售 | stock | 比依股份 | SH603215 | 家用电器 | stock | 梦天家居 | SH603216 | 轻工制造 | stock | 元利科技 | SH603217 | 基础化工 | stock | 日月股份 | SH603218 | 电力设备 | stock | 富佳股份 | SH603219 | 家用电器 | stock | 中贝通信 | SH603220 | 通信 | stock | 爱丽家居 | SH603221 | 轻工制造 | stock | 济民健康 | SH603222 | 医药生物 | stock | 恒通股份 | SH603223 | 石油石化 | stock | 新凤鸣 | SH603225 | 基础化工 | stock | 菲林格尔 | SH603226 | 轻工制造 | stock | 雪峰科技 | SH603227 | 基础化工 | stock | 景旺电子 | SH603228 | 电子 | stock | 奥翔药业 | SH603229 | 医药生物 | stock | 内蒙新华 | SH603230 | 传媒 | stock | 索宝蛋白 | SH603231 | 农林牧渔 | stock | 格尔软件 | SH603232 | 计算机 | stock | 大参林 | SH603233 | 医药生物 | stock | 天新药业 | SH603235 | 基础化工 | stock | 移远通信 | SH603236 | 通信 | stock | 五芳斋 | SH603237 | 食品饮料 | stock | 诺邦股份 | SH603238 | 纺织服饰 | stock | 浙江仙通 | SH603239 | 汽车 | stock | 锡华科技 | SH603248 | 电力设备 | stock | 鼎际得 | SH603255 | 基础化工 | stock | 宏和科技 | SH603256 | 建筑材料 | stock | 中国瑞林 | SH603257 | 建筑装饰 | stock | 电魂网络 | SH603258 | 传媒 | stock | 药明康德 | SH603259 | 医药生物 | stock | 合盛硅业 | SH603260 | 基础化工 | stock | 立航科技 | SH603261 | 国防军工 | stock | 技源集团 | SH603262 | 食品饮料 | stock | 天龙股份 | SH603266 | 汽车 | stock | 鸿远电子 | SH603267 | 国防军工 | stock | 松发股份 | SH603268 | 国防军工 | stock | 海鸥股份 | SH603269 | 机械设备 | stock | 金帝股份 | SH603270 | 机械设备 | stock | 永杰新材 | SH603271 | 有色金属 | stock | *ST联翔 | SH603272 | 轻工制造 | stock | 天元智能 | SH603 -
stock_hk.yaml 129.4 KB
# ============================================================ # 港股个股 # 来源:MongoDB tkrsInfo 集合(type=stock,ticker 以 HK 开头) # 格式:stock | name | ticker | 行业 | short_names # ⚠️ 仅支持行情价格数据(收盘/开盘/涨跌幅等),不支持估值/财务 # ============================================================ stock | 02396.HK | HK2396 | | 华芢生物 stock | 1957 & CO. | HK8495 | 可选消费 | stock | 21世纪教育 | HK1598 | 可选消费 | stock | 361度 | HK1361 | 可选消费 | stock | 5100藏冰川 | HK1115 | 日常消费 | 西藏水资源 stock | AEON CREDIT | HK0900 | 金融 | stock | AI X TECH | HK1490 | 可选消费 | 车市科技 stock | ALCO HOLDINGS | HK0328 | 可选消费 | stock | APOLLO出行 | HK0860 | 可选消费 | stock | ASIA COMM HOLD | HK0104 | 可选消费 | stock | ASMPT | HK0522 | 信息技术 | stock | ATLINKS | HK8043 | 信息技术 | stock | AV CONCEPT HOLD | HK0595 | 信息技术 | stock | AV策划推广 | HK8419 | 可选消费 | stock | BBSB INTL | HK8610 | | stock | BENG SOON MACH | HK1987 | 工业 | stock | BFB HEALTH | HK0205 | 可选消费 | 财讯传媒 stock | BHCC HOLDING | HK1552 | 工业 | stock | BOSS直聘-W | HK2076 | 信息技术 | BOSS直聘 stock | BRILLIANCE CHI | HK1114 | 可选消费 | stock | C-LINK SQ | HK1463 | 工业 | C-LINK stock | CAI控股 | HK0080 | 金融 | 中国新经济投资 stock | CEC INT'L HOLD | HK0759 | 日常消费 | stock | CGII HLDGS | HK1940 | 能源 | stock | CHEVALIER INT'L | HK0025 | 工业 | stock | CHI HO DEV | HK8423 | 工业 | stock | CHINANEWENERGY | HK1156 | 工业 | stock | CMON | HK1792 | 可选消费 | stock | CNT GROUP | HK0701 | 材料 | stock | COOL LINK | HK8491 | 日常消费 | stock | COSMOPOL INT'L | HK0120 | 房地产 | stock | CTR HOLDINGS | HK1416 | 工业 | stock | CWT INT'L | HK0521 | 工业 | stock | DYNAM JAPAN | HK6889 | 可选消费 | stock | ECI TECH | HK8013 | 信息技术 | stock | EDA集团控股 | HK2505 | | stock | EPRINT集团 | HK1884 | 工业 | stock | EPS创健科技 | HK3860 | 可选消费 | stock | F8企业 | HK8347 | 能源 | stock | FIT HON TENG | HK6088 | 信息技术 | stock | FORTIOR | HK1304 | | stock | FSM HOLDINGS | HK1721 | 材料 | stock | FUTURE BRIGHT | HK0703 | 可选消费 | stock | G.A.控股 | HK8126 | 可选消费 | stock | GBA集团 | HK0261 | 房地产 | stock | GC CONSTRUCTION | HK1489 | 材料 | stock | GHW INTL | HK9933 | 材料 | stock | GOLDWAY EDU | HK8160 | 可选消费 | stock | GUANZE MEDICAL | HK2427 | 医疗保健 | stock | GX35美债 | HK3450 | | stock | GX35美债-U | HK9450 | | stock | GXAI基础设施 | HK3401 | | GX AI基础设施 stock | GX中美科技 | HK3402 | | stock | H&H国际控股 | HK1112 | 医疗保健 | stock | HASHKEY HLDGS | HK3887 | | stock | HERALD HOLD | HK0114 | 可选消费 | stock | HKE HOLDINGS | HK1726 | 医疗保健 | stock | HM INTL HLDGS | HK8416 | 工业 | stock | HMVOD视频 | HK8103 | 可选消费 | stock | HOME CONTROL | HK1747 | 可选消费 | stock | HPC HOLDINGS | HK1742 | 工业 | stock | HTSC | HK6886 | 金融 | stock | HYGIEIA GROUP | HK1650 | 工业 | stock | HYPEBEAST | HK0150 | 可选消费 | stock | IBI GROUP HLDGS | HK1547 | 工业 | stock | IDT INT'L | HK0167 | 可选消费 | stock | IFBH | HK6603 | | stock | IGG | HK0799 | 信息技术 | stock | IMAX CHINA | HK1970 | 可选消费 | stock | INTL GENIUS | HK0033 | 可选消费 | stock | ISP GLOBAL | HK8487 | 可选消费 | stock | ITE HOLDINGS | HK8092 | 信息技术 | stock | JBB BUILDERS | HK1903 | 工业 | stock | JS环球生活 | HK1691 | 可选消费 | stock | K CASH集团 | HK2483 | | stock | K W NELSON GP | HK8411 | 工业 | stock | K2 F&B | HK2108 | 可选消费 | stock | KEEP | HK3650 | 信息技术 | stock | KFM金德 | HK3816 | 工业 | stock | KLN | HK0636 | 工业 | stock | LEGION CONSO | HK2129 | 工业 | stock | LFG投资控股 | HK3938 | 金融 | stock | LONG投资集团 | HK2312 | 金融 | 中国金融租赁 stock | MANYCORE TECH | HK0068 | 金融 | 利兴发展 stock | MATRIX HOLDINGS | HK1005 | 可选消费 | stock | METALIGHT | HK2605 | | stock | METROPOLIS CAP | HK8621 | 金融 | stock | MINIMAX-W | HK0100 | | MINIMAX-WP stock | MIRXES-B | HK2629 | | stock | MI能源 | HK1555 | 能源 | stock | MOMENTA-W | HK6880 | 可选消费 | 腾邦控股 stock | MONGOL MINING | HK0975 | 能源 | stock | MOS HOUSE | HK1653 | 可选消费 | stock | MS CONCEPT | HK8447 | 可选消费 | stock | NANYANG HOLD | HK0212 | 房地产 | stock | NATIONAL ELEC H | HK0213 | 信息技术 | stock | NEXION TECH | HK8420 | 信息技术 | stock | NIRAKU | HK1245 | 可选消费 | stock | NIU HOLDINGS | HK8619 | 工业 | stock | OCC CABLES | HK1791 | | stock | OKURA HOLDINGS | HK1655 | 可选消费 | stock | OSL集团 | HK0863 | 信息技术 | stock | PACIFIC LEGEND | HK8547 | 可选消费 | stock | PALADIN | HK0495 | 房地产 | stock | PERFECTECH INTL | HK0765 | 可选消费 | stock | PICO FAR EAST | HK0752 | 可选消费 | stock | PP亚洲美债 | HK3411 | | stock | PP亚洲美债-U | HK9411 | | stock | QPL INT'L | HK0243 | 信息技术 | stock | RAFFLESINTERIOR | HK1376 | 可选消费 | stock | REF HOLDINGS | HK1631 | 工业 | stock | REGAL INT'L | HK0078 | 可选消费 | stock | REPUBLIC HC | HK8357 | 医疗保健 | stock | S.A.S. DRAGON | HK1184 | 信息技术 | stock | SENASIC | HK6675 | | stock | SINCEREWATCH HK | HK0444 | 可选消费 | stock | SINO HOTELS | HK1221 | 可选消费 | stock | SIS INT'L | HK0529 | 信息技术 | stock | SOHO中国 | HK0410 | 房地产 | stock | STERLING GP | HK1825 | 可选消费 | stock | STYLAND HOLD | HK0211 | 金融 | stock | TAI CHEUNG HOLD | HK0088 | 房地产 | stock | TAI PING CARPET | HK0146 | 可选消费 | stock | TATA健康 | HK1255 | 可选消费 | stock | TBKS HLDGS | HK1960 | 工业 | stock | TCL电子 | HK1070 | 可选消费 | TCL stock | TE HEALTHCARE | HK6877 | 金融 | stock | TEAMWAY INTL GP | HK1239 | 材料 | stock | TL NATURAL GAS | HK8536 | 能源 | stock | TOM集团 | HK2383 | 可选消费 | stock | TOPSTANDARDCORP | HK8510 | 可选消费 | stock | TRUE PARTNER | HK8657 | 金融 | stock | TS WONDERS | HK1767 | 日常消费 | stock | TST PROPERTIES | HK0247 | 房地产 | stock | VALA | HK2051 | 金融 | stock | VICON HOLDINGS | HK3878 | 工业 | stock | VITASOY INT'L | HK0345 | 日常消费 | stock | VSING | HK8292 | 工业 | stock | VTECH HOLDINGS | HK0303 | 信息技术 | stock | WAI KEE HOLD | HK0610 | 工业 | stock | WANG ON GROUP | HK1222 | 房地产 | stock | WING ON CO | HK0289 | 可选消费 | stock | WKK INTL (HOLD) | HK0532 | 信息技术 | stock | WT集团 | HK8422 | 工业 | stock | YGM TRADING | HK0375 | 可选消费 | stock | YUSEI | HK0096 | 材料 | stock | Z FIN | HK1168 | 房地产 | stock | 一元宇宙 | HK1616 | 可选消费 | stock | 一木集团 | HK8232 | 可选消费 | CLASSIFIED GP stock | 一脉阳光 | HK2522 | | stock | 七元投资 | HK1660 | 工业 | 兆邦基生活 stock | 七牛智能 | HK2567 | | stock | 万事昌国际 | HK0898 | 房地产 | stock | 万保刚集团 | HK1213 | 信息技术 | stock | 万励达 | HK8482 | 工业 | stock | 万华媒体 | HK0426 | 可选消费 | stock | 万咖壹联 | HK1762 | 信息技术 | stock | 万嘉集团 | HK0401 | 医疗保健 | stock | 万国数据-SW | HK9698 | 信息技术 | 万国数据 stock | 万国黄金集团 | HK3939 | 材料 | stock | 万城控股 | HK2892 | 房地产 | stock | 万宝盛华 | HK2180 | 工业 | stock | 万成环球控股 | HK8309 | 工业 | stock | 万成集团股份 | HK1451 | 工业 | stock | 万景控股 | HK2193 | 工业 | stock | 万洲国际 | HK0288 | 日常消费 | stock | 万物云 | HK2602 | 房地产 | stock | 万科企业 | HK2202 | 房地产 | stock | 万科海外 | HK1036 | 房地产 | stock | 万维智能科技 | HK0209 | 可选消费 | 瀛晟科学,万维智能科技-新 stock | 万裕科技 | HK0894 | 信息技术 | stock | 万达酒店发展 | HK0169 | 可选消费 | stock | 万邦投资 | HK0158 | 房地产 | stock | 万里印刷 | HK8385 | 可选消费 | stock | 万顺瑞强集团 | HK8427 | 工业 | stock | 万顺集团控股 | HK1746 | 工业 | stock | 万马控股 | HK6928 | 可选消费 | stock | 三一国际 | HK0631 | 工业 | stock | 三一重工 | HK6031 | | stock | 三叶草生物-B | HK2197 | 医疗保健 | 三叶草生物 stock | 三和建筑集团 | HK3822 | 工业 | stock | 三和精化 | HK0301 | 材料 | stock | 三宝科技 | HK1708 | 信息技术 | stock | 三巽集团 | HK6611 | 房地产 | stock | 三爱健康集团 | HK1889 | 医疗保健 | stock | 三环集团 | HK6951 | | stock | 三生制药 | HK1530 | 医疗保健 | stock | 三花智控 | HK2050 | | stock | 上善黄金 | HK1939 | 可选消费 | stock | 上实城市开发 | HK0563 | 房地产 | stock | 上海先锋控股 | HK1345 | 医疗保健 | stock | 上海医药 | HK2607 | 医疗保健 | stock | 上海复旦 | HK1385 | 信息技术 | stock | 上海实业控股 | HK0363 | 工业 | stock | 上海实业环境 | HK0807 | 工业 | stock | 上海小南国 | HK3666 | 可选消费 | stock | 上海电气 | HK2727 | 工业 | stock | 上海石油化工股份 | HK0338 | 能源 | stock | 上美股份 | HK2145 | 日常消费 | stock | 上谕集团 | HK1633 | 工业 | stock | 不同集团 | HK6090 | 房地产 | stock | 专业旅运 | HK1235 | 可选消费 | stock | 世大控股 | HK8003 | 可选消费 | stock | 世界(集团) | HK0713 | 日常消费 | stock | 世界华文媒体 | HK0685 | 可选消费 | stock | 世界数字经济产业 | HK3708 | 工业 | 中国供应链产业 stock | 世纪城市国际 | HK0355 | 可选消费 | stock | 世纪娱乐国际 | HK0959 | 可选消费 | stock | 世纪建业 | HK0079 | 可选消费 | stock | 世纪金花 | HK0162 | 可选消费 | stock | 世纪阳光 | HK0509 | 材料 | stock | 世纪集团国际 | HK2113 | 工业 | stock | 世茂服务 | HK0873 | 房地产 | stock | 世茂集团 | HK0813 | 房地产 | stock | 丘钛科技 | HK1478 | 可选消费 | stock | 业聚医疗 | HK6929 | 医疗保健 | stock | 东亚银行 | HK0023 | 金融 | stock | 东光化工 | HK1702 | 材料 | stock | 东北电气 | HK0042 | 工业 | stock | 东原仁知服务 | HK2352 | 房地产 | stock | 东吴水泥 | HK0695 | 材料 | stock | 东岳集团 | HK0189 | 材料 | stock | 东建国际 | HK0329 | 金融 | stock | 东方传媒集团 | HK0018 | 可选消费 | 东方企控集团 stock | 东方兴业控股 | HK0430 | 金融 | stock | 东方大学城控股 | HK8067 | 可选消费 | stock | 东方支付集团控股 | HK8613 | 金融 | stock | 东方海外国际 | HK0316 | 工业 | stock | 东方甄选 | HK1797 | 可选消费 | stock | 东方电气 | HK1072 | 工业 | stock | 东方科脉 | HK1770 | | stock | 东方表行集团 | HK0398 | 可选消费 | stock | 东方证券 | HK3958 | 金融 | stock | 东曜药业-B | HK1875 | 医疗保健 | 东曜药业 stock | 东江环保 | HK0895 | 工业 | stock | 东江集团控股 | HK2283 | 工业 | stock | 东瀛游 | HK6882 | 可选消费 | stock | 东瑞制药 | HK2348 | 医疗保健 | stock | 东莞农商银行 | HK9889 | 金融 | stock | 东软睿新集团 | HK9616 | 可选消费 | stock | 东阳光药 | HK6887 | | stock | 东鹏饮料 | HK9980 | | stock | 中亚烯谷集团 | HK0063 | 房地产 | stock | 中伟新材 | HK2579 | | stock | 中信国际电讯 | HK1883 | 电信服务 | stock | 中信建投证券 | HK6066 | 金融 | stock | 中信股份 | HK0267 | 工业 | stock | 中信证券 | HK6030 | 金融 | stock | 中信资源 | HK1205 | 材料 | stock | 中信金融资产 | HK2799 | 金融 | stock | 中信银行 | HK0998 | 金融 | stock | 中关村科技租赁 | HK1601 | 金融 | stock | 中兴通讯 | HK0763 | 信息技术 | stock | 中创新航 | HK3931 | 工业 | stock | 中创智领 | HK0564 | 工业 | stock | 中加国信 | HK0899 | 房地产 | stock | 中化化肥 | HK0297 | 材料 | stock | 中升控股 | HK0881 | 可选消费 | stock | 中华国际 | HK1064 | 房地产 | stock | 中华汽车 | HK0026 | 房地产 | stock | 中华燃气 | HK8246 | 能源 | stock | 中华节能 | HK8128 | 工业 | 中国恒有源集团 stock | 中华银科技 | HK0515 | 信息技术 | stock | 中原建业 | HK9982 | 房地产 | stock | 中原银行 | HK1216 | 金融 | stock | 中发展控股 | HK0475 | 公用事业 | stock | 中国万天控股 | HK1854 | 日常消费 | stock | 中国万桐园 | HK6966 | 可选消费 | stock | 中国三三集团 | HK8087 | 可选消费 | 中国三三传媒 stock | 中国三江化工 | HK2198 | 材料 | stock | 中国三迪 | HK0910 | 房地产 | stock | 中国上城 | HK2330 | 房地产 | stock | 中国东方教育 | HK0667 | 可选消费 | stock | 中国东方航空股份 | HK0670 | 工业 | stock | 中国东方集团 | HK0581 | 材料 | stock | 中国中免 | HK1880 | 可选消费 | stock | 中国中冶 | HK1618 | 工业 | stock | 中国中药 | HK0570 | 医疗保健 | stock | 中国中车 | HK1766 | 工业 | stock | 中国中铁 | HK0390 | 工业 | stock | 中国丰汇 | HK0727 | 医疗保健 | 皇冠环球集团 stock | 中国交通建设 | HK1800 | 工业 | stock | 中国人寿 | HK2628 | 金融 | stock | 中国人民保险集团 | HK1339 | 金融 | stock | 中国信息科技 | HK8178 | 信息技术 | stock | 中国信达 | HK1359 | 金融 | stock | 中国健康科技集团 | HK1069 | 材料 | stock | 中国储能科技发展 | HK1143 | 信息技术 | stock | 中国儒意 | HK0136 | 可选消费 | stock | 中国光大控股 | HK0165 | 金融 | stock | 中国光大水务 | HK1857 | 工业 | stock | 中国光大绿色环保 | HK1257 | 公用事业 | stock | 中国光大银行 | HK6818 | 金融 | stock | 中国再保险 | HK1508 | 金融 | stock | 中国再生医学 | HK8158 | 医疗保健 | stock | 中国再生能源投资 | HK0987 | 公用事业 | stock | 中国农产品交易 | HK0149 | 房地产 | stock | 中国创意控股 | HK8368 | 可选消费 | stock | 中国创新投资 | HK1217 | 金融 | stock | 中国利郎 | HK1234 | 可选消费 | stock | 中国前沿科技集团 | HK1661 | 可选消费 | stock | 中国动向 | HK3818 | 可选消费 | stock | 中国北大荒 | HK0039 | 日常消费 | stock | 中国医疗集团 | HK8225 | 医疗保健 | stock | 中国华君 | HK0377 | 材料 | stock | 中国华星 | HK0485 | 房地产 | stock | 中国南方航空股份 | HK1055 | 工业 | stock | 中国卫生集团 | HK0673 | 医疗保健 | stock | 中国口腔产业 | HK8406 | 可选消费 | stock | 中国同辐 | HK1763 | 工业 | stock | 中国唐商 | HK0674 | 房地产 | stock | 中国国家文化产业 | HK0745 | 可选消费 | 中国国家-二千 stock | 中国国航 | HK0753 | 工业 | stock | 中国圣牧-PF | HK1432 | 日常消费 | 中国圣牧 stock | 中国基础能源 | HK8117 | 公用事业 | stock | 中国外运 | HK0598 | 工业 | stock | 中国大冶有色金属 | HK0661 | 材料 | stock | 中国天保集团 | HK1427 | 房地产 | stock | 中国天化工 | HK0362 | 材料 | stock | 中国天弓控股 | HK0428 | 金融 | stock | 中国天瑞水泥 | HK1252 | 材料 | stock | 中国太保 | HK2601 | 金融 | stock | 中国太平 | HK0966 | 金融 | stock | 中国奥园 | HK3883 | 房地产 | stock | 中国宇天 | HK8230 | | stock | 中国安储能源 | HK2399 | 可选消费 | stock | 中国宏光 | HK8646 | 可选消费 | stock | 中国宏桥 | HK1378 | 材料 | stock | 中国宝力科技 | HK0164 | 可选消费 | stock | 中国平安 | HK2318 | 金融 | stock | 中国平安-R | HK82318 | 金融 | 中国平安 stock | 中国建材 | HK3323 | 材料 | stock | 中国建筑兴业 | HK0830 | 工业 | stock | 中国建筑国际 | HK3311 | 工业 | stock | 中国心连心化肥 | HK1866 | 材料 | stock | 中国恒天立信国际 | HK0641 | 工业 | stock | 中国技术集团 | HK1725 | 信息技术 | 洲际航天科技 stock | 中国投融资 | HK1226 | 金融 | stock | 中国抗体-B | HK3681 | 医疗保健 | 中国抗体 stock | 中国数字视频 | HK8280 | 可选消费 | stock | 中国数智科技 | HK1796 | 可选消费 | stock | 中国文旅农业 | HK0542 | 可选消费 | stock | 中国新华教育 | HK2779 | 可选消费 | stock | 中国新城市 | HK1321 | 房地产 | stock | 中国新城镇 | HK1278 | 房地产 | stock | 中国新消费集团 | HK8275 | 工业 | stock | 中国新零售供应链 | HK3928 | 工业 | stock | 中国旭阳集团 | HK1907 | 能源 | stock | 中国旺旺 | HK0151 | 日常消费 | stock | 中国星集团 | HK0326 | 可选消费 | stock | 中国春来 | HK1969 | 可选消费 | stock | 中国智慧能源 | HK1004 | 公用事业 | stock | 中国智能交通 | HK1900 | 信息技术 | stock | 中国智能健康 | HK0348 | 日常消费 | stock | 中国智能基础设施 | HK2349 | 房地产 | 中国城市基础设施 stock | 中国智能科技 | HK0464 | 可选消费 | stock | 中国有色矿业 | HK1258 | 材料 | stock | 中国来骑哦 | HK8039 | 工业 | stock | 中国核能科技 | HK0611 | 公用事业 | stock | 中国民航信息网络 | HK0696 | 信息技术 | stock | 中国水业集团 | HK1129 | 公用事业 | stock | 中国水务 | HK0855 | 公用事业 | stock | 中国汇融 | HK1290 | 金融 | stock | 中国汽车内饰 | HK0048 | 可选消费 | stock | 中国油气控股 | HK0702 | 能源 | stock | 中国波顿 | HK3318 | 日常消费 | stock | 中国派对文化 | HK1532 | 可选消费 | stock | 中国海外发展 | HK0688 | 房地产 | stock | 中国海外宏洋集团 | HK0081 | 房地产 | stock | 中国海洋发展 | HK8047 | 工业 | stock | 中国海洋石油 | HK0883 | 能源 | stock | 中国海洋石油-R | HK80883 | 能源 | 中国海洋石油 stock | 中国淀粉 | HK3838 | 日常消费 | stock | 中国港能 | HK0931 | 能源 | stock | 中国煤层气 | HK8270 | 能源 | stock | 中国燃气 | HK0384 | 公用事业 | stock | 中国环保科技 | HK0646 | 公用事业 | stock | 中国环境资源 | HK1130 | 可选消费 | stock | 中国玻璃 | HK3300 | 工业 | stock | 中国生命集团 | HK8296 | 可选消费 | stock | 中国生态旅游 | HK1371 | 可选消费 | stock | 中国生物制药 | HK1177 | 医疗保健 | stock | 中国生物科技服务 | HK8037 | 医疗保健 | stock | 中国电信 | HK0728 | 电信服务 | stock | 中国电力 | HK2380 | 公用事业 | stock | 中国疏浚环保 | HK0871 | 工业 | stock | 中国白银集团 | HK0815 | 材料 | stock | 中国石墨 | HK2237 | 材料 | stock | 中国石油化工股份 | HK0386 | 能源 | stock | 中国石油股份 | HK0857 | 能源 | stock | 中国碳中和 | HK1372 | 可选消费 | stock | 中国神华 | HK1088 | 能源 | stock | 中国科培 | HK1890 | 可选消费 | stock | 中国科技产业集团 | HK8111 | 公用事业 | stock | 中国科教产业 | HK1756 | 可选消费 | stock | 中国秦发 | HK0866 | 能源 | stock | 中国移动 | HK0941 | 电信服务 | stock | 中国移动-R | HK80941 | 电信服务 | 中国移动 stock | 中国稀土 | HK0769 | 材料 | stock | 中国管业 | HK0380 | 材料 | stock | 中国红包 | HK8316 | 工业 | stock | 中国织材控股 | HK3778 | 可选消费 | stock | 中国绿岛科技 | HK2023 | 日常消费 | stock | 中国置业投资 | HK0736 | 金融 | stock | 中国联塑 | HK2128 | 工业 | stock | 中国联通 | HK0762 | 电信服务 | stock | 中国育儿网络 | HK1736 | 信息技术 | stock | 中国能源建设 | HK3996 | 工业 | stock | 中国船舶租赁 | HK3877 | 工业 | stock | 中国艺术金融 | HK1572 | 金融 | stock | 中国蜀塔 | HK8623 | 工业 | stock | 中国诚通发展集团 | HK0217 | 材料 | stock | 中国财险 | HK2328 | 金融 | stock | 中国资源交通 | HK0269 | 工业 | stock | 中国软件国际 | HK0354 | 信息技术 | stock | 中国通信服务 | HK0552 | 电信服务 | stock | 中国通号 | HK3969 | 工业 | stock | 中国通商集团 | HK1719 | 工业 | stock | 中国通才教育 | HK2175 | 可选消费 | stock | 中国重汽 | HK3808 | 工业 | stock | 中国金典集团 | HK8281 | 日常消费 | stock | 中国金属利用 | HK1636 | 材料 | stock | 中国金石 | HK1380 | 材料 | stock | 中国金茂 | HK0817 | 房地产 | stock | 中国金融发展 | HK3623 | 金融 | stock | 中国金融国际 | HK0721 | 金融 | stock | 中国金融投资管理 | HK0605 | 金融 | stock | 中国铁塔 | HK0788 | 电信服务 | stock | 中国铁建 | HK1186 | 工业 | stock | 中国铁钛 | HK0893 | 材料 | stock | 中国铝业 | HK2600 | 材料 | stock | 中国铝罐 | HK6898 | 材料 | stock | 中国银河 | HK6881 | 金融 | stock | 中国银行 | HK3988 | 金融 | stock | 中国铸晨81 | HK0810 | 金融 | stock | 中国长白山国际 | HK0989 | 房地产 | stock | 中国长远 | HK0110 | 信息技术 | stock | 中国顺客隆 | HK0974 | 日常消费 | stock | 中国飞机租赁 | HK1848 | 工业 | stock | 中国飞鹤 | HK6186 | 日常消费 | stock | 中国食品 | HK0506 | 日常消费 | stock | 中国高精密 | HK0591 | 信息技术 | stock | 中国高速传动 | HK0658 | 工业 | stock | 中国鹏飞集团 | HK3348 | 工业 | stock | 中国黄金国际 | HK2099 | 材料 | stock | 中国龙天集团 | HK1863 | 材料 | stock | 中国龙工 | HK3339 | 工业 | stock | 中基长寿科学 | HK0767 | 金融 | stock | 中天宏信 | HK0994 | 公用事业 | 中天顺联 stock | 中天湖南集团 | HK2433 | 房地产 | stock | 中奥到家 | HK1538 | 房地产 | stock | 中安控股集团 | HK8462 | 工业 | stock | 中宝新材 | HK2439 | 材料 | stock | 中州证券 | HK1375 | 金融 | stock | 中广核新能源 | HK1811 | 公用事业 | stock | 中广核电力 | HK1816 | 公用事业 | stock | 中广核矿业 | HK1164 | 材料 | stock | 中庆股份 | HK1855 | 工业 | stock | 中康科技控股 | HK2361 | 信息技术 | 中康控股 stock | 中建富通 | HK0138 | 可选消费 | stock | 中彩网通控股 | HK8071 | 信息技术 | stock | 中微智码 | HK8041 | 可选消费 | stock | 中慧生物-B | HK2627 | | stock | 中手游 | HK0302 | 信息技术 | stock | 中投金融科技集团 | HK8029 | 金融 | 帝国金融集团 stock | 中播数据 | HK0471 | 可选消费 | stock | 中教控股 | HK0839 | 可选消费 | stock | 中昌国际控股 | HK0859 | 房地产 | stock | 中星集团控股 | HK0055 | 工业 | stock | 中显智能齐家控股 | HK8395 | 可选消费 | stock | 中智药业 | HK3737 | 医疗保健 | stock | 中木国际 | HK1822 | 材料 | stock | 中核国际 | HK2302 | 信息技术 | stock | 中梁控股 | HK2772 | 房地产 | stock | 中民控股 | HK0681 | 能源 | stock | 中汇集团 | HK0382 | 可选消费 | stock | 中油洁能控股 | HK1759 | 公用事业 | stock | 中油燃气 | HK0603 | 公用事业 | stock | 中泰期货 | HK1461 | 金融 | stock | 中泽丰 | HK1282 | 工业 | stock | 中海油田服务 | HK2883 | 能源 | stock | 中海物业 | HK2669 | 房地产 | stock | 中海石油化学 | HK3983 | 材料 | stock | 中深建业 | HK2503 | | stock | 中渝置地 | HK1224 | 房地产 | stock | 中港照相 | HK1123 | 可选消费 | stock | 中港石油 | HK0632 | 能源 | stock | 中滔环保(前) | HK01162!AB | | stock | 中漆集团 | HK1932 | 材料 | stock | 中烟香港 | HK6055 | 日常消费 | stock | 中煤能源 | HK1898 | 能源 | stock | 中环新能源 | HK1735 | 工业 | stock | 中生北控生物科技 | HK8247 | 医疗保健 | stock | 中生联合 | HK3332 | 日常消费 | stock | 中电光谷 | HK0798 | 房地产 | stock | 中电华大科技 | HK0085 | 信息技术 | stock | 中电控股 | HK0002 | 公用事业 | stock | 中盈国际 | HK8516 | 工业 | 广骏集团控股 stock | 中盈盛达融资担保 | HK1543 | 金融 | stock | 中石化冠德 | HK0934 | 能源 | stock | 中石化油服 | HK1033 | 能源 | stock | 中石化炼化工程 | HK2386 | 能源 | stock | 中科生物 | HK1237 | 可选消费 | stock | 中科闻歌 | HK1956 | | stock | 中科集团控股 | HK3321 | 可选消费 | 伟鸿集团控股 stock | 中策资本控股 | HK0235 | 材料 | stock | 中粮家佳康 | HK1610 | 日常消费 | stock | 中细软科技 | HK1020 | 信息技术 | 赛伯乐国际控股 stock | 中联发展控股 | HK0264 | 可选消费 | stock | 中联重科 | HK1157 | 工业 | stock | 中聚投资 | HK1959 | 可选消费 | 世纪联合控股 stock | 中能控股 | HK0228 | 能源 | stock | 中航科工 | HK2357 | 工业 | stock | 中船防务 | HK0317 | 工业 | stock | 中芯国际 | HK0981 | 信息技术 | stock | 中薇金融 | HK0245 | 金融 | stock | 中裕能源 | HK3633 | 公用事业 | stock | 中证国际 | HK0943 | 可选消费 | stock | 中资国际控股 | HK8118 | 可选消费 | 濠亮环球 stock | 中赣通信 | HK2545 | | stock | 中远海发 | HK2866 | 工业 | stock | 中远海控 | HK1919 | 工业 | stock | 中远海能 | HK1138 | 工业 | stock | 中远海运国际 | HK0517 | 工业 | stock | 中远海运港口 | HK1199 | 工业 | stock | 中通快递-W | HK2057 | 工业 | 中通快递 stock | 中金公司 | HK3908 | 金融 | stock | 中铝国际 | HK2068 | 工业 | stock | 中银航空租赁 | HK2588 | 工业 | stock | 中银香港 | HK2388 | 金融 | stock | 中银香港-R | HK82388 | 金融 | 中银香港 stock | 中际旭创 | HK3308 | 可选消费 | 金鹰商贸集团 stock | 中集安瑞科 | HK3899 | 能源 | stock | 中集集团 | HK2039 | 工业 | stock | 中食民安 | HK8283 | 工业 | stock | 中骏商管 | HK0606 | 房地产 | stock | 中骏集团控股 | HK1966 | 房地产 | stock | 丰城控股 | HK2295 | 工业 | stock | 丰展控股 | HK1826 | 工业 | stock | 丰德丽控股 | HK0571 | 可选消费 | stock | 丰盛控股 | HK0607 | 公用事业 | stock | 丰盛生活服务 | HK0331 | 工业 | stock | 丰银禾控股 | HK8030 | 房地产 | 丰银禾控股-一千 stock | 丹诺医药-B | HK6872 | | stock | 丽丰控股 | HK1125 | 房地产 | stock | 丽年国际 | HK9918 | 信息技术 | stock | 丽新发展 | HK0488 | 房地产 | stock | 丽新国际 | HK0191 | 房地产 | stock | 丽珠医药 | HK1513 | 医疗保健 | stock | 久久王 | HK1927 | 日常消费 | stock | 久泰邦达能源 | HK2798 | 能源 | stock | 久融控股 | HK2358 | 可选消费 | stock | 义合控股 | HK1662 | 工业 | stock | 乐享集团 | HK6988 | 信息技术 | stock | 乐动机器人 | HK1236 | | stock | 乐华娱乐 | HK2306 | 可选消费 | stock | 乐思集团 | HK2540 | | stock | 乐摩科技 | HK2539 | | stock | 乐普生物 | HK2157 | 医疗保健 | 乐普生物,乐普生物-B stock | 乐欣户外 | HK2720 | | stock | 乐氏国际控股 | HK1529 | 工业 | stock | 乐舒适 | HK2698 | 可选消费 | 魏桥纺织 stock | 乐舱控股 | HK2490 | 工业 | 乐舱物流 stock | 乙德投资控股 | HK6182 | 材料 | stock | 九兴控股 | HK1836 | 可选消费 | stock | 九方智投控股 | HK9636 | 信息技术 | stock | 九毛九 | HK9922 | 可选消费 | stock | 九江银行 | HK6190 | 金融 | stock | 九泰数智 | HK6918 | 可选消费 | 奇士达 stock | 九源基因 | HK2566 | | stock | 九福来 | HK8611 | 信息技术 | stock | 九龙仓置业 | HK1997 | 房地产 | stock | 九龙仓集团 | HK0004 | 房地产 | stock | 九龙建业 | HK0034 | 房地产 | stock | 云南建投混凝土 | HK1847 | 材料 | stock | 云南水务 | HK6839 | 公用事业 | stock | 云工场 | HK2512 | | stock | 云康集团 | HK2325 | 医疗保健 | stock | 云想科技 | HK2131 | 信息技术 | stock | 云晶数智 | HK8147 | 信息技术 | 汇思太平洋 stock | 云智汇科技 | HK1037 | 信息技术 | stock | 云游控股 | HK0484 | 信息技术 | stock | 云白国际 | HK0030 | 工业 | stock | 云知声 | HK9678 | | stock | 云能国际 | HK1298 | 医疗保健 | stock | 云英谷科技 | HK3310 | | stock | 云迹 | HK2670 | | stock | 云锋金融 | HK0376 | 金融 | stock | 云顶新耀 | HK1952 | 医疗保健 | 云顶新耀 stock | 互太纺织 | HK1382 | 可选消费 | stock | 五一视界 | HK6651 | | stock | 五矿资源 | HK1208 | 材料 | stock | 五菱汽车 | HK0305 | 可选消费 | stock | 五谷磨房 | HK1837 | 日常消费 | stock | 亚东集团 | HK1795 | 可选消费 | stock | 亚伦国际 | HK0684 | 可选消费 | stock | 亚信科技 | HK1675 | 信息技术 | stock | 亚势备份 | HK8290 | 信息技术 | stock | 亚博科技控股 | HK8279 | 可选消费 | stock | 亚太卫星 | HK1045 | 电信服务 | stock | 亚太资源 | HK1104 | 材料 | stock | 亚太金融投资 | HK8193 | 金融 | stock | 亚洲先锋娱乐 | HK8400 | 可选消费 | stock | 亚洲实业集团 | HK1737 | 工业 | stock | 亚洲富思 | HK8413 | 日常消费 | stock | 亚洲果业 | HK0073 | 日常消费 | stock | 亚洲水泥(中国) | HK0743 | 材料 | stock | 亚洲电视控股 | HK0707 | 可选消费 | stock | 亚洲策略科技 | HK1027 | 可选消费 | 中国集成控股,亚洲策略(一千) stock | 亚洲联合基建控股 | HK0711 | 工业 | stock | 亚洲联网科技 | HK0679 | 工业 | stock | 亚洲能源物流 | HK0351 | 工业 | stock | 亚洲速运 | HK8620 | 工业 | stock | 亚洲金融 | HK0662 | 金融 | stock | 亚盛医药 | HK6855 | 医疗保健 | 亚盛医药,亚盛医药-B stock | 亚积邦租赁 | HK1496 | 工业 | stock | 亚证地产 | HK0271 | 房地产 | stock | 交个朋友控股 | HK1450 | 信息技术 | stock | 交大慧谷 | HK8205 | 信息技术 | stock | 交运燃气 | HK1407 | 能源 | stock | 交通银行 | HK3328 | 金融 | stock | 交银国际 | HK3329 | 金融 | stock | 亦辰集团 | HK8365 | 金融 | stock | 亨利加集团 | HK3638 | 信息技术 | stock | 亨得利 | HK3389 | 可选消费 | stock | 亨泰 | HK0197 | 日常消费 | stock | 亨鑫科技 | HK1085 | 工业 | stock | 京东健康 | HK6618 | 医疗保健 | stock | 京东健康-R | HK86618 | 可选消费 | 京东健康 stock | 京东工业 | HK7618 | | stock | 京东方精电 | HK0710 | 信息技术 | stock | 京东物流 | HK2618 | 工业 | stock | 京东集团-SW | HK9618 | 可选消费 | 京东集团 stock | 京东集团-SWR | HK89618 | 可选消费 | 京东集团 stock | 京信通信 | HK2342 | 信息技术 | stock | 京城佳业 | HK2210 | 工业 | stock | 京城机电股份 | HK0187 | 工业 | stock | 京投交通科技 | HK1522 | 信息技术 | stock | 京玖康疗 | HK0648 | 医疗保健 | 京玖康疗,京玖康疗-新 stock | 京维集团 | HK1195 | 房地产 | stock | 京能清洁能源 | HK0579 | 公用事业 | stock | 京西国际 | HK2339 | 可选消费 | stock | 亮晴控股 | HK8603 | 医疗保健 | stock | 亮晴控股 | HK3774 | | stock | 亲亲食品 | HK1583 | 日常消费 | stock | 人和科技 | HK8140 | 材料 | stock | 人瑞人才 | HK6919 | 工业 | stock | 亿仕登控股 | HK1656 | 工业 | stock | 亿华通 | HK2402 | 工业 | stock | 亿和控股 | HK0838 | 工业 | stock | 亿胜生物科技 | HK1061 | 医疗保健 | stock | 亿腾嘉和 | HK6998 | 医疗保健 | 嘉和生物,嘉和生物-B stock | 亿达中国 | HK3639 | 房地产 | stock | 亿都(国际控股) | HK0259 | 可选消费 | stock | 仁恒实业控股 | HK3628 | 工业 | stock | 今海医疗科技 | HK2225 | 工业 | stock | 今米房集团 | HK8300 | 可选消费 | stock | 从玉智农 | HK0875 | 日常消费 | stock | 仙工智能 | HK6106 | | stock | 企展控股 | HK1808 | 信息技术 | stock | 伊登软件 | HK1147 | 信息技术 | stock | 众安在线 | HK6060 | 金融 | stock | 众安智慧生活 | HK2271 | 房地产 | stock | 众安集团 | HK0672 | 房地产 | stock | 众淼控股 | HK1471 | | stock | 众诚能源 | HK2337 | 能源 | stock | 优乐赛共享 | HK2649 | | stock | 优博控股 | HK8529 | | stock | 优品360 | HK2360 | 可选消费 | stock | 优地机器人 | HK3231 | | stock | 优必选 | HK9880 | | stock | 优然牧业 | HK9858 | 日常消费 | stock | 优矩控股 | HK1948 | 信息技术 | stock | 优越集团控股 | HK1841 | 工业 | stock | 优趣汇控股 | HK2177 | 可选消费 | stock | 伟业控股 | HK1570 | 房地产 | stock | 伟仕佳杰 | HK0856 | 信息技术 | stock | 伟俊集团控股 | HK1013 | 信息技术 | stock | 伟工控股 | HK1793 | 可选消费 | stock | 伟志控股 | HK1305 | 信息技术 | stock | 伟源控股 | HK1343 | 工业 | stock | 伟禄科技股份 | HK1196 | 工业 | 伟禄集团 stock | 伟立控股 | HK2372 | 材料 | stock | 伟能集团 | HK1608 | 工业 | stock | 传承教育集团 | HK8195 | 可选消费 | stock | 传递娱乐 | HK1326 | 可选消费 | stock | 位元堂 | HK0897 | 医疗保健 | stock | 佐丹奴国际 | HK0709 | 可选消费 | stock | 佐力科创 | HK6866 | 金融 | 佐力小贷 stock | 佑驾创新 | HK2431 | | stock | 佛朗斯股份 | HK2499 | | stock | 佰悦集团 | HK8545 | 可选消费 | stock | 佰泽医疗 | HK2609 | | stock | 佰达国际控股 | HK1949 | 信息技术 | stock | 佰金生命科学 | HK1466 | 可选消费 | stock | 佳兆业健康 | HK0876 | 医疗保健 | stock | 佳兆业美好 | HK2168 | 房地产 | stock | 佳兆业资本 | HK0936 | 工业 | stock | 佳兆业集团 | HK1638 | 房地产 | stock | 佳华百货控股 | HK0602 | 日常消费 | stock | 佳宁娜 | HK0126 | 可选消费 | stock | 佳明集团控股 | HK1271 | 工业 | stock | 佳源服务 | HK1153 | 可选消费 | stock | 佳辰控股 | HK1937 | 工业 | stock | 佳鑫国际资源 | HK3858 | | stock | 依波路 | HK1856 | 可选消费 | stock | 侨洋国际控股 | HK8070 | 信息技术 | stock | 侨雄国际(旧) | HK2938 | | stock | 俄铝 | HK0486 | 材料 | stock | 俊盟国际 | HK8062 | 信息技术 | stock | 俊知集团 | HK1300 | 信息技术 | stock | 保利物业 | HK6049 | 房地产 | stock | 保利置业集团 | HK0119 | 房地产 | stock | 保宝龙科技 | HK1861 | 可选消费 | stock | 保德国际发展 | HK0372 | 金融 | stock | 保诚 | HK2378 | 金融 | stock | 保集健康 | HK1246 | 房地产 | stock | 信义储电 | HK8328 | 公用事业 | stock | 信义光能 | HK0968 | 信息技术 | stock | 信义玻璃 | HK0868 | 可选消费 | stock | 信义能源 | HK3868 | 公用事业 | stock | 信佳国际 | HK0912 | 可选消费 | stock | 信保环球控股 | HK0723 | 材料 | stock | 信利国际 | HK0732 | 信息技术 | stock | 信和置业 | HK0083 | 房地产 | stock | 信基沙溪 | HK3603 | 房地产 | stock | 信德集团 | HK0242 | 工业 | stock | 信恳智能 | HK1967 | 信息技术 | stock | 信控国际资本 | HK0993 | 金融 | stock | 信星集团 | HK1170 | 可选消费 | stock | 信源企业集团 | HK1748 | 工业 | stock | 信能低碳 | HK0145 | 工业 | stock | 信越控股 | HK6038 | 工业 | stock | 信达国际控股 | HK0111 | 金融 | stock | 信达生物 | HK1801 | 医疗保健 | stock | 信邦控股 | HK1571 | 可选消费 | stock | 信铭生命科技 | HK0474 | 工业 | stock | 修身堂 | HK8200 | 可选消费 | stock | 倍升教育科技 | HK8269 | 工业 | 富誉控股 stock | 倍搏集团 | HK8331 | 材料 | stock | 倢冠控股 | HK8606 | 信息技术 | stock | 倩碧控股 | HK8367 | 可选消费 | stock | 健世科技-B | HK9877 | 医疗保健 | 健世科技 stock | 健倍苗苗 | HK2161 | 医疗保健 | stock | 健康160 | HK2656 | | stock | 健康之路 | HK2587 | | stock | 傅里叶 | HK3625 | | stock | 傲基股份 | HK2519 | | stock | 傲迪玛汽车 | HK8418 | 工业 | stock | 元亨燃气 | HK0332 | 能源 | stock | 元力控股 | HK1933 | 信息技术 | stock | 元征科技 | HK2488 | 可选消费 | stock | 元续科技 | HK8637 | | stock | 兆威机电 | HK2692 | | stock | 兆易创新 | HK3986 | | stock | 兆科眼科-B | HK6622 | 医疗保健 | 兆科眼科 stock | 先丰服务集团 | HK0500 | 工业 | stock | 先健科技 | HK1302 | 医疗保健 | stock | 先声药业 | HK2096 | 医疗保健 | stock | 先导智能 | HK0470 | | stock | 先施 | HK0244 | 可选消费 | stock | 先机企业集团 | HK0176 | 可选消费 | stock | 先瑞达医疗-B | HK6669 | 医疗保健 | 先瑞达医疗 stock | 光丽科技 | HK6036 | 可选消费 | stock | 光大永年 | HK3699 | 房地产 | stock | 光大环境 | HK0257 | 工业 | stock | 光大证券 | HK6178 | 金融 | stock | 光正教育 | HK6068 | 可选消费 | stock | 光荣控股 | HK9998 | 工业 | stock | 兑吧 | HK1753 | 可选消费 | stock | 兖煤澳大利亚 | HK3668 | 能源 | stock | 兖矿能源 | HK1171 | 能源 | stock | 全球数字控股 | HK0339 | 金融 | 中国科创产业投资 stock | 全达电器集团控股 | HK1750 | 工业 | stock | 八马茶业 | HK6980 | | stock | 六福集团 | HK0590 | 可选消费 | stock | 兰谷股份 | HK8072 | 金融 | 罗马元宇宙集团 stock | 共生智筑 | HK0627 | 房地产 | 日本共生 stock | 兴业合金 | HK0505 | 材料 | stock | 兴业控股 | HK0132 | 工业 | stock | 兴业物联 | HK9916 | 房地产 | stock | 兴利(香港)控股 | HK0396 | 可选消费 | stock | 兴发铝业 | HK0098 | 材料 | stock | 兴合控股 | HK1891 | 材料 | stock | 兴泸水务 | HK2281 | 公用事业 | stock | 兴科蓉医药 | HK6833 | 医疗保健 | stock | 兴纺控股 | HK1968 | 可选消费 | stock | 兴胜创建 | HK0896 | 工业 | stock | 兴证国际 | HK6058 | 金融 | stock | 兴达国际 | HK1899 | 可选消费 | stock | 兴铭控股 | HK8425 | 工业 | stock | 其利工业集团 | HK1731 | 可选消费 | stock | 再鼎医药 | HK9688 | 医疗保健 | stock | 农业银行 | HK1288 | 金融 | stock | 农夫山泉 | HK9633 | 日常消费 | stock | 冠中地产 | HK0193 | 房地产 | stock | 冠军科技集团 | HK0092 | 能源 | stock | 冠君产业信托 | HK2778 | 房地产 | stock | 冠均国际控股 | HK1629 | 材料 | stock | 冠城钟表珠宝 | HK0256 | 可选消费 | stock | 冠忠巴士集团 | HK0306 | 工业 | stock | 冠轈控股 | HK1872 | 可选消费 | stock | 凌雄科技 | HK2436 | 信息技术 | stock | 凤凰卫视 | HK2008 | 可选消费 | stock | 凯乐士科技 | HK2729 | | stock | 凯富善集团控股 | HK8512 | 日常消费 | stock | 凯普松国际 | HK0469 | | stock | 凯盛新能 | HK1108 | 信息技术 | stock | 凯知乐国际 | HK2122 | 可选消费 | stock | 凯联国际酒店 | HK0105 | 房地产 | stock | 凯莱英 | HK6821 | 医疗保健 | stock | 凯顺控股 | HK8203 | 能源 | stock | 出门问问 | HK2438 | | stock | 创业集团控股 | HK2221 | 工业 | 创业集团控股-新 stock | 创信国际 | HK0676 | 可选消费 | stock | 创徽国际 | HK8335 | | stock | 创想三维 | HK3388 | | stock | 创新奇智 | HK2121 | 信息技术 | stock | 创新实业 | HK2788 | | stock | 创梦天地 | HK1119 | 信息技术 | stock | 创科实业 | HK0669 | 可选消费 | stock | 创维集团 | HK0751 | 可选消费 | stock | 创美药业 | HK2289 | 医疗保健 | stock | 创联控股 | HK2371 | 可选消费 | stock | 创胜集团医药-B | HK6628 | 医疗保健 | 创胜集团,创胜集团-B stock | 创辉珠宝 | HK8537 | 可选消费 | stock | 创达科技控股 | HK1322 | | stock | 利亚零售 | HK0831 | 日常消费 | stock | 利华控股集团 | HK1346 | 可选消费 | stock | 利基控股 | HK0240 | 工业 | stock | 利时集团控股 | HK0526 | 可选消费 | stock | 利民实业 | HK0229 | 可选消费 | stock | 利特米 | HK1936 | 日常消费 | stock | 利福中国 | HK2136 | 可选消费 | stock | 利记 | HK0637 | 工业 | stock | 剂泰科技-P | HK7666 | | stock | 前海健康 | HK0911 | 可选消费 | stock | 剑桥科技 | HK6166 | 房地产 | stock | 剑虹集团控股 | HK1557 | 工业 | stock | 力丰(集团) | HK0387 | 工业 | stock | 力劲科技 | HK0558 | 工业 | stock | 力勤资源 | HK2245 | 材料 | stock | 力图控股 | HK1008 | 工业 | stock | 力天影业 | HK9958 | 可选消费 | stock | 力宝华润 | HK0156 | 日常消费 | stock | 力盟科技 | HK2405 | 信息技术 | stock | 力量发展 | HK1277 | 能源 | stock | 力高健康生活 | HK2370 | 房地产 | stock | 力高集团 | HK1622 | 房地产 | stock | 力鸿检验 | HK1586 | 工业 | stock | 加和国际控股 | HK8513 | 医疗保健 | stock | 加幂科技 | HK8198 | 信息技术 | stock | 加科思-B | HK1167 | 医疗保健 | 加科思 stock | 励时集团 | HK1327 | 可选消费 | stock | 励晶太平洋 | HK0575 | 医疗保健 | stock | 劲投国际 | HK3682 | | stock | 劲方医药-B | HK2595 | | stock | 勇利投资 | HK1145 | 工业 | stock | 勋龙 | HK1930 | 可选消费 | stock | 包浩斯国际 | HK0483 | 可选消费 | stock | 北亚策略 | HK8080 | 工业 | stock | 北京京客隆 | HK0814 | 日常消费 | stock | 北京体育文化-新 | HK1803 | 工业 | 北京体育文化 stock | 北京健康 | HK2389 | 医疗保健 | stock | 北京北辰实业股份 | HK0588 | 房地产 | stock | 北京控股 | HK0392 | 公用事业 | stock | 北京控股环境集团 | HK0154 | 工业 | stock | 北京汽车 | HK1958 | 可选消费 | stock | 北京燃气蓝天 | HK6828 | 公用事业 | stock | 北京能源国际 | HK0686 | 公用事业 | stock | 北京首都机场股份 | HK0694 | 工业 | stock | 北大资源 | HK0618 | 房地产 | stock | 北大青鸟环宇 | HK8095 | 可选消费 | stock | 北控城市资源 | HK3718 | 工业 | stock | 北控水务集团 | HK0371 | 公用事业 | stock | 北方矿业 | HK0433 | 材料 | stock | 北森控股 | HK9669 | 工业 | stock | 北海康成-B | HK1228 | 医疗保健 | 北海康成 stock | 北青传媒 | HK1000 | 可选消费 | stock | 医思健康 | HK2138 | 医疗保健 | stock | 医汇集团 | HK8161 | 医疗保健 | stock | 医渡科技 | HK2158 | 医疗保健 | stock | 医脉通 | HK2192 | 医疗保健 | stock | 十方控股 | HK1831 | 可选消费 | stock | 十月稻田 | HK9676 | 日常消费 | stock | 千循科技 | HK1640 | 可选消费 | stock | 千百度 | HK1028 | 可选消费 | stock | 升柏控股 | HK2340 | 房地产 | stock | 升能集团 | HK2459 | 信息技术 | stock | 升辉清洁 | HK2521 | | stock | 华人置业 | HK0127 | 房地产 | stock | 华亿金控 | HK8123 | 可选消费 | stock | 华众车载 | HK6830 | 可选消费 | stock | 华住集团-S | HK1179 | 可选消费 | 华住集团 stock | 华侨城(亚洲) | HK3366 | 房地产 | stock | 华信地产财务 | HK0252 | 材料 | stock | 华健未来-B | HK6132 | | stock | 华兴资本控股 | HK1911 | 金融 | stock | 华勤技术 | HK3296 | | stock | 华南城 | HK1668 | 房地产 | stock | 华南职业教育 | HK6913 | 可选消费 | stock | 华和控股 | HK9938 | 工业 | stock | 华商能源 | HK0206 | 能源 | stock | 华夏印度-U | HK9404 | | stock | 华夏控股 | HK1981 | 可选消费 | stock | 华夏文化科技 | HK1566 | 可选消费 | stock | 华夏水务集团 | HK8401 | 可选消费 | 源想集团 stock | 华大酒店 | HK0201 | 可选消费 | stock | 华宝国际 | HK0336 | 日常消费 | stock | 华富建业金融 | HK0952 | 金融 | stock | 华控康泰 | HK1312 | 医疗保健 | stock | 华新建材 | HK6655 | 材料 | 华新水泥 stock | 华新手袋国际控股 | HK2683 | 可选消费 | stock | 华昊中天医药-B | HK2563 | | stock | 华星控股 | HK8237 | 可选消费 | stock | 华昱高速 | HK1823 | 可选消费 | stock | 华显光电 | HK0334 | 信息技术 | stock | 华曦达 | HK0901 | 金融 | 金石投资集团 stock | 华检医疗 | HK1931 | 医疗保健 | stock | 华油能源 | HK1251 | 能源 | stock | 华沿机器人 | HK1021 | | stock | 华泰瑞银 | HK8006 | 可选消费 | stock | 华津国际控股 | HK2738 | 材料 | stock | 华润万象生活 | HK1209 | 房地产 | stock | 华润医疗 | HK1515 | 医疗保健 | stock | 华润医药 | HK3320 | 医疗保健 | stock | 华润啤酒 | HK0291 | 日常消费 | stock | 华润啤酒-R | HK80291 | 日常消费 | 华润啤酒 stock | 华润建材科技 | HK1313 | 材料 | stock | 华润燃气 | HK1193 | 公用事业 | stock | 华润电力 | HK0836 | 公用事业 | stock | 华润置地 | HK1109 | 房地产 | stock | 华润饮料 | HK2460 | | stock | 华滋国际海洋 | HK2258 | 工业 | stock | 华电国际电力股份 | HK1071 | 公用事业 | stock | 华盛国际控股 | HK1323 | 工业 | stock | 华禧控股 | HK1689 | 材料 | stock | 华科智能投资 | HK1140 | 金融 | stock | 华章科技 | HK1673 | 工业 | stock | 华美乐乐 | HK8429 | 可选消费 | stock | 华联国际 | HK0969 | 日常消费 | stock | 华能国际电力股份 | HK0902 | 公用事业 | stock | 华荣能源 | HK1101 | 能源 | stock | 华营建筑 | HK1582 | 工业 | stock | 华虹宏力 | HK1347 | 信息技术 | 华虹半导体 stock | 华融投资股份 | HK2277 | | stock | 华视集团控股 | HK1111 | | stock | 华讯 | HK0833 | 可选消费 | stock | 华领医药-B | HK2552 | 医疗保健 | 华领医药 stock | 华鼎控股 | HK3398 | 可选消费 | stock | 协合新能源 | HK0182 | 公用事业 | stock | 协同通信 | HK1613 | 信息技术 | stock | 协鑫科技 | HK3800 | 信息技术 | stock | 卓尔智联 | HK2098 | 房地产 | stock | 卓悦控股 | HK0653 | 医疗保健 | stock | 卓正医疗 | HK2677 | | stock | 卓珈控股 | HK1827 | 医疗保健 | stock | 卓能(集团) | HK0131 | 房地产 | stock | 卓越商企服务 | HK6989 | 房地产 | stock | 卓越教育集团 | HK3978 | 可选消费 | stock | 卓越睿新(五十) | HK2687 | | 卓越睿新,卓越睿新(一百) stock | 南京熊猫电子股份 | HK0553 | 信息技术 | stock | 南华期货股份 | HK2691 | | stock | 南华金融 | HK0619 | 金融 | stock | 南华集团控股 | HK0413 | 可选消费 | stock | 南南资源 | HK1229 | 能源 | stock | 南山铝业国际 | HK2610 | | stock | 南强控股 | HK2680 | 金融 | 创升控股 stock | 南戈壁 | HK1878 | 能源 | stock | 南方通信 | HK1617 | 信息技术 | stock | 南方锰业 | HK1091 | 材料 | stock | 南旋控股 | HK1982 | 可选消费 | stock | 南粤控股 | HK1058 | 可选消费 | stock | 南顺(香港) | HK0411 | 日常消费 | stock | 博奇环保 | HK2377 | 工业 | stock | 博安生物 | HK6955 | 医疗保健 | stock | 博富临置业 | HK0225 | 房地产 | stock | 博尼控股 | HK1906 | 可选消费 | stock | 博泰车联 | HK2889 | | stock | 博维智慧 | HK1204 | 信息技术 | stock | 博耳电力 | HK1685 | 工业 | stock | 博雅互动 | HK0434 | 信息技术 | stock | 博雷顿 | HK1333 | 材料 | stock | 博骏教育 | HK1758 | 可选消费 | stock | 卡姆丹克太阳能 | HK0712 | 公用事业 | stock | 卡宾 | HK2030 | 可选消费 | stock | 卡撒天娇 | HK2223 | 可选消费 | stock | 卡森国际 | HK0496 | 可选消费 | stock | 卡罗特 | HK2549 | | stock | 卧安机器人 | HK6600 | 医疗保健 | 赛生药业 stock | 卫龙美味 | HK9985 | 日常消费 | stock | 印象大红袍 | HK2695 | | stock | 原生态牧业 | HK1431 | 日常消费 | stock | 友和集团 | HK2347 | 可选消费 | stock | 友宝在线 | HK2429 | | stock | 友联国际教育租赁 | HK1563 | 可选消费 | stock | 友芝友生物-B | HK2496 | 医疗保健 | stock | 友谊时光 | HK6820 | 信息技术 | stock | 友邦保险 | HK1299 | 金融 | stock | 友邦保险-R | HK81299 | 金融 | 友邦保险 stock | 双桦控股 | HK1241 | 可选消费 | stock | 双登股份 | HK6960 | | stock | 双良睿能 | HK2481 | 公用事业 | 慧居科技 stock | 双财庄 | HK2321 | 日常消费 | stock | 叙福楼集团 | HK1978 | 可选消费 | stock | 古兜控股 | HK8308 | 可选消费 | stock | 古茗 | HK1364 | | stock | 叮当健康 | HK9886 | 医疗保健 | stock | 可孚医疗 | HK1187 | | stock | 台州水务 | HK1542 | 公用事业 | stock | 叶氏化工集团 | HK0408 | 材料 | stock | 合丰集团 | HK2320 | 材料 | stock | 合和实业 | HK0054 | | stock | 合富辉煌 | HK0733 | 房地产 | stock | 合景悠活 | HK3913 | 房地产 | stock | 合景泰富集团 | HK1813 | 房地产 | stock | 合生创展集团 | HK0754 | 房地产 | stock | 吉利汽车 | HK0175 | 可选消费 | stock | 吉利汽车-R | HK80175 | 可选消费 | 吉利汽车 stock | 吉宏股份 | HK2603 | | stock | 吉星新能源 | HK3395 | 能源 | stock | 吉林长龙药业 | HK8049 | 医疗保健 | stock | 吉盛集团控股 | HK8133 | 材料 | stock | 吉辉控股 | HK8027 | 工业 | stock | 同仁堂医养 | HK2667 | | stock | 同仁堂国药 | HK3613 | 医疗保健 | stock | 同仁堂科技 | HK1666 | 医疗保健 | stock | 同得仕(集团) | HK0518 | 可选消费 | stock | 同方友友 | HK1868 | 可选消费 | stock | 同方泰德 | HK1206 | 信息技术 | stock | 同景新能源 | HK8326 | 公用事业 | stock | 同源康医药-B | HK2410 | | stock | 同程旅行 | HK0780 | 可选消费 | stock | 同道猎聘 | HK6100 | 工业 | stock | 名仕快相 | HK8483 | 可选消费 | stock | 名创优品 | HK9896 | 可选消费 | stock | 名科国际 | HK8100 | 信息技术 | stock | 向中国际 | HK1871 | 可选消费 | stock | 君圣泰医药-B | HK2511 | | stock | 君实生物 | HK1877 | 医疗保健 | stock | 君正股份 | HK3223 | | stock | 君百延集团 | HK8372 | 医疗保健 | stock | 启明东方控股 | HK3626 | 工业 | HSSP INTL stock | 启明医疗-B | HK2500 | 医疗保健 | 启明医疗 stock | 周六福 | HK6168 | 电信服务 | stock | 周大福 | HK1929 | 可选消费 | stock | 周大福创建 | HK0659 | 工业 | stock | 周大福媒体娱乐 | HK1097 | 可选消费 | 有线宽频 stock | 周生生 | HK0116 | 可选消费 | stock | 周黑鸭 | HK1458 | 日常消费 | stock | 味丹国际 | HK2317 | 日常消费 | stock | 味千(中国) | HK0538 | 可选消费 | stock | 呷哺呷哺 | HK0520 | 可选消费 | stock | 和嘉控股 | HK0704 | 能源 | stock | 和泓服务 | HK6093 | 房地产 | stock | 和誉-B | HK2256 | 医疗保健 | 和誉 stock | 和记电讯香港 | HK0215 | 电信服务 | stock | 和谐汽车 | HK3836 | 可选消费 | stock | 和铂医药-B | HK2142 | 医疗保健 | 和铂医药 stock | 和黄医药 | HK0013 | 医疗保健 | stock | 品创控股 | HK8066 | 信息技术 | stock | 哈尔滨电气 | HK1133 | 工业 | stock | 哈尔滨银行 | HK6138 | 金融 | stock | 哔哩哔哩-W | HK9626 | 信息技术 | 哔哩哔哩 stock | 唐宫中国 | HK1181 | 可选消费 | stock | 商汤-W | HK0020 | 信息技术 | 商汤 stock | 商汤-WR | HK80020 | 信息技术 | 商汤 stock | 商米科技-W | HK6810 | | stock | 喆丽控股 | HK2209 | 可选消费 | stock | 喜得龙 | HK0977 | | stock | 喜相逢集团 | HK2473 | | stock | 嘀嗒出行-PF | HK2559 | | 嘀嗒出行 stock | 嘉兴燃气 | HK9908 | 公用事业 | stock | 嘉创地产 | HK2421 | 房地产 | stock | 嘉利国际 | HK1050 | 信息技术 | stock | 嘉华国际 | HK0173 | 房地产 | stock | 嘉士利集团 | HK1285 | 日常消费 | stock | 嘉宏教育 | HK1935 | 可选消费 | stock | 嘉文世纪投资公司 | HK0612 | 金融 | stock | 嘉泓物流 | HK2130 | 工业 | stock | 嘉涛(香港)控股 | HK2189 | 医疗保健 | stock | 嘉瑞国际 | HK0822 | 工业 | stock | 嘉耀控股 | HK1626 | 材料 | stock | 嘉艺控股 | HK1025 | 可选消费 | stock | 嘉进投资国际 | HK0310 | 金融 | stock | 嘉里建设 | HK0683 | 房地产 | stock | 嘉高达资本 | HK1468 | 工业 | stock | 嘉鼎国际集团 | HK8153 | 可选消费 | stock | 四威科技 | HK1202 | 信息技术 | stock | 四川成渝高速公路 | HK0107 | 工业 | stock | 四川能投发展 | HK1713 | 公用事业 | stock | 四洲集团 | HK0374 | 日常消费 | stock | 四环医药 | HK0460 | 医疗保健 | stock | 固生堂 | HK2273 | 医疗保健 | stock | 国农金融投资 | HK8120 | 日常消费 | stock | 国华(旧) | HK2937 | | stock | 国家联合资源 | HK0254 | 工业 | stock | 国富氢能 | HK2582 | | stock | 国富量子 | HK0290 | 金融 | 国富量子(二千) stock | 国开国际投资 | HK1062 | 金融 | stock | 国微控股 | HK2239 | 信息技术 | stock | 国恩控股 | HK8121 | 可选消费 | stock | 国恩科技 | HK2768 | 房地产 | 佳源国际控股 stock | 国民技术 | HK2701 | | stock | 国泰君安国际 | HK1788 | 金融 | stock | 国泰海通 | HK2611 | 金融 | stock | 国泰航空 | HK0293 | 工业 | stock | 国浩集团 | HK0053 | 房地产 | stock | 国瑞健康 | HK2329 | 房地产 | stock | 国美零售 | HK0493 | 可选消费 | stock | 国联民生 | HK1456 | 金融 | stock | 国联通信 | HK8060 | 信息技术 | stock | 国药控股 | HK1099 | 医疗保健 | stock | 国药科技股份 | HK8156 | 医疗保健 | stock | 国银金租 | HK1606 | 金融 | stock | 国际商业数字技术 | HK1782 | 信息技术 | stock | 国际商业结算 | HK0147 | 房地产 | stock | 国际娱乐 | HK1009 | 房地产 | stock | 国际家居零售 | HK1373 | 可选消费 | stock | 国际永胜集团 | HK6663 | 工业 | stock | 国际精密 | HK0929 | 可选消费 | stock | 国际资源 | HK1051 | 金融 | stock | 国鸿氢能 | HK9663 | | stock | 图达通 | HK2665 | | stock | 圆美光电 | HK8311 | 信息技术 | stock | 圆通国际快递 | HK6123 | 工业 | stock | 圣唐控股 | HK8305 | 工业 | stock | 圣诺医药-B | HK2257 | 医疗保健 | 圣诺医药 stock | 圣贝拉集团 | HK2508 | | stock | 圣邦股份 | HK3661 | | stock | 圣马丁国际 | HK0482 | 可选消费 | stock | 地平线机器人-W | HK9660 | | stock | 均安控股 | HK1559 | 工业 | stock | 均胜电子 | HK0699 | | stock | 坚宝国际 | HK0675 | 信息技术 | stock | 坛金矿业 | HK0621 | 材料 | stock | 坤集团 | HK0924 | 工业 | stock | 埃斯顿 | HK2715 | | stock | 城市酷选 | HK8050 | 信息技术 | stock | 城建设计 | HK1599 | 工业 | stock | 域能控股 | HK0442 | 可选消费 | stock | 域高国际控股 | HK1621 | 能源 | stock | 培力农本方 | HK1498 | 医疗保健 | stock | 基地锦标集团 | HK8460 | 工业 | stock | 基本半导体 | HK9971 | | stock | 基石控股 | HK1592 | 材料 | 基石控股(新) stock | 基石科技控股 | HK8391 | 工业 | stock | 基石药业-B | HK2616 | 医疗保健 | 基石药业 stock | 基石金融 | HK8112 | 可选消费 | stock | 堃博医疗-B | HK2216 | 医疗保健 | 堃博医疗 stock | 壁仞科技 | HK6082 | | stock | 声扬集团 | HK8163 | 可选消费 | stock | 声通科技 | HK2495 | | stock | 壹照明 | HK8222 | 可选消费 | stock | 复兴亚洲 | HK0274 | 材料 | stock | 复宏汉霖 | HK2696 | 医疗保健 | stock | 复旦张江 | HK1349 | 医疗保健 | stock | 复星医药 | HK2196 | 医疗保健 | stock | 复星国际 | HK0656 | 工业 | stock | 复锐医疗科技 | HK1696 | 医疗保健 | stock | 多想云 | HK6696 | 可选消费 | stock | 多点数智 | HK2586 | | stock | 多牛科技 | HK1961 | 信息技术 | stock | 大中华控股 | HK0021 | 房地产 | stock | 大中华金融 | HK0431 | 金融 | stock | 大人国际 | HK1957 | 可选消费 | stock | 大众公用 | HK1635 | 公用事业 | stock | 大众口腔 | HK2651 | | stock | 大众金融控股 | HK0626 | 金融 | stock | 大健康国际 | HK2211 | 医疗保健 | stock | 大同机械 | HK0118 | 工业 | stock | 大同集团 | HK0544 | 工业 | stock | 大唐发电 | HK0991 | 公用事业 | stock | 大唐新能源 | HK1798 | 公用事业 | stock | 大唐环境 | HK1272 | 工业 | stock | 大唐西市 | HK0620 | 工业 | stock | 大唐黄金 | HK8299 | 材料 | stock | 大喜屋集团 | HK6813 | | stock | 大地国际集团 | HK8130 | 可选消费 | stock | 大地教育 | HK8417 | 可选消费 | stock | 大家乐集团 | HK0341 | 可选消费 | stock | 大山教育 | HK9986 | 可选消费 | stock | 大快活集团 | HK0052 | 可选消费 | stock | 大成玉米集团 | HK3889 | 日常消费 | stock | 大成生化科技 | HK0809 | 材料 | stock | 大成食品 | HK3999 | 日常消费 | stock | 大新金融 | HK0440 | 金融 | stock | 大新银行集团 | HK2356 | 金融 | stock | 大方广瑞德 | HK0755 | 房地产 | stock | 大族数控 | HK3200 | | stock | 大明国际 | HK1090 | 材料 | stock | 大森控股 | HK1580 | 材料 | stock | 大洋环球控股 | HK9876 | | stock | 大洋集团 | HK1991 | 信息技术 | stock | 大生地产 | HK0089 | 房地产 | stock | 大禹金融 | HK1073 | 金融 | stock | 大行科工 | HK2543 | | stock | 大象控股集团 | HK8635 | 金融 | stock | 大象未来集团 | HK2309 | 可选消费 | stock | 大酒店 | HK0045 | 可选消费 | stock | 大金重工 | HK1081 | | stock | 大陆航空科技控股 | HK0232 | 工业 | stock | 大麦娱乐 | HK1060 | 可选消费 | stock | 天业节水 | HK0840 | 工业 | stock | 天任集团 | HK1429 | 工业 | stock | 天伦燃气 | HK1600 | 公用事业 | stock | 天保能源 | HK1671 | 公用事业 | stock | 天元医疗 | HK0557 | 医疗保健 | stock | 天兆猪业 | HK1248 | | stock | 天利控股集团 | HK0117 | 信息技术 | stock | 天图投资 | HK1973 | 金融 | stock | 天域半导体 | HK2658 | | stock | 天大药业 | HK0455 | 医疗保健 | stock | 天安 | HK0028 | 房地产 | stock | 天安卓健 | HK0383 | 医疗保健 | stock | 天宝集团 | HK1979 | 信息技术 | stock | 天岳先进 | HK2631 | | stock | 天工国际 | HK0826 | 材料 | stock | 天彩控股 | HK3882 | 可选消费 | stock | 天德化工 | HK0609 | 材料 | stock | 天德地产 | HK0266 | 房地产 | stock | 天成控股(新) | HK2110 | 工业 | 天成控股 stock | 天数智芯 | HK9903 | | stock | 天时资源 | HK8028 | 材料 | stock | 天星医疗 | HK1609 | 工业 | 创建集团控股 stock | 天机控股 | HK1520 | 可选消费 | stock | 天泓文创 | HK8500 | 可选消费 | stock | 天洁环境 | HK1527 | 工业 | stock | 天津创业环保股份 | HK1065 | 公用事业 | stock | 天津发展 | HK0882 | 工业 | stock | 天津建发 | HK2515 | | stock | 天津津燃公用 | HK1265 | 公用事业 | stock | 天津港发展 | HK3382 | 工业 | stock | 天津银行 | HK1578 | 金融 | stock | 天润云 | HK2167 | 信息技术 | stock | 天源集团 | HK6119 | 工业 | stock | 天玺曜11 | HK1010 | 信息技术 | stock | 天瑞汽车内饰 | HK6162 | 可选消费 | stock | 天福 | HK6868 | 日常消费 | stock | 天立国际控股 | HK1773 | 可选消费 | stock | 天聚地合 | HK2479 | | stock | 天能动力 | HK0819 | 工业 | stock | 天臣控股 | HK1201 | 信息技术 | stock | 天虹国际集团 | HK2678 | 可选消费 | stock | 天誉置业 | HK0059 | 房地产 | stock | 天辰生物-B | HK1779 | | stock | 天长集团 | HK2182 | 可选消费 | stock | 天鸽互动 | HK1980 | 信息技术 | stock | 天齐锂业 | HK9696 | 材料 | stock | 太兴置业 | HK0277 | 房地产 | stock | 太兴集团 | HK6811 | 可选消费 | stock | 太古地产 | HK1972 | 房地产 | stock | 太古地产 | HK0962 | | stock | 太古股份公司A | HK0019 | 工业 | stock | 太古股份公司B | HK0087 | 工业 | stock | 太和控股 | HK0718 | 医疗保健 | stock | 太平洋网络 | HK0543 | 可选消费 | stock | 太平洋航运 | HK2343 | 工业 | stock | 太平洋酒吧 | HK8432 | 可选消费 | stock | 太美医疗科技 | HK2576 | | stock | 奇富科技-S | HK3660 | 金融 | 奇富科技 stock | 奇点国峰 | HK1280 | 可选消费 | stock | 奇瑞汽车 | HK9973 | | stock | 奈雪的茶 | HK2150 | 日常消费 | stock | 奥传思维控股 | HK8091 | 可选消费 | stock | 奥克斯国际 | HK2080 | 房地产 | stock | 奥克斯电气 | HK2580 | | stock | 奥威控股 | HK1370 | 材料 | stock | 奥思集团 | HK1161 | 医疗保健 | stock | 奥星生命科技 | HK6118 | 医疗保健 | stock | 奥邦建筑 | HK1615 | 工业 | stock | 好孩子国际 | HK1086 | 可选消费 | stock | 如祺出行 | HK9680 | | stock | 威华达控股 | HK0622 | 金融 | stock | 威扬酒业控股 | HK8509 | 日常消费 | stock | 威海银行 | HK9677 | 金融 | stock | 威胜控股 | HK3393 | 信息技术 | stock | 威讯控股 | HK1087 | 信息技术 | stock | 威铖国际 | HK1002 | 工业 | stock | 威雅利 | HK0854 | 信息技术 | stock | 威高国际 | HK1173 | 日常消费 | stock | 威高股份 | HK1066 | 医疗保健 | stock | 嬴集团 | HK0397 | 金融 | stock | 子不语 | HK2420 | 可选消费 | stock | 宁德� -
stock_us.yaml 40.4 KB
# ============================================================ # 美股及境外 ETF(保留平台 ticker 后缀 .N/.O/.A/.BAT) # 来源:MongoDB tkrsInfo 集合(type=stock/etf,ticker 以 .N/.O/.A/.BAT 结尾) # 格式:stock | name | ticker | 备注 | short_names # ⚠️ 行情优先;估值/财务按 fast_query 字段覆盖及实际返回判断 # ============================================================ stock | 3M公司 | MMM.N | | stock | AAON_Inc | AAON.O | | stock | Acadia_Healthcare_Company_Inc | ACHC.O | | stock | Acuity_Brands_Inc | AYI.N | | stock | ADT_Inc | ADT.N | | stock | Advanced_Drainage_Systems_Inc | WMS.N | | stock | Affiliated_Managers_Group_Inc | AMG.N | | stock | Affirm_Holdings_Inc | AFRM.O | | stock | AGNC_Investment_Corp | AGNC.O | | stock | Agree_Realty_Corporation | ADC.N | | stock | Air_Lease_Corporation | AL.N | | stock | Alamos_Gold_Inc | AGI.N | | stock | Albertsons_Companies_Inc | ACI.N | | stock | Alexandria_Real_Estate_Equities_Inc | ARE.N | | stock | Allegion_plc | ALLE.N | | stock | Allegro_MicroSystems_Inc | ALGM.O | | stock | Alliant_Energy_Corporation | LNT.O | | stock | Ally_Financial_Inc | ALLY.N | | stock | Alnylam_Pharmaceuticals_Inc | ALNY.O | | stock | Amdocs_Limited | DOX.O | | stock | Amentum_Holdings_Inc | AMTM.N | | stock | American_Financial_Group_Inc | AFG.N | | stock | American_Homes_4_Rent | AMH.N | | stock | Americold_Realty_Trust_Inc | COLD.N | | stock | Annaly_Capital_Management_Inc | NLY.N | | stock | Antero_Midstream_Corporation | AM.N | | stock | Antero_Resources_Corporation | AR.N | | stock | APA_Corporation | APA.O | | stock | Apellis_Pharmaceuticals_Inc | APLS.O | | stock | APi_Group_Corporation | APG.N | | stock | AppFolio_Inc | APPF.O | | stock | Applied_Industrial_Technologies_Inc | AIT.N | | stock | AppLovin_Corporation | APP.O | | stock | AptarGroup_Inc | ATR.N | | stock | Arch_Capital_Group_Ltd | ACGL.O | | stock | Ares_Management_Corporation | ARES.N | | stock | Arista_Networks_Inc | ANET.N | | stock | Arm_Holdings_plc_American_Depositary_Shares | ARM.O | | stock | Arthur_J_Gallagher__Co | AJG.N | | stock | Assurant_Inc | AIZ.N | | stock | Assured_Guaranty_Ltd | AGO.N | | stock | Astera_Labs_Inc_Common_Stock | ALAB.O | | stock | AST_SpaceMobile_Inc | ASTS.O | | stock | ATI_Inc | ATI.N | | stock | Atlassian_Corporation | TEAM.O | | stock | Atmos_Energy_Corporation | ATO.N | | stock | Aurora_Innovation_Inc | AUR.O | | stock | AutoNation_Inc | AN.N | | stock | AvalonBay_Communities_Inc | AVB.N | | stock | Avantor_Inc | AVTR.N | | stock | AXIS_Capital_Holdings_Limited | AXS.N | | stock | Axon_Enterprise_Inc | AXON.O | | stock | Bank_OZK | OZK.O | | stock | Bath__Body_Works_Inc | BBWI.N | | stock | BellRing_Brands_Inc | BRBR.N | | stock | BeOne_Medicines_Ltd | ONC.O | | stock | Billcom_Holdings_Inc | BILL.N | | stock | Bio-Techne_Corporation | TECH.O | | stock | BioMarin_Pharmaceutical_Inc | BMRN.O | | stock | BJs_Wholesale_Club_Holdings_Inc | BJ.N | | stock | Block_Inc | XYZ.N | | stock | Blue_Owl_Capital_Inc | OWL.N | | stock | BOK_Financial_Corporation | BOKF.O | | stock | Boyd_Gaming_Corporation | BYD.N | | stock | Brighthouse_Financial_Inc | BHF.O | | stock | Bright_Horizons_Family_Solutions_Inc | BFAM.N | | stock | Brixmor_Property_Group_Inc | BRX.N | | stock | Broadridge_Financial_Solutions_Inc | BR.N | | stock | Brown__Brown_Inc | BRO.N | | stock | Builders_FirstSource_Inc | BLDR.N | | stock | Bullish | BLSH.N | | stock | Burlington_Stores_Inc | BURL.N | | stock | BWX_Technologies_Inc | BWXT.N | | stock | BXP_Inc | BXP.N | | stock | CACI_International_Inc | CACI.N | | stock | Camden_Property_Trust | CPT.N | | stock | Caris_Life_Sciences_Inc | CAI.O | | stock | Carlisle_Companies_Incorporated | CSL.N | | stock | CarMax_Inc | KMX.N | | stock | Carpenter_Technology_Corporation | CRS.N | | stock | Carvana_Co | CVNA.N | | stock | Caseys_General_Stores_Inc | CASY.O | | stock | CAVA_Group_Inc | CAVA.N | | stock | CDW_Corporation | CDW.O | | stock | Celsius_Holdings_Inc | CELH.O | | stock | Cencora_Inc | COR.N | | stock | Centene_Corporation | CNC.N | | stock | CenterPoint_Energy_Inc | CNP.N | | stock | Certara_Inc | CERT.O | | stock | CF_Industries_Holdings_Inc | CF.N | | stock | Charter_Communications_Inc | CHTR.O | | stock | Chemed_Corporation | CHE.N | | stock | Cheniere_Energy_Inc | LNG.N | | stock | Chewy_Inc | CHWY.N | | stock | Chipotle_Mexican_Grill_Inc | CMG.N | | stock | Chord_Energy_Corporation | CHRD.O | | stock | Churchill_Downs_Incorporated | CHDN.O | | stock | Church__Dwight_Co_Inc | CHD.N | | stock | Ciena_Corporation | CIEN.N | | stock | Cincinnati_Financial_Corporation | CINF.O | | stock | Circle_Internet_Group | CRCL.N | | stock | Cirrus_Logic_Inc | CRUS.O | | stock | Citizens_Financial_Group_Inc | CFG.N | | stock | Clean_Harbors_Inc | CLH.N | | stock | Clearway_Energy_Inc | CWEN-A.N | | stock | Clearway_Energy_Inc | CWEN.N | | stock | Cloudflare_Inc | NET.N | | stock | CMS_Energy_Corporation | CMS.N | | stock | CNA_Financial_Corporation | CNA.N | | stock | Coca-Cola_Consolidated_Inc | COKE.O | | stock | Coinbase_Global_Inc | COIN.O | | stock | Columbia_Banking_System_Inc | COLB.O | | stock | Comfort_Systems_USA_Inc | FIX.N | | stock | Commerce_Bancshares_Inc | CBSH.O | | stock | Concentrix_Corporation | CNXC.O | | stock | Constellation_Energy_Corporation | CEG.O | | stock | Copart_Inc | CPRT.O | | stock | Corcept_Therapeutics_Incorporated | CORT.O | | stock | CoreWeave_Inc_Class_A | CRWV.O | | stock | Core__Main_Inc | CNM.N | | stock | Corpay_Inc | CPAY.N | | stock | CoStar_Group_Inc | CSGP.O | | stock | Coterra_Energy_Inc | CTRA.N | | stock | Cousins_Properties_Incorporated | CUZ.N | | stock | Crane_NXT_Co | CXT.N | | stock | Credit_Acceptance_Corporation | CACC.O | | stock | Credo_Technology_Group_Holding_Ltd | CRDO.O | | stock | CRH_plc | CRH.N | | stock | CrowdStrike_Holdings_Inc | CRWD.O | | stock | CSX_Corporation | CSX.O | | stock | CubeSmart | CUBE.N | | stock | CullenFrost_Bankers_Inc | CFR.N | | stock | Darling_Ingredients_Inc | DAR.N | | stock | Datadog_Inc | DDOG.O | | stock | Diamondback_Energy_Inc | FANG.O | | stock | DICKS_Sporting_Goods_Inc | DKS.N | | stock | Digital_Realty_Trust_Inc | DLR.N | | stock | Dillards_Inc | DDS.N | | stock | DocuSign_Inc | DOCU.O | | stock | DoorDash_Inc | DASH.O | | stock | DoubleVerify_Holdings_Inc | DV.N | | stock | Doximity_Inc | DOCS.N | | stock | DraftKings_Inc | DKNG.O | | stock | Dropbox_Inc | DBX.O | | stock | DTE_Energy_Company | DTE.N | | stock | DT_Midstream_Inc | DTM.N | | stock | Dutch_Bros_Inc | BROS.N | | stock | DXC_Technology_Company | DXC.N | | stock | Dynatrace_Inc | DT.N | | stock | Eagle_Materials_Inc | EXP.N | | stock | EastGroup_Properties_Inc | EGP.N | | stock | Elastic_NV | ESTC.N | | stock | Element_Solutions_Inc | ESI.N | | stock | Elevance_Health_Inc | ELV.N | | stock | elf_Beauty_Inc | ELF.N | | stock | EMCOR_Group_Inc | EME.N | | stock | Encompass_Health_Corporation | EHC.N | | stock | Enphase_Energy_Inc | ENPH.O | | stock | Envista_Holdings_Corp | NVST.N | | stock | EOG_Resources_Inc | EOG.N | | stock | EPAM_Systems_Inc | EPAM.N | | stock | EPR_Properties | EPR.N | | stock | EQT_Corporation | EQT.N | | stock | Equitable_Holdings_Inc | EQH.N | | stock | Equity_LifeStyle_Properties_Inc | ELS.N | | stock | Equity_Residential | EQR.N | | stock | Erie_Indemnity_Company | ERIE.O | | stock | Essential_Utilities_Inc | WTRG.N | | stock | Essex_Property_Trust_Inc | ESS.N | | stock | Etsy_Inc | ETSY.N | | stock | Euronet_Worldwide_Inc | EEFT.O | | stock | Evercore_Inc | EVR.N | | stock | Everest_Re_Group_Ltd | EG.N | | stock | Evergy_Inc | EVRG.O | | stock | Eversource_Energy | ES.N | | stock | Everus_Construction_Group_Inc | ECG.N | | stock | Exact_Sciences_Corporation | EXAS.O | | stock | Exelixis_Inc | EXEL.O | | stock | ExlService_Holdings_Inc | EXLS.O | | stock | Expand_Energy_Corporation | EXE.O | | stock | Extra_Space_Storage_Inc | EXR.N | | stock | F5_Inc | FFIV.O | | stock | FactSet_Research_Systems_Inc | FDS.N | | stock | Federal_Realty_Investment_Trust | FRT.N | | stock | Fermi_Inc_Common_Stock | FRMI.O | | stock | Ferrovial_SE | FER.O | | stock | Fidelity_National_Financial_Inc | FNF.N | | stock | Fidelity_National_Information_Services_Inc | FIS.N | | stock | Figure_Technology_Solutions_Inc_Class_A_Common_Stock | FIGR.O | | stock | First_American_Financial_Corporation | FAF.N | | stock | First_Citizens_BancShares_Inc | FCNCA.O | | stock | First_Hawaiian_Inc | FHB.O | | stock | First_Horizon_Corporation | FHN.N | | stock | First_Industrial_Realty_Trust_Inc | FR.N | | stock | Five_Below_Inc | FIVE.O | | stock | Floor__Decor_Holdings_Inc | FND.N | | stock | Flowers_Foods_Inc | FLO.N | | stock | FNB_Corporation | FNB.N | | stock | Formula_One_Group | FWONA.O | | stock | Formula_One_Group | FWONK.O | | stock | Freedom_Holding_Corp | FRHC.O | | stock | Freshpet_Inc | FRPT.O | | stock | FTAI_Aviation_Ltd | FTAI.O | | stock | FTI_Consulting_Inc | FCN.N | | stock | GameStop_Corp | GME.N | | stock | Gaming_and_Leisure_Properties_Inc | GLPI.O | | stock | GCI_Liberty_Inc | GLIBA.O | | stock | GCI_Liberty_Inc | GLIBK.O | | stock | Generac_Holdings_Inc | GNRC.N | | stock | Genpact_Limited | G.N | | stock | Gen_Digital_Inc | GEN.O | | stock | GE_Vernova_Inc | GEV.N | | stock | GitLab_Inc | GTLB.O | | stock | Globant_SA | GLOB.N | | stock | Globe_Life_Inc | GL.N | | stock | Globus_Medical_Inc | GMED.N | | stock | GoDaddy_Inc | GDDY.N | | stock | Goldcom_Inc | GOLD.N | | stock | Grand_Canyon_Education_Inc | LOPE.O | | stock | Graphic_Packaging_Holding_Company | GPK.N | | stock | Guidewire_Software_Inc | GWRE.N | | stock | GXO_Logistics_Inc | GXO.N | | stock | Halozyme_Therapeutics_Inc | HALO.O | | stock | Hamilton_Lane_Incorporated | HLNE.O | | stock | Hayward_Holdings_Inc | HAYW.N | | stock | HCA_Healthcare_Inc | HCA.N | | stock | HDFC银行 | HDB.N | | stock | Healthcare_Realty_Trust_Incorporated | HR.N | | stock | Healthpeak_Properties_Inc | DOC.N | | stock | HEICO_Corporation | HEI-A.N | | stock | HEICO_Corporation | HEI.N | | stock | HF_Sinclair_Corporation | DINO.N | | stock | Highwoods_Properties_Inc | HIW.N | | stock | Host_Hotels__Resorts_Inc | HST.O | | stock | Houlihan_Lokey_Inc | HLI.N | | stock | Howard_Hughes_Holdings_Inc | HHH.N | | stock | HR_Block_Inc | HRB.N | | stock | HubSpot_Inc | HUBS.N | | stock | Huntington_Bancshares_Incorporated | HBAN.O | | stock | IAC_InterActive_Corp | IAC.O | | stock | ICICI银行 | IBN.N | | stock | IDACORP_Inc | IDA.N | | stock | Incyte_Corporation | INCY.O | | stock | Insmed_Incorporated | INSM.O | | stock | Inspire_Medical_Systems_Inc | INSP.N | | stock | Instacart_Maplebear_Inc | CART.O | | stock | Insulet_Corporation | PODD.O | | stock | Invitation_Homes_Inc | INVH.N | | stock | Ionis_Pharmaceuticals_Inc | IONS.O | | stock | IPG_Photonics_Corporation | IPGP.O | | stock | ITT_Inc | ITT.N | | stock | Jack_Henry__Associates_Inc | JKHY.O | | stock | Jazz_Pharmaceuticals_plc | JAZZ.N | | stock | JB_Hunt_Transport_Services_Inc | JBHT.O | | stock | Karman_Holdings_Inc | KRMN.N | | stock | KBR_Inc | KBR.N | | stock | Kemper_Corporation | KMPR.N | | stock | KeyCorp | KEY.N | | stock | Kilroy_Realty_Corporation | KRC.N | | stock | Kimco_Realty_Corporation | KIM.N | | stock | Kinsale_Capital_Group_Inc | KNSL.N | | stock | Kirby_Corporation | KEX.N | | stock | KKR__Co_Inc | KKR.N | | stock | Knight-Swift_Transportation_Holdings_Inc | KNX.N | | stock | KraneShares中证中国互联网ETF | KWEB.A | | stock | Kyndryl_Holdings_Inc | KD.N | | stock | Lamar_Advertising_Company | LAMR.O | | stock | Lamb_Weston_Holdings_Inc | LW.N | | stock | Landstar_System_Inc | LSTR.O | | stock | Leonardo_DRS_Inc | DRS.O | | stock | Liberty_Broadband_Corporation | LBRDA.O | | stock | Liberty_Broadband_Corporation | LBRDK.O | | stock | Liberty_Global_plc | LBTYA.O | | stock | Liberty_Global_plc | LBTYK.O | | stock | Liberty_Live_Group | LLYVA.O | | stock | Liberty_Live_Group | LLYVK.O | | stock | Lineage_Inc | LINE.O | | stock | Lithia_Motors_Inc | LAD.N | | stock | Live_Nation_Entertainment_Inc | LYV.N | | stock | LKQ_Corporation | LKQ.O | | stock | Loar_Holdings_Inc | LOAR.N | | stock | Louisiana-Pacific_Corporation | LPX.N | | stock | LPL_Financial_Holdings_Inc | LPLA.O | | stock | Lucid_Group_Inc | LCID.O | | stock | Lumentum_Holdings_Inc | LITE.O | | stock | Lyft_Inc | LYFT.O | | stock | MACOM_Technology_Solutions_Holdings_Inc | MTSI.O | | stock | Madison_Square_Garden_Sports_Corp | MSGS.N | | stock | Manhattan_Associates_Inc | MANH.O | | stock | MarketAxess_Holdings_Inc | MKTX.O | | stock | Martin_Marietta_Materials_Inc | MLM.N | | stock | MasTec_Inc | MTZ.N | | stock | Matador_Resources_Company | MTDR.N | | stock | Match_Group_Inc | MTCH.O | | stock | MDU_Resources_Group_Inc | MDU.N | | stock | Medical_Properties_Trust_Inc | MPT.N | | stock | Medpace_Holdings_Inc | MEDP.O | | stock | MercadoLibre_Inc | MELI.O | | stock | MGIC_Investment_Corporation | MTG.N | | stock | Mid-America_Apartment_Communities_Inc | MAA.N | | stock | Millicom_International_Cellular_SA | TIGO.O | | stock | Millrose_Properties_Inc | MRP.N | | stock | MKS_Inc | MKSI.O | | stock | Molina_Healthcare_Inc | MOH.N | | stock | MongoDB_Inc | MDB.O | | stock | MP_Materials_Corp | MP.N | | stock | MSA_Safety_Incorporated | MSA.N | | stock | MSC_Industrial_Direct_Co_Inc | MSM.N | | stock | MT_Bank_Corporation | MTB.N | | stock | Mueller_Industries_Inc | MLI.N | | stock | Murphy_USA_Inc | MUSA.N | | stock | Natera_Inc | NTRA.O | | stock | National_Fuel_Gas_Company | NFG.N | | stock | National_Storage_Affiliates_Trust | NSA.N | | stock | nCino_Inc | NCNO.O | | stock | NetApp_Inc | NTAP.O | | stock | Neurocrine_Biosciences_Inc | NBIX.O | | stock | NewMarket_Corporation | NEU.N | | stock | Nexstar_Media_Group_Inc | NXST.O | | stock | NIQ_Global_Intelligence_Plc | NIQ.N | | stock | NiSource_Inc | NI.N | | stock | NNN_REIT_Inc | NNN.N | | stock | NOV_Inc | NOV.N | | stock | NRG_Energy_Inc | NRG.N | | stock | Nutanix_Inc | NTNX.O | | stock | Nu_Holdings_Ltd | NU.N | | stock | nVent_Electric_plc | NVT.N | | stock | NVR_Inc | NVR.N | | stock | OGE_Energy_Corp | OGE.N | | stock | Okta_Inc | OKTA.O | | stock | Old_Dominion_Freight_Line_Inc | ODFL.O | | stock | Old_Republic_International_Corporation | ORI.N | | stock | Ollies_Bargain_Outlet_Holdings_Inc | OLLI.O | | stock | Omega_Healthcare_Investors_Inc | OHI.N | | stock | OneMain_Holdings_Inc | OMF.N | | stock | ONEOK_Inc | OKE.N | | stock | Onto_Innovation_Inc | ONTO.N | | stock | OReilly_Automotive_Inc | ORLY.O | | stock | Ovintiv_Inc | OVV.N | | stock | Packaging_Corporation_of_America | PKG.N | | stock | Palantir_Technologies_Inc | PLTR.O | | stock | Paramount_Skydance_Corporation_Class_B_Common_Stock | PSKY.O | | stock | Park_Hotels__Resorts_Inc | PK.N | | stock | Parsons_Corporation | PSN.N | | stock | Paychex_Inc | PAYX.O | | stock | Paycom_Software_Inc | PAYC.N | | stock | Paylocity_Holding_Corporation | PCTY.O | | stock | Pegasystems_Inc | PEGA.O | | stock | PENN_Entertainment_Inc | PENN.O | | stock | Penske_Automotive_Group_Inc | PAG.N | | stock | Penumbra_Inc | PEN.N | | stock | Performance_Food_Group_Company | PFGC.N | | stock | Permian_Resources_Corporation | PR.N | | stock | Pilgrims_Pride_Corporation | PPC.O | | stock | Pinnacle_Financial_Partners_Inc | PNFP.O | | stock | Pinnacle_West_Capital_Corporation | PNW.N | | stock | Pinterest_Inc | PINS.N | | stock | Planet_Fitness_Inc | PLNT.N | | stock | POET_Technologies_Inc | POET.N | | stock | Pool_Corporation | POOL.O | | stock | Popular_Inc | BPOP.O | | stock | Post_Holdings_Inc | POST.N | | stock | PPG工业 | PPG.N | | stock | PPL_Corporation | PPL.N | | stock | Primerica_Inc | PRI.N | | stock | Primo_Brands_Corporation | PRMB.N | | stock | Procore_Technologies_Inc | PCOR.N | | stock | Prosperity_Bancshares_Inc | PB.N | | stock | PTC_Inc | PTC.O | | stock | Public_Service_Enterprise_Group_Incorporated | PEG.N | | stock | Public_Storage | PSA.N | | stock | Pure_Storage_Inc | PSTG.N | | stock | PVH_Corp | PVH.N | | stock | Qnity_Electronics_Inc | Q.N | | stock | Qorvo_Inc | QRVO.O | | stock | QuantumScape_Corporation | QS.O | | stock | QXO_Inc | QXO.N | | stock | Ralliant_Corp | RAL.N | | stock | Range_Resources_Corporation | RRC.N | | stock | Rayonier_Inc | RYN.N | | stock | RBC_Bearings_Incorporated | RBC.N | | stock | Realty_Income_Corporation | O.N | | stock | Reddit_Inc | RDDT.N | | stock | Regal_Rexnord_Corporation | RRX.N | | stock | Regency_Centers_Corporation | REG.O | | stock | Regions_Financial_Corporation | RF.N | | stock | Reinsurance_Group_of_America_Incorporated | RGA.N | | stock | RenaissanceRe_Holdings_Ltd | RNR.N | | stock | Repligen_Corporation | RGEN.O | | stock | Revolution_Medicines_Inc | RVMD.O | | stock | Revvity_Inc | RVTY.N | | stock | Rexford_Industrial_Realty_Inc | REXR.N | | stock | Reynolds_Consumer_Products_Inc | REYN.O | | stock | Rh | RH.N | | stock | RingCentral_Inc | RNG.N | | stock | Rithm_Capital_Corp | RITM.N | | stock | Rivian_Automotive_Inc | RIVN.O | | stock | RLI_Corp | RLI.N | | stock | Robinhood_Markets_Inc | HOOD.O | | stock | Roblox_Corporation | RBLX.N | | stock | Rocket_Companies_Inc | RKT.N | | stock | Rocket_Lab_USA_Inc | RKLB.O | | stock | Roivant_Sciences_Ltd | ROIV.O | | stock | Roku_Inc | ROKU.O | | stock | Rollins_Inc | ROL.N | | stock | Ross_Stores_Inc | ROST.O | | stock | Royalty_Pharma_plc | RPRX.O | | stock | Royal_Gold_Inc | RGLD.O | | stock | RPM_International_Inc | RPM.N | | stock | Rubrik_Inc | RBRK.N | | stock | Ryan_Specialty_Holdings_Inc | RYAN.N | | stock | Saia_Inc | SAIA.O | | stock | SailPoint_Inc | SAIL.O | | stock | Samsara_Inc | IOT.N | | stock | SAP_SE | SAP.N | | stock | Sarepta_Therapeutics_Inc | SRPT.O | | stock | SBA_Communications_Corporation | SBAC.O | | stock | Schneider_National_Inc | SNDR.N | | stock | Science_Applications_International_Corporation | SAIC.O | | stock | Seaboard_Corporation | SEB.A | | stock | SEI_Investments_Company | SEIC.O | | stock | SentinelOne_Inc | S.N | | stock | ServiceNow_Inc | NOW.N | | stock | Service_Corporation_International | SCI.N | | stock | SharkNinja_Inc | SN.N | | stock | Shift4_Payments_Inc | FOUR.N | | stock | Silgan_Holdings_Inc | SLGN.N | | stock | Simpson_Manufacturing_Co_Inc | SSD.N | | stock | Sirius_XM_Holdings_Inc | SIRI.O | | stock | SiteOne_Landscape_Supply_Inc | SITE.N | | stock | SLM_Corporation | SLM.O | | stock | Smurfit_Westrock_Plc | SW.N | | stock | Snowflake_Inc | SNOW.N | | stock | SoFi_Technologies_Inc | SOFI.O | | stock | Solstice_Advanced_Materials_Inc | SOLS.O | | stock | Solventum_Corporation | SOLV.N | | stock | Somnigroup_International_Inc | SGI.N | | stock | SouthState_Corporation | SSB.N | | stock | SpaceX | SPCX.N | | stock | SPDR医疗保健精选行业ETF | XLV.A | | stock | SPDR标普500ETF信托 | SPY.A | | stock | SPDR标普地区银行ETF | KRE.A | | stock | SPDR标普银行ETF | KBE.A | | stock | SPDR欧洲斯托克50ETF | FEZ.A | | stock | SPDR科技精选行业ETF | XLK.A | | stock | SPDR能源精选行业ETF | XLE.A | | stock | SPDR金融精选行业ETF | XLF.A | | stock | Sprouts_Farmers_Market_Inc | SFM.O | | stock | SSC_Technologies_Holdings_Inc | SSNC.O | | stock | STAG_Industrial_Inc | STAG.N | | stock | StandardAero_Inc | SARO.N | | stock | Starwood_Property_Trust_Inc | STWD.N | | stock | Stifel_Financial_Corp | SF.N | | stock | Strategy_Inc | MSTR.O | | stock | Summit_Therapeutics_Inc | SMMT.O | | stock | Sun_Communities_Inc | SUI.N | | stock | Synchrony_Financial | SYF.N | | stock | T-Mobile_US_Inc | TMUS.O | | stock | Take-Two_Interactive_Software_Inc | TTWO.O | | stock | Talen_Energy_Corporation | TLN.O | | stock | Targa_Resources_Corp | TRGP.N | | stock | TD_SYNNEX_Corporation | SNX.N | | stock | TechnipFMC_plc | FTI.N | | stock | Tempus_AI_Inc | TEM.O | | stock | Tenet_Healthcare_Corporation | THC.N | | stock | Tetra_Tech_Inc | TTEK.O | | stock | Texas_Pacific_Land_Corporation | TPL.N | | stock | Texas_Roadhouse_Inc | TXRH.O | | stock | TFS_Financial_Corporation | TFSL.O | | stock | The_AES_Corporation | AES.N | | stock | The_Hanover_Insurance_Group_Inc | THG.N | | stock | The_Middleby_Corporation | MIDD.O | | stock | The_PNC_Financial_Services_Group_Inc | PNC.N | | stock | The_TJX_Companies_Inc | TJX.N | | stock | The_Trade_Desk_Inc | TTD.O | | stock | Thor_Industries_Inc | THO.N | | stock | TKO_Group_Holdings_Inc | TKO.N | | stock | Toast_Inc | TOST.N | | stock | TopBuild_Corp | BLD.N | | stock | Tower半导体 | TSEM.N | | stock | TPG_Inc | TPG.O | | stock | Tractor_Supply_Company | TSCO.O | | stock | Tradeweb_Markets_Inc | TW.O | | stock | TransDigm_Group_Incorporated | TDG.N | | stock | Travel__Leisure_Co | TNL.N | | stock | Trex_Company_Inc | TREX.N | | stock | Truist_Financial_Corporation | TFC.N | | stock | Trump_Media__Technology_Group_Corp | DJT.O | | stock | Twilio_Inc | TWLO.N | | stock | Tyler_Technologies_Inc | TYL.N | | stock | U-Haul_Holding_Company | UHAL.N | | stock | Ubiquiti_Inc | UI.N | | stock | UDR_Inc | UDR.N | | stock | UGI_Corporation | UGI.N | | stock | UiPath_Inc | PATH.N | | stock | Ulta_Beauty_Inc | ULTA.O | | stock | Ultragenyx_Pharmaceutical_Inc | RARE.O | | stock | United_Therapeutics_Corporation | UTHR.O | | stock | Unity_Software_Inc | U.N | | stock | Universal_Display_Corporation | OLED.O | | stock | Universal_Health_Services_Inc | UHS.N | | stock | Unum_Group | UNM.N | | stock | US_Foods_Holding_Corp | USFD.N | | stock | UWM_Holdings_Corporation | UWMC.N | | stock | Valmont_Industries_Inc | VMI.N | | stock | VanEck半导体ETF | SMH.O | | stock | Veeva_Systems_Inc | VEEV.N | | stock | Ventas_Inc | VTR.N | | stock | Veralto_Corporation | VLTO.N | | stock | VICI_Properties_Inc | VICI.N | | stock | Viking_Holdings_Ltd | VIK.N | | stock | Viking_Therapeutics_Inc | VKTX.O | | stock | Viper_Energy_Inc | VNOM.O | | stock | Virtu_Financial_Inc | VIRT.N | | stock | Vistra_Corp | VST.N | | stock | Vontier_Corporation | VNT.N | | stock | Vornado_Realty_Trust | VNO.N | | stock | Voya_Financial_Inc | VOYA.N | | stock | Watsco_Inc | WSO.N | | stock | Wayfair_Inc | W.N | | stock | Webster_Financial_Corporation | WBS.N | | stock | WEC_Energy_Group_Inc | WEC.N | | stock | Welltower_Inc | WELL.N | | stock | WESCO_International_Inc | WCC.N | | stock | Western_Alliance_Bancorporation | WAL.N | | stock | WEX_Inc | WEX.N | | stock | White_Mountains_Insurance_Group_Ltd | WTM.N | | stock | WillScot_Holdings_Corporation | WSC.O | | stock | Wingstop_Inc | WING.O | | stock | Wintrust_Financial_Corporation | WTFC.O | | stock | Workday_Inc | WDAY.O | | stock | W_P_Carey_Inc | WPC.N | | stock | W_R_Berkley_Corporation | WRB.N | | stock | Xcel_Energy_Inc | XEL.O | | stock | XPO_Logistics_Inc | XPO.N | | stock | XP_Inc | XP.O | | stock | YETI_Holdings_Inc | YETI.N | | stock | Zillow_Group_Inc_Class_A | ZG.O | | stock | Zillow_Group_Inc_Class_C | Z.O | | stock | Zions_Bancorporation_National_Association | ZION.O | | stock | Zoom_Communications_Inc | ZM.O | | stock | Zscaler_Inc | ZS.O | | stock | 万事达 | MA.N | | stock | 万宝盛华 | MAN.N | | stock | 万豪国际 | MAR.O | | stock | 三井住友金融集团 | SMFG.N | | stock | 三倍做多纳斯达克100ETF-ProShare | TQQQ.O | | stock | 三菱日联金融集团 | MUFG.N | | stock | 世邦魏理仕 | CBRE.N | | stock | 丘博 | CB.N | | stock | 丰田汽车 | TM.N | | stock | 丹纳赫 | DHR.N | | stock | 五三银行 | FITB.O | | stock | 亚什兰 | ASH.N | | stock | 亚德诺半导体 | ADI.O | | stock | 亚玛芬体育 | AS.N | | stock | 亚马逊 | AMZN.O | | stock | 亨利香恩 | HSIC.O | | stock | 亨廷顿英格尔斯 | HII.N | | stock | 亨斯迈 | HUN.N | | stock | 京东 | JD.O | | stock | 亿客行 | EXPE.O | | stock | 亿滋国际 | MDLZ.O | | stock | 仲量联行 | JLL.N | | stock | 伊利诺伊工具 | ITW.N | | stock | 伊塔乌联合银行 | ITUB.N | | stock | 伊士曼化工 | EMN.N | | stock | 伊格尔矿业 | AEM.N | | stock | 伊萨 | ESAB.N | | stock | 伊顿 | ETN.N | | stock | 伍德沃德 | WWD.O | | stock | 优步 | UBER.N | | stock | 伟创力 | FLEX.O | | stock | 伯乐 | BIO.N | | stock | 伯克希尔哈撒韦 | BRK-B.N | | stock | 佳明 | GRMN.N | | stock | 保德信金融 | PRU.N | | stock | 信安金融 | PFG.O | | stock | 信实钢铁 | RS.N | | stock | 信诺 | CI.N | | stock | 信达思 | CTAS.O | | stock | 元平台 | META.O | | stock | 克兰 | CR.N | | stock | 克利夫兰克里夫斯 | CLF.N | | stock | 克罗格 | KR.N | | stock | 全球X-MSCI阿根廷ETF | ARGT.A | | stock | 共和服务 | RSG.N | | stock | 再生元 | REGN.O | | stock | 冠城国际 | CCI.N | | stock | 凯悦酒店 | H.N | | stock | 凯撒娱乐 | CZR.O | | stock | 凯雷集团 | CG.O | | stock | 利安德巴塞尔 | LYB.N | | stock | 前进保险 | PGR.N | | stock | 力特 | LFUS.O | | stock | 劳氏 | LOW.N | | stock | 勃肯 | BIRK.N | | stock | 北方信托 | NTRS.O | | stock | 华丽七号ETF | MAGS.BAT | | stock | 华纳兄弟探索 | WBD.O | | stock | 华美银行 | EWBC.O | | stock | 南方公司 | SO.N | | stock | 南方铜业 | SCCO.N | | stock | 博思艾伦 | BAH.N | | stock | 博格华纳 | BWA.N | | stock | 博通 | AVGO.O | | stock | 卡夫亨氏 | KHC.O | | stock | 卡特彼勒 | CAT.N | | stock | 卡骆驰 | CROX.O | | stock | 印孚瑟斯 | INFY.N | | stock | 可口可乐 | KO.N | | stock | 可口可乐欧洲太平洋合伙 | CCEP.O | | stock | 台积电 | TSM.N | | stock | 史丹利百得 | SWK.N | | stock | 史密斯 | AOS.N | | stock | 史密斯菲尔德 | SFD.O | | stock | 史赛克 | STE.N | | stock | 史赛克 | SYK.N | | stock | 吉利德科学 | GILD.O | | stock | 味好美 | MKC.N | | stock | 哈勃 | HUBB.N | | stock | 哈特福德金融 | HIG.N | | stock | 哈里伯顿 | HAL.N | | stock | 哈里斯 | LHX.N | | stock | 哈门那 | HUM.N | | stock | 哈雷戴维森 | HOG.N | | stock | 哥伦比亚运动服装 | COLM.O | | stock | 唐纳森 | DCI.N | | stock | 嘉信理财 | SCHW.N | | stock | 嘉年华邮轮 | CCL.N | | stock | 因美纳 | ILMN.O | | stock | 固安捷 | GWW.N | | stock | 固瑞克 | GGG.N | | stock | 国际商业机器 | IBM.N | | stock | 国际纸业 | IP.N | | stock | 国际香料香精 | IFF.N | | stock | 埃克森美孚 | XOM.N | | stock | 埃森哲 | ACN.N | | stock | 塔吉特 | TGT.N | | stock | 塞拉尼斯 | CE.N | | stock | 多佛 | DOV.N | | stock | 多邻国 | DUOL.O | | stock | 大都会人寿 | MET.N | | stock | 天宝 | TRMB.O | | stock | 天睿 | TDC.N | | stock | 太平洋瓦电 | PCG.N | | stock | 奈飞 | NFLX.O | | stock | 奎斯特诊断 | DGX.N | | stock | 奥多比 | ADBE.O | | stock | 奥林 | OLN.N | | stock | 奥的斯 | OTIS.N | | stock | 奥驰亚 | MO.N | | stock | 好事达 | ALL.N | | stock | 好市多 | COST.O | | stock | 好时 | HSY.N | | stock | 威富 | VFC.N | | stock | 威廉姆斯 | WMB.N | | stock | 威廉姆斯索诺玛 | WSM.N | | stock | 威德福 | WFRD.O | | stock | 威瑞信 | VRSN.O | | stock | 威瑞森 | VZ.N | | stock | 威达信 | MRSH.N | | stock | 孩之宝 | HAS.O | | stock | 安姆科 | AMCR.N | | stock | 安富利 | AVT.O | | stock | 安德玛 | UA.N | | stock | 安德玛 | UAA.N | | stock | 安捷伦科技 | A.N | | stock | 安森美 | ON.O | | stock | 安波福 | APTV.N | | stock | 安特吉 | ETR.N | | stock | 安硕iBoxx投资级公司债ETF | LQD.A | | stock | 安硕iBoxx高收益公司债ETF | HYG.A | | stock | 安硕MSCI台湾ETF | EWT.A | | stock | 安硕MSCI巴西ETF | EWZ.A | | stock | 安硕MSCI日本ETF | EWJ.A | | stock | 安硕MSCI英国ETF | EWU.A | | stock | 安硕半导体ETF | SOXX.O | | stock | 安硕生物科技ETF | IBB.O | | stock | 安硕罗素2000ETF | IWM.A | | stock | 安费诺 | APH.N | | stock | 安进 | AMGN.O | | stock | 安靠科技 | AMKR.O | | stock | 安飞士 | CAR.O | | stock | 宏盟集团 | OMC.N | | stock | 宜瑞安 | INGR.N | | stock | 宝洁 | PG.N | | stock | 实耐宝 | SNA.N | | stock | 家得宝 | HD.N | | stock | 宾利系统 | BSY.O | | stock | 宾士域 | BC.N | | stock | 富兰克林资源 | BEN.N | | stock | 富国银行 | WFC.N | | stock | 富美实 | FMC.N | | stock | 小型黄金矿业ETF-VanEck | GDXJ.A | | stock | 小鹏汽车 | XPEV.N | | stock | 布鲁克 | BRKR.O | | stock | 希尔顿 | HLT.N | | stock | 希悦尔 | SEE.N | | stock | 希捷科技 | STX.O | | stock | 帕卡 | PCAR.O | | stock | 广达服务 | PWR.N | | stock | 库珀 | COO.O | | stock | 应用光电 | AAOI.N | | stock | 应用材料 | AMAT.O | | stock | 废物管理 | WM.N | | stock | 康卡斯特 | CMCSA.O | | stock | 康宁 | GLW.N | | stock | 康尼格拉 | CAG.N | | stock | 康德乐 | CAH.N | | stock | 康捷空 | EXPD.N | | stock | 康明斯 | CMI.N | | stock | 康耐视 | CGNX.O | | stock | 康菲石油 | COP.N | | stock | 开利 | CARR.N | | stock | 弗格森 | FERG.N | | stock | 强生 | JNJ.N | | stock | 徕博科 | LH.N | | stock | 微芯科技 | MCHP.O | | stock | 微软 | MSFT.O | | stock | 德事隆 | TXT.N | | stock | 德克斯户外 | DECK.N | | stock | 德州仪器 | TXN.O | | stock | 德康医疗 | DXCM.O | | stock | 德文能源 | DVN.N | | stock | 必和必拓 | BHP.N | | stock | 快扣 | FAST.O | | stock | 思佳讯 | SWKS.O | | stock | 思科 | CSCO.O | | stock | 怡安 | AON.N | | stock | 怪兽饮料 | MNST.O | | stock | 恩智浦 | NXPI.O | | stock | 恩特格里斯 | ENTG.O | | stock | 惠好 | WY.N | | stock | 惠普 | HPQ.N | | stock | 惠而浦 | WHR.N | | stock | 意法半导体 | STM.N | | stock | 慧与 | HPE.N | | stock | 戴尔科技 | DELL.N | | stock | 托尔兄弟 | TOL.N | | stock | 托罗 | TTC.N | | stock | 拉夫劳伦 | RL.N | | stock | 拉扎德 | LAZ.N | | stock | 拼多多 | PDD.O | | stock | 挪威邮轮 | NCLH.N | | stock | 捷普 | JBL.N | | stock | 捷迈邦美 | ZBH.N | | stock | 携程 | TCOM.O | | stock | 摩托罗拉系统 | MSI.N | | stock | 摩根士丹利 | MS.N | | stock | 摩根大通 | JPM.N | | stock | 摩森康胜 | TAP.N | | stock | 斑马技术 | ZBRA.O | | stock | 斯伦贝谢 | SLB.N | | stock | 斯马克 | SJM.N | | stock | 新思科技 | SNPS.O | | stock | 新纪元能源 | NEE.N | | stock | 新闻集团 | NWS.O | | stock | 新闻集团 | NWSA.O | | stock | 旅行者保险 | TRV.N | | stock | 日月光投控 | ASX.N | | stock | 昂跑 | ONON.N | | stock | 明晟 | MSCI.N | | stock | 易昆尼克斯 | EQIX.O | | stock | 易贝 | EBAY.O | | stock | 星巴克 | SBUX.O | | stock | 星座品牌 | STZ.N | | stock | 是德科技 | KEYS.N | | stock | 晖致 | VTRS.O | | stock | 晨星 | MORN.O | | stock | 普信集团 | TROW.O | | stock | 普尔特房屋 | PHM.N | | stock | 普洛斯 | PLD.N | | stock | 景顺 | IVZ.N | | stock | 景顺QQQ信托ETF | QQQ.O | | QQQ stock | 李尔 | LEA.N | | stock | 杜克能源 | DUK.N | | stock | 杜比实验室 | DLB.N | | stock | 杜邦 | DD.N | | stock | 杰富瑞 | JEF.N | | stock | 林德 | LIN.O | | stock | 林肯国民 | LNC.N | | stock | 林肯电气 | LECO.O | | stock | 查尔斯河实验室 | CRL.N | | stock | 柯蒂斯莱特 | CW.N | | stock | 标普全球 | SPGI.N | | stock | 标普北美科技软件ETF | IGV.BAT | | stock | 标普生物科技ETF-SPDR | XBI.A | | stock | 格芯 | GFS.O | | stock | 桑坦德银行 | SAN.N | | stock | 桑普拉能源 | SRE.N | | stock | 梅特勒托利多 | MTD.N | | stock | 梅西百货 | M.N | | stock | 森萨塔科技 | ST.N | | stock | 楷登电子 | CDNS.O | | stock | 欧加隆 | OGN.N | | stock | 欧文斯科宁 | OC.N | | stock | 欧特克 | ADSK.O | | stock | 永利度假村 | WYNN.O | | stock | 汇丰控股 | HSBC.N | | stock | 江森自控 | JCI.N | | stock | 汤森路透 | TRI.O | | stock | 汽车地带 | AZO.N | | stock | 沃尔玛 | WMT.O | | stock | 沃特世 | WAT.N | | stock | 泛林 | LRCX.O | | stock | 泛美白银 | PAAS.N | | stock | 波士顿啤酒 | SAM.N | | stock | 波士顿科学 | BSX.N | | stock | 波尔 | BALL.N | | stock | 波音 | BA.N | | stock | 泰佩思琪 | TPR.N | | stock | 泰利福 | TFX.N | | stock | 泰利达 | TDY.N | | stock | 泰森食品 | TSN.N | | stock | 泰瑞达 | TER.O | | stock | 泰科电子 | TEL.N | | stock | 洛克希德马丁 | LMT.N | | stock | 洲际交易所 | ICE.N | | stock | 派克汉尼汾 | PH.N | | stock | 派拓网络 | PANW.O | | stock | 海力士 | SKHY.N | | stock | 淡水河谷 | VALE.N | | stock | 渤健 | BIIB.O | | stock | 温德姆酒店 | WH.N | | stock | 温蒂 | WEN.O | | stock | 滨特尔 | PNR.N | | stock | 火神材料 | VMC.N | | stock | 爱克斯龙 | EXC.O | | stock | 爱彼迎 | ABNB.O | | stock | 爱德华生命科学 | EW.N | | stock | 爱德士 | IDXX.O | | stock | 爱玛客 | ARMK.N | | stock | 爱科 | AGCO.N | | stock | 爱迪生国际 | EIX.N | | stock | 爱齐科技 | ALGN.O | | stock | 特斯拉 | TSLA.O | | stock | 特灵科技 | TT.N | | stock | 环球支付 | GPN.N | | stock | 环联 | TRU.N | | stock | 理想汽车 | LI.O | | stock | 瑞思迈 | RMD.N | | stock | 瑞杰金融 | RJF.N | | stock | 瑞穗金融集团 | MFG.N | | stock | 瓦莱罗能源 | VLO.N | | stock | 甲骨文 | ORCL.N | | stock | 登士柏西诺德 | XRAY.O | | stock | 百事可乐 | PEP.O | | 百事 stock | 百富门 | BF-A.N | | stock | 百富门 | BF-B.N | | stock | 百度 | BIDU.O | | stock | 百思买 | BBY.N | | stock | 百时美施贵宝 | BMY.N | | stock | 百特国际 | BAX.N | | stock | 百胜餐饮 | YUM.N | | stock | 皇冠控股 | CCK.N | | stock | 皇家加勒比 | RCL.N | | stock | 盈透证券 | IBKR.O | | stock | 盖璞 | GAP.N | | stock | 盖茨工业 | GTES.N | | stock | 直觉外科 | ISRG.O | | stock | 真品零件 | GPC.N | | stock | 硕腾 | ZTS.N | | stock | 碧迪 | BDX.N | | stock | 礼来 | LLY.N | | stock | 礼蓝动保 | ELAN.N | | stock | 福克斯 | FOX.O | | stock | 福克斯 | FOXA.O | | stock | 福斯 | FLS.N | | stock | 福泰制药 | VRTX.O | | stock | 福特汽车 | F.N | | stock | 福迪威 | FTV.N | | stock | 科睿唯安 | CLVT.N | | stock | 科磊 | KLAC.O | | stock | 科蒂 | COTY.N | | stock | 科赴 | KVUE.N | | stock | 科迪华 | CTVA.N | | stock | 穆迪 | MCO.N | | stock | 空气化工产品 | APD.N | | stock | 第一太阳能 | FSLR.O | | stock | 第一能源 | FE.N | | stock | 第一资本 | COF.N | | stock | 精选国际酒店 | CHH.N | | stock | 索尼 | SONY.N | | stock | 索诺科 | SON.N | | stock | 纳斯达克 | NDAQ.O | | stock | 纽威品牌 | NWL.O | | stock | 纽柯 | NUE.N | | stock | 纽约时报 | NYT.N | | stock | 纽约梅隆银行 | BK.N | | stock | 纽蒙特 | NEM.N | | stock | 维尔度假村 | MTN.N | | stock | 维萨 | V.N | | stock | 维谛 | VRT.N | | stock | 绿山咖啡 | KDP.O | | stock | 缤客控股 | BKNG.O | | stock | 网易 | NTES.O | | stock | 罗克韦尔自动化 | ROK.N | | stock | 罗宾逊全球物流 | CHRW.O | | stock | 罗柏科技 | ROP.O | | stock | 罗氏 | L.N | | stock | 罗致恒富 | RHI.N | | stock | 美元将军 | DG.N | | stock | 美元树 | DLTR.O | | stock | 美光科技 | MU.O | | stock | 美国全球喷气机ETF | JETS.A | | stock | 美国合众银行 | USB.N | | stock | 美国国际集团 | AIG.N | | stock | 美国家庭人寿保险 | AFL.N | | stock | 美国普信金融 | AMP.N | | stock | 美国水务 | AWK.N | | stock | 美国电力 | AEP.O | | stock | 美国电塔 | AMT.N | | stock | 美国电话电报 | T.N | | stock | 美国航空 | AAL.O | | stock | 美国运通 | AXP.N | | stock | 美国铝业 | AA.N | | stock | 美国银行 | BAC.N | | stock | 美敦力 | MDT.N | | stock | 美泰 | MAT.O | | stock | 美满电子 | MRVL.O | | stock | 美瑞科 | SMG.N | | stock | 美盛 | MOS.N | | stock | 美联航 | UAL.O | | stock | 美高梅 | MGM.N | | stock | 耐克 | NKE.N | | stock | 联合健康 | UNH.N | | stock | 联合利华 | UL.N | | stock | 联合包裹 | UPS.N | | stock | 联合太平洋 | UNP.N | | stock | 联合爱迪生 | ED.N | | stock | 联合租赁 | URI.N | | stock | 联电 | UMC.N | | stock | 联邦快递 | FDX.N | | stock | 胜牌 | VVV.N | | stock | 自动数据处理 | ADP.O | | stock | 自由港麦克莫兰 | FCX.N | | stock | 艺康 | ECL.N | | stock | 艺电 | EA.O | | stock | 艾伯维 | ABBV.N | | stock | 艾利丹尼森 | AVY.N | | stock | 艾可飞 | EFX.N | | stock | 艾奕康 | ACM.N | | stock | 艾昆纬 | IQV.N | | stock | 艾睿电子 | ARW.N | | stock | 艾迪科 | IEX.N | | stock | 艾里逊变速箱 | ALSN.N | | stock | 艾默生电气 | EMR.N | | stock | 芝加哥商品交易所 | CME.O | | stock | 芯源系统 | MPWR.O | | stock | 花旗集团 | C.N | | stock | 英伟达 | NVDA.O | | NVDA stock | 英格索兰 | IR.N | | stock | 英特尔 | INTC.O | | stock | 英迈 | INGM.N | | stock | 苹果公司 | AAPL.O | | 苹果 stock | 荷美尔食品 | HRL.N | | stock | 莫德纳 | MRNA.O | | stock | 莫霍克工业 | MHK.N | | stock | 莱德 | R.N | | stock | 莱纳 | LEN-B.N | | stock | 莱纳 | LEN.N | | stock | 莱迪思半导体 | LSCC.O | | stock | 菲利普斯66 | PSX.N | | stock | 菲利普莫里斯 | PM.N | | stock | 西南航空 | LUV.N | | stock | 西屋制动 | WAB.N | | stock | 西斯科 | SYY.N | | stock | 西方石油 | OXY.N | | stock | 西氏医药 | WST.N | | stock | 西湖化学 | WLK.N | | stock | 西维斯健康 | CVS.N | | stock | 西联汇款 | WU.N | | stock | 西蒙地产 | SPG.N | | stock | 西部数据 | WDC.O | | stock | 詹姆斯哈迪 | JHX.N | | stock | 诺信 | NDSN.O | | stock | 诺华 | NVS.N | | stock | 诺和诺德 | NVO.N | | stock | 诺斯罗普格鲁曼 | NOC.N | | stock | 诺福克南方 | NSC.N | | stock | 谷歌 | GOOG.O | | stock | 谷歌 | GOOGL.O | | stock | 豪士科 | OSK.N | | stock | 豪洛捷 | HOLX.O | | stock | 豪迈航空 | HWM.N | | stock | 贝克休斯 | BKR.O | | stock | 贝壳 | BEKE.N | | stock | 贝宝 | PYPL.O | | stock | 贝莱德 | BLK.N | | stock | 财捷 | INTU.O | | stock | 贰陆 | COHR.N | | stock | 费哲金融 | FISV.O | | stock | 费埃哲 | FICO.N | | stock | 赛富时 | CRM.N | | stock | 赛莱默 | XYL.N | | stock | 赛默飞世尔 | TMO.N | | stock | 赫氏 | HXL.N | | stock | 超威半导体 | AMD.O | | stock | 超微电脑 | SMCI.O | | stock | 辉瑞 | PFE.N | | stock | 达登餐饮 | DRI.N | | stock | 达维塔 | DVA.N | | stock | 达美乐 | DPZ.O | | stock | 达美航空 | DAL.N | | stock | 迈心诺 | MASI.O | | stock | 迪士尼 | DIS.N | | stock | 迪尔 | DE.N | | stock | 通用动力 | GD.N | | stock | 通用汽车 | GM.N | | stock | 通用电气医疗 | GEHC.O | | stock | 通用电气航空 | GE.N | | stock | 通用磨坊 | GIS.N | | stock | 道富 | STT.N | | stock | 道明尼能源 | D.N | | stock | 道达尔能源 | TTE.N | | stock | 邦吉 | BG.N | | stock | 酷澎 | CPNG.N | | stock | 金佰利 | KMB.O | | stock | 金宝汤 | CPB.O | | stock | 金德摩根 | KMI.N | | stock | 金沙集团 | LVS.N | | stock | 钢铁动力 | STLD.O | | stock | 铁姆肯 | TKR.N | | stock | 铁山 | IRM.N | | stock | 铱星通讯 | IRDM.O | | stock | 镜泰 | GNTX.O | | stock | 闪迪 | SNDK.O | | stock | 阿卡迈 | AKAM.O | | stock | 阿姆斯壮世界工业 | AWI.N | | stock | 阿彻丹尼尔斯米德兰 | ADM.N | | stock | 阿拉斯加航空 | ALK.N | | stock | 阿斯利康 | AZN.N | | stock | 阿斯麦 | ASML.O | | stock | 阿波罗全球管理 | APO.N | | stock | 阿美特克 | AME.N | | stock | 阿莫林 | AEE.N | | stock | 阿里巴巴 | BABA.N | | stock | 陶氏 | DOW.N | | stock | 雅保 | ALB.N | | stock | 雅各布斯 | J.N | | stock | 雅培 | ABT.N | | stock | 雅诗兰黛 | EL.N | | stock | 雪佛龙 | CVX.N | | stock | 雷多斯 | LDOS.N | | stock | 雷神技术 | RTX.N | | stock | 雷诺士 | LII.N | | stock | 霍尼韦尔 | HON.O | | stock | 霍顿房屋 | DHI.N | | stock | 露露柠檬 | LULU.O | | stock | 韦莱韬悦 | WTW.O | | stock | 韦里斯克 | VRSK.O | | stock | 韩国ETF-iShares-MSCI | EWY.A | | stock | 飞塔 | FTNT.O | | stock | 马克尔 | MKL.N | | stock | 马拉松石油 | MPC.N | | stock | 马斯科 | MAS.N | | stock | 高乐氏 | CLX.N | | stock | 高德纳 | IT.N | | stock | 高盛 | GS.N | | stock | 高知特 | CTSH.O | | stock | 高通 | QCOM.O | | stock | 高露洁棕榄 | CL.N | | stock | 麦克森 | MCK.N | | stock | 麦当劳 | MCD.N | | stock | 黄金ETF-SPDR | GLD.A | | stock | 黄金矿业ETF-VanEck | GDX.A | | stock | 黑石集团 | BX.N | | stock | 默克 | MRK.N | |
-
-
index_info_catalog
-
a_share_financials.yaml 112.8 KB
# Auto-generated business catalog; do not edit records by hand. schema_version: 2 category: a_share_financials record_count: 980 records: - {id: "68d26fd5c80a86bb381a163e", index_title: "A股一年内到期的可转换公司债券〔报告期现金流量表〕"} - {id: "68d27009c80a86bb381a226a", index_title: "A股一年内到期的可转换公司债券:按发布日〔报告期现金流量表〕"} - {id: "68d260b1c80a86bb3815b644", index_title: "A股一年内到期的非流动负债〔报告期资产负债表〕"} - {id: "68da5752c80a86bb38ccc19a", index_title: "A股一年内到期的非流动负债:单季〔资产负债表〕"} - {id: "68da5755c80a86bb38ccc1e1", index_title: "A股一年内到期的非流动负债:单季:按发布日〔资产负债表〕"} - {id: "68d260cdc80a86bb3815d587", index_title: "A股一年内到期的非流动负债:按发布日〔报告期资产负债表〕"} - {id: "68d2606bc80a86bb381595d7", index_title: "A股一年内到期的非流动资产〔报告期资产负债表〕"} - {id: "68da574cc80a86bb38ccc11b", index_title: "A股一年内到期的非流动资产:单季〔资产负债表〕"} - {id: "68da574fc80a86bb38ccc154", index_title: "A股一年内到期的非流动资产:单季:按发布日〔资产负债表〕"} - {id: "68d2608fc80a86bb381599e7", index_title: "A股一年内到期的非流动资产:按发布日〔报告期资产负债表〕"} - {id: "68d26142c80a86bb3815f35d", index_title: "A股一般风险准备〔报告期资产负债表〕"} - {id: "68da5763c80a86bb38ccc347", index_title: "A股一般风险准备:单季〔资产负债表〕"} - {id: "68da5766c80a86bb38ccc382", index_title: "A股一般风险准备:单季:按发布日〔资产负债表〕"} - {id: "68d2614ec80a86bb3815f433", index_title: "A股一般风险准备:按发布日〔报告期资产负债表〕"} - {id: "68d268c7c80a86bb3817c068", index_title: "A股专项储备〔报告期资产负债表〕"} - {id: "68da5801c80a86bb38cce832", index_title: "A股专项储备:单季〔资产负债表〕"} - {id: "68da5803c80a86bb38cce86d", index_title: "A股专项储备:单季:按发布日〔资产负债表〕"} - {id: "68d2692bc80a86bb381877f6", index_title: "A股专项储备:按发布日〔报告期资产负债表〕"} - {id: "68d2687ec80a86bb38177e99", index_title: "A股专项应付款〔报告期资产负债表〕"} - {id: "68da57fbc80a86bb38cce7a3", index_title: "A股专项应付款:单季〔资产负债表〕"} - {id: "68da57fec80a86bb38cce7e6", index_title: "A股专项应付款:单季:按发布日〔资产负债表〕"} - {id: "68d26896c80a86bb381780cf", index_title: "A股专项应付款:按发布日〔报告期资产负债表〕"} - {id: "68d25551c80a86bb38122b2a", index_title: "A股买入返售金融资产〔报告期资产负债表〕"} - {id: "68da5636c80a86bb38cc875e", index_title: "A股买入返售金融资产:单季〔资产负债表〕"} - {id: "68da5639c80a86bb38cc8796", index_title: "A股买入返售金融资产:单季:按发布日〔资产负债表〕"} - {id: "68d2556ac80a86bb38122d44", index_title: "A股买入返售金融资产:按发布日〔报告期资产负债表〕"} - {id: "68d26bf6c80a86bb38197cca", index_title: "A股交易性金融负债〔报告期资产负债表〕"} - {id: "68da5848c80a86bb38ccefc2", index_title: "A股交易性金融负债:单季〔资产负债表〕"} - {id: "68da584bc80a86bb38cceff7", index_title: "A股交易性金融负债:单季:按发布日〔资产负债表〕"} - {id: "68d26c0bc80a86bb38197e3a", index_title: "A股交易性金融负债:按发布日〔报告期资产负债表〕"} - {id: "68d26bbdc80a86bb3819787f", index_title: "A股交易性金融资产〔报告期资产负债表〕"} - {id: "68da5843c80a86bb38ccef29", index_title: "A股交易性金融资产:单季〔资产负债表〕"} - {id: "68da5845c80a86bb38ccef74", index_title: "A股交易性金融资产:单季:按发布日〔资产负债表〕"} - {id: "68d26bddc80a86bb38197b2c", index_title: "A股交易性金融资产:按发布日〔报告期资产负债表〕"} - {id: "68d2657cc80a86bb3816e9d9", index_title: "A股代理买卖证券款〔报告期资产负债表〕"} - {id: "68da57bfc80a86bb38ccdb6b", index_title: "A股代理买卖证券款:单季〔资产负债表〕"} - {id: "68da57c1c80a86bb38ccdca3", index_title: "A股代理买卖证券款:单季:按发布日〔资产负债表〕"} - {id: "68d265a3c80a86bb38170c20", index_title: "A股代理买卖证券款:按发布日〔报告期资产负债表〕"} - {id: "68d2663ec80a86bb381718e6", index_title: "A股代理承销证券款〔报告期资产负债表〕"} - {id: "68da57c9c80a86bb38cce26f", index_title: "A股代理承销证券款:单季〔资产负债表〕"} - {id: "68da57ccc80a86bb38cce2b0", index_title: "A股代理承销证券款:单季:按发布日〔资产负债表〕"} - {id: "68d2666cc80a86bb38171edd", index_title: "A股代理承销证券款:按发布日〔报告期资产负债表〕"} - {id: "68d8cdb4c80a86bb3888eebf", index_title: "A股价值变动净收益/利润总额:单季〔财务指标〕"} - {id: "68d8cdb6c80a86bb3888ef06", index_title: "A股价值变动净收益/利润总额:单季:按发布日〔财务指标〕"} - {id: "68d8cdf3c80a86bb3888f46c", index_title: "A股价值变动净收益:单季〔财务指标〕"} - {id: "68d8cdf5c80a86bb3888f4af", index_title: "A股价值变动净收益:单季:按发布日〔财务指标〕"} - {id: "68d2650cc80a86bb3816cacd", index_title: "A股优先股-所有者权益〔报告期资产负债表〕"} - {id: "68da57b2c80a86bb38ccd5be", index_title: "A股优先股-所有者权益:单季〔资产负债表〕"} - {id: "68da57b5c80a86bb38ccd793", index_title: "A股优先股-所有者权益:单季:按发布日〔资产负债表〕"} - {id: "68d26525c80a86bb3816cccd", index_title: "A股优先股-所有者权益:按发布日〔报告期资产负债表〕"} - {id: "68d26c47c80a86bb3819852d", index_title: "A股使用权资产〔报告期资产负债表〕"} - {id: "68da5854c80a86bb38ccf0d5", index_title: "A股使用权资产:单季〔资产负债表〕"} - {id: "68da5856c80a86bb38ccf11a", index_title: "A股使用权资产:单季:按发布日〔资产负债表〕"} - {id: "68d26c5bc80a86bb381988d3", index_title: "A股使用权资产:按发布日〔报告期资产负债表〕"} - {id: "68d24ee0c80a86bb3810493c", index_title: "A股保单红利支出〔报告期利润表〕"} - {id: "68da551ec80a86bb38cc6cbb", index_title: "A股保单红利支出:单季〔利润表〕"} - {id: "68da5521c80a86bb38cc6cf1", index_title: "A股保单红利支出:单季:按发布日〔利润表〕"} - {id: "68d24ef7c80a86bb38104b12", index_title: "A股保单红利支出:按发布日〔报告期利润表〕"} - {id: "68d2779fc80a86bb381b711e", index_title: "A股保户储金及投资款净增加额〔报告期现金流量表〕"} - {id: "68da5977c80a86bb38cd3501", index_title: "A股保户储金及投资款净增加额:单季〔现金流量表〕"} - {id: "68da597ac80a86bb38cd3543", index_title: "A股保户储金及投资款净增加额:单季:按发布日〔现金流量表〕"} - {id: "68d277b2c80a86bb381b7249", index_title: "A股保户储金及投资款净增加额:按发布日〔报告期现金流量表〕"} - {id: "68d25b83c80a86bb38147926", index_title: "A股保险合同准备金〔报告期资产负债表〕"} - {id: "68da56e2c80a86bb38ccb664", index_title: "A股保险合同准备金:单季〔资产负债表〕"} - {id: "68da56e4c80a86bb38ccb6a5", index_title: "A股保险合同准备金:单季:按发布日〔资产负债表〕"} - {id: "68d25baec80a86bb381482fd", index_title: "A股保险合同准备金:按发布日〔报告期资产负债表〕"} - {id: "68d2701ec80a86bb381a2dd9", index_title: "A股信用减值损失(现金流量表补充科目)〔报告期现金流量表〕"} - {id: "68d27039c80a86bb381a3c43", index_title: "A股信用减值损失(现金流量表补充科目):按发布日〔报告期现金流量表〕"} - {id: "68d246c9c80a86bb380ed564", index_title: "A股信用减值损失〔报告期利润表〕"} - {id: "68da547cc80a86bb38cc5791", index_title: "A股信用减值损失:单季〔利润表〕"} - {id: "68da547fc80a86bb38cc588a", index_title: "A股信用减值损失:单季:按发布日〔利润表〕"} - {id: "68d24719c80a86bb380edd12", index_title: "A股信用减值损失:按发布日〔报告期利润表〕"} - {id: "68d27052c80a86bb381a4266", index_title: "A股债务转为资本〔报告期现金流量表〕"} - {id: "68d27071c80a86bb381a4cba", index_title: "A股债务转为资本:按发布日〔报告期现金流量表〕"} - {id: "68d2546ac80a86bb38120481", index_title: "A股债权投资〔报告期资产负债表〕"} - {id: "68da562bc80a86bb38cc8644", index_title: "A股债权投资:单季〔资产负债表〕"} - {id: "68da562ec80a86bb38cc868e", index_title: "A股债权投资:单季:按发布日〔资产负债表〕"} - {id: "68d2547dc80a86bb3812080b", index_title: "A股债权投资:按发布日〔报告期资产负债表〕"} - {id: "68d26d98c80a86bb3819d31f", index_title: "A股偿还债务支付的现金〔报告期现金流量表〕"} - {id: "68da58c8c80a86bb38ccfcd8", index_title: "A股偿还债务支付的现金:单季〔现金流量表〕"} - {id: "68da58cbc80a86bb38ccfe73", index_title: "A股偿还债务支付的现金:单季:按发布日〔现金流量表〕"} - {id: "68d26dadc80a86bb3819e03e", index_title: "A股偿还债务支付的现金:按发布日〔报告期现金流量表〕"} - {id: "68d271c4c80a86bb381a6297", index_title: "A股公允价值变动损失〔报告期现金流量表〕"} - {id: "68d271d5c80a86bb381a6392", index_title: "A股公允价值变动损失:按发布日〔报告期现金流量表〕"} - {id: "68d248f0c80a86bb380f1984", index_title: "A股公允价值变动收益〔报告期利润表〕"} - {id: "68da549ac80a86bb38cc6001", index_title: "A股公允价值变动收益:单季〔利润表〕"} - {id: "68da549dc80a86bb38cc6045", index_title: "A股公允价值变动收益:单季:按发布日〔利润表〕"} - {id: "68d2490ec80a86bb380f1b8d", index_title: "A股公允价值变动收益:按发布日〔报告期利润表〕"} - {id: "68d279e4c80a86bb381bc4fb", index_title: "A股其他〔报告期现金流量表〕"} - {id: "68d26180c80a86bb3815f7ef", index_title: "A股其他债权投资〔报告期资产负债表〕"} - {id: "68da5769c80a86bb38ccc3c6", index_title: "A股其他债权投资:单季〔资产负债表〕"} - {id: "68da576bc80a86bb38ccc413", index_title: "A股其他债权投资:单季:按发布日〔资产负债表〕"} - {id: "68d261a1c80a86bb381601d3", index_title: "A股其他债权投资:按发布日〔报告期资产负债表〕"} - {id: "68d279a6c80a86bb381bc0a5", index_title: "A股其他原因对现金的影响_间接法〔报告期现金流量表〕"} - {id: "68d279b5c80a86bb381bc1a9", index_title: "A股其他原因对现金的影响_间接法:按发布日〔报告期现金流量表〕"} - {id: "68d27970c80a86bb381bbb8f", index_title: "A股其他原因对现金的影响〔报告期现金流量表〕"} - {id: "68d27986c80a86bb381bbd8b", index_title: "A股其他原因对现金的影响:按发布日〔报告期现金流量表〕"} - {id: "68d263c4c80a86bb38168557", index_title: "A股其他应付款〔报告期资产负债表〕"} - {id: "68da5797c80a86bb38ccc94b", index_title: "A股其他应付款:单季〔资产负债表〕"} - {id: "68da579ac80a86bb38ccca61", index_title: "A股其他应付款:单季:按发布日〔资产负债表〕"} - {id: "68d263e2c80a86bb38168770", index_title: "A股其他应付款:按发布日〔报告期资产负债表〕"} - {id: "68d263fac80a86bb381689d4", index_title: "A股其他应收款〔报告期资产负债表〕"} - {id: "68d26413c80a86bb3816957d", index_title: "A股其他应收款:按发布日〔报告期资产负债表〕"} - {id: "68d24e2dc80a86bb38103bfc", index_title: "A股其他收益〔报告期利润表〕"} - {id: "68da5518c80a86bb38cc6c29", index_title: "A股其他收益:单季〔利润表〕"} - {id: "68da551bc80a86bb38cc6c72", index_title: "A股其他收益:单季:按发布日〔利润表〕"} - {id: "68d24e59c80a86bb38104083", index_title: "A股其他收益:按发布日〔报告期利润表〕"} - {id: "68d2629ac80a86bb381666d9", index_title: "A股其他权益工具〔报告期资产负债表〕"} - {id: "68d262b5c80a86bb38166c74", index_title: "A股其他权益工具投资〔报告期资产负债表〕"} - {id: "68da5780c80a86bb38ccc625", index_title: "A股其他权益工具投资:单季〔资产负债表〕"} - {id: "68da5783c80a86bb38ccc662", index_title: "A股其他权益工具投资:单季:按发布日〔资产负债表〕"} - {id: "68d262d1c80a86bb38167166", index_title: "A股其他权益工具投资:按发布日〔报告期资产负债表〕"} - {id: "68da577ac80a86bb38ccc585", index_title: "A股其他权益工具:单季〔资产负债表〕"} - {id: "68da577dc80a86bb38ccc5e0", index_title: "A股其他权益工具:单季:按发布日〔资产负债表〕"} - {id: "68d262aac80a86bb38166a70", index_title: "A股其他权益工具:按发布日〔报告期资产负债表〕"} - {id: "68d2626bc80a86bb38165c80", index_title: "A股其他流动负债〔报告期资产负债表〕"} - {id: "68da5774c80a86bb38ccc4eb", index_title: "A股其他流动负债:单季〔资产负债表〕"} - {id: "68da5777c80a86bb38ccc52a", index_title: "A股其他流动负债:单季:按发布日〔资产负债表〕"} - {id: "68d26280c80a86bb38166142", index_title: "A股其他流动负债:按发布日〔报告期资产负债表〕"} - {id: "68d26223c80a86bb38164175", index_title: "A股其他流动资产〔报告期资产负债表〕"} - {id: "68da576ec80a86bb38ccc44a", index_title: "A股其他流动资产:单季〔资产负债表〕"} - {id: "68da5771c80a86bb38ccc497", index_title: "A股其他流动资产:单季:按发布日〔资产负债表〕"} - {id: "68d26253c80a86bb381657a4", index_title: "A股其他流动资产:按发布日〔报告期资产负债表〕"} - {id: "68d24dcfc80a86bb38100480", index_title: "A股其他综合收益〔报告期利润表〕"} - {id: "68d261cac80a86bb381617fc", index_title: "A股其他综合收益〔报告期资产负债表〕"} - {id: "68da550dc80a86bb38cc6b18", index_title: "A股其他综合收益:单季〔利润表〕"} - {id: "68da5510c80a86bb38cc6b55", index_title: "A股其他综合收益:单季:按发布日〔利润表〕"} - {id: "68d24de7c80a86bb3810189a", index_title: "A股其他综合收益:按发布日〔报告期利润表〕"} - {id: "68d261f2c80a86bb381627ef", index_title: "A股其他综合收益:按发布日〔报告期资产负债表〕"} - {id: "68d26395c80a86bb38168227", index_title: "A股其他非流动负债〔报告期资产负债表〕"} - {id: "68da5791c80a86bb38ccc833", index_title: "A股其他非流动负债:单季〔资产负债表〕"} - {id: "68da5794c80a86bb38ccc883", index_title: "A股其他非流动负债:单季:按发布日〔资产负债表〕"} - {id: "68d263acc80a86bb381683ad", index_title: "A股其他非流动负债:按发布日〔报告期资产负债表〕"} - {id: "68d262f5c80a86bb38167604", index_title: "A股其他非流动资产〔报告期资产负债表〕"} - {id: "68da5785c80a86bb38ccc6ab", index_title: "A股其他非流动资产:单季〔资产负债表〕"} - {id: "68da5788c80a86bb38ccc6f5", index_title: "A股其他非流动资产:单季:按发布日〔资产负债表〕"} - {id: "68d26316c80a86bb38167866", index_title: "A股其他非流动资产:按发布日〔报告期资产负债表〕"} - {id: "68d26343c80a86bb38167bd6", index_title: "A股其他非流动金融资产〔报告期资产负债表〕"} - {id: "68da578cc80a86bb38ccc760", index_title: "A股其他非流动金融资产:单季〔资产负债表〕"} - {id: "68da578ec80a86bb38ccc7e0", index_title: "A股其他非流动金融资产:单季:按发布日〔资产负债表〕"} - {id: "68d2637cc80a86bb38168042", index_title: "A股其他非流动金融资产:按发布日〔报告期资产负债表〕"} - {id: "68d27a0ac80a86bb381bc8e6", index_title: "A股其他:按发布日〔报告期现金流量表〕"} - {id: "68d8cdc0c80a86bb3888efdd", index_title: "A股净利润/营业总收入:单季〔财务指标〕"} - {id: "68d8cdc2c80a86bb3888f024", index_title: "A股净利润/营业总收入:单季:按发布日〔财务指标〕"} - {id: "68d24bf7c80a86bb380fb0cd", index_title: "A股净利润〔报告期利润表〕"} - {id: "68d278c3c80a86bb381b9d1a", index_title: "A股净利润〔报告期现金流量表〕"} - {id: "68d8cd86c80a86bb3888eaea", index_title: "A股净利润同比增长率:单季〔财务指标〕"} - {id: "68d8cd89c80a86bb3888eb20", index_title: "A股净利润同比增长率:单季:按发布日〔财务指标〕"} - {id: "68d8cd75c80a86bb3888e97c", index_title: "A股净利润环比增长率:单季〔财务指标〕"} - {id: "68d8cd78c80a86bb3888e9a6", index_title: "A股净利润环比增长率:单季:按发布日〔财务指标〕"} - {id: "68da54ddc80a86bb38cc6688", index_title: "A股净利润:单季〔利润表〕"} - {id: "68da54e0c80a86bb38cc66d8", index_title: "A股净利润:单季:按发布日〔利润表〕"} - {id: "68d24c0fc80a86bb380fb269", index_title: "A股净利润:按发布日〔报告期利润表〕"} - {id: "68d278dfc80a86bb381b9ec6", index_title: "A股净利润:按发布日〔报告期现金流量表〕"} - {id: "68d24b98c80a86bb380f8d71", index_title: "A股净敞口套期收益〔报告期利润表〕"} - {id: "68da54d3c80a86bb38cc6580", index_title: "A股净敞口套期收益:单季〔利润表〕"} - {id: "68da54d5c80a86bb38cc65cb", index_title: "A股净敞口套期收益:单季:按发布日〔利润表〕"} - {id: "68d24bb5c80a86bb380f9769", index_title: "A股净敞口套期收益:按发布日〔报告期利润表〕"} - {id: "68d8cd97c80a86bb3888ec50", index_title: "A股净资产收益率(扣除非经常损益):单季〔财务指标〕"} - {id: "68d8cd9ac80a86bb3888ec9a", index_title: "A股净资产收益率(扣除非经常损益):单季:按发布日〔财务指标〕"} - {id: "68d8cdedc80a86bb3888f3fa", index_title: "A股净资产收益率ROE:单季〔财务指标〕"} - {id: "68d8cdf0c80a86bb3888f427", index_title: "A股净资产收益率ROE:单季:按发布日〔财务指标〕"} - {id: "68ee0a23596cb29c3b85b95e", index_title: "A股净资产收益率ROE:按发布日"} - {id: "68d24fbcc80a86bb381082f5", index_title: "A股分保费用〔报告期利润表〕"} - {id: "68da5534c80a86bb38cc6f0d", index_title: "A股分保费用:单季〔利润表〕"} - {id: "68da5536c80a86bb38cc6f4c", index_title: "A股分保费用:单季:按发布日〔利润表〕"} - {id: "68d24fd5c80a86bb38108498", index_title: "A股分保费用:按发布日〔报告期利润表〕"} - {id: "68d27107c80a86bb381a5619", index_title: "A股分配股利、利润或偿付利息支付的现金〔报告期现金流量表〕"} - {id: "68da58f7c80a86bb38cd1f29", index_title: "A股分配股利、利润或偿付利息支付的现金:单季〔现金流量表〕"} - {id: "68da58fac80a86bb38cd24ec", index_title: "A股分配股利、利润或偿付利息支付的现金:单季:按发布日〔现金流量表〕"} - {id: "68d27125c80a86bb381a5809", index_title: "A股分配股利、利润或偿付利息支付的现金:按发布日〔报告期现金流量表〕"} - {id: "68d25afbc80a86bb3813a33c", index_title: "A股划分为持有待售的负债〔报告期资产负债表〕"} - {id: "68da56d7c80a86bb38ccb558", index_title: "A股划分为持有待售的负债:单季〔资产负债表〕"} - {id: "68da56d9c80a86bb38ccb592", index_title: "A股划分为持有待售的负债:单季:按发布日〔资产负债表〕"} - {id: "68d25b1cc80a86bb3813d093", index_title: "A股划分为持有待售的负债:按发布日〔报告期资产负债表〕"} - {id: "68d25a79c80a86bb38130324", index_title: "A股划分为持有待售的资产〔报告期资产负债表〕"} - {id: "68da56d1c80a86bb38ccb4ac", index_title: "A股划分为持有待售的资产:单季〔资产负债表〕"} - {id: "68da56d4c80a86bb38ccb4fc", index_title: "A股划分为持有待售的资产:单季:按发布日〔资产负债表〕"} - {id: "68d25a9dc80a86bb3813187d", index_title: "A股划分为持有待售的资产:按发布日〔报告期资产负债表〕"} - {id: "68d24a12c80a86bb380f55cf", index_title: "A股利息支出〔报告期利润表〕"} - {id: "68da54b1c80a86bb38cc6240", index_title: "A股利息支出:单季〔利润表〕"} - {id: "68da54b4c80a86bb38cc6284", index_title: "A股利息支出:单季:按发布日〔利润表〕"} - {id: "68d24a33c80a86bb380f5806", index_title: "A股利息支出:按发布日〔报告期利润表〕"} - {id: "68d24a68c80a86bb380f6858", index_title: "A股利息收入〔报告期利润表〕"} - {id: "68da54b6c80a86bb38cc62bf", index_title: "A股利息收入:单季〔利润表〕"} - {id: "68da54b9c80a86bb38cc6312", index_title: "A股利息收入:单季:按发布日〔利润表〕"} - {id: "68d24a92c80a86bb380f76e6", index_title: "A股利息收入:按发布日〔报告期利润表〕"} - {id: "68d2510dc80a86bb3810b8bc", index_title: "A股利润总额〔报告期利润表〕"} - {id: "68da5557c80a86bb38cc7289", index_title: "A股利润总额:单季〔利润表〕"} - {id: "68da5559c80a86bb38cc72e4", index_title: "A股利润总额:单季:按发布日〔利润表〕"} - {id: "68d25121c80a86bb3810ba25", index_title: "A股利润总额:按发布日〔报告期利润表〕"} - {id: "68d26839c80a86bb38177980", index_title: "A股卖出回购金融资产款〔报告期资产负债表〕"} - {id: "68da57f6c80a86bb38cce70c", index_title: "A股卖出回购金融资产款:单季〔资产负债表〕"} - {id: "68da57f9c80a86bb38cce75f", index_title: "A股卖出回购金融资产款:单季:按发布日〔资产负债表〕"} - {id: "68d2684fc80a86bb38177b25", index_title: "A股卖出回购金融资产款:按发布日〔报告期资产负债表〕"} - {id: "68da5714c80a86bb38ccbb79", index_title: "A股发放委托贷款及垫款:单季〔资产负债表〕"} - {id: "68da5717c80a86bb38ccbbbc", index_title: "A股发放委托贷款及垫款:单季:按发布日〔资产负债表〕"} - {id: "68d25e72c80a86bb381548f0", index_title: "A股发放贷款及垫款-流动资产〔报告期资产负债表〕"} - {id: "68d25e86c80a86bb38154a69", index_title: "A股发放贷款及垫款-流动资产:按发布日〔报告期资产负债表〕"} - {id: "68d25ea3c80a86bb38154c9d", index_title: "A股发放贷款及垫款-非流动资产〔报告期资产负债表〕"} - {id: "68d25eb9c80a86bb38154e93", index_title: "A股发放贷款及垫款-非流动资产:按发布日〔报告期资产负债表〕"} - {id: "68d26eedc80a86bb381a0451", index_title: "A股发行债券收到的现金〔报告期现金流量表〕"} - {id: "68da58e4c80a86bb38cd10d0", index_title: "A股发行债券收到的现金:单季〔现金流量表〕"} - {id: "68da58e8c80a86bb38cd136c", index_title: "A股发行债券收到的现金:单季:按发布日〔现金流量表〕"} - {id: "68d26f07c80a86bb381a05ee", index_title: "A股发行债券收到的现金:按发布日〔报告期现金流量表〕"} - {id: "68d26f1ec80a86bb381a0763", index_title: "A股取得借款收到的现金〔报告期现金流量表〕"} - {id: "68d26f44c80a86bb381a0a0c", index_title: "A股取得借款收到的现金:按发布日〔报告期现金流量表〕"} - {id: "68d27645c80a86bb381b512e", index_title: "A股取得子公司及其他营业单位支付的现金净额〔报告期现金流量表〕"} - {id: "68da5950c80a86bb38cd30f7", index_title: "A股取得子公司及其他营业单位支付的现金净额:单季〔现金流量表〕"} - {id: "68da5953c80a86bb38cd3133", index_title: "A股取得子公司及其他营业单位支付的现金净额:单季:按发布日〔现金流量表〕"} - {id: "68d27655c80a86bb381b5220", index_title: "A股取得子公司及其他营业单位支付的现金净额:按发布日〔报告期现金流量表〕"} - {id: "68d27502c80a86bb381b18bd", index_title: "A股取得投资收益收到的现金〔报告期现金流量表〕"} - {id: "68da5930c80a86bb38cd2ddb", index_title: "A股取得投资收益收到的现金:单季〔现金流量表〕"} - {id: "68da5932c80a86bb38cd2e13", index_title: "A股取得投资收益收到的现金:单季:按发布日〔现金流量表〕"} - {id: "68d27520c80a86bb381b1adb", index_title: "A股取得投资收益收到的现金:按发布日〔报告期现金流量表〕"} - {id: "68d25a32c80a86bb3812fd48", index_title: "A股可供出售金融资产〔报告期资产负债表〕"} - {id: "68da56ccc80a86bb38ccb3d2", index_title: "A股可供出售金融资产:单季〔资产负债表〕"} - {id: "68da56cec80a86bb38ccb457", index_title: "A股可供出售金融资产:单季:按发布日〔资产负债表〕"} - {id: "68d25a5ec80a86bb38130096", index_title: "A股可供出售金融资产:按发布日〔报告期资产负债表〕"} - {id: "68d25692c80a86bb3812745e", index_title: "A股合同负债〔报告期资产负债表〕"} - {id: "68da565ec80a86bb38cc8b69", index_title: "A股合同负债:单季〔资产负债表〕"} - {id: "68da5661c80a86bb38cc8bb9", index_title: "A股合同负债:单季:按发布日〔资产负债表〕"} - {id: "68d256b2c80a86bb38127755", index_title: "A股合同负债:按发布日〔报告期资产负债表〕"} - {id: "68d25655c80a86bb38126fec", index_title: "A股合同资产〔报告期资产负债表〕"} - {id: "68da5659c80a86bb38cc8ad8", index_title: "A股合同资产:单季〔资产负债表〕"} - {id: "68da565bc80a86bb38cc8b21", index_title: "A股合同资产:单季:按发布日〔资产负债表〕"} - {id: "68d2567ac80a86bb38127291", index_title: "A股合同资产:按发布日〔报告期资产负债表〕"} - {id: "68d25515c80a86bb38122506", index_title: "A股向中央银行借款〔报告期资产负债表〕"} - {id: "68d2757ec80a86bb381b20a2", index_title: "A股向中央银行借款净增加额〔报告期现金流量表〕"} - {id: "68da593bc80a86bb38cd2ee9", index_title: "A股向中央银行借款净增加额:单季〔现金流量表〕"} - {id: "68da593ec80a86bb38cd2f2c", index_title: "A股向中央银行借款净增加额:单季:按发布日〔现金流量表〕"} - {id: "68d275b2c80a86bb381b27ca", index_title: "A股向中央银行借款净增加额:按发布日〔报告期现金流量表〕"} - {id: "68d25536c80a86bb3812288c", index_title: "A股向中央银行借款:按发布日〔报告期资产负债表〕"} - {id: "68d275cbc80a86bb381b2e41", index_title: "A股向其他金融机构拆入资金净增加额〔报告期现金流量表〕"} - {id: "68da5940c80a86bb38cd2f72", index_title: "A股向其他金融机构拆入资金净增加额:单季〔现金流量表〕"} - {id: "68da5943c80a86bb38cd2fb2", index_title: "A股向其他金融机构拆入资金净增加额:单季:按发布日〔现金流量表〕"} - {id: "68d275e2c80a86bb381b33a5", index_title: "A股向其他金融机构拆入资金净增加额:按发布日〔报告期现金流量表〕"} - {id: "68d25775c80a86bb381287ec", index_title: "A股吸收存款及同业存放〔报告期资产负债表〕"} - {id: "68da567ec80a86bb38cc956f", index_title: "A股吸收存款及同业存放:单季〔资产负债表〕"} - {id: "68da5681c80a86bb38cc979f", index_title: "A股吸收存款及同业存放:单季:按发布日〔资产负债表〕"} - {id: "68d25781c80a86bb38128c92", index_title: "A股吸收存款及同业存放:按发布日〔报告期资产负债表〕"} - {id: "68d26f61c80a86bb381a0bf4", index_title: "A股吸收投资收到的现金〔报告期现金流量表〕"} - {id: "68da58ebc80a86bb38cd15fc", index_title: "A股吸收投资收到的现金:单季〔现金流量表〕"} - {id: "68da58eec80a86bb38cd1898", index_title: "A股吸收投资收到的现金:单季:按发布日〔现金流量表〕"} - {id: "68d26f81c80a86bb381a0e12", index_title: "A股吸收投资收到的现金:按发布日〔报告期现金流量表〕"} - {id: "68d259fec80a86bb3812f999", index_title: "A股商誉〔报告期资产负债表〕"} - {id: "68da56c6c80a86bb38ccb334", index_title: "A股商誉:单季〔资产负债表〕"} - {id: "68da56c9c80a86bb38ccb372", index_title: "A股商誉:单季:按发布日〔资产负债表〕"} - {id: "68d25a15c80a86bb3812fb2a", index_title: "A股商誉:按发布日〔报告期资产负债表〕"} - {id: "68d275f4c80a86bb381b3db8", index_title: "A股回购业务资金净增加额〔报告期现金流量表〕"} - {id: "68da5945c80a86bb38cd2fe7", index_title: "A股回购业务资金净增加额:单季〔现金流量表〕"} - {id: "68da5948c80a86bb38cd3028", index_title: "A股回购业务资金净增加额:单季:按发布日〔现金流量表〕"} - {id: "68d27608c80a86bb381b4609", index_title: "A股回购业务资金净增加额:按发布日〔报告期现金流量表〕"} - {id: "68d25978c80a86bb3812db54", index_title: "A股固定资产〔报告期资产负债表〕"} - {id: "68d27327c80a86bb381aa67e", index_title: "A股固定资产折旧、油气资产折耗、生产性生物资产折旧〔报告期现金流量表〕"} - {id: "68d2733ac80a86bb381aa7cb", index_title: "A股固定资产折旧、油气资产折耗、生产性生物资产折旧:按发布日〔报告期现金流量表〕"} - {id: "68d272fcc80a86bb381aa3f5", index_title: "A股固定资产报废损失〔报告期现金流量表〕"} - {id: "68d27312c80a86bb381aa540", index_title: "A股固定资产报废损失:按发布日〔报告期现金流量表〕"} - {id: "68d259abc80a86bb3812e839", index_title: "A股固定资产清理〔报告期资产负债表〕"} - {id: "68da56bbc80a86bb38ccb20b", index_title: "A股固定资产清理:单季〔资产负债表〕"} - {id: "68da56bec80a86bb38ccb254", index_title: "A股固定资产清理:单季:按发布日〔资产负债表〕"} - {id: "68d259bcc80a86bb3812ee78", index_title: "A股固定资产清理:按发布日〔报告期资产负债表〕"} - {id: "68da56b5c80a86bb38ccb179", index_title: "A股固定资产:单季〔资产负债表〕"} - {id: "68da56b8c80a86bb38ccb1bf", index_title: "A股固定资产:单季:按发布日〔资产负债表〕"} - {id: "68d2598ec80a86bb3812dfab", index_title: "A股固定资产:按发布日〔报告期资产负债表〕"} - {id: "68d255f6c80a86bb38123c3b", index_title: "A股在建工程〔报告期资产负债表〕"} - {id: "68da564dc80a86bb38cc899b", index_title: "A股在建工程:单季〔资产负债表〕"} - {id: "68da5650c80a86bb38cc89ec", index_title: "A股在建工程:单季:按发布日〔资产负债表〕"} - {id: "68d25611c80a86bb38124e93", index_title: "A股在建工程:按发布日〔报告期资产负债表〕"} - {id: "68d244cbc80a86bb380e7588", index_title: "A股基本每股收益〔报告期利润表〕"} - {id: "68da545bc80a86bb38cc49df", index_title: "A股基本每股收益:单季〔利润表〕"} - {id: "68da545dc80a86bb38cc4d19", index_title: "A股基本每股收益:单季:按发布日〔利润表〕"} - {id: "68d24528c80a86bb380e7f48", index_title: "A股基本每股收益:按发布日〔报告期利润表〕"} - {id: "68d248b8c80a86bb380f15ea", index_title: "A股堪探费用〔报告期利润表〕"} - {id: "68d248d7c80a86bb380f1805", index_title: "A股堪探费用:按发布日〔报告期利润表〕"} - {id: "68d2768fc80a86bb381b55b1", index_title: "A股处置交易性金融资产净增加额〔报告期现金流量表〕"} - {id: "68da595bc80a86bb38cd3213", index_title: "A股处置交易性金融资产净增加额:单季〔现金流量表〕"} - {id: "68da595ec80a86bb38cd3264", index_title: "A股处置交易性金融资产净增加额:单季:按发布日〔现金流量表〕"} - {id: "68d276a2c80a86bb381b56ef", index_title: "A股处置交易性金融资产净增加额:按发布日〔报告期现金流量表〕"} - {id: "68d27284c80a86bb381a9673", index_title: "A股处置固定资产、无形资产和其他长期资产收回的现金净额〔报告期现金流量表〕"} - {id: "68da5909c80a86bb38cd2a04", index_title: "A股处置固定资产、无形资产和其他长期资产收回的现金净额:单季〔现金流量表〕"} - {id: "68da590bc80a86bb38cd2a4c", index_title: "A股处置固定资产、无形资产和其他长期资产收回的现金净额:单季:按发布日〔现金流量表〕"} - {id: "68d2729bc80a86bb381a9b99", index_title: "A股处置固定资产、无形资产和其他长期资产收回的现金净额:按发布日〔报告期现金流量表〕"} - {id: "68d272b1c80a86bb381a9cef", index_title: "A股处置固定资产、无形资产和其他长期资产的损失〔报告期现金流量表〕"} - {id: "68d272e2c80a86bb381aa07e", index_title: "A股处置固定资产、无形资产和其他长期资产的损失:按发布日〔报告期现金流量表〕"} - {id: "68d2761bc80a86bb381b4e8d", index_title: "A股处置子公司及其他营业单位收到的现金净额〔报告期现金流量表〕"} - {id: "68da594bc80a86bb38cd3077", index_title: "A股处置子公司及其他营业单位收到的现金净额:单季〔现金流量表〕"} - {id: "68da594dc80a86bb38cd30bc", index_title: "A股处置子公司及其他营业单位收到的现金净额:单季:按发布日〔现金流量表〕"} - {id: "68d2762dc80a86bb381b4f8e", index_title: "A股处置子公司及其他营业单位收到的现金净额:按发布日〔报告期现金流量表〕"} - {id: "68d259d5c80a86bb3812f6bd", index_title: "A股外币报表折算差额〔报告期资产负债表〕"} - {id: "68da56c1c80a86bb38ccb29f", index_title: "A股外币报表折算差额:单季〔资产负债表〕"} - {id: "68da56c3c80a86bb38ccb2e1", index_title: "A股外币报表折算差额:单季:按发布日〔资产负债表〕"} - {id: "68d259ecc80a86bb3812f861", index_title: "A股外币报表折算差额:按发布日〔报告期资产负债表〕"} - {id: "68d26f92c80a86bb381a0f15", index_title: "A股子公司吸收少数股东投资收到的现金〔报告期现金流量表〕"} - {id: "68da58f1c80a86bb38cd1a4e", index_title: "A股子公司吸收少数股东投资收到的现金:单季〔现金流量表〕"} - {id: "68da58f4c80a86bb38cd1b54", index_title: "A股子公司吸收少数股东投资收到的现金:单季:按发布日〔现金流量表〕"} - {id: "68d26fb3c80a86bb381a11c1", index_title: "A股子公司吸收少数股东投资收到的现金:按发布日〔报告期现金流量表〕"} - {id: "68d27a50c80a86bb381bcde1", index_title: "A股子公司支付给少数股东的股利、利润〔报告期现金流量表〕"} - {id: "68da59bfc80a86bb38cd3c15", index_title: "A股子公司支付给少数股东的股利、利润:单季〔现金流量表〕"} - {id: "68da59c1c80a86bb38cd3c5b", index_title: "A股子公司支付给少数股东的股利、利润:单季:按发布日〔现金流量表〕"} - {id: "68d27a77c80a86bb381bf104", index_title: "A股子公司支付给少数股东的股利、利润:按发布日〔报告期现金流量表〕"} - {id: "68d276c1c80a86bb381b5984", index_title: "A股存放中央银行和同业款项净增加额〔报告期现金流量表〕"} - {id: "68da5960c80a86bb38cd32ab", index_title: "A股存放中央银行和同业款项净增加额:单季〔现金流量表〕"} - {id: "68da5963c80a86bb38cd32fa", index_title: "A股存放中央银行和同业款项净增加额:单季:按发布日〔现金流量表〕"} - {id: "68d276d2c80a86bb381b5cb6", index_title: "A股存放中央银行和同业款项净增加额:按发布日〔报告期现金流量表〕"} - {id: "68d25ce7c80a86bb3814e07a", index_title: "A股存货〔报告期资产负债表〕"} - {id: "68d2745ac80a86bb381ae218", index_title: "A股存货的减少〔报告期现金流量表〕"} - {id: "68d27473c80a86bb381ae41c", index_title: "A股存货的减少:按发布日〔报告期现金流量表〕"} - {id: "68da56fec80a86bb38ccb930", index_title: "A股存货:单季〔资产负债表〕"} - {id: "68da5701c80a86bb38ccb972", index_title: "A股存货:单季:按发布日〔资产负债表〕"} - {id: "68d25d05c80a86bb3814ec45", index_title: "A股存货:按发布日〔报告期资产负债表〕"} - {id: "68d26432c80a86bb3816aa02", index_title: "A股实收资本(或股本)〔报告期资产负债表〕"} - {id: "68da579ec80a86bb38cccbe0", index_title: "A股实收资本(或股本):单季〔资产负债表〕"} - {id: "68da57a1c80a86bb38cccd2c", index_title: "A股实收资本(或股本):单季:按发布日〔资产负债表〕"} - {id: "68d2646ac80a86bb3816bec8", index_title: "A股实收资本(或股本):按发布日〔报告期资产负债表〕"} - {id: "68d276e4c80a86bb381b60e9", index_title: "A股客户存款和同业存放款项净增加额〔报告期现金流量表〕"} - {id: "68da5966c80a86bb38cd334d", index_title: "A股客户存款和同业存放款项净增加额:单季〔现金流量表〕"} - {id: "68da5968c80a86bb38cd338f", index_title: "A股客户存款和同业存放款项净增加额:单季:按发布日〔现金流量表〕"} - {id: "68d2771bc80a86bb381b68fe", index_title: "A股客户存款和同业存放款项净增加额:按发布日〔报告期现金流量表〕"} - {id: "68d277f6c80a86bb381b7714", index_title: "A股客户贷款及垫款净增加额〔报告期现金流量表〕"} - {id: "68da5982c80a86bb38cd360a", index_title: "A股客户贷款及垫款净增加额:单季〔现金流量表〕"} - {id: "68da5984c80a86bb38cd3648", index_title: "A股客户贷款及垫款净增加额:单季:按发布日〔现金流量表〕"} - {id: "68d27804c80a86bb381b7c33", index_title: "A股客户贷款及垫款净增加额:按发布日〔报告期现金流量表〕"} - {id: "68d24ae7c80a86bb380f7c49", index_title: "A股对联营企业和合营企业的投资收益〔报告期利润表〕"} - {id: "68da54c1c80a86bb38cc63e1", index_title: "A股对联营企业和合营企业的投资收益:单季〔利润表〕"} - {id: "68da54c4c80a86bb38cc6422", index_title: "A股对联营企业和合营企业的投资收益:单季:按发布日〔利润表〕"} - {id: "68d24b06c80a86bb380f7e5a", index_title: "A股对联营企业和合营企业的投资收益:按发布日〔报告期利润表〕"} - {id: "68d24b4cc80a86bb380f830e", index_title: "A股少数股东损益〔报告期利润表〕"} - {id: "68da54cdc80a86bb38cc64fb", index_title: "A股少数股东损益:单季〔利润表〕"} - {id: "68da54d0c80a86bb38cc6535", index_title: "A股少数股东损益:单季:按发布日〔利润表〕"} - {id: "68d24b7cc80a86bb380f88b4", index_title: "A股少数股东损益:按发布日〔报告期利润表〕"} - {id: "68d2601fc80a86bb38158f41", index_title: "A股少数股东权益〔报告期资产负债表〕"} - {id: "68da5747c80a86bb38ccc083", index_title: "A股少数股东权益:单季〔资产负债表〕"} - {id: "68da5749c80a86bb38ccc0c6", index_title: "A股少数股东权益:单季:按发布日〔资产负债表〕"} - {id: "68d2604dc80a86bb3815937c", index_title: "A股少数股东权益:按发布日〔报告期资产负债表〕"} - {id: "68d2562ec80a86bb38126a05", index_title: "A股工程物资〔报告期资产负债表〕"} - {id: "68da5653c80a86bb38cc8a4b", index_title: "A股工程物资:单季〔资产负债表〕"} - {id: "68da5656c80a86bb38cc8a8d", index_title: "A股工程物资:单季:按发布日〔资产负债表〕"} - {id: "68d25640c80a86bb38126e6d", index_title: "A股工程物资:按发布日〔报告期资产负债表〕"} - {id: "68d24f07c80a86bb38105157", index_title: "A股已赚保费〔报告期利润表〕"} - {id: "68da5523c80a86bb38cc6d56", index_title: "A股已赚保费:单季〔利润表〕"} - {id: "68da5526c80a86bb38cc6da8", index_title: "A股已赚保费:单季:按发布日〔利润表〕"} - {id: "68d24f1fc80a86bb381061b8", index_title: "A股已赚保费:按发布日〔报告期利润表〕"} - {id: "68d8a6d5c80a86bb38849069", index_title: "A股市净率(PB)〔估值数据〕"} - {id: "68d8a70ac80a86bb388494d9", index_title: "A股市现率(PCF,现金净流量TTM)〔估值数据〕"} - {id: "68d8a740c80a86bb38849b22", index_title: "A股市现率(PCF,经营活动现金流TTM)〔估值数据〕"} - {id: "68d8a77dc80a86bb3884adae", index_title: "A股市盈率(PE, TTM)〔估值数据〕"} - {id: "68d8a7b3c80a86bb3884b21f", index_title: "A股市盈率(PE)〔估值数据〕"} - {id: "68d8a7e9c80a86bb3884b6b2", index_title: "A股市销率(PS,TTM)〔估值数据〕"} - {id: "68d26c1cc80a86bb38197f75", index_title: "A股库存股〔报告期资产负债表〕"} - {id: "68da584ec80a86bb38ccf048", index_title: "A股库存股:单季〔资产负债表〕"} - {id: "68da5851c80a86bb38ccf095", index_title: "A股库存股:单季:按发布日〔资产负债表〕"} - {id: "68d26c33c80a86bb38198116", index_title: "A股库存股:按发布日〔报告期资产负债表〕"} - {id: "68d269bcc80a86bb381909ca", index_title: "A股应交税费〔报告期资产负债表〕"} - {id: "68da580cc80a86bb38cce952", index_title: "A股应交税费:单季〔资产负债表〕"} - {id: "68da580fc80a86bb38cce992", index_title: "A股应交税费:单季:按发布日〔资产负债表〕"} - {id: "68d269d0c80a86bb38190d70", index_title: "A股应交税费:按发布日〔报告期资产负债表〕"} - {id: "68d2548ac80a86bb38120aa0", index_title: "A股应付债券〔报告期资产负债表〕"} - {id: "68da5631c80a86bb38cc86ce", index_title: "A股应付债券:单季〔资产负债表〕"} - {id: "68da5633c80a86bb38cc8716", index_title: "A股应付债券:单季:按发布日〔资产负债表〕"} - {id: "68d25496c80a86bb38120cbe", index_title: "A股应付债券:按发布日〔报告期资产负债表〕"} - {id: "68d2532fc80a86bb3811b5ef", index_title: "A股应付关联公司款〔报告期资产负债表〕"} - {id: "68d2533ec80a86bb3811b70b", index_title: "A股应付关联公司款:按发布日〔报告期资产负债表〕"} - {id: "68d266bdc80a86bb38173e9d", index_title: "A股应付分保账款〔报告期资产负债表〕"} - {id: "68da57d4c80a86bb38cce37b", index_title: "A股应付分保账款:单季〔资产负债表〕"} - {id: "68da57d7c80a86bb38cce3be", index_title: "A股应付分保账款:单季:按发布日〔资产负债表〕"} - {id: "68d266cac80a86bb38173f9d", index_title: "A股应付分保账款:按发布日〔报告期资产负债表〕"} - {id: "68d25c7ac80a86bb3814d80a", index_title: "A股应付利息〔报告期资产负债表〕"} - {id: "68da56f2c80a86bb38ccb810", index_title: "A股应付利息:单季〔资产负债表〕"} - {id: "68da56f5c80a86bb38ccb84d", index_title: "A股应付利息:单季:按发布日〔资产负债表〕"} - {id: "68d25c87c80a86bb3814d8e6", index_title: "A股应付利息:按发布日〔报告期资产负债表〕"} - {id: "68d255d2c80a86bb381235d7", index_title: "A股应付手续费及佣金〔报告期资产负债表〕"} - {id: "68da5648c80a86bb38cc8919", index_title: "A股应付手续费及佣金:单季〔资产负债表〕"} - {id: "68da564ac80a86bb38cc8957", index_title: "A股应付手续费及佣金:单季:按发布日〔资产负债表〕"} - {id: "68d255e5c80a86bb381237c7", index_title: "A股应付手续费及佣金:按发布日〔报告期资产负债表〕"} - {id: "68d260f4c80a86bb3815e8df", index_title: "A股应付票据〔报告期资产负债表〕"} - {id: "68d253a6c80a86bb3811c218", index_title: "A股应付票据及应付账款〔报告期资产负债表〕"} - {id: "68d253c1c80a86bb3811c70e", index_title: "A股应付票据及应付账款:按发布日〔报告期资产负债表〕"} - {id: "68da5758c80a86bb38ccc239", index_title: "A股应付票据:单季〔资产负债表〕"} - {id: "68da575bc80a86bb38ccc275", index_title: "A股应付票据:单季:按发布日〔资产负债表〕"} - {id: "68d2610dc80a86bb3815efb1", index_title: "A股应付票据:按发布日〔报告期资产负债表〕"} - {id: "68d267b0c80a86bb38175a4a", index_title: "A股应付职工薪酬〔报告期资产负债表〕"} - {id: "68da57e4c80a86bb38cce531", index_title: "A股应付职工薪酬:单季〔资产负债表〕"} - {id: "68da57e7c80a86bb38cce56b", index_title: "A股应付职工薪酬:单季:按发布日〔资产负债表〕"} - {id: "68d267cdc80a86bb38176ad9", index_title: "A股应付职工薪酬:按发布日〔报告期资产负债表〕"} - {id: "68d25868c80a86bb3812b916", index_title: "A股应付股利〔报告期资产负债表〕"} - {id: "68da5698c80a86bb38cca4a1", index_title: "A股应付股利:单季〔资产负债表〕"} - {id: "68da569bc80a86bb38cca63d", index_title: "A股应付股利:单季:按发布日〔资产负债表〕"} - {id: "68d2587fc80a86bb3812c291", index_title: "A股应付股利:按发布日〔报告期资产负债表〕"} - {id: "68d252b4c80a86bb3811a117", index_title: "A股应付账款〔报告期资产负债表〕"} - {id: "68da560ec80a86bb38cc8369", index_title: "A股应付账款:单季〔资产负债表〕"} - {id: "68da5610c80a86bb38cc83a7", index_title: "A股应付账款:单季:按发布日〔资产负债表〕"} - {id: "68d252cac80a86bb3811ae6b", index_title: "A股应付账款:按发布日〔报告期资产负债表〕"} - {id: "68d25c0bc80a86bb3814bead", index_title: "A股应收保费〔报告期资产负债表〕"} - {id: "68da56e7c80a86bb38ccb6f2", index_title: "A股应收保费:单季〔资产负债表〕"} - {id: "68da56eac80a86bb38ccb733", index_title: "A股应收保费:单季:按发布日〔资产负债表〕"} - {id: "68d25c1ac80a86bb3814cd07", index_title: "A股应收保费:按发布日〔报告期资产负债表〕"} - {id: "68d25364c80a86bb3811b9ad", index_title: "A股应收关联公司款〔报告期资产负债表〕"} - {id: "68d2537bc80a86bb3811bb67", index_title: "A股应收关联公司款:按发布日〔报告期资产负债表〕"} - {id: "68d26697c80a86bb38173b42", index_title: "A股应收分保合同准备金〔报告期资产负债表〕"} - {id: "68da57cfc80a86bb38cce2fb", index_title: "A股应收分保合同准备金:单季〔资产负债表〕"} - {id: "68da57d1c80a86bb38cce33b", index_title: "A股应收分保合同准备金:单季:按发布日〔资产负债表〕"} - {id: "68d266acc80a86bb38173d71", index_title: "A股应收分保合同准备金:按发布日〔报告期资产负债表〕"} - {id: "68d266e4c80a86bb3817415c", index_title: "A股应收分保账款〔报告期资产负债表〕"} - {id: "68da57d9c80a86bb38cce410", index_title: "A股应收分保账款:单季〔资产负债表〕"} - {id: "68da57dcc80a86bb38cce449", index_title: "A股应收分保账款:单季:按发布日〔资产负债表〕"} - {id: "68d26717c80a86bb381744f0", index_title: "A股应收分保账款:按发布日〔报告期资产负债表〕"} - {id: "68d25c93c80a86bb3814d9ed", index_title: "A股应收利息〔报告期资产负债表〕"} - {id: "68da56f8c80a86bb38ccb899", index_title: "A股应收利息:单季〔资产负债表〕"} - {id: "68da56fbc80a86bb38ccb8e4", index_title: "A股应收利息:单季:按发布日〔资产负债表〕"} - {id: "68d25caec80a86bb3814dc3a", index_title: "A股应收利息:按发布日〔报告期资产负债表〕"} - {id: "68d265d4c80a86bb381710e2", index_title: "A股应收款项融资〔报告期资产负债表〕"} - {id: "68da57c4c80a86bb38cce0ad", index_title: "A股应收款项融资:单季〔资产负债表〕"} - {id: "68da57c7c80a86bb38cce201", index_title: "A股应收款项融资:单季:按发布日〔资产负债表〕"} - {id: "68d26602c80a86bb38171456", index_title: "A股应收款项融资:按发布日〔报告期资产负债表〕"} - {id: "68d25411c80a86bb3811e02e", index_title: "A股应收票据〔报告期资产负债表〕"} - {id: "68d253e2c80a86bb3811cd65", index_title: "A股应收票据及应收账款〔报告期资产负债表〕"} - {id: "68d253f9c80a86bb3811d562", index_title: "A股应收票据及应收账款:按发布日〔报告期资产负债表〕"} - {id: "68da5620c80a86bb38cc852d", index_title: "A股应收票据:单季〔资产负债表〕"} - {id: "68da5623c80a86bb38cc856f", index_title: "A股应收票据:单季:按发布日〔资产负债表〕"} - {id: "68d2542ec80a86bb3811eff7", index_title: "A股应收票据:按发布日〔报告期资产负债表〕"} - {id: "68d25895c80a86bb3812ca54", index_title: "A股应收股利〔报告期资产负债表〕"} - {id: "68da569ec80a86bb38ccaa88", index_title: "A股应收股利:单季〔资产负债表〕"} - {id: "68da56a0c80a86bb38ccaec9", index_title: "A股应收股利:单季:按发布日〔资产负债表〕"} - {id: "68d258a9c80a86bb3812cbc7", index_title: "A股应收股利:按发布日〔报告期资产负债表〕"} - {id: "68d2527cc80a86bb38118666", index_title: "A股应收账款〔报告期资产负债表〕"} - {id: "68da5607c80a86bb38cc82c5", index_title: "A股应收账款:单季〔资产负债表〕"} - {id: "68da560ac80a86bb38cc8309", index_title: "A股应收账款:单季:按发布日〔资产负债表〕"} - {id: "68d25295c80a86bb381190ff", index_title: "A股应收账款:按发布日〔报告期资产负债表〕"} - {id: "68d2583bc80a86bb3812aff9", index_title: "A股开发支出〔报告期资产负债表〕"} - {id: "68da5691c80a86bb38cca0d4", index_title: "A股开发支出:单季〔资产负债表〕"} - {id: "68da5694c80a86bb38cca2f5", index_title: "A股开发支出:单季:按发布日〔资产负债表〕"} - {id: "68d25851c80a86bb3812b1e4", index_title: "A股开发支出:按发布日〔报告期资产负债表〕"} - {id: "68da5513c80a86bb38cc6bae", index_title: "A股归属于少数股东的其他综合收益:单季〔利润表〕"} - {id: "68da5516c80a86bb38cc6be3", index_title: "A股归属于少数股东的其他综合收益:单季:按发布日〔利润表〕"} - {id: "68d24566c80a86bb380e835d", index_title: "A股归属于少数股东的综合收益总额〔报告期利润表〕"} - {id: "68da5461c80a86bb38cc4e89", index_title: "A股归属于少数股东的综合收益总额:单季〔利润表〕"} - {id: "68da5464c80a86bb38cc4f81", index_title: "A股归属于少数股东的综合收益总额:单季:按发布日〔利润表〕"} - {id: "68d24592c80a86bb380e8762", index_title: "A股归属于少数股东的综合收益总额:按发布日〔报告期利润表〕"} - {id: "68d245b7c80a86bb380e8bcf", index_title: "A股归属于母公司所有者的综合收益总额〔报告期利润表〕"} - {id: "68da5468c80a86bb38cc50d2", index_title: "A股归属于母公司所有者的综合收益总额:单季〔利润表〕"} - {id: "68da546cc80a86bb38cc51e9", index_title: "A股归属于母公司所有者的综合收益总额:单季:按发布日〔利润表〕"} - {id: "68d245ebc80a86bb380e9b1f", index_title: "A股归属于母公司所有者的综合收益总额:按发布日〔报告期利润表〕"} - {id: "68d258bec80a86bb3812cd3b", index_title: "A股归属于母公司股东权益合计〔报告期资产负债表〕"} - {id: "68da56a3c80a86bb38ccaf51", index_title: "A股归属于母公司股东权益合计:单季〔资产负债表〕"} - {id: "68da56a6c80a86bb38ccafa2", index_title: "A股归属于母公司股东权益合计:单季:按发布日〔资产负债表〕"} - {id: "68d258d0c80a86bb3812ce75", index_title: "A股归属于母公司股东权益合计:按发布日〔报告期资产负债表〕"} - {id: "68d24cd6c80a86bb380fc86d", index_title: "A股归属于母公司股东的净利润〔报告期利润表〕"} - {id: "68da54efc80a86bb38cc684c", index_title: "A股归属于母公司股东的净利润:单季〔利润表〕"} - {id: "68da54f2c80a86bb38cc6886", index_title: "A股归属于母公司股东的净利润:单季:按发布日〔利润表〕"} - {id: "68d24d07c80a86bb380fd33c", index_title: "A股归属于母公司股东的净利润:按发布日〔报告期利润表〕"} - {id: "68d8cd80c80a86bb3888ea6d", index_title: "A股归属母公司股东的净利润同比增长率:单季〔财务指标〕"} - {id: "68d8cd83c80a86bb3888eaaa", index_title: "A股归属母公司股东的净利润同比增长率:单季:按发布日〔财务指标〕"} - {id: "68d8cd7bc80a86bb3888e9e9", index_title: "A股归属母公司股东的净利润环比增长率:单季〔财务指标〕"} - {id: "68d8cd7dc80a86bb3888ea32", index_title: "A股归属母公司股东的净利润环比增长率:单季:按发布日〔财务指标〕"} - {id: "68d24e92c80a86bb38104433", index_title: "A股影响净利润的其他科目〔报告期利润表〕"} - {id: "68d24ea3c80a86bb38104551", index_title: "A股影响净利润的其他科目:按发布日〔报告期利润表〕"} - {id: "68d24eb5c80a86bb38104679", index_title: "A股影响利润总额的其他科目〔报告期利润表〕"} - {id: "68d24ec7c80a86bb38104782", index_title: "A股影响利润总额的其他科目:按发布日〔报告期利润表〕"} - {id: "68d24e6ec80a86bb381041f9", index_title: "A股影响营业利润的其他科目〔报告期利润表〕"} - {id: "68d24e80c80a86bb381042ff", index_title: "A股影响营业利润的其他科目:按发布日〔报告期利润表〕"} - {id: "68d8a51ac80a86bb38845d83", index_title: "A股总市值〔估值数据〕"} - {id: "68d8a69fc80a86bb38848ba7", index_title: "A股总市值(包含A股、B股、H股)〔估值数据〕"} - {id: "68d8a4dfc80a86bb38844bc4", index_title: "A股总股本〔估值数据〕"} - {id: "68d8a553c80a86bb388462ac", index_title: "A股总股本(包含A股、B股、H股)〔估值数据〕"} - {id: "68d8cde8c80a86bb3888f372", index_title: "A股总资产净利率ROA:单季〔财务指标〕"} - {id: "68d8cdeac80a86bb3888f3bd", index_title: "A股总资产净利率ROA:单季:按发布日〔财务指标〕"} - {id: "68d24969c80a86bb380f3d6d", index_title: "A股所得税〔报告期利润表〕"} - {id: "68da54a6c80a86bb38cc6126", index_title: "A股所得税费用:单季〔利润表〕"} - {id: "68da54a8c80a86bb38cc6165", index_title: "A股所得税费用:单季:按发布日〔利润表〕"} - {id: "68d249a2c80a86bb380f4de9", index_title: "A股所得税:按发布日〔报告期利润表〕"} - {id: "68d26487c80a86bb3816c0f5", index_title: "A股所有者权益-永续债〔报告期资产负债表〕"} - {id: "68da57a5c80a86bb38cccf27", index_title: "A股所有者权益-永续债:单季〔资产负债表〕"} - {id: "68da57a8c80a86bb38ccd170", index_title: "A股所有者权益-永续债:单季:按发布日〔资产负债表〕"} - {id: "68d264a7c80a86bb3816c334", index_title: "A股所有者权益-永续债:按发布日〔报告期资产负债表〕"} - {id: "68d24630c80a86bb380eb234", index_title: "A股手续费及佣金支出〔报告期利润表〕"} - {id: "68da546fc80a86bb38cc5321", index_title: "A股手续费及佣金支出:单季〔利润表〕"} - {id: "68da5472c80a86bb38cc5437", index_title: "A股手续费及佣金支出:单季:按发布日〔利润表〕"} - {id: "68d2466cc80a86bb380ec395", index_title: "A股手续费及佣金支出:按发布日〔报告期利润表〕"} - {id: "68d24692c80a86bb380ecb45", index_title: "A股手续费及佣金收入〔报告期利润表〕"} - {id: "68da5476c80a86bb38cc5522", index_title: "A股手续费及佣金收入:单季〔利润表〕"} - {id: "68da5479c80a86bb38cc5654", index_title: "A股手续费及佣金收入:单季:按发布日〔利润表〕"} - {id: "68d246b3c80a86bb380ed226", index_title: "A股手续费及佣金收入:按发布日〔报告期利润表〕"} - {id: "68d8cd4dc80a86bb3888e616", index_title: "A股扣除非经常损益后的净利润/归属于母公司所有者的净利润:单季〔财务指标〕"} - {id: "68d8cd50c80a86bb3888e655", index_title: "A股扣除非经常损益后的净利润/归属于母公司所有者的净利润:单季:按发布日〔财务指标〕"} - {id: "68d8cd47c80a86bb3888e595", index_title: "A股扣除非经常损益后的净利润:单季〔财务指标〕"} - {id: "68d8cd4ac80a86bb3888e5d5", index_title: "A股扣除非经常损益后的净利润:单季:按发布日〔财务指标〕"} - {id: "68d25d50c80a86bb38150301", index_title: "A股投资性房地产〔报告期资产负债表〕"} - {id: "68d27558c80a86bb381b1e49", index_title: "A股投资性房地产的折旧及摊销〔报告期现金流量表〕"} - {id: "68d2756dc80a86bb381b1f94", index_title: "A股投资性房地产的折旧及摊销:按发布日〔报告期现金流量表〕"} - {id: "68da5704c80a86bb38ccb9d1", index_title: "A股投资性房地产:单季〔资产负债表〕"} - {id: "68da5707c80a86bb38ccba1f", index_title: "A股投资性房地产:单季:按发布日〔资产负债表〕"} - {id: "68d25d7cc80a86bb38150f41", index_title: "A股投资性房地产:按发布日〔报告期资产负债表〕"} - {id: "68d274d1c80a86bb381b15be", index_title: "A股投资损失〔报告期现金流量表〕"} - {id: "68d274ecc80a86bb381b1761", index_title: "A股投资损失:按发布日〔报告期现金流量表〕"} - {id: "68d27487c80a86bb381aef56", index_title: "A股投资支付的现金〔报告期现金流量表〕"} - {id: "68da592ac80a86bb38cd2d48", index_title: "A股投资支付的现金:单季〔现金流量表〕"} - {id: "68da592dc80a86bb38cd2d90", index_title: "A股投资支付的现金:单季:按发布日〔现金流量表〕"} - {id: "68d2749ac80a86bb381afd3f", index_title: "A股投资支付的现金:按发布日〔报告期现金流量表〕"} - {id: "68d24b30c80a86bb380f813c", index_title: "A股投资收益〔报告期利润表〕"} - {id: "68da54c7c80a86bb38cc6465", index_title: "A股投资收益:单季〔利润表〕"} - {id: "68da54cac80a86bb38cc64a1", index_title: "A股投资收益:单季:按发布日〔利润表〕"} - {id: "68d24b40c80a86bb380f8253", index_title: "A股投资收益:按发布日〔报告期利润表〕"} - {id: "68d277c5c80a86bb381b737e", index_title: "A股投资活动产生的现金流量净额〔报告期现金流量表〕"} - {id: "68da597cc80a86bb38cd3585", index_title: "A股投资活动产生的现金流量净额:单季〔现金流量表〕"} - {id: "68da597fc80a86bb38cd35cc", index_title: "A股投资活动产生的现金流量净额:单季:按发布日〔现金流量表〕"} - {id: "68d277dac80a86bb381b74b8", index_title: " -
a_share_option_iv.yaml 384 B
# Auto-generated business catalog; do not edit records by hand. schema_version: 2 category: a_share_option_iv record_count: 3 records: - {id: "698c48b53ab9fa86573e7c21", index_title: "A股期权隐含波动率"} - {id: "698c49cf3ab9fa86573ea572", index_title: "A股认沽期权隐含波动率"} - {id: "698c49b43ab9fa86573ea16e", index_title: "A股认购期权隐含波动率"} -
a_share_trading_labels.yaml 6.3 KB
# Auto-generated business catalog; do not edit records by hand. schema_version: 2 category: a_share_trading_labels record_count: 65 records: - {id: "6a3111b6cd1348d747629e1d", index_title: "A股龙虎榜三日上塘路标签净主导比例"} - {id: "6a3111b3cd1348d747629ce4", index_title: "A股龙虎榜三日上塘路标签净买额"} - {id: "6a3111dfcd1348d74762aa66", index_title: "A股龙虎榜三日佛山系标签净主导比例"} - {id: "6a3111dbcd1348d74762a9a7", index_title: "A股龙虎榜三日佛山系标签净买额"} - {id: "6a311188cd1348d747628e9c", index_title: "A股龙虎榜三日北向专用净主导比例"} - {id: "6a311185cd1348d747628d64", index_title: "A股龙虎榜三日北向专用净买额"} - {id: "6a31118fcd1348d7476290f8", index_title: "A股龙虎榜三日拉萨系净主导比例"} - {id: "6a31118ccd1348d747628fb8", index_title: "A股龙虎榜三日拉萨系净买额"} - {id: "6a3111b0cd1348d747629bb6", index_title: "A股龙虎榜三日拉萨系标签净主导比例"} - {id: "6a3111a8cd1348d747629972", index_title: "A股龙虎榜三日拉萨系标签净买额"} - {id: "6a3111cacd1348d74762a4e5", index_title: "A股龙虎榜三日方新侠候选标签净主导比例"} - {id: "6a3111c7cd1348d74762a41a", index_title: "A股龙虎榜三日方新侠候选标签净买额"} - {id: "6a311179cd1348d747628a08", index_title: "A股龙虎榜三日普通营业部净主导比例"} - {id: "6a311170cd1348d747628719", index_title: "A股龙虎榜三日普通营业部净买额"} - {id: "6a311181cd1348d747628be2", index_title: "A股龙虎榜三日机构专用净主导比例"} - {id: "6a31117dcd1348d747628adf", index_title: "A股龙虎榜三日机构专用净买额"} - {id: "6a3111bdcd1348d74762a0c3", index_title: "A股龙虎榜三日桑田路标签净主导比例"} - {id: "6a3111bacd1348d747629f83", index_title: "A股龙虎榜三日桑田路标签净买额"} - {id: "6a3111d1cd1348d74762a6aa", index_title: "A股龙虎榜三日欢乐海岸候选标签净主导比例"} - {id: "6a3111cdcd1348d74762a5d6", index_title: "A股龙虎榜三日欢乐海岸候选标签净买额"} - {id: "6a3111a4cd1348d747629857", index_title: "A股龙虎榜三日活跃营业部净主导比例"} - {id: "6a3111a1cd1348d7476296f2", index_title: "A股龙虎榜三日活跃营业部净买额"} - {id: "6a3111d7cd1348d74762a891", index_title: "A股龙虎榜三日溧阳路标签净主导比例"} - {id: "6a3111d4cd1348d74762a7e5", index_title: "A股龙虎榜三日溧阳路标签净买额"} - {id: "6a311196cd1348d74762935f", index_title: "A股龙虎榜三日知名游资候选净主导比例"} - {id: "6a311193cd1348d74762920f", index_title: "A股龙虎榜三日知名游资候选净买额"} - {id: "6a3111c4cd1348d74762a331", index_title: "A股龙虎榜三日赵老哥候选标签净主导比例"} - {id: "6a3111c0cd1348d74762a1c0", index_title: "A股龙虎榜三日赵老哥候选标签净买额"} - {id: "6a31119dcd1348d7476295c5", index_title: "A股龙虎榜三日量化疑似净主导比例"} - {id: "6a31119acd1348d747629493", index_title: "A股龙虎榜三日量化疑似净买额"} - {id: "6a3111e5cd1348d74762abe5", index_title: "A股龙虎榜三日量化疑似标签净主导比例"} - {id: "6a3111e2cd1348d74762ab09", index_title: "A股龙虎榜三日量化疑似标签净买额"} - {id: "6a31113acd1348d747627b30", index_title: "A股龙虎榜上塘路标签净主导比例"} - {id: "6a311137cd1348d747627ade", index_title: "A股龙虎榜上塘路标签净买额"} - {id: "6a3110e2cd1348d747627539", index_title: "A股龙虎榜上榜股票"} - {id: "6a311166cd1348d747628384", index_title: "A股龙虎榜佛山系标签净主导比例"} - {id: "6a311162cd1348d747628226", index_title: "A股龙虎榜佛山系标签净买额"} - {id: "6a311106cd1348d74762775c", index_title: "A股龙虎榜北向专用净主导比例"} - {id: "6a311102cd1348d747627736", index_title: "A股龙虎榜北向专用净买额"} - {id: "6a31110dcd1348d7476277cd", index_title: "A股龙虎榜拉萨系净主导比例"} - {id: "6a311109cd1348d74762779b", index_title: "A股龙虎榜拉萨系净买额"} - {id: "6a311133cd1348d747627aa8", index_title: "A股龙虎榜拉萨系标签净主导比例"} - {id: "6a311126cd1348d7476279f8", index_title: "A股龙虎榜拉萨系标签净买额"} - {id: "6a311150cd1348d747627c85", index_title: "A股龙虎榜方新侠候选标签净主导比例"} - {id: "6a31114ccd1348d747627c4f", index_title: "A股龙虎榜方新侠候选标签净买额"} - {id: "6a3110f7cd1348d74762767e", index_title: "A股龙虎榜普通营业部净主导比例"} - {id: "6a3110e8cd1348d74762759d", index_title: "A股龙虎榜普通营业部净买额"} - {id: "6a3110ffcd1348d7476276f9", index_title: "A股龙虎榜机构专用净主导比例"} - {id: "6a3110fbcd1348d7476276b8", index_title: "A股龙虎榜机构专用净买额"} - {id: "6a311141cd1348d747627b8d", index_title: "A股龙虎榜桑田路标签净主导比例"} - {id: "6a31113ecd1348d747627b65", index_title: "A股龙虎榜桑田路标签净买额"} - {id: "6a311156cd1348d747627de1", index_title: "A股龙虎榜欢乐海岸候选标签净主导比例"} - {id: "6a311153cd1348d747627d1f", index_title: "A股龙虎榜欢乐海岸候选标签净买额"} - {id: "6a311122cd1348d7476279a4", index_title: "A股龙虎榜活跃营业部净主导比例"} - {id: "6a31111fcd1348d747627966", index_title: "A股龙虎榜活跃营业部净买额"} - {id: "6a31115ecd1348d747628076", index_title: "A股龙虎榜溧阳路标签净主导比例"} - {id: "6a31115acd1348d747627f30", index_title: "A股龙虎榜溧阳路标签净买额"} - {id: "6a311114cd1348d74762784f", index_title: "A股龙虎榜知名游资候选净主导比例"} - {id: "6a311111cd1348d74762780d", index_title: "A股龙虎榜知名游资候选净买额"} - {id: "6a311149cd1348d747627c10", index_title: "A股龙虎榜赵老哥候选标签净主导比例"} - {id: "6a311145cd1348d747627bc4", index_title: "A股龙虎榜赵老哥候选标签净买额"} - {id: "6a31111ccd1348d74762790b", index_title: "A股龙虎榜量化疑似净主导比例"} - {id: "6a311118cd1348d7476278a5", index_title: "A股龙虎榜量化疑似净买额"} - {id: "6a31116ccd1348d7476285e5", index_title: "A股龙虎榜量化疑似标签净主导比例"} - {id: "6a311169cd1348d7476284e9", index_title: "A股龙虎榜量化疑似标签净买额"} -
global_classification.yaml 297 B
# Auto-generated business catalog; do not edit records by hand. schema_version: 2 category: global_classification record_count: 2 records: - {id: "6a2138a9031ae7c8e91bb77f", index_title: "GICS一级行业所属指数"} - {id: "6a2138a4031ae7c8e91bb592", index_title: "GICS板块所属指数"} -
global_financials.yaml 133.8 KB
# Auto-generated business catalog; do not edit records by hand. schema_version: 2 category: global_financials record_count: 1324 records: - {id: "6a8bafa20162cdd13a1a4385", index_title: "港股EBITDA利润率:半年〔财务比率〕"} - {id: "69ff0133031ae7c8e9a92278", index_title: "港股EBITDA利润率:单季〔财务比率〕"} - {id: "69ff0198031ae7c8e9a92ccd", index_title: "港股EBITDA利润率:年度〔财务比率〕"} - {id: "69ff0445031ae7c8e9a97023", index_title: "港股EBITDA:TTM〔利润表〕"} - {id: "6a8bafa70162cdd13a1a4780", index_title: "港股EBITDA:半年〔利润表〕"} - {id: "69ff03e2031ae7c8e9a966cc", index_title: "港股EBITDA:单季〔利润表〕"} - {id: "69ff0418031ae7c8e9a96bca", index_title: "港股EBITDA:年度〔利润表〕"} - {id: "6a8bafa20162cdd13a1a4379", index_title: "港股EBIT利润率:半年〔财务比率〕"} - {id: "69ff0131031ae7c8e9a92237", index_title: "港股EBIT利润率:单季〔财务比率〕"} - {id: "69ff0196031ae7c8e9a92c93", index_title: "港股EBIT利润率:年度〔财务比率〕"} - {id: "69ff0443031ae7c8e9a96ffa", index_title: "港股EBIT:TTM〔利润表〕"} - {id: "6a8bafa70162cdd13a1a477a", index_title: "港股EBIT:半年〔利润表〕"} - {id: "69ff03e1031ae7c8e9a966b2", index_title: "港股EBIT:单季〔利润表〕"} - {id: "69ff0416031ae7c8e9a96b9c", index_title: "港股EBIT:年度〔利润表〕"} - {id: "69ff0067031ae7c8e9a90c40", index_title: "港股EV/EBITDA:单季〔关键指标〕"} - {id: "69ff00af031ae7c8e9a91438", index_title: "港股EV/EBITDA:年度〔关键指标〕"} - {id: "69ff006a031ae7c8e9a90c8d", index_title: "港股EV/经营现金流:单季〔关键指标〕"} - {id: "69ff00b2031ae7c8e9a91486", index_title: "港股EV/经营现金流:年度〔关键指标〕"} - {id: "69ff0069031ae7c8e9a90c52", index_title: "港股EV/自由现金流:单季〔关键指标〕"} - {id: "69ff00b0031ae7c8e9a91465", index_title: "港股EV/自由现金流:年度〔关键指标〕"} - {id: "69ff006c031ae7c8e9a90cc2", index_title: "港股EV/销售额:单季〔关键指标〕"} - {id: "69ff00b3031ae7c8e9a914ce", index_title: "港股EV/销售额:年度〔关键指标〕"} - {id: "6a8bafa30162cdd13a1a452a", index_title: "港股PEG比率:半年〔财务比率〕"} - {id: "69ff0161031ae7c8e9a9276e", index_title: "港股PEG比率:单季〔财务比率〕"} - {id: "69ff01bf031ae7c8e9a93083", index_title: "港股PEG比率:年度〔财务比率〕"} - {id: "6a8bafa30162cdd13a1a453b", index_title: "港股价格/公允价值:半年〔财务比率〕"} - {id: "69ff0164031ae7c8e9a927b2", index_title: "港股价格/公允价值:单季〔财务比率〕"} - {id: "69ff01c2031ae7c8e9a930dc", index_title: "港股价格/公允价值:年度〔财务比率〕"} - {id: "6a8bafa20162cdd13a1a43c1", index_title: "港股企业价值倍数:半年〔财务比率〕"} - {id: "69ff0139031ae7c8e9a9230f", index_title: "港股企业价值倍数:单季〔财务比率〕"} - {id: "69ff019d031ae7c8e9a92d3c", index_title: "港股企业价值倍数:年度〔财务比率〕"} - {id: "69ff0065031ae7c8e9a90c18", index_title: "港股企业价值:单季〔关键指标〕"} - {id: "69ff00ad031ae7c8e9a91427", index_title: "港股企业价值:年度〔关键指标〕"} - {id: "69ff03c6031ae7c8e9a96418", index_title: "港股优先股净发行:TTM〔现金流量表〕"} - {id: "6a8bafaa0162cdd13a1a49ec", index_title: "港股优先股净发行:半年〔现金流量表〕"} - {id: "69ff034e031ae7c8e9a956b1", index_title: "港股优先股净发行:单季〔现金流量表〕"} - {id: "69ff0388031ae7c8e9a95d52", index_title: "港股优先股净发行:年度〔现金流量表〕"} - {id: "69ff03d2031ae7c8e9a96541", index_title: "港股优先股股息支付:TTM〔现金流量表〕"} - {id: "6a8bafaa0162cdd13a1a4a49", index_title: "港股优先股股息支付:半年〔现金流量表〕"} - {id: "69ff035a031ae7c8e9a957fc", index_title: "港股优先股股息支付:单季〔现金流量表〕"} - {id: "69ff0392031ae7c8e9a95f12", index_title: "港股优先股股息支付:年度〔现金流量表〕"} - {id: "69ff02fb031ae7c8e9a94f09", index_title: "港股优先股:TTM〔资产负债表〕"} - {id: "6a8bafa60162cdd13a1a467d", index_title: "港股优先股:半年〔资产负债表〕"} - {id: "69ff0257031ae7c8e9a93f69", index_title: "港股优先股:单季〔资产负债表〕"} - {id: "69ff02a9031ae7c8e9a9473c", index_title: "港股优先股:年度〔资产负债表〕"} - {id: "69ff008a031ae7c8e9a910de", index_title: "港股使用资本回报率:单季〔关键指标〕"} - {id: "69ff00ce031ae7c8e9a91740", index_title: "港股使用资本回报率:年度〔关键指标〕"} - {id: "6a8bafa20162cdd13a1a4344", index_title: "港股债务/市值:半年〔财务比率〕"} - {id: "69ff0129031ae7c8e9a92159", index_title: "港股债务/市值:单季〔财务比率〕"} - {id: "69ff018f031ae7c8e9a92c05", index_title: "港股债务/市值:年度〔财务比率〕"} - {id: "69ff03c1031ae7c8e9a963a4", index_title: "港股债务净发行:TTM〔现金流量表〕"} - {id: "6a8bafaa0162cdd13a1a49b9", index_title: "港股债务净发行:半年〔现金流量表〕"} - {id: "69ff0349031ae7c8e9a9562f", index_title: "港股债务净发行:单季〔现金流量表〕"} - {id: "69ff0384031ae7c8e9a95cc5", index_title: "港股债务净发行:年度〔现金流量表〕"} - {id: "69ff030e031ae7c8e9a950c9", index_title: "港股债务合计:TTM〔资产负债表〕"} - {id: "6a8bafa60162cdd13a1a46dd", index_title: "港股债务合计:半年〔资产负债表〕"} - {id: "69ff026a031ae7c8e9a94130", index_title: "港股债务合计:单季〔资产负债表〕"} - {id: "69ff02b8031ae7c8e9a948a9", index_title: "港股债务合计:年度〔资产负债表〕"} - {id: "6a8bafa20162cdd13a1a4339", index_title: "港股债务权益比:半年〔财务比率〕"} - {id: "69ff0127031ae7c8e9a9210b", index_title: "港股债务权益比:单季〔财务比率〕"} - {id: "69ff018e031ae7c8e9a92bc0", index_title: "港股债务权益比:年度〔财务比率〕"} - {id: "6a8bafa40162cdd13a1a4580", index_title: "港股偿债比率:半年〔财务比率〕"} - {id: "69ff0175031ae7c8e9a92977", index_title: "港股偿债比率:单季〔财务比率〕"} - {id: "69ff01d0031ae7c8e9a93213", index_title: "港股偿债比率:年度〔财务比率〕"} - {id: "6a8bafa10162cdd13a1a431d", index_title: "港股偿债覆盖率:半年〔财务比率〕"} - {id: "69ff0121031ae7c8e9a9208c", index_title: "港股偿债覆盖率:单季〔财务比率〕"} - {id: "69ff0189031ae7c8e9a92b5d", index_title: "港股偿债覆盖率:年度〔财务比率〕"} - {id: "69ff0070031ae7c8e9a90d1a", index_title: "港股公司自由现金流:单季〔关键指标〕"} - {id: "69ff00b6031ae7c8e9a91508", index_title: "港股公司自由现金流:年度〔关键指标〕"} - {id: "69ff02f6031ae7c8e9a94e9b", index_title: "港股其他应付款:TTM〔资产负债表〕"} - {id: "6a8bafa50162cdd13a1a466e", index_title: "港股其他应付款:半年〔资产负债表〕"} - {id: "69ff0252031ae7c8e9a93ee4", index_title: "港股其他应付款:单季〔资产负债表〕"} - {id: "69ff02a4031ae7c8e9a946c6", index_title: "港股其他应付款:年度〔资产负债表〕"} - {id: "69ff02f8031ae7c8e9a94ec6", index_title: "港股其他应收款:TTM〔资产负债表〕"} - {id: "6a8bafa60162cdd13a1a4672", index_title: "港股其他应收款:半年〔资产负债表〕"} - {id: "69ff0253031ae7c8e9a93efe", index_title: "港股其他应收款:单季〔资产负债表〕"} - {id: "69ff02a6031ae7c8e9a946e4", index_title: "港股其他应收款:年度〔资产负债表〕"} - {id: "69ff03cc031ae7c8e9a964d0", index_title: "港股其他投资活动:TTM〔现金流量表〕"} - {id: "6a8bafaa0162cdd13a1a4a20", index_title: "港股其他投资活动:半年〔现金流量表〕"} - {id: "69ff0355031ae7c8e9a9576d", index_title: "港股其他投资活动:单季〔现金流量表〕"} - {id: "69ff038d031ae7c8e9a95eb1", index_title: "港股其他投资活动:年度〔现金流量表〕"} - {id: "69ff046c031ae7c8e9a973c3", index_title: "港股其他收入费用净额:TTM〔利润表〕"} - {id: "6a8bafa80162cdd13a1a4840", index_title: "港股其他收入费用净额:半年〔利润表〕"} - {id: "69ff040b031ae7c8e9a96a91", index_title: "港股其他收入费用净额:单季〔利润表〕"} - {id: "69ff0437031ae7c8e9a96ee1", index_title: "港股其他收入费用净额:年度〔利润表〕"} - {id: "69ff02f0031ae7c8e9a94df5", index_title: "港股其他流动负债:TTM〔资产负债表〕"} - {id: "6a8bafa50162cdd13a1a4654", index_title: "港股其他流动负债:半年〔资产负债表〕"} - {id: "69ff024b031ae7c8e9a93e45", index_title: "港股其他流动负债:单季〔资产负债表〕"} - {id: "69ff029f031ae7c8e9a94656", index_title: "港股其他流动负债:年度〔资产负债表〕"} - {id: "69ff02ee031ae7c8e9a94dcf", index_title: "港股其他流动资产:TTM〔资产负债表〕"} - {id: "6a8bafa50162cdd13a1a464d", index_title: "港股其他流动资产:半年〔资产负债表〕"} - {id: "69ff0249031ae7c8e9a93e06", index_title: "港股其他流动资产:单季〔资产负债表〕"} - {id: "69ff029e031ae7c8e9a94636", index_title: "港股其他流动资产:年度〔资产负债表〕"} - {id: "69ff02fa031ae7c8e9a94ee8", index_title: "港股其他股东权益:TTM〔资产负债表〕"} - {id: "6a8bafa60162cdd13a1a4677", index_title: "港股其他股东权益:半年〔资产负债表〕"} - {id: "69ff0255031ae7c8e9a93f37", index_title: "港股其他股东权益:单季〔资产负债表〕"} - {id: "69ff02a7031ae7c8e9a94728", index_title: "港股其他股东权益:年度〔资产负债表〕"} - {id: "69ff03d0031ae7c8e9a96511", index_title: "港股其他营运资本:TTM〔现金流量表〕"} - {id: "6a8bafaa0162cdd13a1a4a3d", index_title: "港股其他营运资本:半年〔现金流量表〕"} - {id: "69ff0359031ae7c8e9a957ba", index_title: "港股其他营运资本:单季〔现金流量表〕"} - {id: "69ff0390031ae7c8e9a95ef9", index_title: "港股其他营运资本:年度〔现金流量表〕"} - {id: "69ff03ca031ae7c8e9a964a1", index_title: "港股其他融资活动:TTM〔现金流量表〕"} - {id: "6a8bafaa0162cdd13a1a4a14", index_title: "港股其他融资活动:半年〔现金流量表〕"} - {id: "69ff0353031ae7c8e9a95747", index_title: "港股其他融资活动:单季〔现金流量表〕"} - {id: "69ff038c031ae7c8e9a95e12", index_title: "港股其他融资活动:年度〔现金流量表〕"} - {id: "69ff02f2031ae7c8e9a94e10", index_title: "港股其他负债:TTM〔资产负债表〕"} - {id: "6a8bafa50162cdd13a1a465c", index_title: "港股其他负债:半年〔资产负债表〕"} - {id: "69ff024d031ae7c8e9a93e7e", index_title: "港股其他负债:单季〔资产负债表〕"} - {id: "69ff02a1031ae7c8e9a94673", index_title: "港股其他负债:年度〔资产负债表〕"} - {id: "69ff0464031ae7c8e9a972f3", index_title: "港股其他费用:TTM〔利润表〕"} - {id: "6a8bafa80162cdd13a1a481c", index_title: "港股其他费用:半年〔利润表〕"} - {id: "69ff0402031ae7c8e9a969cd", index_title: "港股其他费用:单季〔利润表〕"} - {id: "69ff0430031ae7c8e9a96e34", index_title: "港股其他费用:年度〔利润表〕"} - {id: "69ff02ed031ae7c8e9a94db6", index_title: "港股其他资产:TTM〔资产负债表〕"} - {id: "6a8bafa50162cdd13a1a4642", index_title: "港股其他资产:半年〔资产负债表〕"} - {id: "69ff0248031ae7c8e9a93de6", index_title: "港股其他资产:单季〔资产负债表〕"} - {id: "69ff029d031ae7c8e9a94615", index_title: "港股其他资产:年度〔资产负债表〕"} - {id: "69ff02f4031ae7c8e9a94e55", index_title: "港股其他非流动负债:TTM〔资产负债表〕"} - {id: "6a8bafa50162cdd13a1a466a", index_title: "港股其他非流动负债:半年〔资产负债表〕"} - {id: "69ff0250031ae7c8e9a93ec9", index_title: "港股其他非流动负债:单季〔资产负债表〕"} - {id: "69ff02a3031ae7c8e9a94698", index_title: "港股其他非流动负债:年度〔资产负债表〕"} - {id: "69ff02f3031ae7c8e9a94e20", index_title: "港股其他非流动资产:TTM〔资产负债表〕"} - {id: "6a8bafa50162cdd13a1a4663", index_title: "港股其他非流动资产:半年〔资产负债表〕"} - {id: "69ff024e031ae7c8e9a93ea0", index_title: "港股其他非流动资产:单季〔资产负债表〕"} - {id: "69ff02a1031ae7c8e9a94685", index_title: "港股其他非流动资产:年度〔资产负债表〕"} - {id: "69ff03ce031ae7c8e9a964ed", index_title: "港股其他非现金项目:TTM〔现金流量表〕"} - {id: "6a8bafaa0162cdd13a1a4a31", index_title: "港股其他非现金项目:半年〔现金流量表〕"} - {id: "69ff0357031ae7c8e9a95786", index_title: "港股其他非现金项目:单季〔现金流量表〕"} - {id: "69ff038f031ae7c8e9a95ed6", index_title: "港股其他非现金项目:年度〔现金流量表〕"} - {id: "69ff0082031ae7c8e9a91038", index_title: "港股净债务/EBITDA:单季〔关键指标〕"} - {id: "69ff00c6031ae7c8e9a916ac", index_title: "港股净债务/EBITDA:年度〔关键指标〕"} - {id: "69ff02ea031ae7c8e9a94d47", index_title: "港股净债务:TTM〔资产负债表〕"} - {id: "6a8bafa50162cdd13a1a4632", index_title: "港股净债务:半年〔资产负债表〕"} - {id: "69ff0244031ae7c8e9a93d8f", index_title: "港股净债务:单季〔资产负债表〕"} - {id: "69ff029a031ae7c8e9a945b7", index_title: "港股净债务:年度〔资产负债表〕"} - {id: "69ff045c031ae7c8e9a97225", index_title: "港股净利息收入:TTM〔利润表〕"} - {id: "6a8bafa80162cdd13a1a47fd", index_title: "港股净利息收入:半年〔利润表〕"} - {id: "69ff03f9031ae7c8e9a968fd", index_title: "港股净利息收入:单季〔利润表〕"} - {id: "69ff0429031ae7c8e9a96d6e", index_title: "港股净利息收入:年度〔利润表〕"} - {id: "6a8bafa30162cdd13a1a443f", index_title: "港股净利润/税前利润:半年〔财务比率〕"} - {id: "69ff014c031ae7c8e9a924e4", index_title: "港股净利润/税前利润:单季〔财务比率〕"} - {id: "69ff01ae031ae7c8e9a92ef3", index_title: "港股净利润/税前利润:年度〔财务比率〕"} - {id: "69ff0463031ae7c8e9a972dc", index_title: "港股净利润其他调整:TTM〔利润表〕"} - {id: "6a8bafa80162cdd13a1a4816", index_title: "港股净利润其他调整:半年〔利润表〕"} - {id: "69ff0401031ae7c8e9a969a8", index_title: "港股净利润其他调整:单季〔利润表〕"} - {id: "69ff042f031ae7c8e9a96e1e", index_title: "港股净利润其他调整:年度〔利润表〕"} - {id: "69ff0458031ae7c8e9a971d5", index_title: "港股净利润扣除项:TTM〔利润表〕"} - {id: "6a8bafa80162cdd13a1a47df", index_title: "港股净利润扣除项:半年〔利润表〕"} - {id: "69ff03f5031ae7c8e9a968a1", index_title: "港股净利润扣除项:单季〔利润表〕"} - {id: "69ff0426031ae7c8e9a96d3f", index_title: "港股净利润扣除项:年度〔利润表〕"} - {id: "6a8bafa30162cdd13a1a4473", index_title: "港股净利润率:半年〔财务比率〕"} - {id: "69ff014f031ae7c8e9a925a7", index_title: "港股净利润率:单季〔财务比率〕"} - {id: "69ff01b1031ae7c8e9a92f37", index_title: "港股净利润率:年度〔财务比率〕"} - {id: "69ff0456031ae7c8e9a971b1", index_title: "港股净利润:TTM〔利润表〕"} - {id: "69ff03c4031ae7c8e9a963f4", index_title: "港股净利润:TTM〔现金流量表〕"} - {id: "6a8bafa70162cdd13a1a47cf", index_title: "港股净利润:半年〔利润表〕"} - {id: "6a8bafaa0162cdd13a1a49dd", index_title: "港股净利润:半年〔现金流量表〕"} - {id: "69ff03f3031ae7c8e9a9687b", index_title: "港股净利润:单季〔利润表〕"} - {id: "69ff034c031ae7c8e9a95675", index_title: "港股净利润:单季〔现金流量表〕"} - {id: "69ff0425031ae7c8e9a96d16", index_title: "港股净利润:年度〔利润表〕"} - {id: "69ff0387031ae7c8e9a95d1e", index_title: "港股净利润:年度〔现金流量表〕"} - {id: "69ff0080031ae7c8e9a90ff3", index_title: "港股净流动资产价值:单季〔关键指标〕"} - {id: "69ff00c5031ae7c8e9a9167a", index_title: "港股净流动资产价值:年度〔关键指标〕"} - {id: "69ff03b1031ae7c8e9a96208", index_title: "港股利息支付:TTM〔现金流量表〕"} - {id: "6a8bafa90162cdd13a1a48fb", index_title: "港股利息支付:半年〔现金流量表〕"} - {id: "69ff0339031ae7c8e9a954a1", index_title: "港股利息支付:单季〔现金流量表〕"} - {id: "69ff0377031ae7c8e9a95b30", index_title: "港股利息支付:年度〔现金流量表〕"} - {id: "69ff0452031ae7c8e9a9713f", index_title: "港股利息支出:TTM〔利润表〕"} - {id: "6a8bafa70162cdd13a1a47b9", index_title: "港股利息支出:半年〔利润表〕"} - {id: "69ff03f0031ae7c8e9a9680f", index_title: "港股利息支出:单季〔利润表〕"} - {id: "69ff0422031ae7c8e9a96cbe", index_title: "港股利息支出:年度〔利润表〕"} - {id: "69ff0454031ae7c8e9a9716e", index_title: "港股利息收入:TTM〔利润表〕"} - {id: "6a8bafa70162cdd13a1a47c4", index_title: "港股利息收入:半年〔利润表〕"} - {id: "69ff03f2031ae7c8e9a96851", index_title: "港股利息收入:单季〔利润表〕"} - {id: "69ff0423031ae7c8e9a96cf7", index_title: "港股利息收入:年度〔利润表〕"} - {id: "6a8bafa20162cdd13a1a4412", index_title: "港股利息覆盖率:半年〔财务比率〕"} - {id: "69ff0145031ae7c8e9a9241d", index_title: "港股利息覆盖率:单季〔财务比率〕"} - {id: "69ff01a8031ae7c8e9a92e3f", index_title: "港股利息覆盖率:年度〔财务比率〕"} - {id: "69ff007b031ae7c8e9a90e1f", index_title: "港股利息负担:单季〔关键指标〕"} - {id: "69ff00c0031ae7c8e9a91607", index_title: "港股利息负担:年度〔关键指标〕"} - {id: "69ff0077031ae7c8e9a90ddc", index_title: "港股利润质量:单季〔关键指标〕"} - {id: "69ff00bd031ae7c8e9a915c2", index_title: "港股利润质量:年度〔关键指标〕"} - {id: "69ff046e031ae7c8e9a973de", index_title: "港股加权平均股数:TTM〔利润表〕"} - {id: "6a8bafa80162cdd13a1a484c", index_title: "港股加权平均股数:半年〔利润表〕"} - {id: "69ff040d031ae7c8e9a96abc", index_title: "港股加权平均股数:单季〔利润表〕"} - {id: "69ff0438031ae7c8e9a96efc", index_title: "港股加权平均股数:年度〔利润表〕"} - {id: "69ff02df031ae7c8e9a94c47", index_title: "港股商誉及无形资产:TTM〔资产负债表〕"} - {id: "6a8bafa50162cdd13a1a460c", index_title: "港股商誉及无形资产:半年〔资产负债表〕"} - {id: "69ff023a031ae7c8e9a93c9a", index_title: "港股商誉及无形资产:单季〔资产负债表〕"} - {id: "69ff0291031ae7c8e9a944e5", index_title: "港股商誉及无形资产:年度〔资产负债表〕"} - {id: "69ff02de031ae7c8e9a94c35", index_title: "港股商誉:TTM〔资产负债表〕"} - {id: "6a8bafa50162cdd13a1a4604", index_title: "港股商誉:半年〔资产负债表〕"} - {id: "69ff0238031ae7c8e9a93c70", index_title: "港股商誉:单季〔资产负债表〕"} - {id: "69ff0290031ae7c8e9a944ba", index_title: "港股商誉:年度〔资产负债表〕"} - {id: "69ff02fe031ae7c8e9a94f40", index_title: "港股固定资产净额:TTM〔资产负债表〕"} - {id: "6a8bafa60162cdd13a1a468a", index_title: "港股固定资产净额:半年〔资产负债表〕"} - {id: "69ff025a031ae7c8e9a93fb7", index_title: "港股固定资产净额:单季〔资产负债表〕"} - {id: "69ff02ab031ae7c8e9a94776", index_title: "港股固定资产净额:年度〔资产负债表〕"} - {id: "6a8bafa20162cdd13a1a43e6", index_title: "港股固定资产周转率:半年〔财务比率〕"} - {id: "69ff013c031ae7c8e9a9234f", index_title: "港股固定资产周转率:单季〔财务比率〕"} - {id: "69ff01a0031ae7c8e9a92d73", index_title: "港股固定资产周转率:年度〔财务比率〕"} - {id: "69ff03b4031ae7c8e9a9627e", index_title: "港股固定资产投资:TTM〔现金流量表〕"} - {id: "6a8bafa90162cdd13a1a4920", index_title: "港股固定资产投资:半年〔现金流量表〕"} - {id: "69ff033c031ae7c8e9a954ef", index_title: "港股固定资产投资:单季〔现金流量表〕"} - {id: "69ff0379031ae7c8e9a95b80", index_title: "港股固定资产投资:年度〔现金流量表〕"} - {id: "69ff005e031ae7c8e9a90b63", index_title: "港股存货周转天数:单季〔关键指标〕"} - {id: "69ff00a7031ae7c8e9a91390", index_title: "港股存货周转天数:年度〔关键指标〕"} - {id: "6a8bafa30162cdd13a1a4420", index_title: "港股存货周转率:半年〔财务比率〕"} - {id: "69ff0149031ae7c8e9a92494", index_title: "港股存货周转率:单季〔财务比率〕"} - {id: "69ff01ab031ae7c8e9a92e98", index_title: "港股存货周转率:年度〔财务比率〕"} - {id: "69ff03b3031ae7c8e9a9622e", index_title: "港股存货:TTM〔现金流量表〕"} - {id: "69ff02e3031ae7c8e9a94cc1", index_title: "港股存货:TTM〔资产负债表〕"} - {id: "6a8bafa90162cdd13a1a490b", index_title: "港股存货:半年〔现金流量表〕"} - {id: "6a8bafa50162cdd13a1a461c", index_title: "港股存货:半年〔资产负债表〕"} - {id: "69ff033a031ae7c8e9a954bf", index_title: "港股存货:单季〔现金流量表〕"} - {id: "69ff023d031ae7c8e9a93cdd", index_title: "港股存货:单季〔资产负债表〕"} - {id: "69ff0378031ae7c8e9a95b46", index_title: "港股存货:年度〔现金流量表〕"} - {id: "69ff0294031ae7c8e9a9453e", index_title: "港股存货:年度〔资产负债表〕"} - {id: "69ff02e8031ae7c8e9a94d26", index_title: "港股少数股东权益:TTM〔资产负债表〕"} - {id: "6a8bafa50162cdd13a1a462c", index_title: "港股少数股东权益:半年〔资产负债表〕"} - {id: "69ff0243031ae7c8e9a93d63", index_title: "港股少数股东权益:单季〔资产负债表〕"} - {id: "69ff0298031ae7c8e9a94599", index_title: "港股少数股东权益:年度〔资产负债表〕"} - {id: "69ff007e031ae7c8e9a90fd1", index_title: "港股市值:单季〔关键指标〕"} - {id: "69ff00c3031ae7c8e9a91637", index_title: "港股市值:年度〔关键指标〕"} - {id: "6a0c543b031ae7c8e9d52455", index_title: "港股市净率PB〔估值数据〕"} - {id: "6a8bafa30162cdd13a1a451e", index_title: "港股市净率:半年〔财务比率〕"} - {id: "69ff015f031ae7c8e9a92744", index_title: "港股市净率:单季〔财务比率〕"} - {id: "69ff01bd031ae7c8e9a93049", index_title: "港股市净率:年度〔财务比率〕"} - {id: "6a8bafa30162cdd13a1a454c", index_title: "港股市现率(经营现金流):半年〔财务比率〕"} - {id: "69ff0168031ae7c8e9a92830", index_title: "港股市现率(经营现金流):单季〔财务比率〕"} - {id: "69ff01c5031ae7c8e9a93119", index_title: "港股市现率(经营现金流):年度〔财务比率〕"} - {id: "6a8bafa30162cdd13a1a4544", index_title: "港股市现率(自由现金流):半年〔财务比率〕"} - {id: "69ff0166031ae7c8e9a927fd", index_title: "港股市现率(自由现金流):单季〔财务比率〕"} - {id: "69ff01c4031ae7c8e9a93101", index_title: "港股市现率(自由现金流):年度〔财务比率〕"} - {id: "6a0c5466031ae7c8e9d5273d", index_title: "港股市现率PCF(现金净流量):TTM〔估值数据〕"} - {id: "6a0c5470031ae7c8e9d527d4", index_title: "港股市现率PCF(经营活动现金流):TTM〔估值数据〕"} - {id: "6a0c5449031ae7c8e9d5254e", index_title: "港股市盈率PE:TTM〔估值数据〕"} - {id: "6a8bafa30162cdd13a1a4535", index_title: "港股市盈率:半年〔财务比率〕"} - {id: "69ff0162031ae7c8e9a92781", index_title: "港股市盈率:单季〔财务比率〕"} - {id: "69ff01c0031ae7c8e9a930b4", index_title: "港股市盈率:年度〔财务比率〕"} - {id: "6a0c5453031ae7c8e9d52603", index_title: "港股市销率PS:TTM〔估值数据〕"} - {id: "6a8bafa40162cdd13a1a4555", index_title: "港股市销率:半年〔财务比率〕"} - {id: "69ff016a031ae7c8e9a9285b", index_title: "港股市销率:单季〔财务比率〕"} - {id: "69ff01c7031ae7c8e9a9312c", index_title: "港股市销率:年度〔财务比率〕"} - {id: "69ff004f031ae7c8e9a90a27", index_title: "港股平均存货:单季〔关键指标〕"} - {id: "69ff009b031ae7c8e9a91265", index_title: "港股平均存货:年度〔关键指标〕"} - {id: "69ff0051031ae7c8e9a90a43", index_title: "港股平均应付账款:单季〔关键指标〕"} - {id: "69ff009c031ae7c8e9a91290", index_title: "港股平均应付账款:年度〔关键指标〕"} - {id: "69ff0053031ae7c8e9a90a69", index_title: "港股平均应收账款:单季〔关键指标〕"} - {id: "69ff009e031ae7c8e9a912ab", index_title: "港股平均应收账款:年度〔关键指标〕"} - {id: "69ff031f031ae7c8e9a95250", index_title: "港股库藏股:TTM〔资产负债表〕"} - {id: "6a8bafa70162cdd13a1a474a", index_title: "港股库藏股:半年〔资产负债表〕"} - {id: "69ff027b031ae7c8e9a942c5", index_title: "港股库藏股:单季〔资产负债表〕"} - {id: "69ff02c6031ae7c8e9a94a07", index_title: "港股库藏股:年度〔资产负债表〕"} - {id: "69ff0307031ae7c8e9a95007", index_title: "港股应交税费:TTM〔资产负债表〕"} - {id: "6a8bafa60162cdd13a1a46be", index_title: "港股应交税费:半年〔资产负债表〕"} - {id: "69ff0262031ae7c8e9a9409b", index_title: "港股应交税费:单季〔资产负债表〕"} - {id: "69ff02b2031ae7c8e9a9482b", index_title: "港股应交税费:年度〔资产负债表〕"} - {id: "69ff031b031ae7c8e9a951e1", index_title: "港股应付款项合计:TTM〔资产负债表〕"} - {id: "6a8bafa70162cdd13a1a4731", index_title: "港股应付款项合计:半年〔资产负债表〕"} - {id: "69ff0277031ae7c8e9a94289", index_title: "港股应付款项合计:单季〔资产负债表〕"} - {id: "69ff02c3031ae7c8e9a949b0", index_title: "港股应付款项合计:年度〔资产负债表〕"} - {id: "6a8bafa30162cdd13a1a4508", index_title: "港股应付账款周转率:半年〔财务比率〕"} - {id: "69ff015b031ae7c8e9a926de", index_title: "港股应付账款周转率:单季〔财务比率〕"} - {id: "69ff01ba031ae7c8e9a9301b", index_title: "港股应付账款周转率:年度〔财务比率〕"} - {id: "69ff0060031ae7c8e9a90b8e", index_title: "港股应付账款天数:单季〔关键指标〕"} - {id: "69ff00a8031ae7c8e9a913c1", index_title: "港股应付账款天数:年度〔关键指标〕"} - {id: "69ff0398031ae7c8e9a95fc7", index_title: "港股应付账款:TTM〔现金流量表〕"} - {id: "69ff02c7031ae7c8e9a94a23", index_title: "港股应付账款:TTM〔资产负债表〕"} - {id: "6a8bafa80162cdd13a1a4867", index_title: "港股应付账款:半年〔现金流量表〕"} - {id: "6a8bafa40162cdd13a1a459a", index_title: "港股应付账款:半年〔资产负债表〕"} - {id: "69ff0320031ae7c8e9a95273", index_title: "港股应付账款:单季〔现金流量表〕"} - {id: "69ff0221031ae7c8e9a939e9", index_title: "港股应付账款:单季〔资产负债表〕"} - {id: "69ff0362031ae7c8e9a958b8", index_title: "港股应付账款:年度〔现金流量表〕"} - {id: "69ff027c031ae7c8e9a942ee", index_title: "港股应付账款:年度〔资产负债表〕"} - {id: "69ff02eb031ae7c8e9a94d7c", index_title: "港股应收账款净额:TTM〔资产负债表〕"} - {id: "6a8bafa50162cdd13a1a463b", index_title: "港股应收账款净额:半年〔资产负债表〕"} - {id: "69ff0246031ae7c8e9a93dc8", index_title: "港股应收账款净额:单季〔资产负债表〕"} - {id: "69ff029b031ae7c8e9a945e8", index_title: "港股应收账款净额:年度〔资产负债表〕"} - {id: "6a8bafa40162cdd13a1a4563", index_title: "港股应收账款周转率:半年〔财务比率〕"} - {id: "69ff016e031ae7c8e9a928ac", index_title: "港股应收账款周转率:单季〔财务比率〕"} - {id: "69ff01ca031ae7c8e9a9317c", index_title: "港股应收账款周转率:年度〔财务比率〕"} - {id: "69ff0062031ae7c8e9a90bb6", index_title: "港股应收账款天数:单季〔关键指标〕"} - {id: "69ff00aa031ae7c8e9a913e6", index_title: "港股应收账款天数:年度〔关键指标〕"} - {id: "69ff039a031ae7c8e9a95fe6", index_title: "港股应收账款:TTM〔现金流量表〕"} - {id: "69ff02c9031ae7c8e9a94a48", index_title: "港股应收账款:TTM〔资产负债表〕"} - {id: "6a8bafa80162cdd13a1a4870", index_title: "港股应收账款:半年〔现金流量表〕"} - {id: "6a8bafa40162cdd13a1a45a6", index_title: "港股应收账款:半年〔资产负债表〕"} - {id: "69ff0321031ae7c8e9a95293", index_title: "港股应收账款:单季〔现金流量表〕"} - {id: "69ff0223031ae7c8e9a93a38", index_title: "港股应收账款:单季〔资产负债表〕"} - {id: "69ff0363031ae7c8e9a958e7", index_title: "港股应收账款:年度〔现金流量表〕"} - {id: "69ff027e031ae7c8e9a94311", index_title: "港股应收账款:年度〔资产负债表〕"} - {id: "69ff02cb031ae7c8e9a94a5a", index_title: "港股应计费用:TTM〔资产负债表〕"} - {id: "6a8bafa40162cdd13a1a45ac", index_title: "港股应计费用:半年〔资产负债表〕"} - {id: "69ff0225031ae7c8e9a93a6f", index_title: "港股应计费用:单季〔资产负债表〕"} - {id: "69ff027f031ae7c8e9a94355", index_title: "港股应计费用:年度〔资产负债表〕"} - {id: "6a8bafa10162cdd13a1a42f0", index_title: "港股归属净利润率:半年〔财务比率〕"} - {id: "69ff0114031ae7c8e9a91ecb", index_title: "港股归属净利润率:单季〔财务比率〕"} - {id: "69ff017e031ae7c8e9a92a56", index_title: "港股归属净利润率:年度〔财务比率〕"} - {id: "69ff043c031ae7c8e9a96f49", index_title: "港股归属净利润:TTM〔利润表〕"} - {id: "6a8bafa70162cdd13a1a475a", index_title: "港股归属净利润:半年〔利润表〕"} - {id: "69ff03d9031ae7c8e9a965e7", index_title: "港股归属净利润:单季〔利润表〕"} - {id: "69ff0410031ae7c8e9a96b2c", index_title: "港股归属净利润:年度〔利润表〕"} - {id: "69ff02d2031ae7c8e9a94b19", index_title: "港股当期资本租赁义务:TTM〔资产负债表〕"} - {id: "6a8bafa40162cdd13a1a45c8", index_title: "港股当期资本租赁义务:半年〔资产负债表〕"} - {id: "69ff022c031ae7c8e9a93b1f", index_title: "港股当期资本租赁义务:单季〔资产负债表〕"} - {id: "69ff0285031ae7c8e9a943be", index_title: "港股当期资本租赁义务:年度〔资产负债表〕"} - {id: "69ff043d031ae7c8e9a96f5d", index_title: "港股成本及费用:TTM〔利润表〕"} - {id: "6a8bafa70162cdd13a1a4762", index_title: "港股成本及费用:半年〔利润表〕"} - {id: "69ff03db031ae7c8e9a9661a", index_title: "港股成本及费用:单季〔利润表〕"} - {id: "69ff0412031ae7c8e9a96b4f", index_title: "港股成本及费用:年度〔利润表〕"} - {id: "69ff03b0031ae7c8e9a961f9", index_title: "港股所得税支付:TTM〔现金流量表〕"} - {id: "6a8bafa90162cdd13a1a48ec", index_title: "港股所得税支付:半年〔现金流量表〕"} - {id: "69ff0338031ae7c8e9a95493", index_title: "港股所得税支付:单季〔现金流量表〕"} - {id: "69ff0376031ae7c8e9a95afc", index_title: "港股所得税支付:年度〔现金流量表〕"} - {id: "69ff0450031ae7c8e9a9711c", index_title: "港股所得税费用:TTM〔利润表〕"} - {id: "6a8bafa70162cdd13a1a47b1", index_title: "港股所得税费用:半年〔利润表〕"} - {id: "69ff03ee031ae7c8e9a967e0", index_title: "港股所得税费用:单季〔利润表〕"} - {id: "69ff0420031ae7c8e9a96c90", index_title: "港股所得税费用:年度〔利润表〕"} - {id: "69ff008e031ae7c8e9a91151", index_title: "港股投入资本回报率:单季〔关键指标〕"} - {id: "69ff00d1031ae7c8e9a917b1", index_title: "港股投入资本回报率:年度〔关键指标〕"} - {id: "69ff007c031ae7c8e9a90e40", index_title: "港股投入资本:单季〔关键指标〕"} - {id: "69ff00c1031ae7c8e9a91621", index_title: "港股投入资本:年度〔关键指标〕"} - {id: "69ff03d4031ae7c8e9a96594", index_title: "港股投资出售及到期:TTM〔现金流量表〕"} - {id: "6a8bafaa0162cdd13a1a4a61", index_title: "港股投资出售及到期:半年〔现金流量表〕"} - {id: "69ff035d031ae7c8e9a95852", index_title: "港股投资出售及到期:单季〔现金流量表〕"} - {id: "69ff0394031ae7c8e9a95f3d", index_title: "港股投资出售及到期:年度〔现金流量表〕"} - {id: "69ff0312031ae7c8e9a9510d", index_title: "港股投资合计:TTM〔资产负债表〕"} - {id: "6a8bafa60162cdd13a1a46ec", index_title: "港股投资合计:半年〔资产负债表〕"} - {id: "69ff026e031ae7c8e9a9419e", index_title: "港股投资合计:单季〔资产负债表〕"} - {id: "69ff02bb031ae7c8e9a9490d", index_title: "港股投资合计:年度〔资产负债表〕"} - {id: "69ff03b9031ae7c8e9a962f6", index_title: "港股投资活动现金流:TTM〔现金流量表〕"} - {id: "6a8bafa90162cdd13a1a4963", index_title: "港股投资活动现金流:半年〔现金流量表〕"} - {id: "69ff0342031ae7c8e9a9557b", index_title: "港股投资活动现金流:单季〔现金流量表〕"} - {id: "69ff037e031ae7c8e9a95bf5", index_title: "港股投资活动现金流:年度〔现金流量表〕"} - {id: "69ff03d3031ae7c8e9a9656b", index_title: "港股投资购买:TTM〔现金流量表〕"} - {id: "6a8bafaa0162cdd13a1a4a5c", index_title: "港股投资购买:半年〔现金流量表〕"} - {id: "69ff035b031ae7c8e9a9581b", index_title: "港股投资购买:单季〔现金流量表〕"} - {id: "69ff0393031ae7c8e9a95f1c", index_title: "港股投资购买:年度〔现金流量表〕"} - {id: "69ff0441031ae7c8e9a96fd8", index_title: "港股折旧和摊销:TTM〔利润表〕"} - {id: "69ff03ab031ae7c8e9a96197", index_title: "港股折旧和摊销:TTM〔现金流量表〕"} - {id: "6a8bafa70162cdd13a1a4770", index_title: "港股折旧和摊销:半年〔利润表〕"} - {id: "6a8bafa90162cdd13a1a48be", index_title: "港股折旧和摊销:半年〔现金流量表〕"} - {id: "69ff03df031ae7c8e9a96689", index_title: "港股折旧和摊销:单季〔利润表〕"} - {id: "69ff0332031ae7c8e9a95422", index_title: "港股折旧和摊销:单季〔现金流量表〕"} - {id: "69ff0415031ae7c8e9a96b7c", index_title: "港股折旧和摊销:年度〔利润表〕"} - {id: "69ff0371031ae7c8e9a95a92", index_title: "港股折旧和摊销:年度〔现金流量表〕"} - {id: "69ff0459031ae7c8e9a971ef", index_title: "港股持续经营净利润:TTM〔利润表〕"} - {id: "6a8bafa80162cdd13a1a47ea", index_title: "港股持续经营净利润:半年〔利润表〕"} - {id: "69ff03f6031ae7c8e9a968af", index_title: "港股持续经营净利润:单季〔利润表〕"} - {id: "69ff0427031ae7c8e9a96d51", index_title: "港股持续经营净利润:年度〔利润表〕"} - {id: "6a8bafa10162cdd13a1a430e", index_title: "港股持续经营利润率:半年〔财务比率〕"} - {id: "69ff011e031ae7c8e9a91fec", index_title: "港股持续经营利润率:单季〔财务比率〕"} - {id: "69ff0186031ae7c8e9a92b17", index_title: "港股持续经营利润率:年度〔财务比率〕"} - {id: "69ff039b031ae7c8e9a9600a", index_title: "港股收购净额:TTM〔现金流量表〕"} - {id: "6a8bafa90162cdd13a1a4877", index_title: "港股收购净额:半年〔现金流量表〕"} - {id: "69ff0323031ae7c8e9a952b8", index_title: "港股收购净额:单季〔现金流量表〕"} - {id: "69ff0365031ae7c8e9a95925", index_title: "港股收购净额:年度〔现金流量表〕"} - {id: "69ff0079031ae7c8e9a90dfb", index_title: "港股无形资产/总资产:单季〔关键指标〕"} - {id: "69ff00be031ae7c8e9a915e1", index_title: "港股无形资产/总资产:年度〔关键指标〕"} - {id: "69ff02e1031ae7c8e9a94c85", index_title: "港股无形资产:TTM〔资产负债表〕"} - {id: "6a8bafa50162cdd13a1a4612", index_title: "港股无形资产:半年〔资产负债表〕"} - {id: "69ff023b031ae7c8e9a93cb9", index_title: "港股无形资产:单季〔资产负债表〕"} - {id: "69ff0292031ae7c8e9a94517", index_title: "港股无形资产:年度〔资产负债表〕"} - {id: "69ff03bf031ae7c8e9a96379", index_title: "港股普通股净发行:TTM〔现金流量表〕"} - {id: "6a8bafaa0162cdd13a1a499f", index_title: "港股普通股净发行:半年〔现金流量表〕"} - {id: "69ff0347031ae7c8e9a95611", index_title: "港股普通股净发行:单季〔现金流量表〕"} - {id: "69ff0382031ae7c8e9a95c98", index_title: "港股普通股净发行:年度〔现金流量表〕"} - {id: "69ff03a6031ae7c8e9a960fd", index_title: "港股普通股发行:TTM〔现金流量表〕"} - {id: "6a8bafa90162cdd13a1a489e", index_title: "港股普通股发行:半年〔现金流量表〕"} - {id: "69ff032e031ae7c8e9a953a7", index_title: "港股普通股发行:单季〔现金流量表〕"} - {id: "69ff036e031ae7c8e9a95a13", index_title: "港股普通股发行:年度〔现金流量表〕"} - {id: "69ff03a8031ae7c8e9a9611f", index_title: "港股普通股回购:TTM〔现金流量表〕"} - {id: "6a8bafa90162cdd13a1a48ab", index_title: "港股普通股回购:半年〔现金流量表〕"} - {id: "69ff0330031ae7c8e9a953c8", index_title: "港股普通股回购:单季〔现金流量表〕"} - {id: "69ff036f031ae7c8e9a95a4a", index_title: "港股普通股回购:年度〔现金流量表〕"} - {id: "69ff03a5031ae7c8e9a960e7", index_title: "港股普通股股息支付:TTM〔现金流量表〕"} - {id: "6a8bafa90162cdd13a1a4898", index_title: "港股普通股股息支付:半年〔现金流量表〕"} - {id: "69ff032d031ae7c8e9a95390", index_title: "港股普通股股息支付:单季〔现金流量表〕"} - {id: "69ff036d031ae7c8e9a959e8", index_title: "港股普通股股息支付:年度〔现金流量表〕"} - {id: "69ff02d7031ae7c8e9a94b96", index_title: "港股普通股:TTM〔资产负债表〕"} - {id: "6a8bafa40162cdd13a1a45e3", index_title: "港股普通股:半年〔资产负债表〕"} - {id: "69ff0232031ae7c8e9a93bd3", index_title: "港股普通股:单季〔资产负债表〕"} - {id: "69ff028a031ae7c8e9a9444d", index_title: "港股普通股:年度〔资产负债表〕"} - {id: "69ff0095031ae7c8e9a91206", index_title: "港股有形资产价值:单季〔关键指标〕"} - {id: "69ff00d7031ae7c8e9a91816", index_title: "港股有形资产价值:年度〔关键指标〕"} - {id: "69ff0090031ae7c8e9a9116b", index_title: "港股有形资产回报率:单季〔关键指标〕"} - {id: "69ff00d2031ae7c8e9a917cb", index_title: "港股有形资产回报率:年度〔关键指标〕"} - {id: "6a8bafa20162cdd13a1a43b2", index_title: "港股有效税率:半年〔财务比率〕"} - {id: "69ff0137031ae7c8e9a922ed", index_title: "港股有效税率:单季〔财务比率〕"} - {id: "69ff019b031ae7c8e9a92d1e", index_title: "港股有效税率:年度〔财务比率〕"} - {id: "69ff039f031ae7c8e9a9605c", index_title: "港股期初现金:TTM〔现金流量表〕"} - {id: "6a8bafa90162cdd13a1a4884", index_title: "港股期初现金:半年〔现金流量表〕"} - {id: "69ff0327031ae7c8e9a95307", index_title: "港股期初现金:单季〔现金流量表〕"} - {id: "69ff0368031ae7c8e9a95972", index_title: "港股期初现金:年度〔现金流量表〕"} - {id: "69ff03a1031ae7c8e9a9609b", index_title: "港股期末现金:TTM〔现金流量表〕"} - {id: "6a8bafa90162cdd13a1a488a", index_title: "港股期末现金:半年〔现金流量表〕"} - {id: "69ff0329031ae7c8e9a95345", index_title: "港股期末现金:单季〔现金流量表〕"} - {id: "69ff0369031ae7c8e9a9599d", index_title: "港股期末现金:年度〔现金流量表〕"} - {id: "69ff0310031ae7c8e9a950eb", index_title: "港股权益合计:TTM〔资产负债表〕"} - {id: "6a8bafa60162cdd13a1a46e5", index_title: "港股权益合计:半年〔资产负债表〕"} - {id: "69ff026c031ae7c8e9a94177", index_title: "港股权益合计:单季〔资产负债表〕"} - {id: "69ff02ba031ae7c8e9a948c9", index_title: "港股权益合计:年度〔资产负债表〕"} - {id: "69ff008c031ae7c8e9a91125", index_title: "港股权益回报率:单季〔关键指标〕"} - {id: "69ff00cf031ae7c8e9a91770", index_title: "港股权益回报率:年度〔关键指标〕"} - {id: "69ff0074031ae7c8e9a90d63", index_title: "港股格雷厄姆净净值:单季〔关键指标〕"} - {id: "69ff00ba031ae7c8e9a91551", index_title: "港股格雷厄姆净净值:年度〔关键指标〕"} - {id: "69ff0075031ae7c8e9a90d93", index_title: "港股格雷厄姆数:单季〔关键指标〕"} - {id: "69ff00bb031ae7c8e9a91580", index_title: "港股格雷厄姆数:年度〔关键指标〕"} - {id: "6a8bafa30162cdd13a1a4456", index_title: "港股每股净利润:半年〔财务比率〕"} - {id: "69ff014d031ae7c8e9a9251d", index_title: "港股每股净利润:单季〔财务比率〕"} - {id: "69ff01af031ae7c8e9a92f10", index_title: "港股每股净利润:年度〔财务比率〕"} - {id: "6a8bafa30162cdd13a1a4418", index_title: "港股每股带息债务:半年〔财务比率〕"} - {id: "69ff0147031ae7c8e9a9245e", index_title: "港股每股带息债务:单季〔财务比率〕"} - {id: "69ff01a9031ae7c8e9a92e5c", index_title: "港股每股带息债务:年度〔财务比率〕"} - {id: "69ff0447031ae7c8e9a9703b", index_title: "港股每股收益:TTM〔利润表〕"} - {id: "6a8bafa70162cdd13a1a4787", index_title: "港股每股收益:半年〔利润表〕"} - {id: "69ff03e4031ae7c8e9a966f2", index_title: "港股每股收益:单季〔利润表〕"} - {id: "69ff0419031ae7c8e9a96c04", index_title: "港股每股收益:年度〔利润表〕"} - {id: "6a8bafa40162cdd13a1a4588", index_title: "港股每股有形账面价值:半年〔财务比率〕"} - {id: "69ff0177031ae7c8e9a92990", index_title: "港股每股有形账面价值:单季〔财务比率〕"} - {id: "69ff01d2031ae7c8e9a93232", index_title: "港股每股有形账面价值:年度〔财务比率〕"} - {id: "6a8bafa10162cdd13a1a42fe", index_title: "港股每股现金:半年〔财务比率〕"} - {id: "69ff011a031ae7c8e9a91f91", index_title: "港股每股现金:单季〔财务比率〕"} - {id: "69ff0183031ae7c8e9a92abf", index_title: "港股每股现金:年度〔财务比率〕"} - {id: "6a8bafa30162cdd13a1a4495", index_title: "港股每股经营现金流:半年〔财务比率〕"} - {id: "69ff0153031ae7c8e9a9262d", index_title: "港股每股经营现金流:单季〔财务比率〕"} - {id: "69ff01b4031ae7c8e9a92f6b", index_title: "港股每股经营现金流:年度〔财务比率〕"} - {id: "6a8bafa40162cdd13a1a4572", index_title: "港股每股股东权益:半年〔财务比率〕"} - {id: "69ff0171031ae7c8e9a92931", index_title: "港股每股股东权益:单季〔财务比率〕"} - {id: "69ff01cd031ae7c8e9a931de", index_title: "港股每股股东权益:年度〔财务比率〕"} - {id: "6a8bafa20162cdd13a1a4404", index_title: "港股每股自由现金流:半年〔财务比率〕"} - {id: "69ff0141031ae7c8e9a923e5", index_title: "港股每股自由现金流:单季〔财务比率〕"} - {id: "69ff01a4031ae7c8e9a92dfc", index_title: "港股每股自由现金流:年度〔财务比率〕"} - {id: "6a8bafa40162cdd13a1a456c", index_title: "港股每股营收:半年〔财务比率〕"} - {id: "69ff0170031ae7c8e9a928e7", index_title: "港股每股营收:单季〔财务比率〕"} - {id: "69ff01cb031ae7c8e9a931c1", index_title: "港股每股营收:年度〔财务比率〕"} - {id: "6a8bafa10162cdd13a1a42e8", index_title: "港股每股账面价值:半年〔财务比率〕"} - {id: "69ff0112031ae7c8e9a91e8a", index_title: "港股每股账面价值:单季〔财务比率〕"} - {id: "69ff017c031ae7c8e9a92a2b", index_title: "港股每股账面价值:年度〔财务比率〕"} - {id: "6a8bafa10162cdd13a1a42f4", index_title: "港股每股资本支出:半年〔财务比率〕"} - {id: "69ff0116031ae7c8e9a91f0f", index_title: "港股每股资本支出:单季〔财务比率〕"} - {id: "69ff0180031ae7c8e9a92a6e", index_title: "港股每股资本支出:年度〔财务比率〕"} - {id: "69ff044c031ae7c8e9a970d5", index_title: "港股毛利润:TTM〔利润表〕"} - {id: "6a8bafa70162cdd13a1a47a1", index_title: "港股毛利润:半年〔利润表〕"} - {id: "69ff03ea031ae7c8e9a96797", index_title: "港股毛利润:单季〔利润表〕"} - {id: "69ff041d031ae7c8e9a96c5f", index_title: "港股毛利润:年度〔利润表〕"} - {id: "6a8bafa20162cdd13a1a440a", index_title: "港股毛利率:半年〔财务比率〕"} - {id: "69ff0143031ae7c8e9a9240a", index_title: "港股毛利率:单季〔财务比率〕"} - {id: "69ff01a6031ae7c8e9a92e23", index_title: "港股毛利率:年度〔财务比率〕"} - {id: "69ff03ad031ae7c8e9a961b7", index_title: "港股汇率变动影响:TTM〔现金流量表〕"} - {id: "6a8bafa90162cdd13a1a48c6", index_title: "港股汇率变动影响:半年〔现金流量表〕"} - {id: "69ff0334031ae7c8e9a9544e", index_title: "港股汇率变动影响:单季〔现金流量表〕"} - {id: "69ff0373031ae7c8e9a95abf", index_title: "港股汇率变动影响:年度〔现金流量表〕"} - {id: "6a8bafa10162cdd13a1a4314", index_title: "港股流动比率:半年〔财务比率〕"} - {id: "69ff005c031ae7c8e9a90b46", index_title: "港股流动比率:单季〔关键指标〕"} - {id: "69ff011f031ae7c8e9a92033", index_title: "港股流动比率:单季〔财务比率〕"} - {id: "69ff00a5031ae7c8e9a91358", index_title: "港股流动比率:年度〔关键指标〕"} - {id: "69ff0187031ae7c8e9a92b39", index_title: "港股流动比率:年度〔财务比率〕"} - {id: "69ff030d031ae7c8e9a950a1", index_title: "港股流动负债合计:TTM〔资产负债表〕"} - {id: "6a8bafa60162cdd13a1a46d6", index_title: "港股流动负债合计:半年〔资产负债表〕"} - {id: "69ff0268031ae7c8e9a940fe", index_title: "港股流动负债合计:单季〔资产负债表〕"} - {id: "69ff02b7031ae7c8e9a9487d", index_title: "港股流动负债合计:年度〔资产负债表〕"} - {id: "69ff030b031ae7c8e9a95080", index_title: "港股流动资产合计:TTM〔资产负债表〕"} - {id: "6a8bafa60162cdd13a1a46ce", index_title: "港股流动资产合计:半年〔资产负债表〕"} - {id: "69ff0266031ae7c8e9a940dc", index_title: "港股流动资产合计:单季〔资产负债表〕"} - {id: "69ff02b5031ae7c8e9a94869", index_title: "港股流动资产合计:年度〔资产负债表〕"} - {id: "69ff03bd031ae7c8e9a96338", index_title: "港股现金净变动:TTM〔现金流量表〕"} - {id: "6a8bafaa0162cdd13a1a498e", index_title: "港股现金净变动:半年〔现金流量表〕"} - {id: "69ff0345031ae7c8e9a955cd", index_title: "港股现金净变动:单季〔现金流量表〕"} - {id: "69ff0381031ae7c8e9a95c58", index_title: "港股现金净变动:年度〔现金流量表〕"} - {id: "69ff02d3031ae7c8e9a94b37", index_title: "港股现金及现金等价物:TTM〔资产负债表〕"} - {id: "6a8bafa40162cdd13a1a45d0", index_title: "港股现金及现金等价物:半年〔资产负债表〕"} - {id: "69ff022e031ae7c8e9a93b4b", index_title: "港股现金及现金等价物:单季〔资产负债表〕"} - {id: "69ff0287031ae7c8e9a943f3", index_title: "港股现金及现金等价物:年度〔资产负债表〕"} - {id: "69ff02d5031ae7c8e9a94b59", index_title: "港股现金及短期投资:TTM〔资产负债表〕"} - {id: "6a8bafa40162cdd13a1a45d7", index_title: "港股现金及短期投资:半年〔资产负债表〕"} - {id: "69ff0230031ae7c8e9a93b9e", index_title: "港股现金及短期投资:单季〔资产负债表〕"} - {id: "69ff0288031ae7c8e9a94425", index_title: "港股现金及短期投资:年度〔资产负债表〕"} - {id: "6a8bafa10162cdd13a1a4307", index_title: "港股现金比率:半年〔财务比率〕"} - {id: "69ff011c031ae7c8e9a91fb6", index_title: "港股现金比率:单季〔财务比率〕"} - {id: "69ff0184031ae7c8e9a92aee", index_title: "港股现金比率:年度〔财务比率〕"} - {id: "69ff005a031ae7c8e9a90b20", index_title: "港股现金转换周期:单季〔关键指标〕"} - {id: "69ff00a4031ae7c8e9a91341", index_title: "港股现金转换周期:年度〔关键指标〕"} - {id: "69ff0300031ae7c8e9a94f85", index_title: "港股留存收益:TTM〔资产负债表〕"} - {id: "6a8bafa60162cdd13a1a4693", index_title: "港股留存收益:半年〔资产负债表〕"} - {id: "69ff025c031ae7c8e9a93fd0", index_title: "港股留存收益:单季〔资产负债表〕"} - {id: "69ff02ad031ae7c8e9a94786", index_title: "港股留存收益:年度〔资产负债表〕"} - {id: "69ff0063031ae7c8e9a90bfa", index_title: "港股盈利收益率:单季〔关键指标〕"} - {id: "69ff00ab031ae7c8e9a9140c", index_title: "港股盈利收益率:年度〔关键指标〕"} - {id: "69ff03d6031ae7c8e9a965b4", index_title: "港股短期债务净发行:TTM〔现金流量表〕"} - {id: "6a8bafaa0162cdd13a1a4a74", index_title: "港股短期债务净发行:半年〔现金流量表〕"} - {id: "69ff035f031ae7c8e9a9587e", index_title: "港股短期债务净发行:单季〔现金流量表〕"} - {id: "69ff0396031ae7c8e9a95f74", index_title: "港股短期债务净发行:年度〔现金流量表〕"} - {id: "69ff0302031ae7c8e9a94fb1", index_title: "港股短期债务:TTM〔资产负债表〕"} - {id: "6a8bafa60162cdd13a1a469d", index_title: "港股短期债务:半年〔资产负债表〕"} - {id: "69ff025d031ae7c8e9a93ff5", index_title: "港股短期债务:单季〔资产负债表〕"} - {id: "69ff02ae031ae7c8e9a947a5", index_title: "港股短期债务:年度〔资产负债表〕"} - {id: "69ff0304031ae7c8e9a94fda", index_title: "港股短期投资:TTM〔资产负债表〕"} - {id: "6a8bafa60162cdd13a1a46a8", index_title: "港股短期投资:半年〔资产负债表〕"} - {id: "69ff025f031ae7c8e9a94030", index_title: "港股短期投资:单季〔资产负债表〕"} - {id: "69ff02b0031ae7c8e9a947d6", index_title: "港股短期投资:年度〔资产负债表〕"} - {id: "6a8bafa40162cdd13a1a457a", index_title: "港股短期经营现金流覆盖率:半年〔财务比率〕"} - {id: "69ff0173031ae7c8e9a92955", index_title: "港股短期经营现金流覆盖率:单季〔财务比率〕"} - {id: "69ff01ce031ae7c8e9a931fc", index_title: "港股短期经营现金流覆盖率:年度〔财务比率〕"} - {id: "69ff0087031ae7c8e9a91096", index_title: "港股研发费用/营收:单季〔关键指标〕"} - {id: "69ff00cb031ae7c8e9a91713", index_title: "港股研发费用/营收:年度〔关键指标〕"} - {id: "69ff0466031ae7c8e9a97314", index_title: "港股研发费用:TTM〔利润表〕"} - {id: "6a8bafa80162cdd13a1a4822", index_title: "港股研发费用:半年〔利润表〕"} - {id: "69ff0404031ae7c8e9a96a14", index_title: "港股研发费用:单季〔利润表〕"} - {id: "69ff0432031ae7c8e9a96e4d", index_title: "港股研发费用:年度〔利润表〕"} - {id: "69ff0470031ae7c8e9a97400", index_title: "港股稀释加权平均股数:TTM〔利润表〕"} - {id: "6a8bafa80162cdd13a1a4855", index_title: "港股稀释加权平均股数:半年〔利润表〕"} - {id: "69ff040f031ae7c8e9a96b03", index_title: "港股稀释加权平均股数:单季〔利润表〕"} - {id: "69ff043a031ae7c8e9a96f1e", index_title: "港股稀释加权平均股数:年度〔利润表〕"} - {id: "69ff0449031ae7c8e9a97066", index_title: "港股稀释每股收益:TTM〔利润表〕"} - {id: "6a8bafa70162cdd13a1a4793", index_title: "港股稀释每股收益:半年〔利润表〕"} - {id: "69ff03e6031ae7c8e9a9674d", index_title: "港股稀释每股收益:单季〔利润表〕"} - {id: "69ff041b031ae7c8e9a96c1f", index_title: "港股稀释每股收益:年度〔利润表〕"} - {id: "6a8bafa20162cdd13a1a4394", index_title: "港股税前利润/EBIT:半年〔财务比率〕"} - {id: "69ff0135031ae7c8e9a922ce", index_title: "港股税前利润/EBIT:单季〔财务比率〕"} - {id: "69ff0199031ae7c8e9a92cf8", index_title: "港股税前利润/EBIT:年度〔财务比率〕"} - {id: "6a8bafa30162cdd13a1a4514", index_title: "港股税前利润率:半年〔财务比率〕"} - {id: "69ff015d031ae7c8e9a92720", index_title: "港股税前利润率:单季〔财务比率〕"} - {id: "69ff01bc031ae7c8e9a93034", index_title: "港股税前利润率:年度〔财务比率〕"} - {id: "69ff044e031ae7c8e9a97100", index_title: "港股税前利润:TTM〔利润表〕"} - {id: "6a8bafa70162cdd13a1a47a7", index_title: "港股税前利润:半年〔利润表〕"} - {id: "69ff03ec031ae7c8e9a967bc", index_title: "港股税前利润:单季〔利润表〕"} - {id: "69ff041f031ae7c8e9a96c6f", index_title: "港股税前利润:年度〔利润表〕"} - {id: "69ff0097031ae7c8e9a91224", index_title: "港股税负:单季〔关键指标〕"} - {id: "69ff00d8031ae7c8e9a91847", index_title: "港股税负:年度〔关键指标〕"} - {id: "69ff0306031ae7c8e9a94ff4", index_title: "港股税项资产:TTM〔资产负债表〕"} - {id: "6a8bafa60162cdd13a1a46b4", index_title: "港股税项资产:半年〔资产负债表〕"} - {id: "69ff0261031ae7c8e9a94063", index_title: "港股税项资产:单季〔资产负债表〕"} - {id: "69ff02b1031ae7c8e9a9480d", index_title: "港股税项资产:年度〔资产负债表〕"} - {id: "69ff044b031ae7c8e9a970ab", index_title: "港股管理费用:TTM〔利润表〕"} - {id: "6a8bafa70162cdd13a1a4799", index_title: "港股管理费用:半年〔利润表〕"} - {id: "69ff03e8031ae7c8e9a96777", index_title: "港股管理费用:单季〔利润表〕"} - {id: "69ff041c031ae7c8e9a96c44", index_title: "港股管理费用:年度〔利润表〕"} - {id: "69ff02cc031ae7c8e9a94a82", index_title: "港股累计其他综合收益:TTM〔资产负债表〕"} - {id: "6a8bafa40162cdd13a1a45b2", index_title: "港股累计其他综合收益:半年〔资产负债表〕"} - {id: "69ff0227031ae7c8e9a93a9b", index_title: "港股累计其他综合收益:单季〔资产负债表〕"} - {id: "69ff0281031ae7c8e9a94371", index_title: "港股累计其他综合收益:年度〔资产负债表〕"} - {id: "69ff045b031ae7c8e9a97208", index_title: "港股终止经营净利润:TTM〔利润表〕"} - {id: "6a8bafa80162cdd13a1a47f5", index_title: "港股终止经营净利润:半年〔利润表〕"} - {id: "69ff03f8031ae7c8e9a968d5", index_title: "港股终止经营净利润:单季〔利润表〕"} - {id: "69ff0429031ae7c8e9a96d60", index_title: "港股终止经营净利润:年度〔利润表〕"} - {id: "69ff03bb031ae7c8e9a96308", index_title: "港股经营活动现金流:TTM〔现金流量表〕"} - {id: "6a8bafaa0162cdd13a1a497d", index_title: "港股经营活动现金流:半年〔现金流量表〕"} - {id: "69ff0343031ae7c8e9a95595", index_title: "港股经营活动现金流:单季〔现金流量表〕"} - {id: "69ff037f031ae7c8e9a95c14", index_title: "港股经营活动现金流:年度〔现金流量表〕"} - {id: "6a8bafa30162cdd13a1a44b5", index_title: "港股经营现金流比率:半年〔财务比率〕"} - {id: "69ff0155031ae7c8e9a9264d", index_title: "港股经营现金流比率:单季〔财务比率〕"} - {id: "69ff01b5031ae7c8e9a92f9b", index_title: "港股经营现金流比率:年度〔财务比率〕"} - {id: "6a8bafa30162cdd13a1a4480", index_title: "港股经营现金流覆盖率:半年〔财务比率〕"} - {id: "69ff0151031ae7c8e9a925e5", index_title: "港股经营现金流覆盖率:单季〔财务比率〕"} - {id: "69ff01b2031ae7c8e9a92f4b", index_title: "港股经营现金流覆盖率:年度〔财务比率〕"} - {id: "69ff03c8031ae7c8e9a96472", index_title: "港股经营现金流:TTM〔现金流量表〕"} - {id: "6a8bafaa0162cdd13a1a4a08", index_title: "港股经营现金流:半年〔现金流量表〕"} - {id: "69ff0351031ae7c8e9a95712", index_title: "港股经营现金流:单季〔现金流量表〕"} - {id: "69ff038a031ae7c8e9a95d8e", index_title: "港股经营现金流:年度〔现金流量表〕"} - {id: "69ff0085031ae7c8e9a9107f", index_title: "港股经营资产回报率:单季〔关键指标〕"} - {id: "69ff00c9031ae7c8e9a916f7", index_title: "港股经营资产回报率:年度〔关键指标〕"} - {id: "69ff031d031ae7c8e9a9521c", index_title: "港股股东权益合计:TTM〔资产负债表〕"} - {id: "6a8bafa70162cdd13a1a473d", index_title: "港股股东权益合计:半年〔资产负债表〕"} - {id: "69ff0279031ae7c8e9a942b0", index_title: "港股股东权益合计:单季〔资产负债表〕"} - {id: "69ff02c5031ae7c8e9a949de", index_title: "港股股东权益合计:年度〔资产负债表〕"} - {id: "6a8bafa20162cdd13a1a4350", index_title: "港股股息及资本支出覆盖率:半年〔财务比率〕"} - {id: "69ff012b031ae7c8e9a92199", index_title: "港股股息及资本支出覆盖率:单季〔财务比率〕"} - {id: "69ff0191031ae7c8e9a92c22", index_title: "港股股息及资本支出覆盖率:年度〔财务比率〕"} - {id: "69ff03c2031ae7c8e9a963cd", index_title: "港股股息支付净额:TTM〔现金流量表〕"} - {id: "6a8bafaa0162cdd13a1a49c8", index_title: "港股股息支付净额:半年〔现金流量表〕"} - {id: "69ff034b031ae7c8e9a95654", index_title: "港股股息支付净额:单季〔现金流量表〕"} - {id: "69ff0385031ae7c8e9a95cf9", index_title: "港股股息支付净额:年度〔现金流量表〕"} - {id: "6a8bafa20162cdd13a1a435b", index_title: "港股股息支付率:半年〔财务比率〕"} - {id: "69ff012d031ae7c8e9a921d1", index_title: "港股股息支付率:单季〔财务比率〕"} - {id: "69ff0192031ae7c8e9a92c40", index_title: "港股股息支付率:年度〔财务比率〕"} - {id: "6a8bafa20162cdd13a1a436f", index_title: "港股股息收益率百分比:半年〔财务比率〕"} - {id: "69ff0130031ae7c8e9a92217", index_title: "港股股息收益率百分比:单季〔财务比率〕"} - {id: "69ff0195031ae7c8e9a92c64", index_title: "港股股息收益率百分比:年度〔� -
macro_strategy.yaml 11.4 KB
# Auto-generated business catalog; do not edit records by hand. schema_version: 2 category: macro_strategy record_count: 144 records: - {id: "6a1e83c7031ae7c8e9b89731", index_title: "A股中单资金当日流量净占比"} - {id: "6a1e83af031ae7c8e9b893fe", index_title: "A股中单资金当日流量净额"} - {id: "6a1e832c031ae7c8e9b87d16", index_title: "A股主力资金当日流量净占比"} - {id: "6a1e8313031ae7c8e9b879f5", index_title: "A股主力资金当日流量净额"} - {id: "68ee0a23596cb29c3b85b95d", index_title: "A股净资产收益率ROE"} - {id: "6a1e8299031ae7c8e9b86d98", index_title: "A股北向十大活跃股成交额"} - {id: "6a1e826a031ae7c8e9b8615c", index_title: "A股北向资金成交量"} - {id: "6a1e826a031ae7c8e9b86139", index_title: "A股北向资金成交额"} - {id: "6a1e826c031ae7c8e9b86204", index_title: "A股北向资金持股市值"} - {id: "6a1e8283031ae7c8e9b86ab7", index_title: "A股北向资金持股比例"} - {id: "6a1e8395031ae7c8e9b8911c", index_title: "A股大单资金当日流量净占比"} - {id: "6a1e837e031ae7c8e9b88a50", index_title: "A股大单资金当日流量净额"} - {id: "6a1e83f9031ae7c8e9b89dfa", index_title: "A股小单资金当日流量净占比"} - {id: "6a1e83e2031ae7c8e9b89b6b", index_title: "A股小单资金当日流量净额"} - {id: "6a3b86bd3daab68d2f3e0e26", index_title: "A股收盘涨停"} - {id: "69ca3df24f760ff70564dc17", index_title: "A股收盘涨停(分钟刷新)"} - {id: "6a3b86cd3daab68d2f3e0e27", index_title: "A股收盘跌停"} - {id: "69ca3df24f760ff70564dc18", index_title: "A股收盘跌停(分钟刷新)"} - {id: "69aaadab0647ae948c11519b", index_title: "A股融券空头持仓比例"} - {id: "63bfcff7846429fd6d572d06", index_title: "A股财报季度标注"} - {id: "6a1e8362031ae7c8e9b88382", index_title: "A股超大单资金当日流量净占比"} - {id: "6a1e8348031ae7c8e9b880b1", index_title: "A股超大单资金当日流量净额"} - {id: "67c6ada2bdf8e329800a6a2e", index_title: "BTC现货ETF_总flow"} - {id: "67c6adf3bdf8e329800a6a2f", index_title: "BTC现货ETF_灰度flow"} - {id: "67c6af21bdf8e329800a6a30", index_title: "ETH现货ETF_总flow"} - {id: "67c6af56bdf8e329800a6a31", index_title: "ETH现货ETF_灰度flow"} - {id: "68aedeaf6f3b0259d3cc25bf", index_title: "GS-Memes-Stocks"} - {id: "667953a3ae16ac3f475e7a7d", index_title: "U3O8 3 Year Spot Price"} - {id: "65f26262e38366bf78e390f0", index_title: "US.QQQ信号"} - {id: "66a21e5d3f72735988c809f6", index_title: "Uranium Market Price Close"} - {id: "66a224602afc2726492a071c", index_title: "Uranium Premium/Discount to Net Asset Value"} - {id: "678f4c6f9909b67eac6b82fe", index_title: "Verisign active .com domain names"} - {id: "678f4c569909b67eac6b82fd", index_title: "Verisign active .net domain names"} - {id: "678f4c759909b67eac6b82ff", index_title: "Verisign active total domain names"} - {id: "669f62b2d028cff5d6e8f630", index_title: "【日频】趋势跟踪"} - {id: "669f60dba04672df21779fab", index_title: "【日频测试】趋势跟踪"} - {id: "6400517a0192979ea4b27978", index_title: "万得资产所属指数"} - {id: "65f25ee7e38366bf78e390ef", index_title: "上证50-中证500信号"} - {id: "691bd8bbc8e3a12d6a9d305e", index_title: "上证50指数成分股"} - {id: "6aaf9fea26fd808eb160fbe3", index_title: "中国交易日"} - {id: "68e761d5f08519ce5c8b0c4a", index_title: "中国股市重要政策干预事件"} - {id: "68f5c774e119f55a66899f5f", index_title: "中美贸易战关键事件时间序列"} - {id: "65670df81c9fd3c9c6a9b829", index_title: "中证1000指数成分股"} - {id: "65670e111c9fd3c9c6a9b82b", index_title: "中证100指数成分股"} - {id: "661f3335737fc3a88d2429b8", index_title: "中证500信号"} - {id: "65670e171c9fd3c9c6a9b82d", index_title: "中证500指数成分股"} - {id: "65670e1d1c9fd3c9c6a9b82f", index_title: "中证800指数成分股"} - {id: "6a753b57c8a907c178f6b1b7", index_title: "中证A500指数成分股"} - {id: "6a3e4c440bd77498e4967c74", index_title: "中证红利指数成分股"} - {id: "69ca3e014f760ff70564e001", index_title: "全市场10:00价格"} - {id: "69ca3e014f760ff70564e003", index_title: "全市场10:00最低价"} - {id: "69ca3e014f760ff70564e002", index_title: "全市场10:00最高价"} - {id: "69ca3e014f760ff70564e004", index_title: "全市场11:30价格"} - {id: "69ca3e014f760ff70564e006", index_title: "全市场11:30最低价"} - {id: "69ca3e014f760ff70564e005", index_title: "全市场11:30最高价"} - {id: "69ca3e014f760ff70564e007", index_title: "全市场14:30价格"} - {id: "69ca3e014f760ff70564e009", index_title: "全市场14:30最低价"} - {id: "69ca3e014f760ff70564e008", index_title: "全市场14:30最高价"} - {id: "69ae209b26fa007702ee253d", index_title: "全市场1分钟开盘价"} - {id: "69ae209b26fa007702ee2541", index_title: "全市场1分钟成交量"} - {id: "69ae209b26fa007702ee2542", index_title: "全市场1分钟成交额"} - {id: "69ae209b26fa007702ee2540", index_title: "全市场1分钟收盘价"} - {id: "69ddfe6758a42edbe7c7efed", index_title: "全市场1分钟收盘价(不复权)"} - {id: "69ae209b26fa007702ee253f", index_title: "全市场1分钟最低价"} - {id: "69ae209b26fa007702ee253e", index_title: "全市场1分钟最高价"} - {id: "68f7a1b20100000000000002", index_title: "全市场商品库存"} - {id: "68f7a1b20100000000000003", index_title: "全市场商品库存:按发布日"} - {id: "68f7a1b20100000000000001", index_title: "全市场商品现货价格"} - {id: "61cbf821c647a1c716b103dc", index_title: "全市场每日回报率"} - {id: "622ee61d0e1f25e6e33770b5", index_title: "全市场每日回报率(开盘价)"} - {id: "6257e8e686f32cc44118c040", index_title: "全市场每日回报率(开盘到开盘,去除上市连续涨停)"} - {id: "69ca3df24f760ff70564dc16", index_title: "全市场每日回报率(分钟刷新)"} - {id: "622ab7047bd9422cf6491864", index_title: "全市场每日开盘价"} - {id: "69ca3df24f760ff70564dc11", index_title: "全市场每日开盘价(分钟刷新)"} - {id: "6257e8ef86f32cc44118c041", index_title: "全市场每日成交量"} - {id: "69ca3df24f760ff70564dc14", index_title: "全市场每日成交量(分钟刷新)"} - {id: "6257e93786f32cc44118c042", index_title: "全市场每日成交额"} - {id: "69ca3df24f760ff70564dc15", index_title: "全市场每日成交额(分钟刷新)"} - {id: "61cbf81cc647a1c716b103db", index_title: "全市场每日收盘价"} - {id: "69ca3df24f760ff70564dc20", index_title: "全市场每日收盘价(不复权分钟刷新)"} - {id: "62b9518c752476b7ce455f18", index_title: "全市场每日收盘价(不复权)"} - {id: "69ca3df24f760ff70564dc10", index_title: "全市场每日收盘价(分钟刷新)"} - {id: "622ab70f7bd9422cf6491866", index_title: "全市场每日最低价"} - {id: "69ca3df24f760ff70564dc13", index_title: "全市场每日最低价(分钟刷新)"} - {id: "622ab7097bd9422cf6491865", index_title: "全市场每日最高价"} - {id: "69ca3df24f760ff70564dc12", index_title: "全市场每日最高价(分钟刷新)"} - {id: "6a3e4c470bd77498e4967c76", index_title: "创业板50指数成分股"} - {id: "6a759dd874f85851f8850028", index_title: "创业板指指数成分股"} - {id: "6a753b54c8a907c178f6b1b5", index_title: "创业板指数成分股"} - {id: "66a1b6cdcfd2bda827bb77a4", index_title: "区间震荡(日频)"} - {id: "64180d4f0873839671939ade", index_title: "半年末基准日期"} - {id: "66a0b964e0c3d2de5123ca94", index_title: "反转(日频)"} - {id: "65328681d1e19c689846ca1d", index_title: "可交易日"} - {id: "626611b09da37f3691b6005e", index_title: "周末基准日期"} - {id: "643e37de4bb9049c6fc43212", index_title: "基准收益(万得)"} - {id: "643e37c34bb9049c6fc43211", index_title: "基准权重(万得)"} - {id: "62833fce127cd770b6fc4b27", index_title: "季末基准日期"} - {id: "663893c27585ad6b62048aef", index_title: "季末自然日期"} - {id: "66432d25abfd1b87d5562a7f", index_title: "年末农历日期"} - {id: "624ea080b1b8730e7ae3a236", index_title: "年末基准日期"} - {id: "66309f74454ccd7a5c28f5eb", index_title: "年末自然日期"} - {id: "636df23ff9a47dc3e6fd2468", index_title: "开盘非涨停"} - {id: "64ad58efa9aea703fe8c66b8", index_title: "开盘非跌停"} - {id: "6a1ff7e2031ae7c8e9ee51e9", index_title: "恒生一级行业所属指数"} - {id: "6a1ff7e7031ae7c8e9ee5310", index_title: "恒生二级行业所属指数"} - {id: "69afc8352c813be28857e6c6", index_title: "收盘涨停"} - {id: "66589e23b2a7fbcec4789cd5", index_title: "最近一个月后复权因子"} - {id: "66432d2cabfd1b87d5562a80", index_title: "月末农历日期"} - {id: "624e9d3db1b8730e7ae3a235", index_title: "月末基准日期"} - {id: "66309f6d454ccd7a5c28f5ea", index_title: "月末自然日期"} - {id: "6aaf9090c7b66e051045a7ef", index_title: "期货同合约收盘涨跌幅"} - {id: "6a3a465e97e2dbd70a82d6a3", index_title: "期货换月合约"} - {id: "6a3a466397e2dbd70a82d6a5", index_title: "期货日内量权价涨减仓强度"} - {id: "6a3a466097e2dbd70a82d6a4", index_title: "期货日内量权价涨增仓强度"} - {id: "6a3a466597e2dbd70a82d6a6", index_title: "期货日内量权价跌减仓强度"} - {id: "6a3a466797e2dbd70a82d6a7", index_title: "期货日内量权价跌增仓强度"} - {id: "6a3a465b97e2dbd70a82d6a2", index_title: "期货每日持仓量"} - {id: "664307a9a46e20605d7c5a66", index_title: "沪深300信号"} - {id: "6642bb32a46e20605d7c5a65", index_title: "沪深300多空信号信号"} - {id: "65670e231c9fd3c9c6a9b831", index_title: "沪深300指数成分股"} - {id: "6a1e829a031ae7c8e9b86ddb", index_title: "港股南向十大活跃股成交额"} - {id: "6a1e826b031ae7c8e9b86182", index_title: "港股南向资金净买入"} - {id: "6a1e826b031ae7c8e9b861df", index_title: "港股南向资金成交量"} - {id: "6a1e826b031ae7c8e9b861b6", index_title: "港股南向资金成交额"} - {id: "6a1e828e031ae7c8e9b86c7a", index_title: "港股南向资金持股市值"} - {id: "6a1e8295031ae7c8e9b86d20", index_title: "港股南向资金持股比例"} - {id: "6a8e27f0812accfdfb929276", index_title: "港股总股本换手率"} - {id: "6a8d0c3835cc131a04986cab", index_title: "港股换手率"} - {id: "68f6152923dc79d2d7949736", index_title: "申万二级行业所属指数"} - {id: "650967b9a318ecd5718efbd5", index_title: "申万资产所属指数"} - {id: "66448014f535c4133d7990ad", index_title: "百川商品参考价"} - {id: "623af0a7a777c60160a108eb", index_title: "社融预期差因子"} - {id: "689ae6c8601e663172c2130a", index_title: "科创50信号"} - {id: "691bd8c1c8e3a12d6a9d3060", index_title: "科创50指数成分股"} - {id: "670ccbf112152b90ea7836e9", index_title: "美股信号"} - {id: "6a8e27f3812accfdfb929277", index_title: "美股总股本换手率"} - {id: "6a8d0c3f35cc131a04986cac", index_title: "美股换手率"} - {id: "643e35460b376461b098ad0d", index_title: "自由流通市值(万得)"} - {id: "6437ae9ad287318c2b50f195", index_title: "自由流通股本(万得)"} - {id: "66a0a571c194dd9d6dabbdc8", index_title: "趋势跟踪(日频)"} - {id: "641d61544da4d6f8bdb3467b", index_title: "长历史全部工作日"} - {id: "650ac8b93bacb9677473254a", index_title: "非ST股"} - {id: "6780c9118011f3039d01d1ca", index_title: "高盛美国金融条件指数"} - {id: "664c47957436d243582bb735", index_title: "黄金海信号"} -
manifest.yaml 837 B
{ "schema_version": 2, "generated_at": "2026-09-23T05:26:59+00:00", "total_records": 2566, "categories": { "a_share_financials": { "file": "a_share_financials.yaml", "record_count": 980 }, "a_share_option_iv": { "file": "a_share_option_iv.yaml", "record_count": 3 }, "a_share_trading_labels": { "file": "a_share_trading_labels.yaml", "record_count": 65 }, "global_classification": { "file": "global_classification.yaml", "record_count": 2 }, "global_financials": { "file": "global_financials.yaml", "record_count": 1324 }, "macro_strategy": { "file": "macro_strategy.yaml", "record_count": 144 }, "us_option_market_data": { "file": "us_option_market_data.yaml", "record_count": 48 } } } -
README.md 1.2 KB
# 系统数据名全量索引 本目录保存平台已支持的数据名,是 `presets/data_catalog.yaml` 的补充。 ## 何时使用 - 常见行情、估值、财务字段先查 `presets/data_catalog.yaml`。 - 未命中时,用 `rg "关键词" presets/index_info_catalog` 搜索本目录。 - 公式中使用精确的 `index_title`,不要把用户口语词或分类名写进公式。 - 高频、已验证的数据名可由维护人员补进 `data_catalog.yaml`。 ## 业务分类 - `a_share_financials.yaml`:A 股财务及报告期字段。 - `global_financials.yaml`:港股、美股财务字段。 - `a_share_trading_labels.yaml`:A 股龙虎榜标签数据。 - `macro_strategy.yaml`:示例、宏观及策略类数据。 - `a_share_option_iv.yaml`:A 股期权隐含波动率。 - `global_classification.yaml`:GICS 行业、板块所属指数。 - `us_option_market_data.yaml`:美股期权 E1/E2、ATM/25 Delta 的价格、成交量、持仓量、隐含波动率、所选合约到期日及行权价(共 48 项)。 - `manifest.yaml`:业务分类文件清单和记录数。 分类仅用于检索组织,不代表数据的原创归属。不要手工修改记录;维护流程及真实来源记录保存在用户侧 Skill 目录之外。 -
us_option_market_data.yaml 4.2 KB
# Auto-generated business catalog; do not edit records by hand. schema_version: 2 category: us_option_market_data record_count: 48 records: - {id: "6ab0ef2cbc8fee56d30bb960", index_title: "美股期权-E1-25C-到期日"} - {id: "6aa8ed09e9323591af7a870c", index_title: "美股期权-E1-25C-成交量"} - {id: "6aa8ed09e9323591af7a8711", index_title: "美股期权-E1-25C-持仓量"} - {id: "6aa8ed09e9323591af7a8706", index_title: "美股期权-E1-25C-收盘价"} - {id: "6ab0ef2fbc8fee56d30bb9e3", index_title: "美股期权-E1-25C-行权价"} - {id: "6aa8ed09e9323591af7a8717", index_title: "美股期权-E1-25C-隐含波动率"} - {id: "6ab0ef27bc8fee56d30bb861", index_title: "美股期权-E1-25P-到期日"} - {id: "6aa8ed09e9323591af7a86e8", index_title: "美股期权-E1-25P-成交量"} - {id: "6aa8ed09e9323591af7a86f2", index_title: "美股期权-E1-25P-持仓量"} - {id: "6aa8ed09e9323591af7a86e3", index_title: "美股期权-E1-25P-收盘价"} - {id: "6ab0ef2abc8fee56d30bb8d3", index_title: "美股期权-E1-25P-行权价"} - {id: "6aa8ed09e9323591af7a86fc", index_title: "美股期权-E1-25P-隐含波动率"} - {id: "6ab0ef1dbc8fee56d30bb5e9", index_title: "美股期权-E1-ATM-CALL-到期日"} - {id: "6aa8ed08e9323591af7a86a5", index_title: "美股期权-E1-ATM-CALL-成交量"} - {id: "6aa8ed08e9323591af7a86ad", index_title: "美股期权-E1-ATM-CALL-持仓量"} - {id: "6aa8ed08e9323591af7a8673", index_title: "美股期权-E1-ATM-CALL-收盘价"} - {id: "6ab0ef1fbc8fee56d30bb661", index_title: "美股期权-E1-ATM-CALL-行权价"} - {id: "6aa8ed09e9323591af7a86b8", index_title: "美股期权-E1-ATM-CALL-隐含波动率"} - {id: "6ab0ef22bc8fee56d30bb703", index_title: "美股期权-E1-ATM-PUT-到期日"} - {id: "6aa8ed09e9323591af7a86c9", index_title: "美股期权-E1-ATM-PUT-成交量"} - {id: "6aa8ed09e9323591af7a86d1", index_title: "美股期权-E1-ATM-PUT-持仓量"} - {id: "6aa8ed09e9323591af7a86c1", index_title: "美股期权-E1-ATM-PUT-收盘价"} - {id: "6ab0ef24bc8fee56d30bb7d0", index_title: "美股期权-E1-ATM-PUT-行权价"} - {id: "6aa8ed09e9323591af7a86d7", index_title: "美股期权-E1-ATM-PUT-隐含波动率"} - {id: "6ab0ef41bc8fee56d30bbed9", index_title: "美股期权-E2-25C-到期日"} - {id: "6aa8ed0ae9323591af7a879c", index_title: "美股期权-E2-25C-成交量"} - {id: "6aa8ed0ae9323591af7a87a8", index_title: "美股期权-E2-25C-持仓量"} - {id: "6aa8ed0ae9323591af7a8794", index_title: "美股期权-E2-25C-收盘价"} - {id: "6ab0ef44bc8fee56d30bbf76", index_title: "美股期权-E2-25C-行权价"} - {id: "6aa8ed0ae9323591af7a87b0", index_title: "美股期权-E2-25C-隐含波动率"} - {id: "6ab0ef3cbc8fee56d30bbc9f", index_title: "美股期权-E2-25P-到期日"} - {id: "6aa8ed0ae9323591af7a8781", index_title: "美股期权-E2-25P-成交量"} - {id: "6aa8ed0ae9323591af7a8787", index_title: "美股期权-E2-25P-持仓量"} - {id: "6aa8ed09e9323591af7a8777", index_title: "美股期权-E2-25P-收盘价"} - {id: "6ab0ef3fbc8fee56d30bbdb3", index_title: "美股期权-E2-25P-行权价"} - {id: "6aa8ed0ae9323591af7a878b", index_title: "美股期权-E2-25P-隐含波动率"} - {id: "6ab0ef31bc8fee56d30bba5b", index_title: "美股期权-E2-ATM-CALL-到期日"} - {id: "6aa8ed09e9323591af7a8732", index_title: "美股期权-E2-ATM-CALL-成交量"} - {id: "6aa8ed09e9323591af7a873e", index_title: "美股期权-E2-ATM-CALL-持仓量"} - {id: "6aa8ed09e9323591af7a872c", index_title: "美股期权-E2-ATM-CALL-收盘价"} - {id: "6ab0ef34bc8fee56d30bbafc", index_title: "美股期权-E2-ATM-CALL-行权价"} - {id: "6aa8ed09e9323591af7a8757", index_title: "美股期权-E2-ATM-CALL-隐含波动率"} - {id: "6ab0ef36bc8fee56d30bbb9c", index_title: "美股期权-E2-ATM-PUT-到期日"} - {id: "6aa8ed09e9323591af7a8764", index_title: "美股期权-E2-ATM-PUT-成交量"} - {id: "6aa8ed09e9323591af7a876c", index_title: "美股期权-E2-ATM-PUT-持仓量"} - {id: "6aa8ed09e9323591af7a875d", index_title: "美股期权-E2-ATM-PUT-收盘价"} - {id: "6ab0ef39bc8fee56d30bbc30", index_title: "美股期权-E2-ATM-PUT-行权价"} - {id: "6aa8ed09e9323591af7a8772", index_title: "美股期权-E2-ATM-PUT-隐含波动率"}
-
-
cases_index.yaml 25.9 KB
# 知识卡片目录 — cases_index.yaml(112 张,每张一行) # AI 在 Step 1a 一次性读完此文件,按 tags 匹配卡片 # 找到后调 getCardFormulas(card_names=["卡片名称"]) 拉取完整公式(支持多张) # # 格式:name | tags(逗号分隔) # tags 含范式关键词,卡片里的资产名只是示例可替换 # 示例:用户问"铜相关个股" → 匹配 tags 含"产业链筛选,相关性分析"的卡片 → 替换资产 高波动率多因子筛选 | 高波动率, 全A股, 多因子选股, 波动率计算, 动量约束, 质量过滤, 下行波动率, 硬闸条件, VIX指数, 动态权重, 行业中性, 宏观适配, 全A股通用, 板块筛选, 量化选股, 风险控制, 流动性筛选, 基本面筛选, 60日窗口, 年化波动率 沪深300高波筛选 | 高波动率, 沪深300, 单因子选股, 波动率计算, 60日窗口, 年化波动率, 分位数筛选, 横截面排序, 滚动窗口, 纯波动率, 简化策略, 入门案例, 概念验证, 学习场景, 无额外筛选, 严格对齐, 量化选股, 标准差计算, 前30%分位, 快速实现 优质低估值选股 | 价值投资, 质价势选股, QARP策略, 行业内比较, 行业自适应, ROIC筛选, 现金流质量, EV/EBIT估值, FCF收益率, 前视偏差消除, 报告期转发布日, 硬闸条件, 分位数筛选, 行业中性, 综合评分, 0-1标准化, 申万行业分类, 财务健康度, 估值折价率, 动量确认, 实盘回测, 基本面选股 低波强反弹综合评分 | 低波动率, 强势反弹, 均线突破, 涨停级别, 综合评分, 多因子选股, 上穿力度, 历史分位数, 数值水位, 波动率水位, 三重筛选, 等权评分, 按天归一, 流动性筛选, 成交量确认, 行业分散, 反弹行情, 趋势突破, 龙头股筛选, 全A股 PMI铜金多空轮动 | PMI指标, 铜金比, 商品轮动, 多空对冲, 宏观策略, 经济周期, 区间持仓, 荣枯线, COMEX期货, 铜期货, 黄金期货, 前值填充, 穿越信号, 进出场区间, 资产池构造, 双向交易, 相对价值, 低频交易, 中长期持仓, 经济先行指标 小米集团20日均线突破60日止损策略 | 小米集团, 单资产策略, 趋势跟踪, 20日均线, 均线突破, 买入信号, 60日最低价, 止损信号, 持仓区间, 完整策略, 策略回测, 净值曲线, 相对收益, 避免前视偏差, 昨天函数, 资产池函数, 持仓区间函数, 累乘函数, 风险控制, 机械化交易, 港股, 科技股, 成长股, 高波动, 日线级别 低波放量突破三重确认 | 低波动率, 突破监控, 双重波动率, 价格波动率, ATR, 放量确认, 布林带突破, 三重确认, 技术分析, 单资产监控, 多资产扫描, 波动率水位_百分比, 分位数筛选, 持续性确认, 假突破过滤, 无前视偏差, 趋势转折, 横盘突破, 涨停监控, 实盘可执行 A股区间涨幅最优资产筛选策略 | 区间收益, 复利累计, 涨幅排序, TOP100筛选, 按天归一, 多维报价, 成交量分析, 全A股, 事件复盘, 候选池构建 A股信息技术板块科技成长优质股筛选策略 | 信息技术板块, ROE筛选_百分比, 现金流质量, 资产负债率, 研发费用率, PEG, 财务数据发布日, 缺失填充, 多因子选股, 综合评分, Top20, 质量投资, 成长股, 估值过滤, 季度调仓 ROE质量优选策略 | ROE筛选, 经营现金流, 质量投资, 沪深300, 年初重选, 股票池构建, 财务筛选, 发布日对齐 沪深300指数60日均线突破回测与基准对比 | 沪深300, 60日均线, 趋势跟踪, 金叉信号, 回测净值, 基准对比, 风险指标, 止损控制, 趋势过滤, 成交量确认 近两年A股高成长优质股筛选 | 高成长选股, 营收CAGR_小数, 净利润CAGR_小数, 成长评分, 盈利能力, 研发投入, 硬闸筛选, PEG估值, 全A股, 多因子策略, 季度复盘, 风险控制 美联储降息期间多黄金空白银组合策略 | 美联储降息, 黄金多头, 白银空头, 贵金属套利, 宏观择时, 价差交易, 利率下行识别, 复利净值, COMEX黄金, COMEX白银, 风险控制, 央行政策, 趋势确认, 止损机制 宁德时代与沪深300双资产价格突破趋势跟踪策略 | 价格突破, 趋势跟踪, 双资产策略, 宁德时代, 沪深300, 前视偏差, 回测分析, 年化波动率, 最大回撤, 风控止损, 仓位管理, 信号去重 高质量财务指标筛选 | 高质量财务指标, 毛利率, 经营现金流_亿元, 现金流收入比_百分比, ROIC, 资产负债率_百分比, A股筛选, 价值投资, 多条件筛选, 发布日口径, 缺失填充, 财务三表 申万Top4_250日行业轮动分析 | 申万行业, 行业轮动, 250日窗口, TopN筛选, 行业排名, 滚动累计涨跌幅, 成分平均汇总, 表格输出 A股行业拥挤度_Top3成交额占比_资金集中度分析 | 行业拥挤度, 成交额Top3, 资金集中度, 申万行业, 风险预警, 趋势分析, 行业轮动, TopN扩展, 成交量对照, 市场监控 A股营收与净利双增速筛选策略 | 双增速, 营收CAGR, 净利润CAGR, 多因子评分, 成长股, 四维度评分, 硬闸筛选, PEG, 财务发布日, 运营效率, 研发费用率, 万得全A, 快速增长股票, 估值安全边际, 量化选股 贵州茅台20日均线突破止损策略 | 贵州茅台, 20日均线, 60日低点, 均线突破, 趋势跟踪, 止损策略, 单股票, 趋势型股票, 进出场区间, 首次函数, 前视偏差, 沪深300基准, 相对净值, 期望收益 宁德时代50日均线突破与120日低点止损策略 | 宁德时代, 50日均线, 120日低点, 均线突破, 趋势跟踪, 止损策略, 单一资产, 新能源龙头, 进出场区间, 资产池构造, 首次函数, 前视偏差, 沪深300基准, 相对净值, 黄金参数配置 五粮液30日均线突破止损策略 | 五粮液, 30日均线, 均线突破, 90日低点, 止损策略, 趋势跟踪, 中期趋势, 长期支撑, 固定止损, 进出场区间, 首次函数, 前视偏差, 持仓信号, 回测验证, 白酒龙头, 单股票交易, 低频交易, 风险控制, 趋势确认, 假突破 贵州茅台60日滚动最大回撤指标 | 最大回撤, 滚动窗口, 风险监控, 止损决策, 60日窗口, 峰值计算, 回撤率, 避免前视偏差, 实时计算, 贵州茅台, 个股风险, 短期风险, 百分比回撤, 动态更新, 风险度量, 组合管理, 波动监控, 技术指标, 量化风控 宁德时代120日滚动最大回撤历史曲线 | 滚动最大回撤, 宁德时代, 120日窗口, 风险监控, 回撤率, 历史曲线, 风险指标, 仓位管理, 止损设置, 前复权价格, 滚动最高价, 动态监控, 中期趋势, 风险暴露, 时间序列, 个股分析, 波动率监控, 下跌风险, 风险预警, 回撤分析 中证500涨幅突破5%买入8%止损策略 | 中证500, 动量突破, 涨幅突破, 5%阈值, 8%止损, 孤立点信号, 止损后重入, 短线交易, 风险控制, 次日开盘买入, 成分股筛选, 日涨跌幅_百分比, 累计止损, 首次突破, 强势股捕捉, 动量延续, 简单策略, 快速执行 比亚迪与沪深300_60日滚动最大回撤对比分析 | 滚动最大回撤, 个股风险对比, 比亚迪, 沪深300, 风险指标计算, 回撤差值_百分比, 回撤比值_小数, 60日窗口, 前复权价格, 交易日对齐, 风险暴露, 风险预警, 极端回撤, 风险监控, 仓位管理, 止损参考, 择时辅助, 中短期风险, 滚动峰值, 同期对齐 沪深300指数250日滚动波动率历史曲线 | 沪深300, 波动率计算, 250日滚动, 年化波动率, 对数收益率, 标准差, 风险指标, 市场状态, 波动率分层, 动态仓位, 风险管理, 历史分位数, 布尔掩码, 条件判断, 波动率目标化, 动态止损, 风险预算, 资产配置 A股动量强势股票筛选策略_涨幅5%多均线市值200亿 | 动量选股, 涨幅筛选, 均线确认, 多周期趋势, 市值过滤, 流动性筛选, 强势股票, 盘后选股, 趋势确认, 短期动量, 中期趋势, 长期趋势, 10日均线, 20日均线, 60日均线, 5%涨幅, 200亿市值, 全A股, 量化选股 A股5日涨幅10%多均线市值300亿筛选策略 | 短期动量, 多均线, A股全市场, 5日涨幅, 30日均线, 90日均线, 流通市值, 大盘股, 强势股, 日频筛选, 候选池, 盘后计算, 等权持仓, 趋势确认, 流动性门槛, 动量选股, 双均线共振 MACD多均线强势股_大市值筛选策略 | MACD, 双均线, 涨幅筛选, 大市值, 流通市值, 市值分位, 技术面选股, 趋势跟踪, 多因子筛选, 动量确认, 均线系统, 规模过滤, 流动性筛选, A股全市场, 日频策略, 短线轮动, 强势股票, 价格结构, 趋势共振, 按天取分位 A股主要指数区间表现与相对超额收益统计_2024Q4 | A股指数, 区间收益, 超额收益, 风格轮动, 沪深300, 中证500, 创业板指, 科创50, 上证指数, 深证成指, 资产配置, 逐资产回测, 净值曲线, 统计区间, 相对强弱 宁德时代沪深300比价60日极值突破策略 | 宁德时代, 沪深300, 比价策略, 相对强弱, 60日极值, 极值突破, 趋势跟踪, 相对强弱轮动, 比价分析, 首次函数, 昨天函数, 前视偏差, 单资产策略, 资产池构造, 进出场区间 全球大类资产区间表现分析_2024Q4 | 多资产评估, 区间收益, 最大回撤, 年化波动率, 全球资产, 股票指数, 商品期货, 贵金属, 国债期货, 资产池构造, 逐资产回测, 时间对齐, Calmar比率, 资产配置, 绩效评估, 风险度量, 宏观分析, 资产轮动, 收益风险对比, 战术资产配置, 绩效排序, 净值曲线, 跨市场轮动, 风险调整 全球大类资产区间表现与最大回撤分析_2024Q4 | 多资产评估, 区间收益, 最大回撤, Calmar比率, 风险调整, 久期法, 债券收益率, 战术资产配置, 全球资产, 绩效排序 中证1000沪深300比价突破均线回撤策略 | 中证1000, 沪深300, 比价策略, 风格轮动, 小盘成长, 大盘价值, 63日突破, 极值突破, 10日均线, 均线回撤, 趋势跟踪, 动态止损, 相对强弱, 首次函数, 前几期函数, 前视偏差, 资产池构造, 进出场区间, 风格切换, 超额收益 行业内估值历史低位且ROE优质公司筛选策略 | 行业内估值, 相对估值, 全A股, 申万一级行业, 按成分排序分, PE排名, PB排名, ROE筛选, 盈利质量, 价值选股, 横向比较, 行业内排名, 估值低位, 全行业覆盖, 31个行业, 价值陷阱, 基本面筛选, 行业轮动, 估值修复, 行业内比较 沪深300隔夜与日内收益拆解分析 | 沪深300, 隔夜收益, 日内收益, 收益拆解, 收益归因, 净值曲线, 持仓管理, 时间效应, 收益来源, 风险特征, 交易时段, 指数分析, 量化研究, 日频数据, 累计收益 盈利增速由负转正与加速筛选策略 | 盈利修复, 基本面选股, 由负转正, 增速加快, 盈利拐点, 双维度增速, 单季净利润, 累计净利润, YoY增速, 增速变化, 加速度指标, 质量过滤, ROE改善, 现金流匹配, 行业中位数, 扣非净利润, 报告期转发布日, 前视偏差, 财务数据处理, 盈利质量验证 沪深300与中证1000大小盘风格轮动策略 | 大小盘轮动, 风格判断, 沪深300, 中证1000, 相对强弱, RPS, 动量策略, 趋势跟踪, 量化回测, 风格择时 宽基指数历史回撤极端状态分析策略 | 回撤分析, 极端状态, 宽基指数, 择时, 风险监测, 沪深300, 中证500, 创业板指, 中证白酒, 历史分位数, 滚动最高价, 资产池构造, 批量计算, 矩阵运算, 市场温度计, 情绪冰点, 逆向投资, 五年窗口, 风险指标, 多指数分析 申万行业月度季节性与10月胜率Top3分析 | 申万行业, 月度季节性, 10月胜率, Top3行业, 行业轮动, 季节性因子, 月度收益, 胜率计算, 日期筛选, 时间维度聚合, 选取指定日期范围, 行业分析, A股市场 A股低贝塔防御组合_熊市表现 | 低Beta, 防御策略, 熊市抗跌, 全A股, 风险管理, 250日窗口, 滚动Beta, Smart Beta, 低波动异常, 排序筛选 宽基指数上行下行波动率定投筛选策略 | 宽基指数, 上行半方差, 下行半方差, 年化波动率, 多窗口稳健性, 定投策略, Delta差值, 波动率比率, bootstrap显著性, 指数筛选, 风险分解, 长期配置 证券ETF20日均线网格交易策略 | 网格交易, 券商ETF, 申万证券Ⅱ, 20日均线, 回测, 震荡行情 中证1000低换手率组合长期表现策略 | 中证1000, 低换手率, 流动性因子, 小盘股, 等权组合, 换手率分位, 指数增强, 长期回测, 信号矩阵, 前视偏差 A股高换手率拥挤组合短期反转效应验证策略 | 高换手率, 拥挤交易, 短期反转效应, 事件驱动, 历史分位归一化, 全A股, 95%分位, 回测验证, 相对收益, 行为金融, 风险提示, 反向配置, 换手率统计, 固定持有期, 沪深300基准 沪深300指数60日滚动波动率分位分析 | 沪深300, 60日滚动波动率, 年化波动率, 对数收益率, 历史分位, 数值水位, 波动率分层, 低波中波高波, 风险监控, 仓位管理, 波动率目标化, 布尔掩码 沪深300成分股低波稳健组合构建策略 | 沪深300, 低波动, 稳健组合, 防御策略, 指数增强, 3年年化波动率, ROE质量, 双维筛选, 回撤控制, 等权配置, 大盘蓝筹, 风险分层, 盈利质量, 季度调仓, TTM口径 A股3日涨幅8%多均线成交额放大筛选策略 | 短期动量, 多均线, A股全市场, 3日涨幅, 20日均线, 60日均线, 成交额放量, 强势股, 日频筛选, 候选池, 盘后计算, 趋势确认, 资金确认, 动量选股, 双均线共振, 放量筛选 港股主要指数区间表现与相对恒生指数超额收益统计 | 港股指数, 恒生指数, 恒生科技, 恒生中国企业指数, 恒生金融类, 区间收益, 超额收益, 资产配置, 指数表现统计, 基准对比, 逐资产回测, 资产池构造, 日期范围, 取出函数, 风格轮动, 事件窗口分析, 阶段性复盘, 港股市场, 指数对比, 相对表现 A股高股息低波动防御组合_震荡市表现 | 高股息, 低波动, 防御组合, 震荡市, 全A股, TTM股息率, 60日波动率, Top30, 沪深300, 回测, 多因子, 防御策略 A股医药生物板块创新成长优质股筛选策略 | 医药生物, 创新药, 研发费用率, 毛利率稳定性, 经营现金流, 净利润CAGR, 硬闸筛选, 多因子评分, Top50, 板块掩码 沃尔德7日收盘价历史 | 收盘价, 7日, 单股, 个股价格, 历史数据, 单资产收盘价序列 博苑股份30日涨跌幅曲线 | 涨跌幅, 30日, 单股, 收益曲线, 个股, 单资产涨跌幅曲线 A股60日低波分位_当日上涨筛选策略 | 低波动率, 当日上涨, 60日, 分位, 选股, 简单, 波动率低分位, 当日上涨择时 A股单日涨幅3pct放量站上5日均线筛选策略 | 涨幅, 放量, 5日均线, 量价, 短线, 选股, 量价共振筛选, 涨幅+放量+均线 A股毛利率30pct_低负债财务筛选 | 毛利率, 负债率, 财务质量, 基本面, 全A, 财务双条件硬筛, 毛利率+负债率 A股ROE15pct_经营现金流为正质量筛选 | ROE, 经营现金流, 质量, 全A, 基本面, 盈利质量双验证, ROE+现金流 沪深300指数20日滚动波动率指标 | 波动率, 20日滚动, 沪深300, 短期, 风险, 滚动波动率序列, 短周期风险 中证500指数60日滚动最大回撤指标 | 最大回撤, 60日滚动, 中证500, 风险指标, 单指数滚动回撤 A股低换手率Bottom30pct筛选 | 低换手率, 筹码, 换手, 选股, 全A, 单因子截面筛选, 换手率底部 A股60日高波动率Top20pct筛选 | 高波动率, 60日, Top N, 全A, 弹性, 单因子截面筛选, 波动率Top分位 申万一级行业近30日区间涨跌幅排序 | 行业, 申万, 30日, 区间涨跌幅, 排名, 行业轮动, 行业区间涨跌幅排名, 近N日强弱 主要宽基指数近60日区间涨跌幅排序 | 宽基指数, 60日, 区间涨跌幅, 排序, 指数比较, 多指数区间涨跌幅, 排名对比 A股MACD金叉_站上20日均线筛选 | MACD, 金叉, 20日均线, 技术指标, 趋势, 选股, 技术双重确认, MACD金叉+均线 A股突破60日新高_放量筛选 | 新高, 60日, 放量, 突破, 趋势, 强势, 价格新高+放量确认, 突破信号 A股跌破60日新低_缩量筛选 | 新低, 60日, 缩量, 弱势, 反向, 风险, 价格新低+缩量, 弱势风险识别 A股营收同比大于20pct筛选 | 营收增速, 同比, 成长, 基本面, 单因子, 单因子财务增速, 营收同比硬筛 A股RSI超卖小于30_近5日下跌筛选 | RSI, 超卖, 5日下跌, 反转, 技术指标, 逆势, 超卖反转, RSI+持续下跌 创业板指数60日滚动波动率历史曲线 | 创业板, 波动率, 60日滚动, 成长, 历史曲线, 板块指数滚动波动率 沪深300指数30日均线历史曲线 | 均线, 30日, 沪深300, 技术指标, MA, 价格序列, 均线历史曲线, MA可视化 A股净利润同比大于30pct筛选 | 净利润增速, 同比, 成长, 基本面, 单因子, 单因子财务增速, 净利润同比硬筛 A股研发强度大于8pct_营收增速大于15pct科技成长筛选 | 研发费用率, 营收增速, 科技, 成长, 创新, 双条件, 科技成长双硬筛, 研发费率+营收增速 A股昨日涨幅Top10强势股 | 昨日涨幅, 盘后, 涨幅排名, 强势股, 昨日涨幅榜, 涨幅Top10, 日线, 复盘, A股, 选股, [freq:daily] 单个股票两年涨幅计算 | 涨幅, 单股, 两年, 区间收益, 简单, 单资产区间涨幅, 收益计算 宁德时代业务相关A股公司筛选 | 宁德时代, 产业链, 锂电, 新能源, 概念股, 供应链, 产业链筛选, 相关性分析, 业务相关公司, 可替换资产 比亚迪估值历史分位数分析 | 比亚迪, 估值, PE, PB, 历史分位, 个股, 横向纵向, 估值历史分位, PE/PB高低水位, 可替换资产 东山精密四家公司股价对比矩阵 | 股价对比, 多股, 矩阵, 东山精密, 同业比较, 多股走势对比矩阵 中芯国际相对三大半导体龙头涨势顺序分析 | 中芯国际, 半导体, 相对强弱, 龙头, 涨势顺序, 相对强弱顺序分析, 多股涨势排序 A股人工智能概念市值加权周期涨跌幅对比 | AI, 人工智能, 概念股, 市值加权, 周期涨跌幅, 市值加权概念股, 周期涨跌幅统计 宁德时代平均PE历史分位数分析 | 宁德时代, PE, 历史分位, 估值, 个股, PE历史分位, 估值水位识别, 可替换资产 宁德时代估值与行业平均对比 | 宁德时代, PE, PB, 行业均值对比, 相对估值, 横向比较, 个股vs行业估值对比 兆易创新半导体周期涨跌幅对比 | 兆易创新, 半导体, 周期, 涨跌幅, 同业对比, 个股周期涨跌幅对比 寒武纪半导体上行周期市盈率分布分析 | 寒武纪, 半导体, 上行周期, 市盈率, PE分布, 个股上行周期估值分布 比亚迪PEG单公式计算 | 比亚迪, PEG, 估值, 成长性, 简单公式, PEG计算, 成长估值验证 银行股PB最低筛选ROE分析 | 银行, PB, 低估值, ROE, 行业选股, 金融, 行业内估值排名, PB最低+ROE 沪深300上涨条件下中证1000收益表现 | 条件收益, 沪深300上涨, 中证1000, 联动分析, 条件统计, 条件联动统计, A上涨日B收益 锂电龙头双股6比4配置组合历史回测 | 锂电, 宁德时代, 比亚迪, 双股, 6比4, 配置, 组合回测, 固定权重组合回测, 多股配置 计算电池板块的景气周期 | 景气周期, 电池, 新能源, 板块分析, 景气度, 景气周期指标 A股财务高质量成长股营收YoY大于20pct净利润YoY大于30pct筛选与回测 | 全A, 万得全A, 财务选股, 成长股, 营收同比, 净利润同比, YoY, 发布日对齐, 报告期转发布日, 缺失填充, 极端值截断, 月末调仓, 等权回测, 策略净值, 相对净值, 沪深300, 非ST 单因子_营收高增长筛选与回测 | 营收增速, 单因子, 回测, 简单, 入门, 单因子回测, 有效性验证模板 国内期货有色金属多头与黑色化工空头等权指数多空组合策略 | 期货, 有色金属, 黑色化工, 多空, 等权, 商品, 多头, 空头, 多类别资产多空, 商品对冲策略 伦镍–沪镍跨市场价差指标构建 | 镍, 伦镍, 沪镍, 跨市场, 价差, 套利, 期货, 跨市场价差指标, 套利分析 单股诊断_我要买 | 单股诊断, 买入分析, 综合判断, 入门, 快速诊断, 单股综合诊断 股价走势对比分析策略 | 走势对比, 多股, 价格比较, 可视化, 相对表现, 走势归一化对比, 多资产可视化 股价走势对比作图 | 走势对比, 可视化, 多股, 作图, 归一化, 多股走势归一化, 通用可视化 固定历史区间统计量计算 | 区间统计量, 固定区间, 历史区间, 日期范围, 五年计划, 十四五, 十三五, 年度统计, 季度统计, 年化波动率, 标准差, 平均收益, 累计收益, 区间表现, 历史表现, 期间对比, 时间段统计, 段末值, 区间计算后筛选, 区间计算后排序, 时间序列统计 自然语言时间转日期掩码 | 自然语言时间, lookup_domain_knowledge, 日期范围, 事件窗口, 基金经理, 政策周期, 节假日, 财报季 历次事件后固定持有期表现 | 事件研究, 多事件, FOMC, 宏观数据, 调仓, 胜率, 分位数, 基金经理, 降息区间, 加息周期, 下行区间, 上行区间, 数据计算区间, 区间表现, 降息周期表现 制度周期固定长区间对比 | 周期, 规划期, 政策阶段, 熊牛市, 区间统计量, 回撤, 基金经理 节假日季节性效应复盘 | 节假日, 季节性, 事件研究, 胜率, 分位数, 基金经理 财报季日程性事件窗口 | 财报季, 披露窗口, 调仓, 交割日, 日程事件, 基金经理 事件窗口分析 | 事件窗口, 事件驱动, FOMC, 美联储, 降息, 加息, 春节, 财报, 交易日位移, 自然日位移, 自然月位移, 日期位移, 事件后表现, 事件前表现, 时间窗口, N个交易日, N天后, N个月后 个股_昨日收盘至当前分钟涨幅前10 | 涨幅排名, 涨幅最强, 当日涨幅, 选股, 今日涨幅, 强势股, 涨幅榜, TOP10, 日内涨幅, 盘中, [freq:minute] 沪深主板_当前分钟涨停信号 | 沪深主板, 主板, 涨停, 封板, 实时, 当前分钟, 盘中, 涨停股, 涨停名单, 涨停家数, 涨停数量, 多少只涨停, 哪些股票涨停, A股主板, 当前, 现在, 日内, 分钟级, 分钟涨停, 涨停信号, 涨停统计, 涨停票, 涨停明细, [freq:minute] 当前突破历史高点信号 | 突破, 历史高点, 创新高, 新高, 突破新高, 250日新高, 首次突破, 日内, 盘中, 当前分钟, 选股, 突破信号, use_minute_data, [freq:minute] 申万行业_昨日收盘至当前分钟涨幅最强板块 | 涨幅最强, 最强板块, 板块排名, 申万行业, 当日涨幅, 今日涨幅, 盘中, 实时, 行业轮动, 板块名称, [freq:minute] A股全市场_盘中实时涨幅前10 | 全A股, 涨幅前10, 盘中, 实时, 当前, 万得全A, 日频映射, use_minute_data, [freq:minute], TOP10, 非ST, 涨幅榜, 涨幅最强, 日内涨幅, 强势股, 涨跌幅函数 A股普通股高开3pct盘中再涨2pct筛选 | 高开, 盘中再涨, A股, 非ST, 普通股, 开盘涨幅, 盘中涨幅, 筛选, 实时, [freq:minute], use_minute_data, 高开高走, 万得全A A股盘中大振幅_近高点筛选 | 盘中振幅, 大振幅, 近高点, 振幅超8%, 距高点1%, 活跃股, 多头主导, 实时筛选, 盘中, use_minute_data, [freq:minute], 万得全A, 高活跃度, 振幅筛选, 最高价, 近高, 强势股, 冲高回落, 高位, 盘中选股 A股盘中非跳空突破60日新高筛选 | 非跳空, 突破, 60日新高, 盘中突破, 开盘未突破, 低开高走, 日内, 盘中, 选股, 新高, 60日, 非高开突破, [freq:minute], 万得全A, use_minute_data, 非跳空型, 盘中拉升, A股筛选, 开盘价, 最高价, 60日滚动最高价, 昨天函数, 三掩码交集 当前跌破历史低点信号 | 跌破, 历史低点, 创新低, 新低, 跌破新低, 250日新低, 日内, 盘中, 当前分钟, 选股, 跌破信号, 突破信号, use_minute_data, [freq:minute], 全市场, 最低价, 250日滚动最低价, 首次跌破, 昨日未跌破, 连续创新低过滤, 昨天函数 沪深300成分股_盘中跌幅超阈值筛选 | 沪深300, 成分股, 盘中跌幅, 跌幅超阈值, 阈值筛选, 名单查询, 日内, 盘中, 实时, 跌幅, use_minute_data, [freq:minute], 全市场每日回报率, 布尔掩码, 跌超5%, 跌幅名单, 跌超3%, 跌超4%, 跌超8%, 动态阈值, 成分股跌幅筛选, 板块掩码 A股靠近支撑位个股筛选 | 支撑位, 支撑线, 靠近支撑位, 接近支撑线, 支撑阻力, 技术分析, 选股, 筛选, 通道位置, 价格通道, 支撑位附近, 低吸, 阻力线, 支撑阻力通道, 通道归一化, use_minute_data, [freq:minute], 全市场每日开盘价, 全市场每日收盘价, 60日窗口, 趋势线拟合, 对数空间拟合 事件后偏移区间回报 | 事件研究, 排除事件日, T+N到T+M, 偏移起始, 区间涨幅, 事件后第N天, 偏移区间回报, 排除事件当天, 双窗口差值, 某天后累加, 分段最终值, 事件消化期 财报超预期不及预期分组后表现对比 | 事件研究, compare模式, 财报, 超预期, 不及预期, 年报, 半年报, 业绩超预期, 业绩不及预期, 搜索新闻打标, 财报发布后表现, 分组对比, 窗口收益, 5日涨跌幅, 20日涨跌幅, 事件后收益分布, 业绩披露, 业绩预期差 行业政策事件后相关标的窗口表现 | 事件研究, single模式, 政策事件, 行业政策, 宽松政策, 政策后表现, 随后表现, 住建部, 国务院, 房地产政策, 降首付, 限购松绑, 政策主体边界, 发布主体, 窗口涨跌幅, 1个月收益, 3个月收益, 事件后收益分布, 证监会, 发改委, 行业监管政策 A股量价共振涨停捕手盘中筛选 | 量比, 换手率, 涨停, 盘中筛选, 成交额, 量价共振, 分钟刷新, 实时, 次新股过滤, 非ST, 万得全A, 涨幅, 中小盘, 盘中选股, use_minute_data, [freq:minute], 量比放大, 涨幅接近涨停, 成交额3~5亿, 涨幅10%, 量比1.5, 换手率3%, 次新股, 上市天数, 基础掩码, 条件掩码, 命中涨幅, 自算换手率, 流通股本, 缺失填充, 全市场每日成交量, 全市场每日成交额, 全市场每日回报率, 全市场每日收盘价 -
data_catalog.yaml 15.5 KB
# ============================================================ # 常用精选数据集预设 # 来源:平台内置数据集(通过 confirmDataMulti 获取 index_title) # # 关键概念: # index_title = confirmDataMulti 返回的精确名称 # 公式中必须用这个名字,原始描述词无效 # dimension = one(一维/时序)或 two(二维,资产×日期矩阵) # is_bool = true(布尔掩码,可直接用于乘法过滤) # false(数值型,如价格、市值) # # 用法: # 1. 常见行情、估值、财务字段先查此文件 → 直接用 index_title 写公式 # 2. 若此文件无目标数据 → 用 rg "关键词" presets/index_info_catalog # 搜索系统支持数据名全量索引 # 3. 高频、已验证、对 Agent 常用的数据名,再人工补充到此文件 # 4. 不要把用户口语词直接当公式数据名;公式中必须写精确 index_title # ============================================================ # ── 行情类(数值,二维,日频)─────────────────────────────── # minute_mapped: true 表示该数据名在 use_minute_data=true 时 # 会被服务端自动映射到分钟频数据源,公式写法不变。 price: - index_title: 全市场每日收盘价 dimension: two is_bool: false minute_mapped: true description: "A股全市场复权收盘价,二维矩阵(资产×日期)" usage: > 全市场收益矩阵 = 涨跌幅("全市场每日收盘价") 茅台收盘 = "全市场每日收盘价" * 取出(贵州茅台) - index_title: 全市场每日开盘价 dimension: two is_bool: false minute_mapped: true description: "A股全市场开盘价,二维矩阵(资产×日期)" - index_title: 全市场每日最高价 dimension: two is_bool: false minute_mapped: true description: "A股全市场最高价,二维矩阵(资产×日期)" - index_title: 全市场每日最低价 dimension: two is_bool: false minute_mapped: true description: "A股全市场最低价,二维矩阵(资产×日期)" - index_title: 全市场每日收盘价(不复权) dimension: two is_bool: false minute_mapped: true description: "A股全市场不复权收盘价,二维矩阵(资产×日期)。涨停/跌停判断需用不复权价" # 注意:查询「全市场每日涨跌幅」时,平台返回 index_title 为「全市场每日回报率」 # 公式中必须用 index_title,写「全市场每日回报率」否则报「数据名不存在」 - index_title: 全市场每日回报率 dimension: two is_bool: false intention_alias: 全市场每日涨跌幅 description: "A股全市场日度涨跌幅(回报率),二维矩阵" - index_title: 全市场每日成交额 dimension: two is_bool: false minute_mapped: true description: "A股全市场日度成交额(元),二维矩阵" usage: > 行业成交额 = 成分求和汇总("全市场每日成交额","申万资产所属指数") - index_title: 全市场每日成交量 dimension: two is_bool: false minute_mapped: true description: "A股全市场日度成交量(股),二维矩阵" - index_title: A股收盘涨停 dimension: two is_bool: false intention_alias: A股涨停,今日涨停,收盘涨停,涨停名单,A股涨停名单,A股收盘涨停名单 description: "A股全市场收盘涨停数据,二维矩阵(资产×日期)。用于回答今日/收盘涨停股票名单,优先于用涨跌幅阈值自写公式重算。" - index_title: A股收盘跌停 dimension: two is_bool: true intention_alias: A股跌停,今日跌停,收盘跌停,跌停名单,A股跌停名单,A股收盘跌停名单 description: "A股全市场收盘跌停布尔掩码,二维矩阵(资产×日期)。包含不同交易板块及ST股票的交易所跌停口径;用于回答今日/收盘跌停股票名单,优先于用涨跌幅阈值自写公式重算。" - index_title: A股收盘涨停(分钟刷新) dimension: two is_bool: false minute_mapped: true intention_alias: A股涨停分钟刷新,今日涨停分钟刷新,当前涨停,A股当前涨停,盘中涨停,涨停名单分钟刷新,A股收盘涨停分钟刷新 description: "A股全市场收盘涨停数据(分钟刷新),二维矩阵(资产×日期)。用于回答当前/今日/盘中涨停股票名单,优先于用涨跌幅阈值自写公式重算。使用前必须校验 readData/description 返回的实际最新日期;若最新日期早于用户询问日期,不得表述为当前/今日数据。" - index_title: A股收盘跌停(分钟刷新) dimension: two is_bool: false minute_mapped: true intention_alias: A股跌停分钟刷新,今日跌停分钟刷新,当前跌停,A股当前跌停,盘中跌停,跌停名单分钟刷新,A股收盘跌停分钟刷新 description: "A股全市场收盘跌停数据(分钟刷新),二维矩阵(资产×日期)。用于回答当前/今日/盘中跌停股票名单,优先于用涨跌幅阈值自写公式重算。使用前必须校验 readData/description 返回的实际最新日期;若最新日期早于 # ── 估值类(数值,二维,日频)─────────────────────────────── valuation: - index_title: A股市盈率(PE)〔估值数据〕 dimension: two is_bool: false description: "市盈率(静态PE),日频" usage: > 低PE = ("A股市盈率(PE)〔估值数据〕">0) * ("A股市盈率(PE)〔估值数据〕"<20) * 板块(万得全A) - index_title: A股市盈率(PE, TTM)〔估值数据〕 dimension: two is_bool: false intention_alias: PE_TTM description: "市盈率(TTM滚动12个月),日频,负值=亏损股" - index_title: A股市净率(PB)〔估值数据〕 dimension: two is_bool: false description: "市净率PB,日频" usage: > 破净股 = ("A股市净率(PB)〔估值数据〕">0) * ("A股市净率(PB)〔估值数据〕"<1) * 板块(万得全A) - index_title: A股市销率(PS,TTM)〔估值数据〕 dimension: two is_bool: false description: "市销率PS(TTM),日频" - index_title: A股流通市值〔估值数据〕 dimension: two is_bool: false unit: 亿元 description: "日度流通市值(亿元)" usage: > 小盘股 = ("A股流通市值〔估值数据〕"<50) * 板块(万得全A) - index_title: A股总市值〔估值数据〕 dimension: two is_bool: false unit: 亿元 description: "日度总市值(亿元)" - index_title: A股股息率〔估值数据〕 dimension: two is_bool: false unit: "%" description: "股息率(%),日频" usage: > 高股息 = ("A股股息率〔估值数据〕">3) * 板块(万得全A) * 缺失填零("非ST股") - index_title: A股市现率(PCF,经营活动现金流TTM)〔估值数据〕 dimension: two is_bool: false unit: 倍 description: "市现率PCF(经营活动现金流TTM),日频" - index_title: A股市现率(PCF,现金净流量TTM)〔估值数据〕 dimension: two is_bool: false unit: 倍 description: "市现率PCF(现金净流量TTM),日频" # ── 港股 TTM 估值(日频,通过 fast_query 查询)────────────── - index_title: 港股市盈率PE:TTM〔估值数据〕 dimension: one is_bool: false description: "港股市盈率TTM,日频,通过 fast_query 查询" - index_title: 港股市净率PB:TTM〔估值数据〕 dimension: one is_bool: false description: "港股市净率PB TTM,日频" - index_title: 港股市销率PS:TTM〔估值数据〕 dimension: one is_bool: false description: "港股市销率PS TTM,日频" - index_title: 港股股息率:TTM〔估值数据〕 dimension: one is_bool: false unit: "%" description: "港股股息率TTM(%),日频" - index_title: 港股市现率PCF(经营活动现金流):TTM〔估值数据〕 dimension: one is_bool: false unit: 倍 description: "港股市现率PCF(经营活动现金流)TTM,日频" - index_title: 港股市现率PCF(现金净流量):TTM〔估值数据〕 dimension: one is_bool: false unit: 倍 description: "港股市现率PCF(现金净流量)TTM,日频" # ── 美股 TTM 估值(日频,通过 fast_query 查询)────────────── - index_title: 美股市盈率PE:TTM〔估值数据〕 dimension: one is_bool: false description: "美股市盈率TTM,日频,通过 fast_query 查询" - index_title: 美股市净率PB:TTM〔估值数据〕 dimension: one is_bool: false description: "美股市净率PB TTM,日频" - index_title: 美股市销率PS:TTM〔估值数据〕 dimension: one is_bool: false description: "美股市销率PS TTM,日频" - index_title: 美股股息率:TTM〔估值数据〕 dimension: two is_bool: false unit: "%" description: "美股股息率TTM(%),日频" - index_title: 美股市现率PCF(经营活动现金流):TTM〔估值数据〕 dimension: one is_bool: false unit: 倍 description: "美股市现率PCF(经营活动现金流)TTM,日频" - index_title: 美股市现率PCF(现金净流量):TTM〔估值数据〕 dimension: one is_bool: false unit: 倍 description: "美股市现率PCF(现金净流量)TTM,日频" # ── 情绪/量价类(数值,二维,日频)────────────────────────── sentiment: - index_title: A股换手率〔估值数据〕 dimension: two is_bool: false unit: "%" description: "个股日度换手率(%)" usage: > 行业换手率 = 成分平均汇总("A股换手率〔估值数据〕","申万资产所属指数") # ── 财务类(数值,二维,季度频率)──────────────────────────── # ⚠️ 季度数据按财报期对齐(如 2024Q1),非交易日发布日 financials: - index_title: A股营业收入〔报告期利润表〕 dimension: two is_bool: false unit: 元 description: "营业收入(元),季度频率" - index_title: A股净利润〔报告期利润表〕 dimension: two is_bool: false unit: 元 description: "净利润(元),季度频率" - index_title: A股归属于母公司股东的净利润〔报告期利润表〕 dimension: two is_bool: false unit: 元 description: "归母净利润(元),季度频率" - index_title: A股股东权益合计〔报告期资产负债表〕 dimension: two is_bool: false unit: 元 intention_alias: 净资产 description: "股东权益/净资产(元),季度频率" - index_title: A股资产总计〔报告期资产负债表〕 dimension: two is_bool: false unit: 元 description: "总资产(元),季度频率" - index_title: A股净资产收益率ROE dimension: two is_bool: false unit: "%" description: "ROE(%),季度频率" usage: > 高ROE = ("A股净资产收益率ROE">15) * 板块(万得全A) - index_title: 港股EBITDA:TTM〔利润表〕 dimension: two is_bool: false description: "港股 EBITDA TTM,来自系统数据名全量索引;实际可用性以工具返回为准" - index_title: 美股EBITDA:TTM〔利润表〕 dimension: two is_bool: false description: "美股 EBITDA TTM,来自系统数据名全量索引;实际可用性以工具返回为准" - index_title: 港股营业收入:TTM〔利润表〕 dimension: two is_bool: false description: "港股营业收入 TTM,来自系统数据名全量索引;实际可用性以工具返回为准" - index_title: 美股营业收入:TTM〔利润表〕 dimension: two is_bool: false description: "美股营业收入 TTM,来自系统数据名全量索引;实际可用性以工具返回为准" # ── 龙虎榜标签(数值,二维,日频/三日口径以 index_title 为准)──────── lhb: - index_title: A股龙虎榜机构专用净买额 dimension: two is_bool: false description: "机构专用龙虎榜净买额;更多席位/标签见 presets/index_info_catalog/guanzhao_lhb.yaml" - index_title: A股龙虎榜活跃营业部净买额 dimension: two is_bool: false description: "活跃营业部龙虎榜净买额;更多席位/标签见 presets/index_info_catalog/guanzhao_lhb.yaml" - index_title: A股龙虎榜三日机构专用净买额 dimension: two is_bool: false description: "三日机构专用龙虎榜净买额;三日口径字段需使用精确 index_title" # ── 行业归属(索引类,二维)────────────────────────────────── industry: - index_title: 申万资产所属指数 dimension: two is_bool: false description: "每只股票所属申万一级行业指数代码,用于行业聚合函数" usage: > 行业均值 = 成分平均汇总("任意因子","申万资产所属指数") - index_title: GICS一级行业所属指数 dimension: two is_bool: false description: "GICS 一级行业所属指数,来自系统数据名全量索引" - index_title: GICS板块所属指数 dimension: two is_bool: false description: "GICS 板块所属指数,来自系统数据名全量索引" # ── 常见派生指标(需用公式自行计算,非平台内置)───── # 以下指标不是平台数据集,而是由上述基础数据用公式组合得到。 # 直接用 confirmDataMulti 搜索这些名词会失败。 derived_indicators: - name: 资产负债率 formula: | 总负债 = "A股资产总计〔报告期资产负债表〕" - "A股股东权益合计〔报告期资产负债表〕" 资产负债率 = 总负债 / "A股资产总计〔报告期资产负债表〕" note: "平台无直接的'负债合计'数据,用 总资产-股东权益 = 总负债" - name: 净利率 formula: | 净利率 = "A股净利润〔报告期利润表〕" / "A股营业收入〔报告期利润表〕" note: "结果为小数,转百分比需乘100" - name: 营收同比增长率 formula: | 营收同比 = 同比增长("A股营业收入〔报告期利润表〕") note: "同比增长是平台内置函数,自动计算年同比" # ── 过滤掩码(布尔,二维)──────────────────────────────────── filters: - index_title: 非ST股 dimension: two is_bool: true description: "True=非ST/ST*股票,用于过滤垃圾股" usage: > 信号 = 缺失填零("非ST股") * 板块(万得全A) * ("PE">0) # ============================================================ # 扩充指引 # 1. 常用字段缺失时,先用 rg "关键词" presets/index_info_catalog 搜索全量目录 # 2. 仍未命中时,再用 confirmDataMulti 传入描述词(如"市盈率") # 3. 从返回的 matched_items 中取精确 index_title # 4. 高频且验证通过的数据名,才以相同格式追加到本文件对应分类下 # 5. 去掉注释前缀后即为可直接用的 index_title # ============================================================ -
dimensions.yaml 165.3 KB
# ============================================================ # 千维 · 选股维度目录(由 qw_indicator / qw_dimension 导出) # # indicator_id = qw_indicator.ind_id(优先)/ code;selectByComposition 直接用它查库 # direction = 服务端元数据方向;请求不可覆盖 # indicator_type = 细分 / 综合;两者均可参与选股 # output_type = score 用于加权排序,screen 用于集合筛选 # selection = 物化选股状态;仅导出 selection.ready=true 的指标,as_of 为快照日 # asset_scope = 用于校验该指标是否支持用户请求市场 # ============================================================ A股_个股情绪: - asset_scope: - A股 calculation: 价格反馈30%+参与确认20%+资金方向20%+相对需求15%+异常反馈10%+龙虎榜资金方向5%;前一交易日无上榜事件时动态归一 description: 正值表示行为代理的净情绪偏积极,负值表示偏消极;0表示无明确净方向,不等同于必然平静 dimension: 个股情绪 direction: high_bullish indicator_id: ind_a_share_b2c71b60 indicator_type: 综合 name: A股_个股情绪维度分 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_个股情绪维度分 window: 20 - asset_scope: - A股 calculation: 隔夜跳空方向强度20%+日内承接25%+近5日涨跌平衡25%+5日收益方向强度30% description: 正值表示短周期价格反馈偏积极,负值表示偏消极;高开继续上涨或低开继续下跌会同向确认,高开回落或低开修复会相互抵消 dimension: 个股情绪 direction: high_bullish indicator_id: ind_a_share_705da8d0 indicator_type: 细分 name: A股_价格反馈 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_价格反馈 window: 5 - asset_scope: - A股 calculation: 近5日量价方向×参与强度;参与强度为成交额量比40%+短期放量30%+换手爆量30% description: 正值表示上涨方向获得更强交易参与,负值表示下跌方向获得更强参与;低参与会压低贡献 dimension: 个股情绪 direction: high_bullish indicator_id: ind_a_share_6aa3b11b indicator_type: 细分 name: A股_参与确认 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_参与确认 window: 20 - asset_scope: - A股 calculation: 当日极端反馈70%+极端持续方向30%;当日极端反馈在收盘涨停/跌停时为1/-1,否则取维度15有符号价格偏离异动评分;2至5日连续涨跌停状态按40%/30%/20%/10%累积极端持续方向 description: 正值表示向上极端反馈,负值表示向下极端反馈;首次涨跌停的异常反馈绝对值为0.7,2/3/4/5日及以上连续涨跌停时绝对值依次为0.82/0.91/0.97/1 dimension: 个股情绪 direction: high_bullish indicator_id: ind_a_share_6efb77b9 indicator_type: 细分 name: A股_异常反馈 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_异常反馈 window: 5 - asset_scope: - A股 calculation: 相对市场35%+行业相对排名持续性40%+跑赢沪深300稳定性25%,围绕自然中点0.5居中 description: 正值表示相对市场或行业需求偏强,负值表示相对偏弱;不代表绝对收益必然同方向 dimension: 个股情绪 direction: high_bullish indicator_id: ind_a_share_e072759a indicator_type: 细分 name: A股_相对需求 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_相对需求 window: 20 - asset_scope: - A股 calculation: 主力资金净占比符号×绝对净占比横截面强度 description: 主力净流入为正、净流出为负,绝对影响越大强度越高 dimension: 个股情绪 direction: high_bullish indicator_id: ind_a_share_453a2fcf indicator_type: 细分 name: A股_资金方向 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_资金方向 window: 1 - asset_scope: - A股 calculation: 前一交易日的日榜和三日榜有符号净主导比例按75%和25%合成;前一交易日未上榜时为0且5%权重退出有效分母 description: 正值表示最新已披露榜单中的资金净主导偏正,负值表示偏负;上榜原因健康和拉萨系拥挤不负责定向 dimension: 个股情绪 direction: high_bullish indicator_id: ind_a_share_711ea03c indicator_type: 细分 name: A股_龙虎榜资金方向 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_龙虎榜资金方向 window: 3 A股_估值性价比: - asset_scope: - A股 calculation: 正PB样本中,PB横截面低估值分与历史低估值水位各50% description: 高分表示PB估值更有吸引力;PB小于等于0时不参与排序 dimension: 估值性价比 direction: high_bullish indicator_id: ind_a_share_pb_valuation_attractiveness indicator_type: 细分 name: A股PB估值吸引力 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股PB估值吸引力 window: 250 - asset_scope: - A股 calculation: 正PE样本中,PE横截面低估值分与历史低估值水位各50% description: 高分表示PE估值更有吸引力;PE小于等于0时不参与排序 dimension: 估值性价比 direction: high_bullish indicator_id: ind_a_share_pe_valuation_attractiveness indicator_type: 细分 name: A股PE估值吸引力 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股PE估值吸引力 window: 250 - asset_scope: - A股 calculation: 正PS样本中,PS TTM横截面排序取反 description: 高分表示收入维度估值更有吸引力;PS小于等于0时不参与排序 dimension: 估值性价比 direction: high_bullish indicator_id: ind_a_share_ps_revenue_valuation_attractiveness indicator_type: 细分 name: A股PS收入估值吸引力 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股PS收入估值吸引力 window: 1 - asset_scope: - A股 calculation: PE估值吸引力30%、PB估值吸引力25%、PS收入估值吸引力20%、股息率25% description: 高分表示估值和股息回报更有吸引力;无效分项按0贡献,固定权重不转移 dimension: 估值性价比 direction: high_bullish indicator_id: ind_a_share_valuation_composite indicator_type: 综合 name: A股估值性价比 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股估值性价比 window: 250 - asset_scope: - A股 calculation: 非负股息率样本中做横截面排序 description: 高分表示股息回报更高;真实零股息保留为0,负值和缺失不参与排序 dimension: 估值性价比 direction: high_bullish indicator_id: ind_a_share_dividend_yield_attractiveness indicator_type: 细分 name: A股股息率吸引力 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股股息率吸引力 window: 1 A股_动量与反转: - asset_scope: - A股 calculation: 20日前收盘价 / 60日前收盘价 - 1 description: 高分表示较长期动量延续 dimension: 动量与反转 direction: high_bullish indicator_id: ind_a_share_ef1dbac1 indicator_type: 细分 name: A股_21至60日动量 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_21至60日动量 window: 60 - asset_scope: - A股 calculation: 5日累计收益横截面排序 description: 高分表示一周级动量强 dimension: 动量与反转 direction: high_bullish indicator_id: ind_a_share_5d_return_percentile indicator_type: 细分 name: A股_5日涨幅百分位 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_5日涨幅百分位 window: 5 - asset_scope: - A股 calculation: 5日前收盘价 / 20日前收盘价 - 1 description: 高分表示剔除最近5日后的中短期动量强 dimension: 动量与反转 direction: high_bullish indicator_id: ind_a_share_048f80d6 indicator_type: 细分 name: A股_6至20日动量 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_6至20日动量 window: 20 - asset_scope: - A股 calculation: 平均涨跌幅之和大于0时计算RSI,并映射强势但不过热区间 description: 高分表示强而不过热;无波动或数据缺失时不输出 dimension: 动量与反转 direction: high_bullish indicator_id: ind_a_share_rsi_strong_not_overheated indicator_type: 细分 name: A股_RSI强而不过热 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_RSI强而不过热 window: 14 - asset_scope: - A股 calculation: 5日10%、6至20日35%、21至60日25%、加速度15%、RSI健康15%,再乘拥挤修正 description: A股动量与反转最终得分 dimension: 动量与反转 direction: high_bullish indicator_id: ind_a_share_momentum_reversal indicator_type: 综合 name: A股_动量与反转 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_动量与反转 window: 60 - asset_scope: - A股 calculation: 最近5日收益/5 - 第6至10日收益/5 description: 高分表示近期相对前一非重叠窗口加速 dimension: 动量与反转 direction: high_bullish indicator_id: ind_a_share_momentum_acceleration indicator_type: 细分 name: A股_动量加速度 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_动量加速度 window: 10 - asset_scope: - A股 calculation: 5日涨幅、成交额量比、换手率三个分位均超过80%后才连续扣分,最低修正至0.7 description: 越接近1表示短期拥挤风险较低 dimension: 动量与反转 direction: high_bullish indicator_id: ind_a_share_8bcc90dc indicator_type: 细分 name: A股_拥挤修正 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_拥挤修正 window: 20 A股_市场情绪: - asset_scope: - A股 calculation: 8项指标按25%/15%/15%/15%/15%/5%/5%/5%固定权重直接加权求和;不可用分项按0贡献,权重不向其他分项转移 description: 正值表示行业或指数市场情绪偏积极,负值表示偏消极;有效证据减少时绝对值向0收缩 dimension: 市场情绪 direction: high_bullish indicator_id: ind_a_share_market_sentiment indicator_type: 综合 name: A股市场情绪 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股市场情绪 window: 60 - asset_scope: - A股 calculation: 两板以上家数/收盘涨停家数,转60日历史水位,权重5% description: 高分表示连续两日涨停样本在涨停结构中的占比较高 dimension: 市场情绪 direction: high_bullish indicator_id: ind_a_share_three_board_plus_expansion indicator_type: 细分 name: 两板以上扩散 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: 两板以上扩散 window: 60 - asset_scope: - A股 calculation: 收盘涨停家数/盘中触板家数,转60日历史水位,权重15% description: 高分表示盘中触板后收盘封住的比例处于较高历史水位 dimension: 市场情绪 direction: high_bullish indicator_id: ind_a_share_limit_seal_strength indicator_type: 细分 name: 封板承接 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: 封板承接 window: 60 - asset_scope: - A股 calculation: '`(收盘涨停家数-收盘跌停家数)/(两者合计)`,权重15%' description: 正值表示涨停相对占优,负值表示跌停相对占优 dimension: 市场情绪 direction: high_bullish indicator_id: ind_a_share_limit_up_down_breadth indicator_type: 细分 name: 收盘涨跌停广度 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: 收盘涨跌停广度 window: 1 - asset_scope: - A股 calculation: 今日继续涨停家数/昨日涨停有效基数,转60日历史水位,权重15% description: 高分表示昨日涨停股继续晋级的比例较高 dimension: 市场情绪 direction: high_bullish indicator_id: ind_a_share_prior_limit_up_promotion indicator_type: 细分 name: 昨日涨停晋级 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: 昨日涨停晋级 window: 60 - asset_scope: - A股 calculation: 昨日涨停股今日红盘率与平均收益各占50%,权重15% description: 高分表示昨日涨停样本今日赚钱效应更强 dimension: 市场情绪 direction: high_bullish indicator_id: ind_a_share_prior_limit_up_profit_effect indicator_type: 细分 name: 昨日涨停赚钱效应 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: 昨日涨停赚钱效应 window: 60 - asset_scope: - A股 calculation: 继续跌停率反向、红盘修复率正向,各占50%,权重5% description: 高分表示昨日跌停股延续风险较低、修复较强 dimension: 市场情绪 direction: high_bullish indicator_id: ind_a_share_prior_limit_down_followthrough_repair indicator_type: 细分 name: 昨日跌停延续与修复 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: 昨日跌停延续与修复 window: 60 - asset_scope: - A股 calculation: '`(开盘上涨家数-开盘下跌家数)/(两者合计)`,权重5%' description: 正值表示正式开盘上涨家数占优;不代表9:25集合竞价广度 dimension: 市场情绪 direction: high_bullish indicator_id: ind_a_share_official_opening_breadth indicator_type: 细分 name: 正式开盘广度 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: 正式开盘广度 window: 1 - asset_scope: - A股 calculation: '`(上涨家数-下跌家数)/(上涨家数+下跌家数)`,权重25%' description: 正值表示上涨家数占优,负值表示下跌家数占优 dimension: 市场情绪 direction: high_bullish indicator_id: ind_a_share_advance_decline_breadth indicator_type: 细分 name: 涨跌家数广度 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: 涨跌家数广度 window: 1 A股_异动监控: - asset_scope: - A股 calculation: 个股相对对应指数的累计涨跌幅偏离值,主板阈值20%,创业板/科创板阈值30% description: 1表示触发价格偏离异动 dimension: 异动监控 direction: high_bullish indicator_id: ind_a_share_3d_price_deviation_abnormal indicator_type: 细分 name: 3日内价格偏离异动 output_type: screen selection: as_of: 20260826 ready: true vector_type: screen source_name: 3日内价格偏离异动 window: 3 - asset_scope: - A股 calculation: 主板交易所换手异动口径:任一窗口日均换手率/前5日日均换手率≥30,且窗口累计换手率≥20% description: 1表示换手突然放大到交易所异动口径 dimension: 异动监控 direction: high_bullish indicator_id: ind_a_share_3d_turnover_abnormal indicator_type: 细分 name: 3日内换手异动 output_type: screen selection: as_of: 20260826 ready: true vector_type: screen source_name: 3日内换手异动 window: 3 - asset_scope: - A股 calculation: 10日偏离+100%/-50%、30日偏离+200%/-70%,或10日内多次同向异常波动 description: 1表示进入严重异动监控口径 dimension: 异动监控 direction: high_bullish indicator_id: ind_a_share_severe_abnormal_volatility indicator_type: 综合 name: 严重异常波动 output_type: screen selection: as_of: 20260826 ready: true vector_type: screen source_name: 严重异常波动 window: 30 - asset_scope: - A股 calculation: 最大绝对偏离值/适用阈值,再同市场横截面分位;符号由最大偏离窗口方向决定 description: 正值表示向上异动,负值表示向下异动,绝对值越大越异常 dimension: 异动监控 direction: high_bullish indicator_id: ind_a_share_price_deviation_abnormal_score indicator_type: 细分 name: 价格偏离异动评分 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: 价格偏离异动评分 window: 3 - asset_scope: - A股 calculation: 3日内价格偏离异动、3日内换手异动、严重异常波动三者取并集 description: 1表示该股当日进入交易异动监控名单,已包含严重异常波动 dimension: 异动监控 direction: high_bullish indicator_id: ind_a_share_daily_trading_abnormal indicator_type: 综合 name: 当日交易异动 output_type: screen selection: as_of: 20260826 ready: true vector_type: screen source_name: 当日交易异动 window: 1 - asset_scope: - A股 calculation: 价格偏离强度65% + 换手爆量强度35%,方向由价格偏离决定 description: 交易员可直接按绝对值看异动强度,按正负看异动方向 dimension: 异动监控 direction: high_bullish indicator_id: ind_a_share_daily_abnormal_score indicator_type: 综合 name: 当日异动评分 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: 当日异动评分 window: 1 - asset_scope: - A股 calculation: 换手倍率对数分位与累计换手分位几何合成 description: 越接近1表示换手越异常、越拥挤 dimension: 异动监控 direction: high_bullish indicator_id: ind_a_share_turnover_volume_spike_score indicator_type: 细分 name: 换手爆量评分 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: 换手爆量评分 window: 3 A股_形态_波动与风控: - asset_scope: - A股 calculation: 20日收益波动率在自身250日水位中过高或过低都扣分 description: 高分表示波动处于可交易的中性区间 dimension: 形态、波动与风控 direction: high_bullish indicator_id: ind_a_share_20d_hist_vol_moderate indicator_type: 细分 name: A股_历史波动率适中 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_历史波动率适中 window: 250 - asset_scope: - A股 calculation: 当前价相对60日高点回撤 description: 高分表示回撤可控 dimension: 形态、波动与风控 direction: high_bullish indicator_id: ind_a_share_60d_drawdown_control indicator_type: 细分 name: A股_回撤控制 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_回撤控制 window: 60 - asset_scope: - A股 calculation: 站回10日均线且20日乖离健康 description: 1表示二次买点 dimension: 形态、波动与风控 direction: high_bullish indicator_id: ind_a_share_ma_pullback_stabilization indicator_type: 细分 name: A股_回踩均线企稳 output_type: screen selection: as_of: 20260826 ready: true vector_type: screen source_name: A股_回踩均线企稳 window: 20 - asset_scope: - A股 calculation: 收盘价/MA20-1,过高过低都低分 description: 高分表示强而不过热 dimension: 形态、波动与风控 direction: high_bullish indicator_id: ind_a_share_ma20_deviation_health indicator_type: 细分 name: A股_均线偏离健康度 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_均线偏离健康度 window: 20 - asset_scope: - A股 calculation: 收口与开口得分取小作为共同强度,再用价格相对MA20的位置作0.5至1倍连续方向修正 description: 高分表示收口与开口同时较强,向上扩张获得更高分,向下扩张仅软降分 dimension: 形态、波动与风控 direction: high_bullish indicator_id: ind_a_share_bollinger_squeeze_expansion indicator_type: 细分 name: A股_布林带收口后开口 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_布林带收口后开口 window: 20 - asset_scope: - A股 calculation: 开盘相对昨收跳空风险排序后取反 description: 高分表示隔夜跳空风险低 dimension: 形态、波动与风控 direction: high_bullish indicator_id: ind_a_share_stock_gap_risk_health indicator_type: 细分 name: A股_跳空健康 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_跳空健康 window: 1 - asset_scope: - A股 calculation: 历史波动率适中15%、布林收口后开口15%、均线偏离健康20%、60日回撤控制20%、回踩企稳15%、跳空健康15%;再乘跌停流动性健康修正 description: A股形态健康最终得分,跌停时归零以保持综合分越高越健康 dimension: 形态、波动与风控 direction: high_bullish indicator_id: ind_a_share_pattern_health indicator_type: 综合 name: A股形态健康 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股形态健康 window: 250 - asset_scope: - A股 calculation: '`1-"A股收盘跌停"`,仅识别跌停带来的交易受限和踩踏风险,不把涨停作为通用风险扣分' description: 1表示未收盘跌停,0表示收盘跌停 dimension: 形态、波动与风控 direction: high_bullish indicator_id: ind_a_share_limit_down_liquidity indicator_type: 细分 name: A股跌停流动性健康 output_type: screen selection: as_of: 20260826 ready: true vector_type: screen source_name: A股跌停流动性健康 window: 1 A股_现金流质量: - asset_scope: - A股 calculation: 仅归母净利润大于0时计算CFO/归母净利润横截面排序 description: 高分表示正利润更有现金流支撑;亏损样本不计算该比率 dimension: 现金流质量 direction: high_bullish indicator_id: ind_a_share_cfo_net_profit_ratio indicator_type: 细分 name: A股CFO净利润比 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股CFO净利润比 window: 90 - asset_scope: - A股 calculation: CFO/营业收入横截面排序 description: 高分表示收入更能转化为经营现金流 dimension: 现金流质量 direction: high_bullish indicator_id: ind_a_share_cfo_revenue_ratio indicator_type: 细分 name: A股CFO收入比 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股CFO收入比 window: 90 - asset_scope: - A股 calculation: 1-按市场排序分((净利润-CFO)/总资产) description: 高分表示应计利润风险较低 dimension: 现金流质量 direction: high_bullish indicator_id: ind_a_share_accrual_asset_ratio indicator_type: 细分 name: A股应计项资产比 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股应计项资产比 window: 90 - asset_scope: - A股 calculation: CFO净利润比30%、CFO收入比20%、应计项资产比20%、FCF资产比15%、FCF收益率15%;分项缺失按0贡献,固定权重不转移 description: 高分表示盈利质量和现金创造能力更好;数据不完整会保守压低维度分 dimension: 现金流质量 direction: high_bullish indicator_id: ind_a_share_cashflow_quality_composite indicator_type: 综合 name: A股现金流质量 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股现金流质量 window: 90 - asset_scope: - A股 calculation: FCF/总市值横截面排序 description: 高分表示自由现金流相对市值回报更高 dimension: 现金流质量 direction: high_bullish indicator_id: ind_a_share_fcf_yield indicator_type: 细分 name: A股自由现金流收益率 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股自由现金流收益率 window: 90 - asset_scope: - A股 calculation: (CFO-Capex)/资产总计横截面排序 description: 高分表示单位资产产生的自由现金流更好,避免绝对规模偏差 dimension: 现金流质量 direction: high_bullish indicator_id: ind_18b1bfe8 indicator_type: 细分 name: A股自由现金流资产比 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股自由现金流资产比 window: 90 A股_盈利成长: - asset_scope: - A股 calculation: 经营现金流同比增长横截面排序 description: 高分表示经营现金流增长更强 dimension: 盈利成长 direction: high_bullish indicator_id: ind_a_share_cfo_yoy_growth indicator_type: 细分 name: A股CFO同比增长 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股CFO同比增长 window: 90 - asset_scope: - A股 calculation: 归母净利润同比增长横截面排序 description: 高分表示利润增速强 dimension: 盈利成长 direction: high_bullish indicator_id: ind_a_share_net_profit_yoy_growth indicator_type: 细分 name: A股归母净利润同比增长 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股归母净利润同比增长 window: 90 - asset_scope: - A股 calculation: 营收同比35%、归母净利润同比40%、CFO同比25% description: 高分表示收入、利润和现金流同步增长;缺失分项按0贡献,固定权重不转移 dimension: 盈利成长 direction: high_bullish indicator_id: ind_a_share_earnings_growth_composite indicator_type: 综合 name: A股盈利成长 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股盈利成长 window: 90 - asset_scope: - A股 calculation: 营业收入同比增长横截面排序 description: 高分表示收入增速强 dimension: 盈利成长 direction: high_bullish indicator_id: ind_a_share_revenue_yoy_growth indicator_type: 细分 name: A股营收同比增长 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股营收同比增长 window: 90 A股_盈利能力: - asset_scope: - A股 calculation: ROA发布日横截面排序 description: 高分表示资产赚钱效率高 dimension: 盈利能力 direction: high_bullish indicator_id: ind_a_share_roa_level indicator_type: 细分 name: A股ROA水平 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股ROA水平 window: 90 - asset_scope: - A股 calculation: ROE发布日横截面排序 description: 高分表示股东回报能力强 dimension: 盈利能力 direction: high_bullish indicator_id: ind_a_share_roe_level indicator_type: 细分 name: A股ROE水平 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股ROE水平 window: 90 - asset_scope: - A股 calculation: 销售净利率发布日横截面排序 description: 高分表示收入转化为净利润的能力更强 dimension: 盈利能力 direction: high_bullish indicator_id: ind_a_share_net_margin_level indicator_type: 细分 name: A股净利率水平 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股净利率水平 window: 90 - asset_scope: - A股 calculation: 销售毛利率发布日横截面排序 description: 高分表示定价权或毛利空间更强 dimension: 盈利能力 direction: high_bullish indicator_id: ind_a_share_gross_margin_level indicator_type: 细分 name: A股毛利率水平 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股毛利率水平 window: 90 - asset_scope: - A股 calculation: ROE25%、ROA20%、毛利率20%、净利率20%、营业利润率15% description: 高分表示公司赚钱能力更强;缺失分项按0贡献,固定权重不转移 dimension: 盈利能力 direction: high_bullish indicator_id: ind_a_share_profitability_composite indicator_type: 综合 name: A股盈利能力 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股盈利能力 window: 90 - asset_scope: - A股 calculation: 营业利润/营业总收入发布日横截面排序 description: 高分表示主业盈利质量更好 dimension: 盈利能力 direction: high_bullish indicator_id: ind_a_share_operating_profit_margin_level indicator_type: 细分 name: A股营业利润率水平 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股营业利润率水平 window: 90 A股_相对强度: - asset_scope: - A股 calculation: 每日收益率在申万行业内排序 description: 高分表示当日相对同行领先 dimension: 相对强度 direction: high_bullish indicator_id: ind_d174dbb0 indicator_type: 细分 name: A股日行业相对强度 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股日行业相对强度 window: 1 - asset_scope: - A股 calculation: 信息比率35%、行业排名持续性40%、跑赢基准稳定性25% description: A股相对强度最终得分 dimension: 相对强度 direction: high_bullish indicator_id: ind_a_share_relative_strength indicator_type: 综合 name: A股相对强度 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股相对强度 window: 20 - asset_scope: - A股 calculation: 20日平均日超额收益 / 20日超额收益标准差,再做市场内排序 description: 高分表示相对沪深300的超额更高且更稳定 dimension: 相对强度 direction: high_bullish indicator_id: ind_5168b0e9 indicator_type: 细分 name: A股相对沪深300信息比率 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股相对沪深300信息比率 window: 20 - asset_scope: - A股 calculation: 最近20日行业内每日排名均值 description: 高分表示持续而非单日领先同行 dimension: 相对强度 direction: high_bullish indicator_id: ind_a08fc2e2 indicator_type: 细分 name: A股行业相对排名持续性 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股行业相对排名持续性 window: 20 - asset_scope: - A股 calculation: 最近20日跑赢沪深300的交易日比例 description: 高分表示跑赢基准具有持续性 dimension: 相对强度 direction: high_bullish indicator_id: ind_32056947 indicator_type: 细分 name: A股跑赢沪深300稳定性 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股跑赢沪深300稳定性 window: 20 A股_财务安全: - asset_scope: - A股 calculation: 流动资产/流动负债横截面排序;流动负债为0或缺失时不参与排序 description: 高分表示短期流动性缓冲更充足 dimension: 财务安全 direction: high_bullish indicator_id: ind_a_share_current_ratio_safety indicator_type: 细分 name: A股流动比率安全 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股流动比率安全 window: 90 - asset_scope: - A股 calculation: 货币资金/比较取大(短期借款+一年内到期非流动负债,1)横截面排序 description: 高分表示现金覆盖短债能力更强;短债合计为0且货币资金为正时自然排在前列 dimension: 财务安全 direction: high_bullish indicator_id: ind_a_share_cash_short_debt_safety indicator_type: 细分 name: A股现金短债比安全 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股现金短债比安全 window: 90 - asset_scope: - A股 calculation: 资产负债率安全35%、流动比率安全20%、速动比率安全15%、现金短债比安全30%;分项缺失按0贡献,固定权重不转移 description: 高分表示资产负债表和流动性更安全;数据不完整会保守压低维度分 dimension: 财务安全 direction: high_bullish indicator_id: ind_a_share_financial_safety_composite indicator_type: 综合 name: A股财务安全 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股财务安全 window: 90 - asset_scope: - A股 calculation: 负债合计/资产总计横截面排序取反;资产总计为0或缺失时不参与排序 description: 高分表示资产负债表更稳健、杠杆压力更低 dimension: 财务安全 direction: high_bullish indicator_id: ind_a_share_debt_ratio_safety indicator_type: 细分 name: A股资产负债率安全 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股资产负债率安全 window: 90 - asset_scope: - A股 calculation: (流动资产-存货)/流动负债横截面排序;流动负债为0或缺失时不参与排序 description: 高分表示更保守口径下的短期偿债能力更强 dimension: 财务安全 direction: high_bullish indicator_id: ind_a_share_quick_ratio_safety indicator_type: 细分 name: A股速动比率安全 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股速动比率安全 window: 90 A股_财务爆雷风险: - asset_scope: - A股 calculation: 其他应收款/资产总计横截面排序 description: 高分表示其他应收款占资产比例偏高,需排查关联方占用、内部拆借或坏账黑洞 dimension: 财务爆雷风险 direction: high_bullish indicator_id: ind_a_share_other_receivables_occupation_risk indicator_type: 细分 name: A股其他应收占用风险 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股其他应收占用风险 window: 90 - asset_scope: - A股 calculation: 减值冲击风险 ×(70% + 风险资产积累风险30%) description: 高分表示大额减值集中冲击利润,且存在较高风险资产积累,可能集中释放历史包袱 dimension: 财务爆雷风险 direction: high_bullish indicator_id: ind_a_share_earnings_bath_risk indicator_type: 细分 name: A股利润洗澡风险 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股利润洗澡风险 window: 90 - asset_scope: - A股 calculation: 商誉主体风险 ×(70% + 核心利润率同比下滑风险30%);商誉主体由商誉/权益57%和商誉/资产43%构成 description: 高分表示并购溢价高且主业利润率走弱,未来减值弹性大 dimension: 财务爆雷风险 direction: high_bullish indicator_id: ind_a_share_goodwill_blackhole_risk indicator_type: 细分 name: A股商誉黑洞风险 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股商誉黑洞风险 window: 90 - asset_scope: - A股 calculation: 在建工程占比50% + `max(在建工程同比-营收同比, 0)`风险排序50% description: 高分表示在建工程占比高且扩张快,需警惕长期不转固、延迟折旧或资产虚增 dimension: 财务爆雷风险 direction: high_bullish indicator_id: ind_a_share_cip_abnormal_risk indicator_type: 细分 name: A股在建工程异常风险 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股在建工程异常风险 window: 90 - asset_scope: - A股 calculation: '`max(存货同比-营收同比, 0)`,仅正向背离参与排序' description: 高分表示存货扩张快于收入,需警惕压货、滞销或跌价准备不足 dimension: 财务爆雷风险 direction: high_bullish indicator_id: ind_a_share_inventory_abnormal_warning indicator_type: 细分 name: A股存货异常预警 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股存货异常预警 window: 90 - asset_scope: - A股 calculation: 货币资金/资产高 × 有息负债/资产高,并用财务费用压力修正 description: 高分表示“账上有钱还大额借钱”的常识冲突,需排查受限资金、资金占用或虚构货币资金 dimension: 财务爆雷风险 direction: high_bullish indicator_id: ind_a_share_cash_debt_conflict indicator_type: 细分 name: A股存贷双高冲突 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股存贷双高冲突 window: 90 - asset_scope: - A股 calculation: '`max(应收账款同比-营收同比, 0)`,仅正向背离参与排序' description: 高分表示应收扩张快于收入,需警惕赊销、回款变差或收入质量 dimension: 财务爆雷风险 direction: high_bullish indicator_id: ind_a_share_receivable_abnormal_warning indicator_type: 细分 name: A股应收异常预警 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股应收异常预警 window: 90 - asset_scope: - A股 calculation: 1 - 核心利润获现率横截面排序;仅当核心利润+其他收益大于0时计算CFO/(核心利润+其他收益) description: 高分表示正的核心利润难以变成经营现金流;分母非正时该分项不计算 dimension: 财务爆雷风险 direction: high_bullish indicator_id: ind_a_share_core_profit_cash_shortfall indicator_type: 细分 name: A股核心利润获现不足 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股核心利润获现不足 window: 90 - asset_scope: - A股 calculation: 核心利润为正且同比改善 × 核心利润获现不足 × 应收或存货异常 description: 高分表示利润表改善,但经营现金流与营运资本不配合,形成组合异常 dimension: 财务爆雷风险 direction: high_bullish indicator_id: ind_a_share_paper_profit_warning indicator_type: 细分 name: A股纸面富贵预警 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股纸面富贵预警 window: 90 - asset_scope: - A股 calculation: 应收异常10%、存货异常10%、核心利润获现不足10%、纸面富贵组合强度10%、存贷双高15%、商誉黑洞15%、利润洗澡10%、其他应收占用8%、在建工程异常7%、近4期亏损频次5% description: 高分表示多项张氏排雷信号形成风险闭环 dimension: 财务爆雷风险 direction: high_bullish indicator_id: ind_a_share_financial_blowup_warning_composite indicator_type: 综合 name: A股财务爆雷风险 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股财务爆雷风险 window: 360 - asset_scope: - A股 calculation: 最近4个报告期亏损次数横截面排序,0次亏损不参与风险排序 description: 高分表示近期亏损出现频次更高;不等同于连续4个单季度亏损 dimension: 财务爆雷风险 direction: high_bullish indicator_id: ind_b47d3197 indicator_type: 细分 name: A股近4期亏损频次 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股近4期亏损频次 window: 360 A股_资金流向: - asset_scope: - A股 calculation: 当日主力净流入30%、昨日以前3日主力持续30%、行业内主力40% description: A股资金流向最终得分 dimension: 资金流向 direction: high_bullish indicator_id: ind_a_share_capital_flow indicator_type: 综合 name: A股资金流向 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股资金流向 window: 3 - asset_scope: - A股 calculation: 主力资金净占比排序 description: 高分表示主力资金支持 dimension: 资金流向 direction: high_bullish indicator_id: ind_a_share_main_net_inflow_strength indicator_type: 细分 name: 主力净流入强度 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: 主力净流入强度 window: 1 - asset_scope: - A股 calculation: 主力资金净占比滞后1日后的3日均值排序,不包含当日 description: 高分表示此前3日主力资金持续支持 dimension: 资金流向 direction: high_bullish indicator_id: ind_a_share_sustained_main_inflow indicator_type: 细分 name: 主力持续流入 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: 主力持续流入 window: 3 - asset_scope: - A股 calculation: 主力资金净占比在申万行业内排序 description: 高分表示行业内资金更强 dimension: 资金流向 direction: high_bullish indicator_id: ind_a_share_sector_main_capital_relative_strength indicator_type: 细分 name: 行业内主力资金相对强度 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: 行业内主力资金相对强度 window: 1 A股_趋势结构: - asset_scope: - A股 calculation: 当前收盘价 / 过去20日至昨日最高价 description: 高分表示接近或突破短期高点 dimension: 趋势结构 direction: high_bullish indicator_id: ind_a_share_20d_high_breakout_proximity indicator_type: 细分 name: A股_20日高点接近突破 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_20日高点接近突破 window: 20 - asset_scope: - A股 calculation: 当前收盘价 / 过去60日至昨日最低价 description: 高分表示远离中期低点、未进入破位结构 dimension: 趋势结构 direction: high_bullish indicator_id: ind_a_share_60d_low_distance_from_breakdown indicator_type: 细分 name: A股_60日低点远离破位 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_60日低点远离破位 window: 60 - asset_scope: - A股 calculation: 当前收盘价 / 过去60日至昨日最高价 description: 高分表示中期结构强 dimension: 趋势结构 direction: high_bullish indicator_id: ind_a_share_60d_high_breakout_proximity indicator_type: 细分 name: A股_60日高点接近突破 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_60日高点接近突破 window: 60 - asset_scope: - A股 calculation: 乖离率不高于12%时为1,12%~25%连续降至0.5,25%以上保持0.5 description: 越接近1表示趋势未明显过热,避免阈值附近得分跳变 dimension: 趋势结构 direction: high_bullish indicator_id: ind_a_share_overheat_correction indicator_type: 细分 name: A股_不过热修正 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_不过热修正 window: 20 - asset_scope: - A股 calculation: 收盘价 > MA5 > MA10 > MA20 description: 1表示均线顺序确认 dimension: 趋势结构 direction: high_bullish indicator_id: ind_a_share_ma_bullish_alignment indicator_type: 细分 name: A股_均线多头排列 output_type: screen selection: as_of: 20260826 ready: true vector_type: screen source_name: A股_均线多头排列 window: 20 - asset_scope: - A股 calculation: 近5日低点高于MA20,近5日高点不低于昨日近10日高点 description: 1表示高低点同步上移 dimension: 趋势结构 direction: high_bullish indicator_id: ind_a_share_short_term_high_low_lift indicator_type: 细分 name: A股_短期高低点抬升 output_type: screen selection: as_of: 20260826 ready: true vector_type: screen source_name: A股_短期高低点抬升 window: 20 - asset_scope: - A股 calculation: 接近突破连续分 × 成交额量比得分 description: 高分表示突破有量能确认 dimension: 趋势结构 direction: high_bullish indicator_id: ind_a_share_breakout_volume_validity indicator_type: 细分 name: A股_突破放量有效性 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_突破放量有效性 window: 60 - asset_scope: - A股 calculation: MA20 / 20日前MA20 - 1 description: 高分表示上行斜率强 dimension: 趋势结构 direction: high_bullish indicator_id: ind_a_share_trend_slope indicator_type: 细分 name: A股_趋势斜率 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_趋势斜率 window: 20 - asset_scope: - A股 calculation: 20日突破15%、60日突破15%、远离60日低点10%、均线多头15%、趋势斜率20%、突破放量10%、高低点抬升15%,再乘连续不过热修正 description: A股趋势结构最终得分 dimension: 趋势结构 direction: high_bullish indicator_id: ind_a_share_trend_structure indicator_type: 综合 name: A股_趋势结构 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_趋势结构 window: 60 A股_运营效率: - asset_scope: - A股 calculation: 营业成本/存货横截面排序 description: 高分表示存货周转更快;零存货不自动获得高分 dimension: 运营效率 direction: high_bullish indicator_id: ind_a_share_inventory_turnover indicator_type: 细分 name: A股存货周转率 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股存货周转率 window: 90 - asset_scope: - A股 calculation: 营业收入/应收账款横截面排序 description: 高分表示回款和赊销周转效率更高;零应收不自动获得高分 dimension: 运营效率 direction: high_bullish indicator_id: ind_a_share_receivable_turnover indicator_type: 细分 name: A股应收账款周转率 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股应收账款周转率 window: 90 - asset_scope: - A股 calculation: 营业收入/资产总计横截面排序 description: 高分表示资产使用效率更高;零分母不参与排序 dimension: 运营效率 direction: high_bullish indicator_id: ind_a_share_total_asset_turnover indicator_type: 细分 name: A股总资产周转率 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股总资产周转率 window: 90 - asset_scope: - A股 calculation: 总资产周转率40%、存货周转率30%、应收账款周转率30%;分项缺失按0贡献,固定权重不转移 description: 高分表示整体经营周转效率更高;数据不完整会保守压低维度分 dimension: 运营效率 direction: high_bullish indicator_id: ind_a_share_operational_efficiency_composite indicator_type: 综合 name: A股运营效率 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股运营效率 window: 90 A股_量能与流动性: - asset_scope: - A股 calculation: 20日平均(abs(日收益)/去除零值(成交额))横截面排序后取反 description: 高分表示近20日平均价格冲击较低;零成交额和缺失日不计算日冲击 dimension: 量能与流动性 direction: high_bullish indicator_id: ind_a_share_amihud_low_impact_liquidity indicator_type: 细分 name: A股_Amihud低冲击流动性 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_Amihud低冲击流动性 window: 20 - asset_scope: - A股 calculation: 近20日平均成交额排序 description: 高分表示基础流动性好 dimension: 量能与流动性 direction: high_bullish indicator_id: ind_a_share_turnover_amount_activity indicator_type: 细分 name: A股_成交额活跃度 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_成交额活跃度 window: 20 - asset_scope: - A股 calculation: 当日成交额 / 去除零值(过去20日至昨日均额) description: 高分表示相对常态放量;零值或缺失分母不参与排序 dimension: 量能与流动性 direction: high_bullish indicator_id: ind_a_share_turnover_amount_ratio indicator_type: 细分 name: A股_成交额量比 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_成交额量比 window: 20 - asset_scope: - A股 calculation: 换手率处于健康区间 description: 高分表示不过冷不过热 dimension: 量能与流动性 direction: high_bullish indicator_id: ind_a_share_turnover_rate_moderation indicator_type: 细分 name: A股_换手率适中度 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_换手率适中度 window: 1 - asset_scope: - A股 calculation: 当日成交额 / 去除零值(过去5日至昨日均额) description: 1代表达到1.2倍以上;零值或缺失分母不输出 dimension: 量能与流动性 direction: high_bullish indicator_id: ind_a_share_short_term_volume_surge_strength indicator_type: 细分 name: A股_短期放量强度 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_短期放量强度 window: 5 - asset_scope: - A股 calculation: 近5日上涨日成交额 / 去除零值(近5日上涨日成交额+近5日下跌日成交额) description: 高分表示方向性成交额更多集中在上涨日;只有上涨日时比例为1,只有下跌日时为0 dimension: 量能与流动性 direction: high_bullish indicator_id: ind_a_share_price_volume_support indicator_type: 细分 name: A股_量价同步承接 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_量价同步承接 window: 5 - asset_scope: - A股 calculation: 成交额量比20%、短期放量5%、换手适中20%、成交额活跃15%、量价承接20%、低冲击20%;分项缺失按0贡献,固定权重不转移 description: A股量能与流动性最终得分 dimension: 量能与流动性 direction: high_bullish indicator_id: ind_a_share_volume_liquidity indicator_type: 综合 name: A股_量能与流动性 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股_量能与流动性 window: 20 A股_龙虎榜资金席位: - asset_scope: - A股 calculation: 上榜原因健康10%、机构专用确认25%、北向专用确认10%、活跃营业部接力10%、知名游资候选确认15%、三日资金确认20%、拉萨系拥挤健康10% description: 高分表示龙虎榜披露事件中资金结构更偏正向、机构/北向/活跃营业部/候选游资确认更强且拉萨系拥挤风险较低 dimension: 龙虎榜资金席位 direction: high_bullish indicator_id: ind_a_share_lhb_capital_seat indicator_type: 综合 name: A股龙虎榜资金席位 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: A股龙虎榜资金席位 window: 3 - asset_scope: - A股 calculation: 三日机构、三日北向、三日知名游资候选、三日活跃营业部净主导比例合成后排序 description: 高分表示三日累计榜单中多类正向资金确认较强 dimension: 龙虎榜资金席位 direction: high_bullish indicator_id: ind_lhb_three_day_capital_confirm indicator_type: 细分 name: 三日资金确认 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: 三日资金确认 window: 3 - asset_scope: - A股 calculation: 涨幅类上榜给正向确认;下跌类或其它异常波动降低健康度;换手/振幅中性偏低权重 description: 高分表示上榜原因更偏正向,低分提示下跌或异常波动风险 dimension: 龙虎榜资金席位 direction: high_bullish indicator_id: ind_lhb_listing_reason_health indicator_type: 细分 name: 上榜原因健康 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: 上榜原因健康 window: 1 - asset_scope: - A股 calculation: 北向专用净主导比例在上榜样本内排序 description: 高分表示北向专用席位在榜上净买影响更强 dimension: 龙虎榜资金席位 direction: high_bullish indicator_id: ind_lhb_northbound_net_buy_confirm indicator_type: 细分 name: 北向专用净买确认 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: 北向专用净买确认 window: 1 - asset_scope: - A股 calculation: 拉萨系闭合组与开放标签净主导比例合成后排序,取反作为健康度 description: 高分表示拉萨系净买主导较弱;低分提示散户化/拥挤风险较高 dimension: 龙虎榜资金席位 direction: high_bullish indicator_id: ind_lhb_lasa_crowding_health indicator_type: 细分 name: 拉萨系拥挤健康 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: 拉萨系拥挤健康 window: 1 - asset_scope: - A股 calculation: 机构专用净主导比例在上榜样本内排序 description: 高分表示机构专用席位净买入对当日成交影响更强 dimension: 龙虎榜资金席位 direction: high_bullish indicator_id: ind_lhb_institutional_net_buy_confirm indicator_type: 细分 name: 机构专用净买确认 output_type: score selection: as_of: 20260826 ready: true vector_type: score source_name: 机构专用净买确认 window: 1 - asset_scope: - A股 calculation: 活跃营业部净主导比例在上榜样本内排序 description: 高分表示活跃营业部接力资金较强 dimension: 龙虎榜资金席位 direction: high_bullish indicator_id: ind_lhb_active_branch_relay indicator_type: 细分 name: 活跃营业部接力 output_type: score selection: as_of: 20260826 ready: true vector_type: score s -
functions.yaml 14.2 KB
# ============================================================ # 全量函数索引(共 170 条) ← 来源:config_support_function.json # 格式:类别 | 函数名 | 用法(可直接复制进公式) # 有参数疑问时调 searchFunctions 获取详细说明 # # ── 目录 ───────────────────────────────────────────────────── # 行情获取 6 条 收盘价 / 开盘价 / 最高价 / 最低价 / 涨跌幅 / 资产 # 技术指标 10 条 MACD / DIFF / DEA / EMA / K指标 / D指标 # 统计计算 32 条 平均 / 标准差 / 最大 / 最小 / 求和 / 相关系数 # 时间位移 16 条 上期 / 前几天 / 前几期 / 前天 / 昨天 / 今天 # 日期范围 4 条 日期范围 / 时间范围 / 选取日期 / 季节性分位数 # 区间识别 18 条 上行区间 / 下行区间 / 区间统计量 / 进出场区间 / 去除区间 / 分段最终值 # 回测 2 条 回测 / 止损测试 # 选股板块 16 条 板块 / 资产池构造 / 板块内权重 / 取出 / 取前 / 取后 # 变频周期 10 条 变频 / 月度变频 / 季度变频 / 年度变频 / 周期采样 / 周期求和汇总 # 财务数据 9 条 取年报 / 取财报单季 / 同比 / 环比 / 环比绝对数 / 年内按期减前值 # 按天操作 10 条 按天求和 / 按天平均 / 按天归一 / 按天截尾 / 按天排序分 / 按天标记分位 # 按期操作 10 条 按期z分数 / 按期减前值 / 按期归一 / 按期正归一 / 按期最大 / 按期最小 # 填充清洗 13 条 缺失填零 / 缺失填充 / 缺失补全 / 零值填充 / 去除零值 / 去除重复值 # 权重配置 3 条 权重填充 / 权重漂移 / 权重修正 # 逻辑条件 11 条 或 / 全部满足 / 位于 / 接近 / 取符号 / 映射 # ============================================================ # ── 行情获取 ─────────────────────────────────────────── 行情获取 | 收盘价 | 收盘价(资产名) 行情获取 | 开盘价 | 开盘价(资产名) 行情获取 | 最高价 | 最高价(资产名) 行情获取 | 最低价 | 最低价(资产名) 行情获取 | 涨跌幅 | 涨跌幅(数据,n) 行情获取 | 资产 | 资产(资产名) # ── 技术指标 ─────────────────────────────────────────── 技术指标 | MACD | macd(数据) 技术指标 | DIFF | diff(数据) 技术指标 | DEA | dea(数据) 技术指标 | EMA | ema(数据,周期) 技术指标 | K指标 | K指标(滚动天数) 技术指标 | D指标 | D指标(滚动天数) 技术指标 | J指标 | J指标(滚动天数) 技术指标 | 局部高点 | 局部高点(数据,窗口天数) 技术指标 | 局部低点 | 局部低点(数据,窗口天数) 技术指标 | 收敛三角形详情 | 收敛三角形详情(股票代码或名称, 模式=0, 目标日期=None, 检测窗口=240, 最小收敛度=0.45, 突破阈值=0.005, 放量倍数=1.5, 图表天数=300, 自定义权重=None, 快速模式=True) # ── 统计计算 ─────────────────────────────────────────── 统计计算 | 平均 | 平均(数据,滚动天数) 统计计算 | 标准差 | 标准差(数据,滚动天数) 统计计算 | 最大 | 最大(数据,滚动天数) 统计计算 | 最小 | 最小(数据,滚动天数) 统计计算 | 求和 | 求和(数据, 滚动天数, 模式?) 统计计算 | 相关系数 | 相关系数(数据1,数据2,滚动天数) 统计计算 | 回归系数 | 回归系数(数据1,数据2,滚动天数) 统计计算 | 累加 | 累加(数据,滚动天数) 统计计算 | 累乘 | 累乘(数据) 统计计算 | 累计最大 | 累计最大(数据) 统计计算 | 从头平均 | 从头平均(数据) 统计计算 | z分数 | z分数(数据,滚动天数) 统计计算 | 分位线 | 分位线(数据,分位数) 统计计算 | 排序水位 | 排序水位(数据,滚动天数) 统计计算 | 数值水位 | 数值水位(数据,滚动天数) 统计计算 | 夏普值 | 夏普值(数据, 滚动天数) 统计计算 | 回撤 | 回撤(数据, 滚动窗口) 统计计算 | 分段回撤 | 分段回撤(数据) 统计计算 | 归一 | 归一(数据, n) 统计计算 | 正归一 | 正归一(数据,滚动天数) 统计计算 | 智能归一 | 智能归一(数据, n) 统计计算 | 绝对值 | 绝对值(数据) 统计计算 | 次方 | 次方(数据,n) 统计计算 | 开方 | 开方(数据,n) 统计计算 | 对数 | 对数(数据) 统计计算 | 对数 | 对数(数据,底数n) 统计计算 | 四舍五入 | 四舍五入(数据,位数) 统计计算 | 裁剪 | 裁剪(数据,最小值,最大值) 统计计算 | 角度 | 角度(数据,n) 统计计算 | 线性外推 | 线性外推(数据) 统计计算 | 比较取大 | 比较取大(数据1,数据2) 统计计算 | 比较取小 | 比较取小(数据1,数据2) # ── 时间位移 ─────────────────────────────────────────── 时间位移 | 上期 | 上期(数据) 时间位移 | 前几天 | 前几天(数据, 天数) 时间位移 | 前几期 | 前几期(数据,期数) 时间位移 | 前天 | 前天(数据) 时间位移 | 昨天 | 昨天(数据) 时间位移 | 今天 | 今天() 时间位移 | 明天 | 明天(数据) 时间位移 | 后天 | 后天(数据) 时间位移 | 一年前 | 一年前() 时间位移 | 延迟 | 延迟(数据,延迟天数n) 时间位移 | 减前值 | 减前值(数据,n) 时间位移 | 相距几天 | 相距几天(数据) 时间位移 | 取某天 | 取某天(数据,日期) 时间位移 | 第一天 | 第一天(数据) 时间位移 | 低点后天数 | 低点后天数(数据, 窗口大小w) 时间位移 | 高点后天数 | 高点后天数(数据, 窗口大小w) # ── 日期范围 ─────────────────────────────────────────── 日期范围 | 日期范围 | 日期范围(日期1,日期2) 日期范围 | 时间范围 | 时间范围(时间1,时间2) 日期范围 | 选取日期 | 选取日期(日期1,日期2) 日期范围 | 季节性分位数 | 季节性分位数(数据,回看过去多少年,是否使用农历) # ── 区间识别 ─────────────────────────────────────────── 区间识别 | 上行区间 | 上行区间(数据, 窗口大小w,阈值k) 区间识别 | 下行区间 | 下行区间(数据, 窗口大小w,阈值k) 区间识别 | 区间统计量 | 区间统计量() 区间识别 | 进出场区间 | 进出场区间(数据A,数据B) 区间识别 | 去除区间 | 去除区间(区间数据, 去除标记) 区间识别 | 分段最终值 | 分段最终值(数据) 区间识别 | 分段累加 | 分段累加(数据) 区间识别 | 按区间拆分 | 按区间拆分() 区间识别 | 信号停止 | 信号停止(原数据, 出场条件) 区间识别 | 信号延长 | 信号延长(数据, n) 区间识别 | 底部反弹 | 底部反弹(数据,窗口n,数据2) 区间识别 | 顶部下跌 | 顶部下跌(数据,窗口n,数据2) 区间识别 | 底领先 | 底领先(数据, 对比数据,窗口天数) 区间识别 | 顶领先 | 顶领先(数据, 对比数据,窗口期数) 区间识别 | 领先 | 领先(数据, 对比数据) 区间识别 | 标记后累加 | 标记后累加(数据1,数据2) 区间识别 | 某天后最小 | 某天后最小(数据A,数据B) 区间识别 | 某天后累加 | 某天后累加(数据A,数据B, 窗口n天) # ── 回测 ─────────────────────────────────────────── 回测 | 回测 | 回测(信号数据,当天开盘买入/当天收盘买入/第二天开盘买入,返回复利净值/返回收益累加/返回每日收益/返回复利超额净值/返回超额收益累加/返回超额每日收益,信号按列归一/信号不归一,基准数据) 回测 | 止损测试 | 止损测试(快速回测结果, 止损阈值, 入场点) # ── 选股板块 ─────────────────────────────────────────── 选股板块 | 板块 | 板块(板块名) 或 板块(板块名1,板块名2,...) 选股板块 | 资产池构造 | 资产池构造(资产名1,资产名2) 选股板块 | 板块内权重 | 板块内权重(数据,指数数据) 选股板块 | 取出 | 取出(资产名) 选股板块 | 取前 | 取前(数据, n, 排序顺序, 是否保留零值) 选股板块 | 取后 | 取后(数据,n) 选股板块 | 倒序取前 | 倒序取前(数据,n) 选股板块 | 作为资产筛选 | 作为资产筛选(数据) 选股板块 | 商品指标选股 | 商品指标选股(选商品0/1矩阵,商品指标矩阵, 测试个股持有天数k, 商品指标方法) 选股板块 | 随机去除 | 随机去除(数据,去除比例) 选股板块 | 按成分取前 | 按成分取前(数据,n,指数数据) 选股板块 | 按成分取分位 | 按成分取分位(数据,n,指数数据) 选股板块 | 按成分排序分 | 按成分排序分(数据, 指数数据) 选股板块 | 按市场排序分 | 按市场排序分(数据) 选股板块 | 板块构造 | 板块构造(资产名1,资产名2) 选股板块 | 分级靠档 | 分级靠档(数据) # ── 变频周期 ─────────────────────────────────────────── 变频周期 | 变频 | 变频(原数据,日期条件) 变频周期 | 月度变频 | 月度变频(原数据,变频条件) 变频周期 | 季度变频 | 季度变频(原数据,变频方式) 变频周期 | 年度变频 | 年度变频(原数据,变频方式) 变频周期 | 周期采样 | 周期采样(数据1,数据2) 变频周期 | 周期求和汇总 | 周期求和汇总(数据,基准日期) 变频周期 | 月度累计 | 月度累计(数据) 变频周期 | 年度累计 | 年度累计(数据) 变频周期 | 年度累计回溯 | 年度累计回溯(数据) 变频周期 | 按年份拆分 | 按年份拆分(数据) # ── 财务数据 ─────────────────────────────────────────── 财务数据 | 取年报 | 取年报(数据) 财务数据 | 取财报单季 | 取财报单季(数据) 财务数据 | 同比 | 同比(数据) 财务数据 | 环比 | 环比(数据, 期数) 财务数据 | 环比绝对数 | 环比绝对数(数据) 财务数据 | 年内按期减前值 | 年内按期减前值(数据,n) 财务数据 | 按财报期累加 | 按财报期累加(数据) 财务数据 | 按财报期缺失填零 | 按财报期缺失填零(数据) 财务数据 | 报告期转发布日 | 报告期转发布日(数据, <保留最新|保留最早>) # ── 按天操作 ─────────────────────────────────────────── 按天操作 | 按天求和 | 按天求和(数据) 按天操作 | 按天平均 | 按天平均(数据) 按天操作 | 按天归一 | 按天归一(数据) 按天操作 | 按天截尾 | 按天截尾(数据, 下分位数, 上分位数) 按天操作 | 按天排序分 | 按天排序分(数据) 按天操作 | 按天标记分位 | 按天标记分位(数据,分位,宽度) 按天操作 | 按天取分位 | 按天取分位(数据,分位) 按天操作 | 成分平均汇总 | 成分平均汇总(数据,指数数据) 按天操作 | 成分求和汇总 | 成分求和汇总(数据,指数数据) 按天操作 | 按模分组 | 按模分组(数据,除数,余数) # ── 按期操作 ─────────────────────────────────────────── 按期操作 | 按期z分数 | 按期z分数(数据,滚动期数) 按期操作 | 按期减前值 | 按期减前值(数据,滚动期数) 按期操作 | 按期归一 | 按期归一(数据,滚动期数) 按期操作 | 按期正归一 | 按期正归一(数据,滚动期数) 按期操作 | 按期最大 | 按期最大(数据,滚动期数) 按期操作 | 按期最小 | 按期最小(数据,滚动期数) 按期操作 | 按期标准差 | 按期标准差(数据,滚动期数) 按期操作 | 按期累乘 | 按期累乘(数据) 按期操作 | 按期累加 | 按期累加(数据,滚动期数) 按期操作 | 按期平均 | 按期平均(数据,滚动期数) # ── 填充清洗 ─────────────────────────────────────────── 填充清洗 | 缺失填零 | 缺失填零(数据, 掩码) 填充清洗 | 缺失填充 | 缺失填充(数据,填充方式) 填充清洗 | 缺失补全 | 缺失补全(数据) 填充清洗 | 零值填充 | 零值填充(数据, 天数) 填充清洗 | 去除零值 | 去除零值(数据) 填充清洗 | 去除重复值 | 去除重复值(数据) 填充清洗 | 去重 | 去重(数据) 填充清洗 | 首日填充 | 首日填充(数据) 填充清洗 | 首日调零 | 首日调零(数据) 填充清洗 | 设定基准 | 设定基准(数据, 基准值) 填充清洗 | 非空首行 | 非空首行(数据1,数据2) 填充清洗 | 有值 | 有值(数据) 填充清洗 | 数据信息 | 数据信息(数据) # ── 权重配置 ─────────────────────────────────────────── 权重配置 | 权重填充 | 权重填充(数据) 权重配置 | 权重漂移 | 权重漂移(权重数据) 权重配置 | 权重修正 | 返回一年前的日期 # ── 逻辑条件 ─────────────────────────────────────────── 逻辑条件 | 或 | 或(数据A,数据B) 逻辑条件 | 全部满足 | 全部满足(数据,滚动天数) 逻辑条件 | 位于 | 位于(数据,a,b) 逻辑条件 | 接近 | 接近(数据, 数据2, 差异比例) 逻辑条件 | 取符号 | 取符号(数据) 逻辑条件 | 映射 | 映射(数据,a,b,c,d) 逻辑条件 | 末次 | 末次(0-1数据, n) 逻辑条件 | 首次 | 首次(0-1数据, n) 逻辑条件 | 首次变化 | 首次变化(数据) 逻辑条件 | 位或 | 位或(数据A,数据B) 逻辑条件 | 位与 | 位与(数据A,数据B) -
sectors.yaml 41.2 KB
# ============================================================ # 板块与行业预设 # 来源:板块_all_742.xlsx(2026-08-07 人工导出) # 数据:742 条记录,738 个唯一名称,10 个分类 # 关键:公式中必须使用 name 字段,如 板块(万得全A)、板块(银行(申万)) # # 说明: # - 条目按 Excel 原始顺序保留;type 为本文件的稳定分类标识。 # - 源表存在同名记录(可能属于不同分类或不同代码),此处按原记录保留。 # - 若此文件没有目标项,停止并向用户澄清,不再调用远程资产确认兜底。 # # 分类目录: # - sectors: 板块 / 市场分类,25 条(源类型:板块(ban_kuai)) # - gics_primary_industries: GICS 一级行业(美股),25 条(源类型:GICS一级行业(gics_l1)) # - gics_sectors: GICS 板块(美股),11 条(源类型:GICS板块(gics_sector)) # - hang_seng_primary_industries: 恒生一级行业(港股),12 条(源类型:恒生一级行业(hsi_l1)) # - hang_seng_secondary_industries: 恒生二级行业(港股),31 条(源类型:恒生二级行业(hsi_l2)) # - concepts: A股概念分类(A股),399 条(源类型:A股概念分类(jqc)) # - kaipanla_themes: 开盘啦题材(A股),10 条(源类型:开盘啦题材(kplc)) # - market_connect: 沪港通 / 深港通,5 条(源类型:沪港通(market_tong)) # - primary_industries: 申万 / 万得一级行业(A股),42 条(源类型:申万一级行业(shenwan1)) # - secondary_industries: 申万二级行业(A股),182 条(源类型:申万二级行业(swl2)) # ============================================================ # ── 板块 / 市场分类(25 条)──────────────────── sectors: - name: "全部资产" type: sector - name: "万得全A" type: sector - name: "A股" type: sector - name: "创业板" type: sector - name: "商品" type: sector - name: "期货" type: sector - name: "港股" type: sector - name: "沪深主板" type: sector - name: "指数" type: sector - name: "科创板" type: sector - name: "沪市主板" type: sector - name: "深市主板" type: sector - name: "美股" type: sector - name: "万得指数" type: sector - name: "上证50" type: sector - name: "中证1000指数" type: sector - name: "中证100" type: sector - name: "中证500" type: sector - name: "中证800" type: sector - name: "中证A500" type: sector - name: "中证红利" type: sector - name: "创业板50" type: sector - name: "创业板指" type: sector - name: "沪深300" type: sector - name: "科创50" type: sector # ── GICS 一级行业(美股)(25 条)──────────────────── gics_primary_industries: - name: "GICS能源I" type: gics_primary_industry - name: "GICS材料I" type: gics_primary_industry - name: "GICS资本货物I" type: gics_primary_industry - name: "GICS商业与专业服务I" type: gics_primary_industry - name: "GICS运输I" type: gics_primary_industry - name: "GICS汽车与零部件I" type: gics_primary_industry - name: "GICS耐用消费品与服装I" type: gics_primary_industry - name: "GICS消费者服务I" type: gics_primary_industry - name: "GICS零售I" type: gics_primary_industry - name: "GICS食品与日用品零售I" type: gics_primary_industry - name: "GICS食品饮料与烟草I" type: gics_primary_industry - name: "GICS家庭与个人用品I" type: gics_primary_industry - name: "GICS医疗保健设备与服务I" type: gics_primary_industry - name: "GICS制药生物科技与生命科学I" type: gics_primary_industry - name: "GICS银行I" type: gics_primary_industry - name: "GICS多元金融I" type: gics_primary_industry - name: "GICS保险I" type: gics_primary_industry - name: "GICS软件与服务I" type: gics_primary_industry - name: "GICS技术硬件与设备I" type: gics_primary_industry - name: "GICS半导体与半导体设备I" type: gics_primary_industry - name: "GICS电信服务I" type: gics_primary_industry - name: "GICS媒体与娱乐I" type: gics_primary_industry - name: "GICS公用事业I" type: gics_primary_industry - name: "GICS权益型REITsI" type: gics_primary_industry - name: "GICS房地产管理与开发I" type: gics_primary_industry # ── GICS 板块(美股)(11 条)──────────────────── gics_sectors: - name: "GICS能源板块" type: gics_sector - name: "GICS原材料板块" type: gics_sector - name: "GICS工业板块" type: gics_sector - name: "GICS可选消费板块" type: gics_sector - name: "GICS必需消费板块" type: gics_sector - name: "GICS医疗保健板块" type: gics_sector - name: "GICS金融板块" type: gics_sector - name: "GICS信息技术板块" type: gics_sector - name: "GICS通信服务板块" type: gics_sector - name: "GICS公用事业板块" type: gics_sector - name: "GICS房地产板块" type: gics_sector # ── 恒生一级行业(港股)(12 条)──────────────────── hang_seng_primary_industries: - name: "恒生能源业I" type: hang_seng_primary_industry - name: "恒生原材料业I" type: hang_seng_primary_industry - name: "恒生工业制品业I" type: hang_seng_primary_industry - name: "恒生非必需性消费I" type: hang_seng_primary_industry - name: "恒生必需性消费I" type: hang_seng_primary_industry - name: "恒生医疗保健业I" type: hang_seng_primary_industry - name: "恒生电讯业I" type: hang_seng_primary_industry - name: "恒生公用事业I" type: hang_seng_primary_industry - name: "恒生金融业I" type: hang_seng_primary_industry - name: "恒生地产建筑业I" type: hang_seng_primary_industry - name: "恒生资讯科技业I" type: hang_seng_primary_industry - name: "恒生综合企业I" type: hang_seng_primary_industry # ── 恒生二级行业(港股)(31 条)──────────────────── hang_seng_secondary_industries: - name: "恒生石油及天然气II" type: hang_seng_secondary_industry - name: "恒生煤炭II" type: hang_seng_secondary_industry - name: "恒生黄金及贵金属II" type: hang_seng_secondary_industry - name: "恒生一般金属及矿石II" type: hang_seng_secondary_industry - name: "恒生原材料II" type: hang_seng_secondary_industry - name: "恒生工业工程II" type: hang_seng_secondary_industry - name: "恒生工用运输II" type: hang_seng_secondary_industry - name: "恒生工用支援II" type: hang_seng_secondary_industry - name: "恒生汽车II" type: hang_seng_secondary_industry - name: "恒生家庭电器及用品II" type: hang_seng_secondary_industry - name: "恒生纺织及服饰II" type: hang_seng_secondary_industry - name: "恒生旅游及消闲设施II" type: hang_seng_secondary_industry - name: "恒生媒体及娱乐II" type: hang_seng_secondary_industry - name: "恒生支援服务II" type: hang_seng_secondary_industry - name: "恒生专业零售II" type: hang_seng_secondary_industry - name: "恒生食物饮品II" type: hang_seng_secondary_industry - name: "恒生农业产品II" type: hang_seng_secondary_industry - name: "恒生消费者主要零售商II" type: hang_seng_secondary_industry - name: "恒生药品及生物科技II" type: hang_seng_secondary_industry - name: "恒生医疗保健设备和服务II" type: hang_seng_secondary_industry - name: "恒生电讯II" type: hang_seng_secondary_industry - name: "恒生公用事业II" type: hang_seng_secondary_industry - name: "恒生银行II" type: hang_seng_secondary_industry - name: "恒生保险II" type: hang_seng_secondary_industry - name: "恒生其他金融II" type: hang_seng_secondary_industry - name: "恒生地产II" type: hang_seng_secondary_industry - name: "恒生建筑II" type: hang_seng_secondary_industry - name: "恒生资讯科技器材II" type: hang_seng_secondary_industry - name: "恒生软件服务II" type: hang_seng_secondary_industry - name: "恒生半导体II" type: hang_seng_secondary_industry - name: "恒生综合企业II" type: hang_seng_secondary_industry # ── A股概念分类(A股)(399 条)──────────────────── concepts: - name: "石墨烯" type: concept - name: "阿里" type: concept - name: "今日头条" type: concept - name: "安防" type: concept - name: "互联网金融" type: concept - name: "网络游戏" type: concept - name: "物联网" type: concept - name: "在线教育" type: concept - name: "工业互联网" type: concept - name: "电力物联网" type: concept - name: "数据中心" type: concept - name: "超级电容" type: concept - name: "节能照明" type: concept - name: "汽车电子" type: concept - name: "区块链" type: concept - name: "人工智能" type: concept - name: "无人驾驶" type: concept - name: "虚拟现实" type: concept - name: "移动支付" type: concept - name: "无线充电" type: concept - name: "微信小程序" type: concept - name: "无人零售" type: concept - name: "人脸识别" type: concept - name: "独角兽" type: concept - name: "边缘计算" type: concept - name: "数字货币" type: concept - name: "网约车" type: concept - name: "跨境电商" type: concept - name: "冷链物流" type: concept - name: "共享单车" type: concept - name: "新零售" type: concept - name: "免税店" type: concept - name: "社区团购" type: concept - name: "充电桩" type: concept - name: "特斯拉" type: concept - name: "3D打印" type: concept - name: "5G" type: concept - name: "OLED" type: concept - name: "大飞机" type: concept - name: "大数据" type: concept - name: "国产软件" type: concept - name: "基因测序" type: concept - name: "燃料电池" type: concept - name: "网络安全" type: concept - name: "无人机" type: concept - name: "云计算" type: concept - name: "国产操作系统" type: concept - name: "胎压监测" type: concept - name: "MiniLED" type: concept - name: "智慧城市" type: concept - name: "智能穿戴" type: concept - name: "智能电网" type: concept - name: "智能家居" type: concept - name: "智能医疗" type: concept - name: "智能音箱" type: concept - name: "智能交通" type: concept - name: "语音技术" type: concept - name: "数字孪生" type: concept - name: "ETC" type: concept - name: "无线耳机" type: concept - name: "参股券商" type: concept - name: "禽流感" type: concept - name: "生物疫苗" type: concept - name: "超级真菌" type: concept - name: "食品安全" type: concept - name: "杭州亚运会" type: concept - name: "PM2.5" type: concept - name: "风电" type: concept - name: "固废处理" type: concept - name: "节能环保" type: concept - name: "污水处理" type: concept - name: "垃圾分类" type: concept - name: "碳中和" type: concept - name: "装配式建筑" type: concept - name: "创投" type: concept - name: "一带一路" type: concept - name: "知识产权保护" type: concept - name: "上海国资改革" type: concept - name: "深圳国资改革" type: concept - name: "工业4.0" type: concept - name: "海工装备" type: concept - name: "军工" type: concept - name: "通用航空" type: concept - name: "军民融合" type: concept - name: "沪股通" type: concept - name: "深股通" type: concept - name: "地下管网" type: concept - name: "高铁" type: concept - name: "特高压" type: concept - name: "家用电器" type: concept - name: "职业教育" type: concept - name: "供应链金融" type: concept - name: "新疆振兴" type: concept - name: "民营医院" type: concept - name: "核电" type: concept - name: "可燃冰" type: concept - name: "燃料乙醇" type: concept - name: "京津冀一体化" type: concept - name: "雄安新区" type: concept - name: "特色小镇" type: concept - name: "长三角一体化" type: concept - name: "农村电商" type: concept - name: "农机" type: concept - name: "生态农业" type: concept - name: "土地流转" type: concept - name: "乡村振兴" type: concept - name: "电子竞技" type: concept - name: "体育产业" type: concept - name: "新材料" type: concept - name: "健康中国" type: concept - name: "生物医药" type: concept - name: "医药电商" type: concept - name: "青蒿素" type: concept - name: "眼科医疗" type: concept - name: "口罩" type: concept - name: "辅助生殖" type: concept - name: "天然气" type: concept - name: "页岩气" type: concept - name: "稀缺资源" type: concept - name: "稀土永磁" type: concept - name: "福建自贸区" type: concept - name: "上海自贸区" type: concept - name: "天津自贸区" type: concept - name: "自由贸易港" type: concept - name: "债转股" type: concept - name: "养鸡" type: concept - name: "猪肉" type: concept - name: "工业大麻" type: concept - name: "人造肉" type: concept - name: "烟草" type: concept - name: "农业种植" type: concept - name: "转基因" type: concept - name: "电子烟" type: concept - name: "乳业" type: concept - name: "钴" type: concept - name: "盐湖提锂" type: concept - name: "草甘膦" type: concept - name: "石墨电极" type: concept - name: "磷化工" type: concept - name: "可降解塑料" type: concept - name: "柔性屏" type: concept - name: "第三代半导体" type: concept - name: "证金持股" type: concept - name: "高送转" type: concept - name: "举牌" type: concept - name: "壳资源" type: concept - name: "融资融券" type: concept - name: "新股与次新股" type: concept - name: "参股新三板" type: concept - name: "股权转让" type: concept - name: "摘帽" type: concept - name: "超级品牌" type: concept - name: "创业板重组松绑" type: concept - name: "油品升级" type: concept - name: "中韩自贸区" type: concept - name: "O2O概念" type: concept - name: "物流电商平台" type: concept - name: "东盟自贸区" type: concept - name: "移动互联网" type: concept - name: "电力改革" type: concept - name: "量子通信" type: concept - name: "互联网+" type: concept - name: "新型城镇化" type: concept - name: "两桶油改革" type: concept - name: "P2P概念" type: concept - name: "粤港澳自贸区" type: concept - name: "振兴东北" type: concept - name: "太阳能" type: concept - name: "分散染料" type: concept - name: "建筑节能" type: concept - name: "美丽中国" type: concept - name: "参股民营银行" type: concept - name: "电子信息" type: concept - name: "电子发票" type: concept - name: "西安自贸区" type: concept - name: "宽带中国" type: concept - name: "生物质能" type: concept - name: "高校" type: concept - name: "LED" type: concept - name: "蓝宝石" type: concept - name: "新能源" type: concept - name: "高端装备" type: concept - name: "尾气治理" type: concept - name: "转融券标的" type: concept - name: "能源互联网" type: concept - name: "迪士尼" type: concept - name: "金融IC" type: concept - name: "钠离子电池" type: concept - name: "专精特新" type: concept - name: "国家大基金持股" type: concept - name: "华为海思概念" type: concept - name: "元宇宙" type: concept - name: "环氧丙烷" type: concept - name: "工业母机" type: concept - name: "送转填权" type: concept - name: "赛马概念" type: concept - name: "储能" type: concept - name: "培育钻石" type: concept - name: "绿色电力" type: concept - name: "换电概念" type: concept - name: "传感器" type: concept - name: "预制菜" type: concept - name: "新冠检测" type: concept - name: "虚拟数字人" type: concept - name: "民爆概念" type: concept - name: "土壤修复" type: concept - name: "PVDF概念" type: concept - name: "智慧灯杆" type: concept - name: "在线旅游" type: concept - name: "抽水蓄能" type: concept - name: "动力电池回收" type: concept - name: "数据安全" type: concept - name: "华为汽车" type: concept - name: "幽门螺杆菌概念" type: concept - name: "云游戏" type: concept - name: "动物疫苗" type: concept - name: "牙科医疗" type: concept - name: "智慧政务" type: concept - name: "数字乡村" type: concept - name: "汽车芯片" type: concept - name: "电子纸" type: concept - name: "互联网医疗" type: concept - name: "海南自贸区" type: concept - name: "仿制药一致性评价" type: concept - name: "横琴新区" type: concept - name: "碳纤维" type: concept - name: "EDR概念" type: concept - name: "物业管理" type: concept - name: "净水概念" type: concept - name: "托育服务" type: concept - name: "电子身份证" type: concept - name: "化肥" type: concept - name: "金属回收" type: concept - name: "俄乌冲突概念" type: concept - name: "华为鲲鹏" type: concept - name: "家庭医生" type: concept - name: "数字经济" type: concept - name: "恒大概念" type: concept - name: "玉米" type: concept - name: "碳交易" type: concept - name: "中船系" type: concept - name: "统一大市场" type: concept - name: "肝炎概念" type: concept - name: "航空发动机" type: concept - name: "光刻胶" type: concept - name: "大豆" type: concept - name: "方舱医院" type: concept - name: "超超临界发电" type: concept - name: "比亚迪概念" type: concept - name: "5G概念" type: concept - name: "汽车热管理" type: concept - name: "PCB概念" type: concept - name: "租售同权" type: concept - name: "华为欧拉" type: concept - name: "黑龙江自贸区" type: concept - name: "两轮车" type: concept - name: "粮食概念" type: concept - name: "代糖概念" type: concept - name: "机器视觉" type: concept - name: "钒电池" type: concept - name: "虚拟电厂" type: concept - name: "钙钛矿电池" type: concept - name: "减速器" type: concept - name: "光热发电" type: concept - name: "宠物经济" type: concept - name: "氢能源" type: concept - name: "信创" type: concept - name: "创新药" type: concept - name: "世界杯" type: concept - name: "供销社" type: concept - name: "三季报预增" type: concept - name: "Web3.0" type: concept - name: "广东自贸区" type: concept - name: "高压氧舱" type: concept - name: "互联网保险" type: concept - name: "MCU芯片" type: concept - name: "国企改革" type: concept - name: "抗病毒面料" type: concept - name: "熊去氧胆酸" type: concept - name: "POE胶膜" type: concept - name: "血氧仪" type: concept - name: "成飞概念" type: concept - name: "固态电池" type: concept - name: "数字水印" type: concept - name: "毫米波雷达" type: concept - name: "时空大数据" type: concept - name: "超导概念" type: concept - name: "ERP概念" type: concept - name: "流感" type: concept - name: "MLOps概念" type: concept - name: "注册制次新股" type: concept - name: "数据要素" type: concept - name: "太赫兹" type: concept - name: "英伟达概念" type: concept - name: "空间计算" type: concept - name: "算力租赁" type: concept - name: "数据确权" type: concept - name: "减肥药" type: concept - name: "核污染防治" type: concept - name: "BC电池" type: concept - name: "光刻机" type: concept - name: "细胞免疫治疗" type: concept - name: "新型工业化" type: concept - name: "阿尔茨海默概念" type: concept - name: "华为昇腾" type: concept - name: "智能座舱" type: concept - name: "PEEK材料" type: concept - name: "小米汽车" type: concept - name: "可控核聚变" type: concept - name: "飞行汽车(eVTOL)" type: concept - name: "人形机器人" type: concept - name: "AI手机" type: concept - name: "低空经济" type: concept - name: "铜缆高速连接" type: concept - name: "AI语料" type: concept - name: "军工信息化" type: concept - name: "合成生物" type: concept - name: "商业航天" type: concept - name: "北交所概念" type: concept - name: "Sora概念(文生视频)" type: concept - name: "氟化工概念" type: concept - name: "煤化工概念" type: concept - name: "钛白粉概念" type: concept - name: "足球概念" type: concept - name: "蚂蚁金服概念" type: concept - name: "机器人概念" type: concept - name: "拼多多概念" type: concept - name: "芯片概念" type: concept - name: "百度概念" type: concept - name: "宁德时代概念" type: concept - name: "独角兽概念" type: concept - name: "抖音概念" type: concept - name: "光伏概念" type: concept - name: "富士康概念" type: concept - name: "黄金概念" type: concept - name: "医疗器械概念" type: concept - name: "小米概念" type: concept - name: "文化传媒概念" type: concept - name: "小金属概念" type: concept - name: "MSCI概念" type: concept - name: "苹果概念" type: concept - name: "参股保险" type: concept - name: "水泥概念" type: concept - name: "特钢概念" type: concept - name: "啤酒概念" type: concept - name: "白酒概念" type: concept - name: "腾讯概念" type: concept - name: "养老概念" type: concept - name: "旅游概念" type: concept - name: "华为概念" type: concept - name: "航运概念" type: concept - name: "期货概念" type: concept - name: "信托概念" type: concept - name: "车联网(车路协同)" type: concept - name: "锂电池概念" type: concept - name: "中芯国际概念" type: concept - name: "三胎概念" type: concept - name: "煤炭概念" type: concept - name: "快手概念" type: concept - name: "集成电路概念" type: concept - name: "维生素" type: concept - name: "24中报预增" type: concept - name: "AI眼镜" type: concept - name: "西部大开发" type: concept - name: "房屋检测" type: concept - name: "24三季报预增" type: concept - name: "回购增持再贷款" type: concept - name: "智谱AI" type: concept - name: "华为盘古" type: concept - name: "化债(AMC)" type: concept - name: "F5G" type: concept - name: "国资云" type: concept - name: "AIGC" type: concept - name: "6G" type: concept - name: "猴痘" type: concept - name: "CRO" type: concept - name: "超导" type: concept - name: "中俄贸易" type: concept - name: "MicroLED" type: concept - name: "DRG/DIP" type: concept - name: "触摸屏" type: concept - name: "有机硅" type: concept - name: "ChatGPT" type: concept - name: "汽车拆解" type: concept - name: "芬太尼" type: concept - name: "年报预增" type: concept - name: "小红书" type: concept - name: "AI智能体" type: concept - name: "DeepSeek" type: concept - name: "华为手机" type: concept - name: "25一季报预增" type: concept - name: "智能制造" type: concept - name: "25中报预增" type: concept - name: "AI应用" type: concept - name: "26一季报预增" type: concept - name: "光纤" type: concept - name: "玻璃基板" type: concept - name: "26中报预增" type: concept - name: "MLCC" type: concept # ── 开盘啦题材(A股)(10 条)──────────────────── kaipanla_themes: - name: "AI硬件" type: kaipanla_theme - name: "AI低代码" type: kaipanla_theme - name: "AI营销" type: kaipanla_theme - name: "AI医疗" type: kaipanla_theme - name: "AIPC" type: kaipanla_theme - name: "AI安全" type: kaipanla_theme - name: "阿里AI" type: kaipanla_theme - name: "端侧AI" type: kaipanla_theme - name: "物理AI" type: kaipanla_theme - name: "腾讯AI" type: kaipanla_theme # ── 沪港通 / 深港通(5 条)──────────────────── market_connect: - name: "港股通" type: market_connect - name: "港股通(沪)" type: market_connect - name: "港股通(深)" type: market_connect - name: "沪股通" type: market_connect - name: "深股通" type: market_connect # ── 申万 / 万得一级行业(A股)(42 条)──────────────────── primary_industries: - name: "农林牧渔(申万)" type: primary_industry - name: "基础化工(申万)" type: primary_industry - name: "钢铁(申万)" type: primary_industry - name: "有色金属(申万)" type: primary_industry - name: "电子(申万)" type: primary_industry - name: "家用电器(申万)" type: primary_industry - name: "食品饮料(申万)" type: primary_industry - name: "纺织服饰(申万)" type: primary_industry - name: "轻工制造(申万)" type: primary_industry - name: "医药生物(申万)" type: primary_industry - name: "公用事业(申万)" type: primary_industry - name: "交通运输(申万)" type: primary_industry - name: "房地产(申万)" type: primary_industry - name: "商贸零售(申万)" type: primary_industry - name: "社会服务(申万)" type: primary_industry - name: "综合(申万)" type: primary_industry - name: "建筑材料(申万)" type: primary_industry - name: "建筑装饰(申万)" type: primary_industry - name: "电力设备(申万)" type: primary_industry - name: "机械设备(申万)" type: primary_industry - name: "国防军工(申万)" type: primary_industry - name: "汽车(申万)" type: primary_industry - name: "计算机(申万)" type: primary_industry - name: "传媒(申万)" type: primary_industry - name: "通信(申万)" type: primary_industry - name: "银行(申万)" type: primary_industry - name: "非银金融(申万)" type: primary_industry - name: "煤炭(申万)" type: primary_industry - name: "石油石化(申万)" type: primary_industry - name: "环保(申万)" type: primary_industry - name: "美容护理(申万)" type: primary_industry - name: "能源(万得)" type: primary_industry - name: "材料(万得)" type: primary_industry - name: "工业(万得)" type: primary_industry - name: "可选消费(万得)" type: primary_industry - name: "日常消费(万得)" type: primary_industry - name: "医疗保健(万得)" type: primary_industry - name: "金融(万得)" type: primary_industry - name: "信息技术(万得)" type: primary_industry - name: "电信服务(万得)" type: primary_industry - name: "公用事业(万得)" type: primary_industry - name: "房地产(万得)" type: primary_industry # ── 申万二级行业(A股)(182 条)──────────────────── secondary_industries: - name: "林业Ⅱ(申万)" type: secondary_industry - name: "农产品加工(申万)" type: secondary_industry - name: "农业综合II" type: secondary_industry - name: "饲料(申万)" type: secondary_industry - name: "渔业(申万)" type: secondary_industry - name: "种植业(申万)" type: secondary_industry - name: "养殖业(申万)" type: secondary_industry - name: "动物保健Ⅱ(申万)" type: secondary_industry - name: "农业综合II" type: secondary_industry - name: "煤炭开采II" type: secondary_industry - name: "其他采掘II" type: secondary_industry - name: "石油开采II" type: secondary_industry - name: "采掘服务II" type: secondary_industry - name: "化工新材料II" type: secondary_industry - name: "化学纤维(申万)" type: secondary_industry - name: "化学原料(申万)" type: secondary_industry - name: "化学制品(申万)" type: secondary_industry - name: "石油化工II" type: secondary_industry - name: "塑料(申万)" type: secondary_industry - name: "橡胶(申万)" type: secondary_industry - name: "农化制品(申万)" type: secondary_industry - name: "非金属材料Ⅱ(申万)" type: secondary_industry - name: "钢铁II" type: secondary_industry - name: "金属制品II" type: secondary_industry - name: "冶钢原料(申万)" type: secondary_industry - name: "普钢(申万)" type: secondary_industry - name: "特钢Ⅱ(申万)" type: secondary_industry - name: "金属新材料(申万)" type: secondary_industry - name: "有色金属冶炼II" type: secondary_industry - name: "贵金属(申万)" type: secondary_industry - name: "小金属(申万)" type: secondary_industry - name: "工业金属(申万)" type: secondary_industry - name: "能源金属(申万)" type: secondary_industry - name: "建筑材料II" type: secondary_industry - name: "建筑装饰II" type: secondary_industry - name: "电气设备II" type: secondary_industry - name: "通用设备(申万)" type: secondary_industry - name: "仪器仪表II" type: secondary_industry - name: "专用设备(申万)" type: secondary_industry - name: "金属制品II" type: secondary_industry - name: "轨交设备Ⅱ(申万)" type: secondary_industry - name: "工程机械(申万)" type: secondary_industry - name: "自动化设备(申万)" type: secondary_industry - name: "半导体(申万)" type: secondary_industry - name: "其他电子Ⅱ(申万)" type: secondary_industry - name: "元件(申万)" type: secondary_industry - name: "光学光电子(申万)" type: secondary_industry - name: "消费电子(申万)" type: secondary_industry - name: "电子化学品Ⅱ(申万)" type: secondary_industry - name: "非汽车交运设备II" type: secondary_industry - name: "汽车服务(申万)" type: secondary_industry - name: "汽车零部件(申万)" type: secondary_industry - name: "汽车整车II" type: secondary_industry - name: "乘用车(申万)" type: secondary_industry - name: "商用车(申万)" type: secondary_industry - name: "计算机设备(申万)" type: secondary_industry - name: "通信设备(申万)" type: secondary_industry - name: "IT服务Ⅱ(申万)" type: secondary_industry - name: "软件开发(申万)" type: secondary_industry - name: "白色家电(申万)" type: secondary_industry - name: "黑色家电(申万)" type: secondary_industry - name: "小家电(申万)" type: secondary_industry - name: "厨卫电器(申万)" type: secondary_industry - name: "照明设备Ⅱ(申万)" type: secondary_industry - name: "家电零部件Ⅱ(申万)" type: secondary_industry - name: "其他家电II" type: secondary_industry - name: "食品加工II" type: secondary_industry - name: "食品制造II" type: secondary_industry - name: "饮料制造II" type: secondary_industry - name: "食品加工(申万)" type: secondary_industry - name: "白酒Ⅱ(申万)" type: secondary_industry - name: "非白酒(申万)" type: secondary_industry - name: "饮料乳品(申万)" type: secondary_industry - name: "休闲食品(申万)" type: secondary_industry - name: "调味发酵品Ⅱ(申万)" type: secondary_industry - name: "纺织制造(申万)" type: secondary_industry - name: "服装家纺(申万)" type: secondary_industry - name: "饰品(申万)" type: secondary_industry - name: "包装印刷(申万)" type: secondary_industry - name: "家居用品(申万)" type: secondary_industry - name: "造纸(申万)" type: secondary_industry - name: "其他轻工制造II" type: secondary_industry - name: "文娱用品(申万)" type: secondary_industry - name: "化学制药(申万)" type: secondary_industry - name: "生物制品(申万)" type: secondary_industry - name: "医疗器械(申万)" type: secondary_industry - name: "医药商业(申万)" type: secondary_industry - name: "中药Ⅱ(申万)" type: secondary_industry - name: "医疗服务(申万)" type: secondary_industry - name: "电力(申万)" type: secondary_industry - name: "环保工程及服务II" type: secondary_industry - name: "燃气Ⅱ(申万)" type: secondary_industry - name: "水务II" type: secondary_industry - name: "港口II" type: secondary_industry - name: "公交II" type: secondary_industry - name: "航空运输II" type: secondary_industry - name: "机场II" type: secondary_industry - name: "高速公路II" type: secondary_industry - name: "航运II" type: secondary_industry - name: "铁路运输II" type: secondary_industry - name: "物流(申万)" type: secondary_industry - name: "铁路公路(申万)" type: secondary_industry - name: "房地产开发(申万)" type: secondary_industry - name: "园区开发II" type: secondary_industry - name: "房地产服务(申万)" type: secondary_industry - name: "多元金融(申万)" type: secondary_industry - name: "银行II" type: secondary_industry - name: "证券Ⅱ(申万)" type: secondary_industry - name: "保险Ⅱ(申万)" type: secondary_industry - name: "零售II" type: secondary_industry - name: "贸易Ⅱ(申万)" type: secondary_industry - name: "一般零售(申万)" type: secondary_industry - name: "专业连锁Ⅱ(申万)" type: secondary_industry - name: "商业物业经营II" type: secondary_industry - name: "互联网电商(申万)" type: secondary_industry - name: "旅游零售II" type: secondary_industry - name: "餐饮II" type: secondary_industry - name: "景点II" type: secondary_industry - name: "酒店II" type: secondary_industry - name: "旅游综合II" type: secondary_industry - name: "其他休闲服务II" type: secondary_industry - name: "体育II" type: secondary_industry - name: "本地生活服务II" type: secondary_industry - name: "专业服务(申万)" type: secondary_industry - name: "酒店餐饮(申万)" type: secondary_industry - name: "传媒II" type: secondary_industry - name: "计算机应用II" type: secondary_industry - name: "通信服务(申万)" type: secondary_industry - name: "网络服务II" type: secondary_industry - name: "综合Ⅱ(申万)" type: secondary_industry - name: "水泥(申万)" type: secondary_industry - name: "玻璃玻纤(申万)" type: secondary_industry - name: "装修建材(申万)" type: secondary_industry - name: "房屋建设Ⅱ(申万)" type: secondary_industry - name: "装修装饰Ⅱ(申万)" type: secondary_industry - name: "基础建设(申万)" type: secondary_industry - name: "专业工程(申万)" type: secondary_industry - name: "园林工程II" type: secondary_industry - name: "工程咨询服务Ⅱ(申万)" type: secondary_industry - name: "电机Ⅱ(申万)" type: secondary_industry - name: "电气自动化设备II" type: secondary_industry - name: "其他电源设备Ⅱ(申万)" type: secondary_industry - name: "高低压设备II" type: secondary_industry - name: "光伏设备(申万)" type: secondary_industry - name: "风电设备(申万)" type: secondary_industry - name: "电池(申万)" type: secondary_industry - name: "电网设备(申万)" type: secondary_industry - name: "航天装备Ⅱ(申万)" type: secondary_industry - name: "航空装备Ⅱ(申万)" type: secondary_industry - name: "地面兵装Ⅱ(申万)" type: secondary_industry - name: "航海装备Ⅱ(申万)" type: secondary_industry - name: "军工电子Ⅱ(申万)" type: secondary_industry - name: "营销传播II" type: secondary_industry - name: "互联网传媒II" type: secondary_industry - name: "文化传媒II" type: secondary_industry - name: "游戏Ⅱ(申万)" type: secondary_industry - name: "广告营销(申万)" type: secondary_industry - name: "影视院线(申万)" type: secondary_industry - name: "数字媒体(申万)" type: secondary_industry - name: "社交II" type: secondary_industry - name: "出版(申万)" type: secondary_industry - name: "国有大型银行Ⅱ(申万)" type: secondary_industry - name: "股份制银行Ⅱ(申万)" type: secondary_industry - name: "城商行Ⅱ(申万)" type: secondary_industry - name: "农商行Ⅱ(申万)" type: secondary_industry - name: "其他银行II" type: secondary_industry - name: "摩托车及其他(申万)" type: secondary_industry - name: "煤炭开采(申万)" type: secondary_industry - name: "焦炭Ⅱ(申万)" type: secondary_industry - name: "油气开采II" type: secondary_industry - name: "油服工程(申万)" type: secondary_industry - name: "炼化及贸易(申万)" type: secondary_industry - name: "环境治理(申万)" type: secondary_industry - name: "环保设备Ⅱ(申万)" type: secondary_industry - name: "个护用品(申万)" type: secondary_industry - name: "化妆品(申万)" type: secondary_industry - name: "医疗美容II" type: secondary_industry - name: "航空机场(申万)" type: secondary_industry - name: "航运港口(申万)" type: secondary_industry - name: "旅游及景区(申万)" type: secondary_industry - name: "教育(申万)" type: secondary_industry - name: "电视广播Ⅱ(申万)" type: secondary_industry # ============================================================ # 注意事项 # 1. 上述 name 均来自平台板块列表,公式中请精确使用 name。 # 2. 扩充时请使用最新平台导出的完整板块清单;未命中时停止并澄清。 # ============================================================ -
themes.yaml 16.6 KB
# ============================================================ # 概念/主题预设 【共 779 行,按字母序排列,查名称时需一次性读完】 # 来源:平台 theme_list / 概念板块列表 # 关键:公式中必须用 name 字段,如 板块(机器人概念)、板块(人工智能) # # ── 字母范围速查(估算,实际以文件内容为准)────────────────── # L11 A-B(3D打印 / 5G / 阿里 / 比亚迪概念…) # ~L120 C-F(ChatGPT / 储能 / 低空经济 / 芯片…) # ~L280 G-J(钙钛矿 / 国资改革 / 华为概念…) # ~L430 K-N(锂电池 / 绿色电力 / 美容护理…) # ~L560 P-S(氢能源 / 人工智能 / 数字经济…) # ~L680 T-Z(碳中和 / 新能源汽车 / 医疗器械 / 中特估…) # # 用法:查看此文件 → 找到目标概念名称 → 放入公式 # 若此文件没有目标项 → 停止并向用户澄清,不再调用远程资产确认兜底 # ============================================================ # ── 概念板块(主题投资)────────────────────────────────────── concepts: - name: 3D打印 type: concept - name: 5G type: concept - name: 6G type: concept - name: 阿尔茨海默概念 type: concept - name: 阿里 type: concept - name: 安防 type: concept - name: 白酒概念 type: concept - name: 百度概念 type: concept - name: 北交所概念 type: concept - name: 比亚迪概念 type: concept - name: 边缘计算 type: concept - name: 参股保险 type: concept - name: 参股民营银行 type: concept - name: 参股券商 type: concept - name: 参股新三板 type: concept - name: 草甘膦 type: concept - name: 超超临界发电 type: concept - name: 超导 type: concept - name: 超级电容 type: concept - name: 超级品牌 type: concept - name: 超级真菌 type: concept - name: 车联网(车路协同) type: concept - name: 成飞概念 type: concept - name: 充电桩 type: concept - name: 宠物经济 type: concept - name: 抽水蓄能 type: concept - name: 储能 type: concept - name: 触摸屏 type: concept - name: 传感器 type: concept - name: 创投 type: concept - name: 创新药 type: concept - name: 创业板重组松绑 type: concept - name: 大豆 type: concept - name: 大飞机 type: concept - name: 大数据 type: concept - name: 代糖概念 type: concept - name: 地下管网 type: concept - name: 低空经济 type: concept - name: 迪士尼 type: concept - name: 第三代半导体 type: concept - name: 电力改革 type: concept - name: 电力物联网 type: concept - name: 电子发票 type: concept - name: 电子竞技 type: concept - name: 电子身份证 type: concept - name: 电子信息 type: concept - name: 电子烟 type: concept - name: 电子纸 type: concept - name: 东盟自贸区 type: concept - name: 动力电池回收 type: concept - name: 动物疫苗 type: concept - name: 抖音概念 type: concept - name: 独角兽 type: concept - name: 俄乌冲突概念 type: concept - name: 钒电池 type: concept - name: 房屋检测 type: concept - name: 仿制药一致性评价 type: concept - name: 飞行汽车(eVTOL) type: concept - name: 分散染料 type: concept - name: 芬太尼 type: concept - name: 风电 type: concept - name: 氟化工概念 type: concept - name: 福建自贸区 type: concept - name: 辅助生殖 type: concept - name: 富士康概念 type: concept - name: 钙钛矿电池 type: concept - name: 肝炎概念 type: concept - name: 高端装备 type: concept - name: 高送转 type: concept - name: 高铁 type: concept - name: 高校 type: concept - name: 高压氧舱 type: concept - name: 工业4.0 type: concept - name: 工业大麻 type: concept - name: 工业互联网 type: concept - name: 工业母机 type: concept - name: 供销社 type: concept - name: 供应链金融 type: concept - name: 共享单车 type: concept - name: 股权转让 type: concept - name: 钴 type: concept - name: 固废处理 type: concept - name: 固态电池 type: concept - name: 光伏概念 type: concept - name: 光刻机 type: concept - name: 光刻胶 type: concept - name: 光热发电 type: concept - name: 广东自贸区 type: concept - name: 国产操作系统 type: concept - name: 国产软件 type: concept - name: 国家大基金持股 type: concept - name: 国企改革 type: concept - name: 国资云 type: concept - name: 海工装备 type: concept - name: 海南自贸区 type: concept - name: 航空发动机 type: concept - name: 航运概念 type: concept - name: 毫米波雷达 type: concept - name: 合成生物 type: concept - name: 核电 type: concept - name: 核污染防治 type: concept - name: 黑龙江自贸区 type: concept - name: 恒大概念 type: concept - name: 横琴新区 type: concept - name: 猴痘 type: concept - name: 互联网+ type: concept - name: 互联网保险 type: concept - name: 互联网金融 type: concept - name: 互联网医疗 type: concept - name: 沪股通 type: concept - name: 华为概念 type: concept - name: 华为海思概念 type: concept - name: 华为鲲鹏 type: concept - name: 华为欧拉 type: concept - name: 华为盘古 type: concept - name: 华为汽车 type: concept - name: 华为昇腾 type: concept - name: 华为手机 type: concept - name: 化肥 type: concept - name: 化债(AMC) type: concept - name: 环氧丙烷 type: concept - name: 换电概念 type: concept - name: 黄金概念 type: concept - name: 回购增持再贷款 type: concept - name: 机器人概念 type: concept - name: 机器视觉 type: concept - name: 基因测序 type: concept - name: 集成电路概念 type: concept - name: 家庭医生 type: concept - name: 家用电器 type: concept - name: 减肥药 type: concept - name: 减速器 type: concept - name: 建筑节能 type: concept - name: 健康中国 type: concept - name: 节能环保 type: concept - name: 节能照明 type: concept - name: 今日头条 type: concept - name: 金融IC type: concept - name: 金属回收 type: concept - name: 京津冀一体化 type: concept - name: 净水概念 type: concept - name: 举牌 type: concept - name: 军工 type: concept - name: 军工信息化 type: concept - name: 军民融合 type: concept - name: 抗病毒面料 type: concept - name: 壳资源 type: concept - name: 可降解塑料 type: concept - name: 可控核聚变 type: concept - name: 可燃冰 type: concept - name: 空间计算 type: concept - name: 口罩 type: concept - name: 跨境电商 type: concept - name: 快手概念 type: concept - name: 宽带中国 type: concept - name: 垃圾分类 type: concept - name: 蓝宝石 type: concept - name: 冷链物流 type: concept - name: 锂电池概念 type: concept - name: 粮食概念 type: concept - name: 两轮车 type: concept - name: 两桶油改革 type: concept - name: 量子通信 type: concept - name: 磷化工 type: concept - name: 流感 type: concept - name: 旅游概念 type: concept - name: 绿色电力 type: concept - name: 蚂蚁金服概念 type: concept - name: 煤化工概念 type: concept - name: 煤炭概念 type: concept - name: 美丽中国 type: concept - name: 免税店 type: concept - name: 民爆概念 type: concept - name: 民营医院 type: concept - name: 钠离子电池 type: concept - name: 能源互联网 type: concept - name: 宁德时代概念 type: concept - name: 农村电商 type: concept - name: 农机 type: concept - name: 农业种植 type: concept - name: 培育钻石 type: concept - name: 啤酒概念 type: concept - name: 拼多多概念 type: concept - name: 苹果概念 type: concept - name: 期货概念 type: concept - name: 汽车拆解 type: concept - name: 汽车电子 type: concept - name: 汽车热管理 type: concept - name: 汽车芯片 type: concept - name: 禽流感 type: concept - name: 青蒿素 type: concept - name: 氢能源 type: concept - name: 区块链 type: concept - name: 燃料电池 type: concept - name: 燃料乙醇 type: concept - name: 人工智能 type: concept - name: 人脸识别 type: concept - name: 人形机器人 type: concept - name: 人造肉 type: concept - name: 融资融券 type: concept - name: 柔性屏 type: concept - name: 乳业 type: concept - name: 赛马概念 type: concept - name: 三胎概念 type: concept - name: 商业航天 type: concept - name: 上海国资改革 type: concept - name: 上海自贸区 type: concept - name: 社区团购 type: concept - name: 深股通 type: concept - name: 深圳国资改革 type: concept - name: 生态农业 type: concept - name: 生物医药 type: concept - name: 生物疫苗 type: concept - name: 生物质能 type: concept - name: 石墨电极 type: concept - name: 石墨烯 type: concept - name: 时空大数据 type: concept - name: 食品安全 type: concept - name: 数据安全 type: concept - name: 数据确权 type: concept - name: 数据要素 type: concept - name: 数据中心 type: concept - name: 数字货币 type: concept - name: 数字经济 type: concept - name: 数字孪生 type: concept - name: 数字水印 type: concept - name: 数字乡村 type: concept - name: 水泥概念 type: concept - name: 送转填权 type: concept - name: 算力租赁 type: concept - name: 胎压监测 type: concept - name: 太赫兹 type: concept - name: 太阳能 type: concept - name: 钛白粉概念 type: concept - name: 碳交易 type: concept - name: 碳纤维 type: concept - name: 碳中和 type: concept - name: 特钢概念 type: concept - name: 特高压 type: concept - name: 特色小镇 type: concept - name: 特斯拉 type: concept - name: 腾讯概念 type: concept - name: 体育产业 type: concept - name: 天津自贸区 type: concept - name: 天然气 type: concept - name: 通用航空 type: concept - name: 铜缆高速连接 type: concept - name: 统一大市场 type: concept - name: 土地流转 type: concept - name: 土壤修复 type: concept - name: 托育服务 type: concept - name: 网络安全 type: concept - name: 网络游戏 type: concept - name: 网约车 type: concept - name: 微信小程序 type: concept - name: 维生素 type: concept - name: 尾气治理 type: concept - name: 文化传媒概念 type: concept - name: 污水处理 type: concept - name: 无人机 type: concept - name: 无人驾驶 type: concept - name: 无人零售 type: concept - name: 无线充电 type: concept - name: 无线耳机 type: concept - name: 物联网 type: concept - name: 物流电商平台 type: concept - name: 物业管理 type: concept - name: 西安自贸区 type: concept - name: 西部大开发 type: concept - name: 稀缺资源 type: concept - name: 稀土永磁 type: concept - name: 细胞免疫治疗 type: concept - name: 乡村振兴 type: concept - name: 小红书 type: concept - name: 小金属概念 type: concept - name: 小米概念 type: concept - name: 小米汽车 type: concept - name: 芯片概念 type: concept - name: 新材料 type: concept - name: 新股与次新股 type: concept - name: 新冠检测 type: concept - name: 新疆振兴 type: concept - name: 新零售 type: concept - name: 新能源 type: concept - name: 新型城镇化 type: concept - name: 新型工业化 type: concept - name: 信创 type: concept - name: 信托概念 type: concept - name: 雄安新区 type: concept - name: 熊去氧胆酸 type: concept - name: 虚拟电厂 type: concept - name: 虚拟数字人 type: concept - name: 虚拟现实 type: concept - name: 血氧仪 type: concept - name: 牙科医疗 type: concept - name: 烟草 type: concept - name: 盐湖提锂 type: concept - name: 眼科医疗 type: concept - name: 养鸡 type: concept - name: 养老概念 type: concept - name: 页岩气 type: concept - name: 一带一路 type: concept - name: 医疗器械概念 type: concept - name: 医药电商 type: concept - name: 移动互联网 type: concept - name: 移动支付 type: concept - name: 英伟达概念 type: concept - name: 幽门螺杆菌概念 type: concept - name: 油品升级 type: concept - name: 有机硅 type: concept - name: 语音技术 type: concept - name: 玉米 type: concept - name: 预制菜 type: concept - name: 元宇宙 type: concept - name: 粤港澳自贸区 type: concept - name: 云计算 type: concept - name: 云游戏 type: concept - name: 在线教育 type: concept - name: 在线旅游 type: concept - name: 摘帽 type: concept - name: 债转股 type: concept - name: 长三角一体化 type: concept - name: 振兴东北 type: concept - name: 证金持股 type: concept - name: 知识产权保护 type: concept - name: 职业教育 type: concept - name: 智慧城市 type: concept - name: 智慧灯杆 type: concept - name: 智慧政务 type: concept - name: 智能穿戴 type: concept - name: 智能电网 type: concept - name: 智能家居 type: concept - name: 智能交通 type: concept - name: 智能医疗 type: concept - name: 智能音箱 type: concept - name: 智能制造 type: concept - name: 智能座舱 type: concept - name: 智谱AI type: concept - name: 中船系 type: concept - name: 中俄贸易 type: concept - name: 中韩自贸区 type: concept - name: 中芯国际概念 type: concept - name: 猪肉 type: concept - name: 注册制次新股 type: concept - name: 专精特新 type: concept - name: 转基因 type: concept - name: 转融券标的 type: concept - name: 装配式建筑 type: concept - name: 自由贸易港 type: concept - name: 租售同权 type: concept - name: 足球概念 type: concept - name: AI手机 type: concept - name: AI眼镜 type: concept - name: AI语料 type: concept - name: AI智能体 type: concept - name: AIGC type: concept - name: BC电池 type: concept - name: ChatGPT type: concept - name: CRO type: concept - name: DeepSeek type: concept - name: DRG/DIP type: concept - name: EDR概念 type: concept - name: ERP概念 type: concept - name: ETC type: concept - name: F5G type: concept - name: LED type: concept - name: MCU芯片 type: concept - name: MicroLED type: concept - name: MiniLED type: concept - name: MLOps概念 type: concept - name: MSCI概念 type: concept - name: O2O概念 type: concept - name: OLED type: concept - name: P2P概念 type: concept - name: PCB概念 type: concept - name: PEEK材料 type: concept - name: PM2.5 type: concept - name: POE胶膜 type: concept - name: PVDF概念 type: concept - name: Sora概念(文生视频) type: concept - name: Web3.0 type: concept # ============================================================ # 注意事项 # 1. 上述 name 均来自平台概念板块列表,公式中请用 name # 2. 扩充时请从 resource: theme_list 获取,未命中时停止并澄清 # ============================================================
-
-
recipes
-
download-data.md 2.8 KB
# Recipe:下载 / 导出数据到本地 CSV ## 触发词 > "下载成 CSV"、"导出到本地"、"保存到本地"、"下载历史数据" --- ## ⛔ Skill 内执行主路径(在当前 skill 运行时必须走这条) 1. 首次问题先 `newSession`;同一 Session 的追问先 `beginTurn`(每条用户消息必登记,不得跳过)。 2. 若是**单资产 + 单字段 + 时序序列**(如"贵州茅台最近 3 年每日收盘价"): - 优先用 `runMultiFormulaBatchStream` 写单股序列公式(如 `茅台收盘价 = 收盘价("贵州茅台")`),从返回中拿到 `data_id`; - 不要先走 `confirmDataMulti("全市场每日收盘价")` 再 `取出(...)` 这种长路径,路径越长越易跑偏。 3. 调用原生 `downloadData '{"id":"<data_id>","begin_date":<YYYYMMDD>,"end_date":<YYYYMMDD>,"format":"csv"}'`。 4. `downloadData` 会自动落盘并返回 `saved_to` 路径(及 `total_rows`、`begin_date`、`end_date`)。把该路径告诉用户,任务即完成。 5. **⛔ 绝对不要**在 `downloadData` 之后调用任何写文件工具(`write_skill_file`、`Bash`、`python3 -c`、`shell` 等均不存在或不允许)。`downloadData` 已完成本地保存,无需任何后续步骤。 ## ⛔ 禁止事项(直接对应 T-035 反例) - 调用任何不存在的工具(`write_skill_file` 不是真实工具,调用必然失败,禁止重试)。 - 拿到 CSV 字符串后跳过 `downloadData`,转去 `Bash` + `python3 -c` 写文件。 - 反复 `Bash` 重试 `&&` / `;` / `python3 -c` / `ls` / `dir` —— 全部违反 Bash 白名单。 --- ## 外部调试示例(仅供仓库外部 CLI 调试,LLM 在 skill 运行时不要走这里) ```bash python scripts/call.py downloadData '{"data_id":"<data_id>","begin_date":<YYYYMMDD>,"end_date":<YYYYMMDD>}' ``` `call.py` 调用 `downloadData` 时会**自动**将 CSV 保存到 `output/<data_name>.csv`,终端输出摘要(total_rows、begin_date、saved_to),不刷屏。**此路径仅适用于命令行调试**,skill 运行时的 LLM 必须走上节「主路径」的原生工具调用。 --- ## 使用限制 | 条件 | 说明 | |------|------| | **可下载** | 持久化一维时序:上传数据 (`provider=mydata`) 或平台数据 (`provider=guanzhao`) | | **不可下载** | `runMultiFormulaBatchStream` 的计算结果 (`provider=dunhe`),普通用户无 `access_dunhe` 权限 → 返回 403 | | **替代方案** | 计算结果用 `readData(mode="range_data", start_date=..., end_date=...)` 读取完整区间数据,再自行保存为 CSV | --- ## 必须确认时间范围 数据通常从 2015 年起,直接下载或 `range_data` 读取可能返回几千行。**调用前先问用户**: > "您需要下载哪段时间的数据?(默认:最近一年)" - 用户给出范围 → 传 `begin_date` / `end_date` - 用户说"所有历史" → 不传日期参数 -
event-study-formulas.md 7 KB
# 事件研究公式模板 保留四条真正稳定的模板。 ## 模板 A: 单组事件固定持有期 适用于绝大多数事件研究问题。 ```text {prefix}_事件日=选取日期({date1},{date2},...) {prefix}_收盘=收盘价({asset_name}) {prefix}_日收益=涨跌幅("{prefix}_收盘") {prefix}_后{N1}日路径=某天后累加("{prefix}_日收益","{prefix}_事件日",{N1}) {prefix}_后{N2}日路径=某天后累加("{prefix}_日收益","{prefix}_事件日",{N2}) {prefix}_后{N1}日收益=分段最终值("{prefix}_后{N1}日路径") {prefix}_后{N2}日收益=分段最终值("{prefix}_后{N2}日路径") ``` ### 窗口映射 | 说法 | N | |------|---| | 1周 | 5 | | 2周 | 10 | | 1月 | 21 | | 3月 | 63 | | 半年 | 126 | | 1年 | 252 | ### 使用建议 1. 同时给逐次结果和均值 2. 窗口不宜超过相邻事件最小间距 3. 样本很少时不要强行做胜率和分位数 ## 模板 B: 两组事件对比(compare) 适用于超预期 vs 不及预期、利好 vs 利空这类问题。 ```text {prefix}_A组=选取日期({group_a_dates...}) {prefix}_B组=选取日期({group_b_dates...}) {prefix}_收盘=收盘价({asset_name}) {prefix}_日收益=涨跌幅("{prefix}_收盘") {prefix}_A后{N}路径=某天后累加("{prefix}_日收益","{prefix}_A组",{N}) {prefix}_A后{N}收益=分段最终值("{prefix}_A后{N}路径") {prefix}_B后{N}路径=某天后累加("{prefix}_日收益","{prefix}_B组",{N}) {prefix}_B后{N}收益=分段最终值("{prefix}_B后{N}路径") ``` ### 使用建议 1. 两组共享同一条价格和日收益序列 2. 输出时先列每次事件,再列各组均值 3. 两组样本数不一致时,要在解释里显式写出来 ## 模板 C: 事件后偏移区间回报(排除事件日) 适用于"事件后第 N 天到第 M 天"的区间涨幅,排除事件当天。 与模板 A 的区别:模板 A 从 T+1 起算固定窗口;本模板通过收盘价比值法精确计算任意偏移区间。 ```text {prefix}_收盘=收盘价({asset_name}) {prefix}_收盘_T加{N}=取某天("{prefix}_收盘",交易日位移({event_date},{N})) {prefix}_收盘_T加{M}=取某天("{prefix}_收盘",交易日位移({event_date},{M})) {prefix}_T{N+1}到T{M}涨幅="{prefix}_收盘_T加{M}"/"{prefix}_收盘_T加{N}"-1 ``` ### 参数映射 | 占位符 | 含义 | 示例值 | |--------|------|--------| | `{prefix}` | 变量前缀 | `fomc` | | `{event_date}` | 事件日期(YYYYMMDD 整数) | `20241218` | | `{asset_name}` | 标的资产名 | `沪深300` | | `{N}` | 起始边界交易日数(用户起始天数 - 1) | `1` | | `{M}` | 终止边界交易日数(用户结束天数) | `20` | ### 数学原理 `取某天` 将指定日期的收盘价填充为常数序列,两个常数序列相除天然对齐: $$\text{T+N+1 到 T+M 涨幅} = \frac{P_{T+M}}{P_{T+N}} - 1$$ 其中 $P_{T+N}$ 和 $P_{T+M}$ 均由 `交易日位移` 按 A 股交易日历精确计数(非自然日)。 例如用户说"第 2 天到第 20 天" → N=1, M=20 → 计算 $P_{T+20} / P_{T+1} - 1$(持有 T+1 收盘到 T+20 收盘)。 ### N 值推导 | 用户表述 | N | M | 实际区间 | |----------|---|---|----------| | "第 2 天到第 20 天" | 1 | 20 | T+1 收盘 → T+20 收盘 | | "第 3 天到第 60 天" | 2 | 60 | T+2 收盘 → T+60 收盘 | | "排除前 2 天,看第 3 天起" | 2 | M | T+2 收盘 → T+M 收盘 | 核心规则:**N = 用户起始天数 - 1,M = 用户结束天数** ### 使用建议 1. 当用户说"排除事件当天"或"T+2 到 T+20"时使用本模板 2. 多事件场景需为每个事件单独建立一套前缀变量(`取某天` 只能绑定一个日期) 3. 同一会话中不同事件使用不同 `{prefix}`,避免变量覆盖 4. 结果为精确价格比值,无累加近似误差 ## 模板 D: 搜索新闻打标分类(财报超预期类 compare 专用) 适用于:需要先搜索新闻判断财报是否超预期,再分组计算事件后收益的场景。 与模板 B 的区别:模板 B 默认两组日期已知;模板 D 需要先把正式年报/半年报披露日逐期打标,再将结果代入 compare 公式链。 ### 执行步骤(配合 `event-study.md` 的财报业绩超预期分类细则使用) Step D1:获取正式财报披露日 ```text REV_2D="A股营业总收入〔报告期利润表〕" PUB_DATE=报告期转发布日("REV_2D") ``` 从 `PUB_DATE` 中提取目标股票的正式年报(Q4)和半年报(Q2)披露日。若需要补证,再用交易所公告页或东财年报季报页交叉验证。 Step D2:逐期搜索并打标 对每个披露日执行搜索,关键词格式: ```text {股票简称} {报告类型} {yyyymmdd} 超预期 OR 不及预期 ``` 打标规则、证据门槛和 `label_confidence` 约束以 `event-study.md` 中的财报业绩超预期分类细则为准。 Step D3:将打标结果代入 compare 公式链 ```text {prefix}_超预期组=选取日期({beat_dates...}) {prefix}_不及预期组=选取日期({miss_dates...}) {prefix}_收盘=收盘价({asset_name}) {prefix}_日收益=涨跌幅("{prefix}_收盘") {prefix}_超预期后{N1}路径=某天后累加("{prefix}_日收益","{prefix}_超预期组",{N1}) {prefix}_超预期后{N1}收益=分段最终值("{prefix}_超预期后{N1}路径") {prefix}_超预期后{N2}路径=某天后累加("{prefix}_日收益","{prefix}_超预期组",{N2}) {prefix}_超预期后{N2}收益=分段最终值("{prefix}_超预期后{N2}路径") {prefix}_不及预期后{N1}路径=某天后累加("{prefix}_日收益","{prefix}_不及预期组",{N1}) {prefix}_不及预期后{N1}收益=分段最终值("{prefix}_不及预期后{N1}路径") {prefix}_不及预期后{N2}路径=某天后累加("{prefix}_日收益","{prefix}_不及预期组",{N2}) {prefix}_不及预期后{N2}收益=分段最终值("{prefix}_不及预期后{N2}路径") ``` ### 参数说明 | 占位符 | 说明 | 示例 | |--------|------|------| | `{prefix}` | 变量前缀 | `MT` | | `{beat_dates...}` | 超预期组日期列表 | `20210330,20220331,20240403` | | `{miss_dates...}` | 不及预期组日期列表 | `20220803` | | `{asset_name}` | 资产名称 | `贵州茅台` | | `{N1}` | 短窗口交易日数 | `5` | | `{N2}` | 长窗口交易日数 | `20` | ### 使用建议 1. 日期分组必须来自 Step D2 的搜索打标结果,不得凭记忆填入 2. 若用户要比较"符合预期"而不是"不及预期",只替换第二组标签和日期列表,不改整条公式链 3. 两组共享同一条价格序列和日收益序列,减少公式冗余 4. 样本数 < 5 时,均值行必须标注"有限样本,仅供参考" 5. 若某组样本为 0(全部证据不足),保留 compare 框架,并在输出中明确说明该组无满足证据标准的样本,不得静默降级为 single 模式 ## 可选扩展: 超额收益 如果用户明确要求相对基准表现,可额外生成: ```text {prefix}_基准收盘=收盘价(沪深300) {prefix}_基准日收益=涨跌幅("{prefix}_基准收盘") {prefix}_超额日收益="{prefix}_日收益"-"{prefix}_基准日收益" ``` 然后用 `{prefix}_超额日收益` 替代 `{prefix}_日收益` 进入同样的窗口链路。 -
formula-package.md 5.7 KB
# Recipe — 公式任务包:注册一组公式 → 本地 HTML 直连取数渲染 > 场景:把一组公式(一个"公式组")固化成长期可用的**只读取数接口**,前端页面凭 > `package_id` + `signature` 直接渲染,**无需 API Key**、数据自动保鲜。 > 工具参数手册见 `tools/formula_package.md`;接口原文见 > `docs/formulaPackage 相关文档/对外接口文档.md`。 ## 何时用 - 用户要"做一个能直接打开的页面/看板,用接口实时取数" - 要把某张卡片的公式组(`getCardFormulas`)或自己设计的公式组对外只读发布 - 取数方不该拿到 api_key(浏览器本地 HTML、第三方接入) ## 端到端步骤 ### 1. 设计公式组并选定对外产出 公式语法与 `runMultiFormulaBatchStream` 完全一致。每个对外产出对应一条公式的左值, 并为其选一个 `read_mode`(见 `tools/formula_package.md`): - 单资产/单序列最新值 → `last_day_stats`(1维序列返回 `last_value{date,value}`) - 单序列一段时间走势(画 sparkline / 折线)→ `range_data`(返回 `range_data{dates,values}`) - 全市场截面 TopN / 覆盖率 → `last_day_stats`(2维返回 `top_values` 等) - 全市场某个截止日的完整截面列 → `last_column_full`(2维返回 `values[]`,适合 table/list) - 每资产最后有效值 → `last_valid_per_asset` `last_day_stats` / `last_column_full` 可选传 `mode_params.date` 或 `offset`:`date` 取不晚于指定日的最近有效数据;`offset` 是相对查询当天的自然日偏移。两者互斥,不传时继续使用原来的最新数据模式。 > 约束:单包公式 ≤ 100、对外产出 ≤ 20;**每个 output 只能一个 read_mode**。 ### 2. 注册(需 api_key,落盘凭证) 把公式组写进 `params.json`(Windows 用 `@file` 传中文): ```json { "formulas": [ "SC_ret = 涨跌幅(收盘价(沪原油主连)) * 100", "AG_ret = 涨跌幅(收盘价(沪银主连)) * 100", "SC_px = 收盘价(沪原油主连)" ], "reads": [ { "output": "SC_ret", "read_mode": "last_day_stats", "mode_params": { "offset": 1 } }, { "output": "AG_ret", "read_mode": "last_day_stats", "mode_params": { "date": "2026-08-31" } }, { "output": "SC_px", "read_mode": "range_data", "mode_params": { "lookback_days": 30 } } ], "begin_date": 20260101, "ttl_days": 365 } ``` ```bash python scripts/formula_package.py register @params.json # → { code:0, package_id:"pkg_xxx", signature:"...64hex...", outputs:[...], expires_at, _saved_credential } ``` 凭证自动保存到 `output/formula_packages/<package_id>.json`(`signature` 服务端不可再取,勿丢)。 ### 3. 取数(无需 api_key) ```bash # signature 可由本地凭证自动补全,传 package_id 即可 FP_PARAMS='{"package_id":"pkg_xxx"}' python scripts/formula_package.py query # → { code:0, outputs:{ SC_ret:{data:{last_value:{date,value}}}, SC_px:{data:{range_data:{dates,values}}}, ... }, done:{stale,...} } ``` ### 4. 前端直连(本地 HTML 直接打开即可渲染) 取数端点 **无需 Authorization、已支持跨域**。用 `fetch` 读 SSE 流(**不要用 `EventSource`**, 它只支持 GET 会把凭证暴露到 URL)。最小内核: ```js const ENDPOINT = 'http://test.guanzhao12.com:3010'; const PACKAGE_ID = 'pkg_xxx'; const SIGNATURE = '...64hex...'; // 这是只读取数凭证,嵌进前端是预期用法 async function queryPackage() { const resp = await fetch(`${ENDPOINT}/skill/queryFormulaPackage`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ package_id: PACKAGE_ID, signature: SIGNATURE }), }); const reader = resp.body.getReader(); const decoder = new TextDecoder(); const outputs = {}; let buf = ''; for (;;) { const { value, done } = await reader.read(); if (done) break; buf += decoder.decode(value, { stream: true }); const blocks = buf.split('\n\n'); buf = blocks.pop(); for (const block of blocks) { const ev = (block.match(/event:\s*(.*)/) || [])[1]; const m = block.match(/data:\s*([\s\S]*)/); if (!ev || !m) continue; const dt = JSON.parse(m[1]); if (ev === 'result') outputs[dt.output] = dt; // {output, read_mode, data_id, data} else if (ev === 'error') throw new Error(`${dt.code}: ${dt.message}`); // 'progress' / 'done' 可按需处理 } } return outputs; } // outputs.SC_ret.data.last_value.value → 原油当日涨跌幅 // outputs.SC_px.data.range_data.{dates,values} → 原油价格走势(非交易日为 null,画图前过滤) ``` ### 5. 管理 ```bash python scripts/formula_package.py list '{"page":1,"page_size":20}' # 我的包 python scripts/formula_package.py refresh '{"package_id":"pkg_xxx","rotate_signature":true}' # 强制重算/轮换签名 python scripts/formula_package.py revoke '{"package_id":"pkg_xxx"}' # 立即失效 ``` ## 一个可参考的页面型用法 把「一个看板需要的所有数据」收敛成一个公式包,前端就只剩"取数 + 画图": - 选 N 个品种/资产,每个用 `<名>_ret = 涨跌幅(收盘价(资产)) * 100` 产出当日涨跌幅(`last_day_stats` → `last_value`),用于横截面/排名/异动板; - 头部几个再加 `<名>_px = 收盘价(资产)` 产出近一段价格(`range_data` → `dates/values`),用于画 sparkline / 折线; - 合计产出 ≤ 20、公式 ≤ 100,注册一次拿到 `package_id`+`signature`; - 单文件 HTML 用上面 §4 的 `fetch` 内核取数,把 `outputs` 映射到你自己的 DOM/SVG 即可本地直接打开渲染,数据自动保鲜。 > 产出变量名(如 `AG_ret`)只是 key,中文名 / 板块 / 单位等展示元数据由前端自己维护一份清单来映射。 -
ic-factor-templates.md 3.1 KB
# IC 因子公式模板 > 来源:`workflows/quant-standard.md` Step 2 手动因子构建 > 通常由 `scanDimensions` 脚本自动完成,仅在需要**自定义指标**或脚本不可用时手动使用 --- ## 基础数据(所有因子前置) ```python B_Close = 收盘价(X) # X = 目标资产名(来自 presets/assets_db 的 name) B_Ind = 收盘价(Ind) # Ind = 行业指数名 B_Mkt = 收盘价(Mkt) # Mkt = 大盘指数(如 万得全A 或 沪深300) ``` --- ## D1 估值 ```python B_PE = "A股市盈率(PE, TTM)〔估值数据〕" * 取出(X的代码) B_PB = "A股市净率(PB)〔估值数据〕" * 取出(X的代码) B_PE_Rank = 数值水位("B_PE", 250) B_PB_Rank = 数值水位("B_PB", 250) ``` > PE 必须使用 TTM 口径(`A股市盈率(PE, TTM)〔估值数据〕`) --- ## D3 资金 ```python B_AllAmt = 按天求和("全市场每日成交额") B_Amt = "全市场每日成交额" * 取出(X的代码) B_AmtRatio = "B_Amt" / "B_AllAmt" B_AmtRank = 数值水位("B_AmtRatio", 250) B_Short = "A股融券空头持仓比例" * 取出(X的代码) B_ShortRank = 数值水位("B_Short", 250) B_Fund = "A股持股市值占基金股票投资市值比" * 取出(X的代码) B_FundRank = 数值水位("B_Fund", 250) ``` --- ## D4 波动率 / 风险 ```python B_PrevClose = 前几天("B_Close", 1) B_High = 最高价(X) B_Low = 最低价(X) B_TR = 比较取大("B_High"-"B_Low", 比较取大(绝对值("B_High"-"B_PrevClose"), 绝对值("B_Low"-"B_PrevClose"))) B_ATR = 平均("B_TR", 14) ``` --- ## D5 宏观 / 大盘环境 ```python B_Mkt_MA60 = 平均("B_Mkt", 60) B_Mkt_Above = ("B_Mkt" > "B_Mkt_MA60") ``` --- ## D7 技术形态 ```python # 均线 B_MA10 = 平均("B_Close", 10) B_MA20 = 平均("B_Close", 20) B_MA60 = 平均("B_Close", 60) # MACD B_EMA12 = EMA("B_Close", 12) B_EMA26 = EMA("B_Close", 26) B_MACD = "B_EMA12" - "B_EMA26" B_Signal = EMA("B_MACD", 9) B_MACD_Hist = "B_MACD" - "B_Signal" # 布林带 B_SD20 = 标准差("B_Close", 20) B_BollZ = ("B_Close" - "B_MA20") / "B_SD20" ``` --- ## D9 财务 ```python B_Profit_Raw = 报告期转发布日("A股净利润同比增长率:单季〔财务指标〕") B_Profit_F = 缺失填充("B_Profit_Raw" * 取出(X的代码)) ``` --- ## 收益率 / 位置(跨维度通用) ```python B_Ret20 = 涨跌幅("B_Close", 20) B_IndRet20 = 涨跌幅("B_Ind", 20) B_Alpha20 = "B_Ret20" - "B_IndRet20" B_Ret20_Next = 前几天(涨跌幅("B_Close", 20), -20) # 未来20日收益(IC计算目标) B_PriceRank = 数值水位("B_Close", 250) B_RS = "B_Close" / "B_Ind" B_RS_Rank = 数值水位("B_RS", 250) ``` --- ## IC 计算公式 ```python # 内联写法(推荐,无跨变量依赖) PE_IC = 相关系数(前几天(数值水位("B_PE", 250), 20), 涨跌幅(收盘价(X), 20), 250) # 引用写法(同一 task_id 内) B_PE_Rank = 数值水位("B_PE", 250) PE_IC = 相关系数(前几天("B_PE_Rank", 20), 涨跌幅("B_Close", 20), 250) ``` > 不同 task_id 之间不能引用变量——newSession 后旧 session 的变量全部丢失 -
industry-aggregation.md 11.3 KB
# 示例五:成交量加权行业聚合——放量日申万行业涨跌幅排名 ## 快速模板:行业近N日平均涨跌幅前K 适用: - 申万一级行业最近一个月平均涨跌幅前5 - 某板块体系近N日涨跌幅排名 标准公式模板: ```text 个股近N日涨跌幅=涨跌幅("全市场每日收盘价",N) 行业近N日涨跌幅=成分平均汇总("个股近N日涨跌幅","申万资产所属指数") 行业TopK=取前("行业近N日涨跌幅",K,返回数值) ``` 注意: - 所有数据名/变量名在函数参数中必须使用双引号 - "最近一个月"默认按近20个交易日处理,回答中需明示口径 - 若 runMultiFormulaBatchStream 已返回最后一天 top 数据,且足以回答用户问题,可不再 readData - 仅当内置数据名无法命中时,才使用 confirmDataMulti **⚠️ 输出口径声明硬规则**:上述模板的计算结果是"**行业近N日平均涨跌幅**"(行业内各成分股N日区间回报的算术均值)。在最终答案中**只能**称为"行业近N日平均涨跌幅"或"行业近N日涨跌幅均值",**绝对禁止**将其改写为"近N日累计涨跌幅"——"累计涨跌幅"与"平均涨跌幅"统计含义不同,用户问的是"平均"就必须说"平均"。 --- ## 用户意图 > "在A股放量日(当日成交量超过5日均值2倍时),哪些申万行业板块的平均涨跌幅最好?帮我排个名" --- ## 核心挑战:跨维度聚合 本示例的关键难点是**维度不匹配**: - 全市场回报率(如万得全A 7700+ 只):资产×日期 二维矩阵 - 申万行业指数(31个):与全市场股票维度不同 **不能直接用全市场列做行业均值**——同一个二维矩阵按行业分组后,维度(资产轴)不一致,必须用 `成分平均汇总()` 做桥接: ``` 成分平均汇总(全市场数据, 申万资产所属指数) → 输出:31个申万行业 × 日期 的二维矩阵(行业轴替换了资产轴) ``` 类比:`成分平均汇总` 是"按组取平均",第二个参数指定分组依据(哪家公司属于哪个行业)。 --- ## 工具调用序列 ### Step 0 — 初始化会话 ```bash python scripts/call.py newSession ``` ```json {"code": 0, "task_id": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"} ``` --- ### Step 1 — 查案例(cases_index 优先) **1a. 读取 `presets/cases_index.yaml`**,搜索 tags 含 `行业`、`聚合`、`成分` 的卡片(约 1-3 张)。 **1b.** 找到后调 `getCardFormulas` 拉取公式,理解 `成分平均汇总` 的参数用法: ```bash python scripts/call.py getCardFormulas '{"card_names": ["<相关卡片名称>"]}' ``` **1c.** 若目录中未找到合适卡片,fallback 到: ```bash python scripts/call.py searchSimilarCases '{"query": "申万行业聚合成分平均汇总涨跌幅", "top_k": 3}' ``` > **重点关注**返回案例中 `成分平均汇总()` 的参数格式、`申万资产所属指数` 的数据名称写法;案例解决的是案例自身的问题,当前问题的公式需根据用户需求重新组织。 --- ### Step 2 — 确认全市场数据 ```bash python scripts/call.py confirmDataMulti '{"data_desc": "全市场每日成交量, 全市场每日回报率, 申万资产所属指数"}' ``` **预期返回**(记录三个 index_title): | 描述词 | index_title(示例) | dimension | 说明 | |-------|-------------------|-----------|------| | 全市场每日成交量 | `全市场每日成交量` | two | 资产×日期 | | 全市场每日回报率 | `全市场每日回报率` | two | 资产×日期 | | 申万资产所属指数 | `申万资产所属指数` | two | 资产×日期,值为申万指数代码 | > **Windows PowerShell 传参**: > ```bash > python scripts/call.py confirmDataMulti '{"data_desc": "全市场每日成交量, 全市场每日回报率, 申万资产所属指数"}' > ``` --- ### Step 3 — 搜索聚合函数(若 presets/functions.yaml 中已有则跳过) ```bash python scripts/call.py searchFunctions '{"query": "成分平均汇总 行业聚合", "top_k": 3}' python scripts/call.py searchFunctions '{"query": "区间统计量 掩码过滤", "top_k": 3}' python scripts/call.py searchFunctions '{"query": "按天求和 全市场聚合", "top_k": 3}' ``` 确认以下函数的参数格式: - `按天求和(数据)` → 将二维数据按日期求和,得到一维时序 - `成分平均汇总(数据, 指数数据)` → 按指数成分分组取平均,输出指数×日期 矩阵 - `区间统计量(数据, 掩码, 统计函数)` → 用布尔掩码过滤时间段,对各段计算统计量 --- ### Step 4 — 执行公式(5条,共享同一 task_id) ```bash python scripts/call.py runMultiFormulaBatchStream '{ "task_id": "<Step 0 的 task_id>", "begin_date": 20150101, "formulas": [ "全市场总成交量=按天求和(\"全市场每日成交量\")", "全市场成交量5日均=平均(\"全市场总成交量\",5)", "放量日标记=(\"全市场总成交量\">\"全市场成交量5日均\"*2)", "申万行业每日回报率=成分平均汇总(\"全市场每日成交量\"*\"全市场每日回报率\",\"申万资产所属指数\")", "放量日行业平均涨跌幅=区间统计量(\"申万行业每日回报率\",\"放量日标记\",平均)" ], "intents": ["全市场每日总量(一维)", "5日均量(一维)", "放量日布尔标记(一维)", "成交量加权行业日回报率(31行业×日期)", "放量日区间内各行业平均涨跌幅(31行业×区间数)"] }' ``` **关键设计决策**: | 设计点 | 说明 | |-------|------| | 公式4使用 `成交量×回报率` 而非直接 `回报率` | 这是**成交量加权平均**的计算模式:分子=成交量×回报率之和,分母=成交量之和,`成分平均汇总` 内部会自动完成加权除法 | | 公式1-3先把全市场压缩到一维 | `按天求和` 把 7700+ 只的成交量矩阵折叠为每天一个数——这是后续做"全市场放量日"判断的前提 | | `放量日标记` 是一维布尔序列 | 可直接作为 `区间统计量` 的掩码,标记哪些日期属于"放量日" | | `申万行业每日回报率` 维度是 31 × 4000+ 天 | 行业轴(31个申万行业)替代了原来的资产轴(7700+ 只股票) | **预期返回(各公式 data_id)**: ```json { "results": [ {"variable_name": "全市场总成交量", "data_id": "xxx", "dimension": "one"}, {"variable_name": "全市场成交量5日均", "data_id": "xxx", "dimension": "one"}, {"variable_name": "放量日标记", "data_id": "xxx", "dimension": "one"}, {"variable_name": "申万行业每日回报率", "data_id": "xxx", "dimension": "two", "assets": 31}, {"variable_name": "放量日行业平均涨跌幅", "data_id": "xxx", "dimension": "two", "assets": 31} ] } ``` --- ### Step 5 — 验证结果(必做) ```bash python scripts/call.py readData '{"ids": ["<放量日行业平均涨跌幅的data_id>"], "mode": "smart_sample"}' ``` **检查项**: - `assets` = 31(申万31个行业全部覆盖) - `dates`(列数)= 识别到的放量日区间数(历史通常 80-150 个) - NaN 率 < 10%(若过高,检查 `申万资产所属指数` 的 index_title 是否正确) - 取样值有正有负,数量级在 0.001 ~ 0.05 之间(日回报率级别) --- ### Step 6 — 读取完整截面排名 ```bash python scripts/call.py readData '{"ids": ["<放量日行业平均涨跌幅的data_id>"], "mode": "last_day_stats"}' ``` **预期返回**:31 个行业在所有放量日的平均涨跌幅,按值降序排列即可得到排名。 --- ### Step 7(可选)— 柱状图可视化 ```bash python scripts/call.py renderChart '{ "title": "放量日申万行业平均涨跌幅排名(2015至今)", "lines": [ {"id": "<放量日行业平均涨跌幅的data_id>", "name": "放量日平均日涨幅"} ], "chart_type": "bar", "task_id": "<task_id>" }' ``` > ```bash > python scripts/call.py renderChart '{"title":"放量日申万行业平均涨跌幅排名", "lines":[{"id":"<data_id>","name":"放量日平均日涨幅"}], "chart_type":"bar", "task_id":"<task_id>"}' > ``` --- ## 常见错误与处理 | 错误 | 原因 | 处理 | |------|------|------| | `成分平均汇总` 返回 assets=0 | `申wx资产所属指数` 的 index_title 写错 | 重新 confirmDataMulti,严格用返回的 index_title | | `放量日行业平均涨跌幅` assets=7700+(未聚合) | 公式4写错,误用的是全市场回报率而非行业聚合结果 | 确认公式4的变量名用的是 `申万行业每日回报率` | | 区间统计量 dates=0 | 放量日标记全为 False(条件过严或数据期太短) | 检查 begin_date / 阈值倍数;可先用 `readData(mode=smart_sample)` 看 `放量日标记` 的 True 比例 | | renderChart 报"仅支持一维数据" | `放量日行业平均涨跌幅` 是二维(31×区间数) | 先用 readData 读出截面均值,整理成一维序列后再图表;或改为 last_day_stats 下载后自行排序呈现文本 | | `数据名不存在:申万资产所属指数` | index_title 与实际返回不符 | 以 confirmDataMulti 返回的 index_title 为准,不要凭记忆猜测 | --- ## 延伸用法 同样的模式可以应用于: - **涨停日** / **大跌日** 行业表现:把放量日的布尔公式替换为涨跌幅阈值条件 - **宏观事件标记**:上传一列 0/1 事件标记 CSV,作为 `区间统计量` 的掩码 - **基于成分平均汇总的因子聚合**:把任意全市场因子(估值、动量、质量)映射到行业维度,做行业风格分析 --- ## 行业聚合排名题执行硬规则 1. **直接执行公式**:行业聚合题的核心公式(如 `成分平均汇总(涨跌幅(...), "申万资产所属指数")`)应直接通过 `runMultiFormulaBatchStream` 执行,不需要先用 `confirmDataMulti` 查"行业涨跌幅"等聚合后数据是否存在——聚合结果是公式计算产出的,不是预置数据集 2. **行业名称原样复用**:最终答案中的行业名称必须与工具返回的完全一致(如工具返回"公用事业",不得改写为"公共事业") 3. **TopN 安全读取**:行业聚合结果通常为 31 行(申万一级),可安全使用 `readData(mode="last_column_full")`;但若聚合维度更细(二级行业/概念板块),必须先用 `readData(mode="precheck")` 确认行数 --- ## 语义恒等:申万一级行业最近一个月涨跌幅排名 TopN ### 用户意图 > "请统计申万一级行业中,最近一个月涨跌幅排名前5的行业,并列出各行业月涨幅。" ### 正确思路 1. "最近一个月"若未特别说明,默认按近 20 个交易日处理 2. "月涨幅 / 最近一个月涨跌幅"应理解为**窗口累计涨跌幅**,不是 20 日日均回报 3. 先计算个股近 20 日涨跌幅,再按申万行业做成分平均汇总 ### 正确公式 ``` 申万行业近20日涨跌幅 = 成分平均汇总(涨跌幅("全市场每日收盘价", 20), "申万资产所属指数") ``` ### 易错写法(不要用) ``` # ❌ 这两步算的是"20日日均回报",不是"20日涨跌幅" 申万行业每日回报率 = 成分平均汇总("全市场每日回报率", "申万资产所属指数") 申万行业月涨跌幅 = 平均("申万行业每日回报率", 20) ``` > 上述结果是"20日日均回报的算术平均"(约 0.05%),不是"20日区间涨跌幅"(约 1%~5%)。用它回答"月涨幅"是语义错误。 -
ma-crossover-backtest.md 3.8 KB
# 示例一:均线策略选股 + 回测 ## 用户意图 > "帮我做一个5日均线上穿20日均线的选股策略,看看历史回测表现怎么样" **本流程硬约束**:均线、持仓、净值、回撤和交易次数必须在平台公式链中完成。禁止走 `fast_query → CSV → 临时 Python/Bash` 自行计算;即使本地文件工具可用,也不得写入或执行 `output/*.py`。平台公式报错时应修正公式或维度后重试。 --- ## 工具调用序列 ### Step 0 — 初始化会话 ```bash python scripts/call.py newSession ``` --- ### Step 1 — 查案例(cases_index 优先) **1a. 读取 `presets/cases_index.yaml`**,搜索 tags 含 `均线`、`MA`、`金叉`、`回测` 的卡片。 **1b.** 找到后调 `getCardFormulas` 批量拉取: ```bash python scripts/call.py getCardFormulas '{"card_names": ["<相关卡片名称>"]}' ``` **1c.** 目录中未找到才 fallback: ```bash python scripts/call.py searchSimilarCases '{"query": "均线金叉买入区间持仓回测", "top_k": 3}' ``` **LLM 操作**:理解案例中均线/金叉公式结构,提炼思路后针对用户的 5/20 均线需求重新组织公式,不照抄案例。 --- ### Step 2 — 确认数据名称 ```bash python scripts/call.py confirmDataMulti '{"data_desc": "全市场每日收盘价, 非ST股"}' ``` **预期返回**: - `全市场每日收盘价` → `index_title: "全市场每日收盘价"`(dimension=two) - `非ST股` → `index_title: "非ST股"`(dimension=two, is_bool=true) **LLM 操作**:用返回的 `index_title` 写进公式,不要用原始查询词。 --- ### Step 3 — 执行公式(生成信号 + 回测) ```bash python scripts/call.py runMultiFormulaBatchStream '{ "task_id": "<Step 0 的 task_id>", "begin_date": 20150101, "formulas": [ "MA5=平均(\"全市场每日收盘价\", 5)", "MA20=平均(\"全市场每日收盘价\", 20)", "金叉信号=(\"MA5\">\"MA20\")*(昨天(\"MA5\")<=昨天(\"MA20\"))*板块(万得全A)*缺失填零(\"非ST股\")", "死叉信号=(\"MA5\"<\"MA20\")*(昨天(\"MA5\")>=昨天(\"MA20\"))*板块(万得全A)*缺失填零(\"非ST股\")", "持仓区间=进出场区间(\"金叉信号\",\"死叉信号\")", "NAV=回测(\"持仓区间\",当天收盘买入,返回复利净值,信号按列归一)" ], "force_reusable_array": ["NAV"], "intents": ["5日均线", "20日均线", "金叉信号(排除ST)", "等权回测净值"] }' ``` **关键点**: - 同批公式共享同一个 `task_id`,后面的公式才能引用前面的变量 - 多公式调用必须显式传 `force_reusable_array`;这里只读取最终净值,因此仅保活 `NAV` - 返回每条公式的 `data_id`,记录 `NAV` 对应的 `data_id` --- ### Step 4 — 验证回测结果 ```bash python scripts/call.py readData '{"ids": ["<NAV的data_id>"], "mode": "precheck"}' ``` **预期返回**: ```json { "type": "nav_1d", "first_value": 1.0, "last_value": 3.xx, "total_return": 2.xx, "curve_samples": [[20150101, 1.0], ..., [20260227, 3.xx]] } ``` --- ### Step 5(可选)— 渲染净值曲线图 ```bash python scripts/call.py renderChart '{ "title": "均线金叉策略净值(2015至今)", "lines": [{"id": "<NAV的data_id>", "name": "MA5/20金叉策略"}], "width": 1400, "height": 600 }' ``` **LLM 操作**:返回 `data.base64`,解码后保存为 PNG 文件,或直接展示给用户。 --- ## 常见错误与处理 | 错误 | 原因 | 处理 | |------|------|------| | `公式变量未找到` | task_id 不一致,Signal 找不到 MA5 | 确保 4 个公式用同一个 task_id | | `数据名不存在` | 公式里写的名称和 confirmDataMulti 返回的 index_title 不一致 | 以 index_title 为准 | | `matchQuality=low` | searchSimilarCases 没搜到好模板 | 换 query 关键词重试一次 | -
render-chart.md 5.3 KB
# 示例四:生成多策略净值对比图 ## 用户意图 > "我已经跑了两个策略的回测,帮我把净值曲线画成一张图,并和沪深300比较" --- ## 前置条件 假设已通过 `runMultiFormulaBatchStream` 得到以下 `data_id`: - 策略A净值:`nav_aaa111` - 策略B净值:`nav_bbb222` - 沪深300净值:`nav_ccc333` 如果还没跑,先参考示例一(均线策略)或示例二(低PE策略)的 Step 3~4。 --- ## 工具调用序列 ### Step 1 — 验证三条净值曲线的基本情况 ```bash python scripts/call.py readData '{ "ids": ["nav_aaa111", "nav_bbb222", "nav_ccc333"], "mode": "precheck", "sample_points": 50 }' ``` **LLM 操作**: - 确认三条曲线的起止日期一致(都从 `begin_date` 开始) - 检查 `last_value` 对比,判断哪个策略更优 - 若曲线出现异常(如 last_value=0 或 NaN 率过高),先排查公式错误 --- ### Step 2 — 渲染对比图 ```bash python scripts/call.py renderChart '{ "title": "策略A vs 策略B vs 沪深300 净值对比", "lines": [ {"id": "nav_aaa111", "name": "策略A:均线金叉", "axis": "left"}, {"id": "nav_bbb222", "name": "策略B:低PE 20%", "axis": "left"}, {"id": "nav_ccc333", "name": "沪深300基准", "axis": "left"} ], "width": 1400, "height": 600, "start_date": 20150101 }' ``` **预期返回**: ```json { "success": true, "data": { "base64": "iVBORw0KGgoAAAANSUhEUgAA...", "lines_count": 3, "width": 1400, "height": 600, "errors": [] } } ``` --- ### Step 3 — 保存图片(Python 示例) LLM 可以把 base64 解码并保存: ```python import base64 b64 = "<返回的 base64 字符串>" with open("strategy_comparison.png", "wb") as f: f.write(base64.b64decode(b64)) print("图片已保存到 strategy_comparison.png") ``` --- ## 二维数据绑定资产(非净值曲线的画法) 如果要画某只个股的价格走势(二维数据中的某一列),需要额外传 `ticker`: ```bash python scripts/call.py renderChart '{ "title": "贵州茅台收盘价", "lines": [ {"id": "<收盘价data_id>", "name": "600519.SH", "axis": "left", "ticker": "600519.SH"} ] }' ``` --- ## 注意事项 | 场景 | 处理 | |------|------| | `errors` 非空 | 某条 line 的 id 不存在或数据类型不支持渲染 | | 净值曲线起点不一致 | 用 `start_date` 截断至同一起点再渲染 | | 图片太小看不清 | 调大 `width`(最大建议 1800)和 `height`(最大建议 900) | | 需要左右双轴 | 把量纲差异大的曲线(如换手率 vs 净值)设为 `"axis": "right"` | | **本地自绘净值曲线**(不走 renderChart,直接用 `curve_samples` / `last_column_full` 点序列 matplotlib 画图) | **必须**先做交易日补齐 + 前向填充,否则稀疏点直接连线会跨缺口斜连,出现视觉上的阶梯/折断(详见下方「本地自绘净值曲线兜底规则」) | --- ## 本地自绘净值曲线兜底规则(硬规则) **触发场景**:LLM 拿到 `readData` 返回的 `curve_samples` / `last_column_full.values` 等**稀疏点序列**(非完整日频)后,在本地用 matplotlib 自行画图(例如批量回测出图、落盘 PNG 用于报告)。 **问题**:`curve_samples` 是等间距采样点,不是逐交易日序列;`last_column_full` 对"持仓区间外"的日期也可能没有点。直接 `plt.plot(dates, navs)` 会把两个相距数月的点用一条斜线连起来,视觉上就是用户反馈的「曲线阶梯状/长斜线」异常。 **强制兜底**:绘图函数内必须做一次交易日重建 + ffill,**仅用于画图,不要回写到落盘数据或指标计算**(年化/夏普/回撤必须用原始 `curve`)。 ```python import pandas as pd import matplotlib.pyplot as plt def plot_nav_curve(curve, title, out_path): """curve: List[Tuple[int, float]] 如 [[20240423, 1.0], [20240918, 0.998], ...]""" df = pd.DataFrame(curve, columns=["date", "nav"]) df["date"] = pd.to_datetime(df["date"].astype(str)) df = df.sort_values("date").drop_duplicates("date") # —— 画图层兜底:按工作日重建 + 前向填充 —— full_idx = pd.bdate_range(df["date"].min(), df["date"].max()) df = (df.set_index("date") .reindex(full_idx) .ffill() .rename_axis("date") .reset_index()) fig, ax = plt.subplots(figsize=(14, 4.5)) ax.plot(df["date"], df["nav"], linewidth=1.2) ax.axhline(1.0, color="gray", linestyle=":", linewidth=0.8) ax.set_title(title) ax.grid(alpha=0.3) fig.tight_layout() fig.savefig(out_path, dpi=140) plt.close(fig) ``` **使用边界**: - `bdate_range` 按自然工作日生成,遇节假日会多几天,ffill 后是水平段,不会再出现跨缺口斜连,视觉已修复。 - 不要把填充后的序列回写到 JSON/Excel,更不要用它重新计算年化/夏普/最大回撤——这些指标必须使用原始 `curve` 的真实成交点。 - 质量自检(可选但推荐):出图前统计 `coverage_ratio = len(curve) / expected_trading_days` 与 `max_gap_days`,若 `coverage_ratio < 0.5` 或 `max_gap_days > 30` 应提示"数据稀疏,建议改用 `readData` 完整模式重取"。 - 若需要更严格的 A 股交易日历,可把 `pd.bdate_range` 替换成真实交易日序列;否则保持最小改动即可。 -
tool-call-checklist.md 5.2 KB
# 工具调用前必读清单(就近激活) > 每次准备调用以下工具前,先在心里勾选对应小节。任一项答不上来 → 暂停,重新设计这一步。 > 这是「最小充分原则」在工具层面的具体落地,与 SKILL.md 顶层原则配套使用。 --- ## confirmDataMulti 调用前必勾选 - [ ] 我要查的字段,是不是公式算子能直接算的? - 均线 → `平均(序列, N)`、涨跌幅 → `涨跌幅(序列, N)`、波动率 → `标准差(序列, N)`、比值 → 直接除 - **能算的必须现算**,不查现成数据集(现成数据集更新时点不可控,可能滞后数周) - [ ] 这个字段名是从哪里来的? - ✅ 来自 workflow 内联模板 / 上一次 confirmDataMulti 成功返回 → **原样复制引号内全部字符**,不得增删任何后缀 - ❌ 来自联想/推测/拼接(如自己加"〔报告期利润表〕"后缀)→ 禁止 - [ ] 上一次同类调用失败过吗? - 是 → 这次的目的必须是"对失败字段重新确认正确字段名",**不允许**改用 `searchSimilarCases` 兜底(会拿到口径不一致的字段) ## readData 调用前必勾选 - [ ] 我要 read 的变量,在公式里是"输出"还是"输入/中间量"? - ✅ 输出(如 `Top10`、`最终结果`、`末值`)→ 可读 - ❌ 中间量(如 `排序值`、`综合条件`、`因子矩阵`、全市场未截断的序列)→ **禁止读**,它只是上游原料,回填进 prompt 只会爆炸 - [ ] 公式有没有提供更窄的下游输出? - 如果公式里有 `取前(..., 10)`、`取出(..., 资产名)`、`末值(...)` 这类收敛算子 → **必须读这些算子的输出变量**,不读它的输入 - 如果没有 → 先回去改公式,加一个收敛算子,再 readData - [ ] 这次 read 后,预估回填进 prompt 的 token 量? - 标量/小表(≤20 行)→ 直接读 - 大表/全市场序列(>100 行 或 >5000 token) → **不读**,回去给公式加截断 ## runMultiFormulaBatchStream 调用前必勾选 - [ ] 是否财务报告期查询(毛利率、ROE、营收、净利润、资产负债率等 one-row 数据)? - 是 → **严禁**传 `use_minute_data: true`,会导致 HTTP 500;直接省略该参数或设为 `false` - 否(行情选股、涨跌幅等二维矩阵数据)→ 可按需传 `use_minute_data: true` - [ ] 公式列表里是否有纯中间变量(只给后续公式引用,不会被 `readData` 或业务步骤读取)? - **多公式(≥ 2 条)→ 必须主动评估,不得直接省略** - 评估方法:逐条问「这个左侧变量会被 `readData` 或最终业务步骤读取吗?」 - 仅被本批后续公式引用 → 不要把变量名写进 `force_reusable_array` - 会被 `readData` 读 / 是最终输出 / 会被后续 batch 引用 → 把变量名写进 `force_reusable_array` - 命名带 `_中间`/`_条件`/`_掩码`/`_排序值`/`_因子矩阵`/`MA5`/`Signal` 等几乎都是中间量 → 不要写进数组 - 单公式(仅 1 条)→ 可不传 - [ ] 是否想刷新同一 session 的分钟数据截止时间? - 是 → **不要**给 `runMultiFormulaBatchStream` 传 `refresh_snapshot_time`;先调用独立 `refreshSnapshotTime`,再执行公式 - 否 → 正常执行公式 ## refreshSnapshotTime 调用前必勾选 - [ ] 当前 session 是否已经跑过 `runMultiFormulaBatchStream`? - 否 → **不要**调用;首次 `runMultiFormulaBatchStream` 会自动建立 snapshot - 是 → 继续判断下一条 - [ ] 是否真的需要把分钟数据推进到最新? - 数据已是最新 / 只是常规追问 → **不要**调用,直接复用现有 snapshot - 用户明确要求"刷新到最新"或盘中重算 → 调用 `refreshSnapshotTime(task_id)`,再继续 `runMultiFormulaBatchStream` - [ ] `task_id` 来自哪里? - 必须是当前 session 的 task_id;禁止凭空构造或复用其他 session 的 task_id ## runMultiFormulaBatchStream 调用后必勾选 - [ ] 工具返回的 description / 完整序列 / 中间变量值,会被回填进下一轮 prompt 吗? - 只回填**当前问题需要的最终值**(如末值、Top10 列表) - 不回填中间过程(如全市场每日因子值) - [ ] 公式失败时(500 / PARTIAL_SUCCESS),下一步是什么? - ✅ 定位失败的具体字段/算子 → 针对性修复(重新 confirmDataMulti 该字段,或换算子) - ❌ 整体重写 / 改用 searchSimilarCases / 换数据集兜底 → 禁止 ## 加载额外文档前必勾选(cases_index.yaml / recipes/ / 知识卡片等) - [ ] 不加载这个文档,我能不能写出可执行的公式? - 能 → **不加载** - 不能 → 说明具体卡在哪一步,再决定加载哪个文档(不是全量加载) - [ ] 加载条件是否真的满足? - `cases_index.yaml`:用户明确要求参考案例 / 任务含 ≥3 维度复合因子且无现成思路 - `recipes/<具体名>.md`:当前任务匹配该 recipe 的标题场景 - 不满足 → **不加载** --- ## 通用原则(贯穿所有工具) > 在动手前问自己:**"我现在要做的这一步,是因为上一步明确告诉我缺什么,还是因为我担心可能会缺什么?"** > > 前者 → 做。后者 → 停。 -
topn-gainers.md 1.7 KB
# TopN 涨幅榜 / 跌幅榜 > 获取全市场(非ST、万得全A成分)盘中涨幅前/后 N 名 > 常用于盘中快照、收盘复盘、热股列表生成 --- ## 核心公式 ### 盘中涨幅前10 ``` 取前( 涨跌幅("全市场每日收盘价") * 板块(万得全A) * 缺失填零("非ST股"), 10, 返回数值 ) ``` > 锚点:昨日收盘 → 当前分钟。在 `use_minute_data: true` 模式下,`"全市场每日收盘价"` 的最新列由服务端替换为最后一分钟价格,因此 `涨跌幅(...)` 等价于 `当前分钟价 / 昨日收盘 - 1`。 ### 收盘涨幅前10 ``` 取前( ("全市场每日收盘价" / 延迟("全市场每日收盘价", 1) - 1) * 板块(万得全A) * 缺失填零("非ST股"), 10, 返回数值 ) ``` ### 跌幅前10(取后) ``` 取后( ("全市场每日收盘价" / 延迟("全市场每日收盘价", 1) - 1) * 板块(万得全A) * 缺失填零("非ST股"), 10, 返回数值 ) ``` --- ## 关键参数说明 | 参数 | 说明 | |------|------| | `use_minute_data: true` | 由服务端把映射表内的日频行情数据名最新列替换为分钟更新版,公式仍写日频数据名 | | `板块(万得全A)` | 过滤万得全A成分股(乘法:成分=1,非成分=NaN) | | `缺失填零("非ST股")` | 非ST=1,ST=0;与上面相乘可排除ST | | `取前(数据, n, 返回数值)` | 取最大的n个,返回原始数值 | | `取前(数据, n, 返回掩码)` | 取最大的n个,返回1/NaN掩码 | --- ## 批量管线中的用法 在 `xiaohongshu-stock-skill` 的 `batch-stock-post.py` 中,该公式用于获取当日热股列表, 然后逐个喂入 `scanDimensions → generate_research_notes → generate_quiz` 管线。 -
upload-custom-data.md 3.1 KB
# 示例三:上传自有因子数据,并在公式中引用 ## 用户意图 > "我有一份自己计算好的因子数据(CSV格式),想上传到平台后,直接在公式里和其他数据组合使用" --- ## 工具调用序列 ### Step 0 — 初始化会话 ```bash python scripts/call.py newSession ``` --- ### Step 1 — 上传因子数据(两阶段:preview → confirm) `uploadData` 自动处理两阶段。调用一次即可: ```bash python scripts/call.py uploadData '{ "file_path": "C:/Users/you/Desktop/my_factor.csv", "name": "我的动量因子", "dimension": "two" }' ``` **CSV 格式要求**: - 二维数据:第一列为日期(格式 `20230101`),其余列标题为股票代码(如 `600519.SH`) - 一维数据:两列——日期 + 数值 **预期返回**: ```json { "success": true, "data_id": "upload_abc123", "name": "我的动量因子", "rows": 250, "cols": 1800 } ``` **LLM 操作**:记录 `data_id = "upload_abc123"` 和 `name = "我的动量因子"`。 --- ### Step 2 — 确认上传后数据可查(可选验证) ```bash python scripts/call.py readData '{"ids": ["upload_abc123"], "mode": "signature"}' ``` **预期返回**:dimension、覆盖率、NaN 率。确认数据已入库、维度正确。 --- ### Step 3 — 查案例理解自定义因子公式写法 **3a. 读取 `presets/cases_index.yaml`**,搜索 tags 含 `自定义因子`、`上传`、`选股回测` 的卡片。 **3b.** 找到后调 `getCardFormulas` 拉取。**3c.** 目录中未找到才 fallback: ```bash python scripts/call.py searchSimilarCases '{"query": "自定义因子选股分组回测"}' ``` --- ### Step 4 — 确认配合使用的平台数据 ```bash python scripts/call.py confirmDataMulti '{"data_desc": "非ST股, 开盘非涨停"}' ``` --- ### Step 5 — 在公式中用 data_id 引用自有数据 ```bash python scripts/call.py runMultiFormulaBatchStream '{ "task_id": "<Step 0 的 task_id>", "begin_date": 20200101, "formulas": [ "Filter=缺失填零(\"非ST股\")*缺失填零(\"开盘非涨停\")", "Factor=取出(\"upload_abc123\")*\"Filter\"", "Signal=(排名(\"Factor\",降序)<百分比(板块(万得全A),0.1))*\"Filter\"", "NAV=回测(\"Signal\",当天收盘买入,返回复利净值,信号按列归一)" ], "intents": ["过滤条件", "引用上传因子", "因子最高10%选股", "等权回测净值"] }' ``` **关键点**: - `取出("upload_abc123")` — 用 data_id 引用上传数据,不用确认数据名称 - 上传数据与平台数据的维度(资产×日期)必须对齐,否则排名结果会有大量 NaN --- ### Step 6 — 验证回测结果 ```bash python scripts/call.py readData '{"ids": ["<NAV的data_id>"], "mode": "precheck"}' ``` --- ## 常见问题 | 问题 | 处理 | |------|------| | 上传后 NaN 率过高(>50%) | 检查 CSV 列标题格式(需带交易所后缀,如 `.SH`/`.SZ`) | | `取出` 返回全零 | begin_date 早于上传数据的起始日期,缩短回测区间 | | 上传超时(大文件) | 拆分为多个文件,分批上传 | -
value-pe-strategy.md 3.4 KB
# 示例二:低PE价值选股,对比沪深300基准 ## 用户意图 > "帮我筛选出全市场PE最低的20%股票,做等权回测,看看跑不跑得过沪深300" --- ## 工具调用序列 ### Step 0 — 初始化会话 ```bash python scripts/call.py newSession ``` --- ### Step 1 — 查案例(cases_index 优先) **1a. 读取 `presets/cases_index.yaml`**,搜索 tags 含 `低价值`、`PE`、`估値因子`、`回测` 的卡片。 **1b.** 找到后调 `getCardFormulas` 批量拉取: ```bash python scripts/call.py getCardFormulas '{"card_names": ["<相关卡片名称>"]}' ``` **1c.** 目录中未找到才 fallback: ```bash python scripts/call.py searchSimilarCases '{"query": "低价値因子选股等权回测", "top_k": 3}' ``` **LLM 操作**:理解案例中低价値选股的公式结构(PE过滤、百分位阈值、负 PE 处理),提炼思路后针对用户需求重新组织公式。 --- ### Step 2 — 确认指数代码 ```bash grep "资产名或代码" presets/assets_db/{类型}.yaml ``` **预期返回**: ```json [{"name": "沪深300", "code": "000300.SH", "type": "index"}] ``` **LLM 操作**:记录 `code = "000300.SH"`,用于回测公式中的基准参数。 --- ### Step 3 — 确认数据名称 ```bash python scripts/call.py confirmDataMulti '{"data_desc": "A股市盈率, 非ST股, 开盘非涨停"}' ``` **预期返回**: - `A股市盈率(PE)〔估值数据〕` → dimension=two - `非ST股` → dimension=two, is_bool=true - `开盘非涨停` → dimension=two, is_bool=true --- ### Step 4 — 执行公式 ```bash python scripts/call.py runMultiFormulaBatchStream '{ "task_id": "<Step 0 的 task_id>", "begin_date": 20150101, "formulas": [ "Filter=缺失填零(\"非ST股\")*缺失填零(\"开盘非涨停\")", "PE_valid=\"A股市盈率(PE)〔估值数据〕\"*(\"A股市盈率(PE)〔估值数据〕\">0)*\"Filter\"", "Signal=(\"PE_valid\">0)*(排名(\"PE_valid\",升序)<百分比(板块(万得全A),0.2))*\"Filter\"", "NAV=回测(\"Signal\",当天收盘买入,返回复利净值,信号按列归一)", "BenchmarkNAV=回测(指数(000300.SH),当天收盘买入,返回复利净值)" ], "intents": ["过滤条件", "有效PE(排除负PE)", "PE最低20%信号", "策略净值", "沪深300基准净值"] }' ``` **关键点**: - PE > 0 排除负值/亏损股 - `排名(..., 升序) < 百分比(...)` 选出最小的 20% - `指数(000300.SH)` 用 Step 2 确认的 code --- ### Step 5 — 同时验证策略和基准 ```bash python scripts/call.py readData '{"ids": ["<NAV的data_id>", "<BenchmarkNAV的data_id>"], "mode": "precheck"}' ``` **预期返回**:两条净值曲线的 `first_value / last_value / total_return`,可直接对比超额收益。 --- ### Step 6(可选)— 画对比图 ```bash python scripts/call.py renderChart '{ "title": "低PE策略 vs 沪深300(2015至今)", "lines": [ {"id": "<NAV的data_id>", "name": "低PE 20% 等权", "axis": "left"}, {"id": "<BenchmarkNAV的data_id>", "name": "沪深300", "axis": "left"} ] }' ``` --- ## 注意事项 - PE 为负代表公司亏损,必须 `PE > 0` 过滤,否则负PE股也会被误选为"低PE" - `百分比(板块(万得全A), 0.2)` 返回全市场20分位的阈值,与 `排名` 结合选出最低20% - 若某日 Signal 全零(极端市场),回测会产生 NaN,属正常;readData 会报告 NaN 率
-
-
references
-
environment.md 2.2 KB
# 环境依赖说明 本文档描述运行 `quant-buddy-skill` 及其子场景所需的环境配置。 --- ## Python - **版本要求**:Python 3.8+(推荐 3.11) - **核心功能**:仅依赖标准库,无需额外 `pip install` - **可选依赖**:`python-dateutil`(事件研究辅助)、`Pillow`(图表格式转换)、`requests`(事件新闻搜索) - **Windows 推荐启动方式**:所有涉及中文路径的脚本加 `-X utf8` 标志 ```bash python -X utf8 scripts/call.py <工具名> ``` --- ## API Key 配置 前往 https://www.quantbuddy.cn/login 登录/注册,在账户页面获取 API Key。获取后可通过以下方式配置: 1. **手动编辑**:直接打开 skill 根目录下的 `config.json`,把 `api_key` 字段改为你的 Key。 2. **本地覆盖**:在 `config.local.json` 中配置 `api_key`,优先级高于 `config.json`。该文件仅供本地使用,不应打包或提交。 3. **环境变量覆盖**:设置 `QUANT_BUDDY_API_KEY`,优先级高于 `config.local.json` 和 `config.json`。 4. **贴给 AI 助手**:在对话中把 `sk-...` 开头的 Key 发给 AI,AI 会写入 `config.json`。 若出现 `401 Unauthorized` 或 `402 Quota`,请重新获取并更新 API Key。 --- ## 可选 Bocha 搜索能力 仅部分 Web 搜索辅助场景需要博查凭证;核心行情、财务、选股、回测能力不依赖该凭证。 可选配置方式(任一即可): - 环境变量 `BOCHA_API_KEY` - `config.local.json` 中手动添加 `bocha_api_key` - `config.json` 中手动添加 `bocha_api_key` --- ## 运行时输出目录 - `output/.session.json` 或 `output/.session.<key>.json`:当前 session 的 task_id(设置 `QBS_SESSION_KEY` 时使用后者) - `output/ic_data/`:IC 扫描结果(若 workflow 触发相关能力) - 其他 `csv / png / json / html`:运行过程中的临时或交付产物 --- ## readData 批量限制 `readData` 单次调用最多传入 **10 个 data_id**。如需读取更多结果,拆分多次调用。 --- ## 终端注意事项 - 终端缓冲可能导致长输出不完整显示,`call.py` 会额外写入系统临时目录下的 `gzq_out.txt` - 若需排查,可在系统临时目录中查看该文件内容 -
minute-data-coverage.md 5.8 KB
# 分钟行情支持市场与历史起点 适用于 `fast_query_minute`、`fast_query_minute_range` 及对应 Data Grant。来源:用户提供的上游文档《000000-分钟数据.md》,于 **2026-09-15** 对齐;下表是本平台覆盖范围,不是交易所成立日、资产上市日或全市场分钟数据通用历史。 ## 已确认覆盖表 | 资产类型 | 中国市场 | 美国市场 | 香港市场 | |---|---|---|---| | 股票行情 | 2026-05-13 | 2026-05-13 | 2026-05-20 | | 指数行情 | 2026-08-13 | 暂不支持 | 暂不支持 | | 期货行情 | 2026-05-13 | 暂不支持 | 暂不支持 | 起始日期包含当日,按行情所属的 **交易日 `trade_date`** 比较,不能用 UTC 时间戳对应的自然日判断。夜盘也按交易日归属判断。 这是**市场 × 资产类型的最早覆盖边界**,不保证每个资产当天已上市/挂牌、有成交或数据齐全。单个股票、指数、具体期货合约实际首条数据可能更晚;最终以接口返回为准。主连/次主连属于国内期货,不因附带换月事件而获得更早的分钟行情。换月事件历史与分钟行情历史不是同一范围。 ## 调用前必须判断并提示 1. 先用资产库确认唯一资产、市场和资产类型;不能仅凭名称或“日频能查到”推断分钟频支持。表中未列明的类别(例如基金/ETF、其他市场)视为**覆盖待确认**,不要自行套用股票起点或断言不支持。 2. 对历史请求比较用户窗口与对应起点;offset 先按**资产市场当地日期**折算为自然日窗口,不能一律按北京时间,也不能当成交易日偏移。起点包含当日;本规则不放宽历史工具“不得含市场今天/未来”、单次三个日历月和 offset `[-70,-1]` 的限制。 3. **全窗口早于起点**(`end_date < 最早日期`):调用前直接说明该类别最早日期,以及本次区间无可提供的分钟行情;不进行明知越界的查询、不为此注册授权、不空转重试。可建议从支持起点重新选择区间,但不能未经说明改查别的日期,也不能用日频数据冒充分钟数据。 4. **部分窗口早于起点**(`start_date < 最早日期 <= end_date`):先提示缺失区间,说明只能提供支持范围内的数据。在满足原有日期限制时,可继续查询原窗口内可用部分;保留用户原始窗口,不静默改参。答复/导出说明/页面标注同时展示“请求区间、平台覆盖起点、实际返回范围”,不得声称已覆盖完整请求。若用户要求完整覆盖,先说明无法满足并让用户选择可用范围,不交付部分结果冒充完整结果。 5. **在覆盖范围内**:正常查询,但空结果不能直接认定为未到支持起点,可能是休市、未上市/挂牌或该资产数据缺失;也不能把某次查询的第一条数据说成该资产有史以来最早数据。 6. **明确暂不支持的组合**:调用前提示“本平台暂不支持该市场该类资产的分钟行情”;不尝试用另一个分钟工具绕过。此限制不代表该资产的日频行情也不支持。 ## 数据授权与活页 注册、Fork 改资产、修改授权窗口前同样检查。授权保留原始日期或 offset 语义;不为凑出非空数据而静默裁剪 payload、替换市场/资产或将 offset 固定为绝对日期。整个窗口越界或明确不支持时,不创建声称能动态取数的授权;仍可按用户需求交付带清晰数据限制说明的静态内容。 部分覆盖页面要标注数据起点与实际返回范围,不补零、不伪造更早数据,不把 `is_live:true` 当成完整覆盖或实时盘中行情保证。动态 offset 窗口每次取数会滚动,实际数据范围不能写死为构建当天的范围。 ## 提示示例 - 国内期货,请求 2026-04-01 至 2026-05-30:“国内期货分钟行情最早从 **2026-05-13** 开始提供。您请求的 2026-04-01 至 2026-05-12 超出覆盖范围;本次只能提供 2026-05-13 至 2026-05-30 内实际存在的数据,无法完整覆盖原区间。” - 国内指数,请求 2026-07-01 至 2026-07-31:“国内指数分钟行情最早从 **2026-08-13** 开始提供,所选区间全部早于支持起点,当前无法提供该区间的分钟行情。” - 香港指数:“本平台暂不支持香港指数分钟行情;不能用港股股票的分钟数据覆盖范围代替。若需要日频行情,可另行确认需求。” ## 服务端返回合同(需部署对应后端版本) `fast_query_minute_range` 及对应 Data Grant 已补服务端护栏,不再只依赖 skill 提示: - 全窗口越界:HTTP 400,`error.code=MINUTE_HISTORY_OUT_OF_RANGE`,错误中有 `earliest_available_date`、`requested_start_date/requested_end_date`、`market/asset_type`、`retryable:false`。解释该边界,不将其当成成功空 CSV,也不原参数重试。 - 部分覆盖:行情成功仍HTTP 200;`data.warnings[]` 包含 `MINUTE_HISTORY_PARTIAL_COVERAGE`,`data.coverage` 给出类别起点、原请求范围、`partial:true` 和 `unavailable_range`。保留提示,不能因CSV非空就宣称完整覆盖;`coverage`不是实际首条/末条日期。支持范围内 `coverage.partial=false`,仍可能合法返回空数据。 - 授权注册/刷新同样校验,存量越界授权查询也报错;部分覆盖注册/刷新成功时 `coverage/warnings` 在顶层,查询成功时在 `data` 内。不要把这些响应字段写入冻结payload。 - 历史覆盖警告可与换月警告同时出现,两者都要保留。旧服务若仍成功返回越界空CSV,按本表提示并说明后端版本待更新,不认为旧行为代表扩大覆盖范围。 上游覆盖范围后续变更时,应重新核对并同步本表,不能仅因时间过去就推定历史已自动补齐。 -
ru-billing.md 1.7 KB
# RU 计费体系 > 更新于 2026-04-15。统一 RU(Resource Unit)体系,所有端点共享一个 RU 池。 > 每个请求按端点类型 + 请求参数动态扣减 RU(1~26 RU)。 > 规划流程时优先减少冗余调用。 --- ## RU 权重速查表 | 工具 | 模式 | RU 消耗 | 说明 | |------|:---:|:---:|------| | `search_functions` | fixed | 1 | | | `get_card_formulas` | fixed | 1 | | | `upload_preview` | fixed | 1 | | | `upload_confirm` | fixed | 1 | | | `download_data` | fixed | 1 | | | `render_kline` | fixed | 1 | | | `re_render_chart` | fixed | 1 | | | `read_data` | fixed | 2 | | | `search_similar_cases` | fixed | 5 | | | `assets_db` 本地资产库 | deferred | 1~6 × 意图数 | DB 精确匹配 1 RU,LLM 路径 6 RU | | `confirm_data_multi` | deferred | 1~26 × 意图数 | DB 精确匹配 1 RU,LLM 路径 26 RU | | `run_multi_formula` | dynamic | **7 × 公式数** | 单次最多 20/30/40 个(free/plus/pro) | | `scan_dimensions` | dynamic | **12 × 维度数** | 全维度(8) = 96 RU | | `render_chart` | dynamic | **1 × 线数** | 如 3 条线 = 3 RU | > **deferred 模式**:先预扣 1 RU/意图,请求完成后按实际路径补扣差额。 > **错误请求退款**:所有端点发生错误时(4xx/5xx),实际 RU 降为 1 RU。 --- ## 配额池说明 | 池 | 重置方式 | 说明 | |------|------|------| | **窗口 RU** | 个人滚动(最早请求 +4h) | 不是全部一次恢复,按请求逐批恢复 | | **日 RU** | 每天 00:00(北京时间) | 次日零点统一重置 | --- ## 场景成本参考 | 场景 | 典型 RU 消耗 | |------|:---:| | 单只股票价格/PE/PB | ~3 RU | | 条件选股(1 条公式)| ~10 RU | | 条件选股+回测(5 公式)| ~37 RU | | IC 全维度扫描 | 96 RU | -
scripts-audit.md 4.8 KB
# Bundled Scripts Audit This document enumerates every Python script shipped inside this skill, what it does, and its network / subprocess / filesystem behavior. Reviewers can use it to verify the skill's declared behavior without reading every line of code. Last audited against version: **4.20.7** --- ## Top-level scripts ### `scripts/call.py` - **Purpose**: Thin CLI dispatcher. Takes a tool name + JSON params, forwards most tool calls to `scripts/executor.py` via a subprocess of the same Python interpreter, and handles local session management / output post-processing. - **Network**: Sends a best-effort `newSession` trace payload to the configured quant-buddy endpoint when both endpoint and API key are available. Other network calls happen inside `executor.py`. - **Authentication**: Resolves API key from `config.json`, then `config.local.json`, then `QUANT_BUDDY_API_KEY` as an environment override. The key is sent only in the `Authorization: Bearer <key>` header. - **Subprocess**: `subprocess.run([sys.executable, "scripts/executor.py", ...])`. No shell, no external binary. - **Filesystem writes**: Session files under `output/`; command output snapshots under the system temp directory; chart / CSV outputs under `output/` for chart or download tools. - **Reads secrets**: quant-buddy API key from config files or `QUANT_BUDDY_API_KEY`. ### `scripts/executor.py` - **Purpose**: Calls the quant-buddy HTTPS API and returns the response. - **Network**: Only the configured quant-buddy endpoint, defaulting to `https://www.quantbuddy.cn/**`, via `urllib.request` (stdlib). No third-party host is used by this script. - **Authentication**: Resolves `api_key` in this order: (1) `config.json`, (2) `config.local.json` override, (3) `QUANT_BUDDY_API_KEY` environment override. The resolved key is sent only in the `Authorization: Bearer <key>` header. It is never logged, printed to stdout/stderr, or written to files. - **Subprocess**: None. - **Filesystem writes**: Optional logs / outputs under the skill root, depending on the invoked tool. ### `scripts/quant_api.py` - **Purpose**: Python wrapper around `executor.py` for use as a library. - **Network**: Same as `executor.py` (delegates to it). - **Authentication**: Uses the same config loading behavior as `executor.py`. - **Subprocess**: None. - **Filesystem writes**: Session-aware calls may read the session file; normal API wrapper calls do not write files directly. ### `scripts/event_study_local.py` - **Purpose**: Optional event-study helper. Combines quant-buddy data with a Bocha web-search step for news context. - **Network**: - `https://www.quantbuddy.cn/**` through the quant-buddy API path. - `https://api.bochaai.com/v1/web-search` only when `BOCHA_API_KEY` or `bocha_api_key` is configured. The function returns an error immediately if no Bocha key is configured. - **Authentication**: Reads `BOCHA_API_KEY` from the environment, or `bocha_api_key` from `config.local.json` / `config.json`. - **Subprocess**: None. - **Filesystem writes**: None. - **Dependency**: Requires the `requests` package when this optional event-news helper is invoked. ### `scripts/self_update.py` - **Purpose**: Self-update helper used only when the server explicitly requires a newer skill version and provides a verified zip package. - **Network**: Downloads the provided zip URL with `urllib.request` when `--url` is used. No network call is made when `--zip-path` is used. - **Integrity checks**: Requires an expected SHA-512 digest and expected version; validates the archive before replacing files. - **Subprocess**: None. - **Filesystem writes**: Extracts into a temporary staging directory; backs up the current skill outside `skills/` by default; replaces the target skill root while preserving `config.json`, `config.local.json`, `output`, and `logs`. `--dry-run` validates without replacement. - **Safety checks**: Rejects absolute paths, Windows drive paths, NUL bytes, and `..` path traversal entries in the zip. --- ## Summary guarantees | Concern | Status | |---|---| | Outbound network hosts | quant-buddy endpoint (required), `api.bochaai.com` (opt-in only), verified update zip URL only during self-update fallback | | quant-buddy API key ever logged / transmitted to third-party host | No | | PII (phone / SMS / email / device ID) collected | No | | Subprocess / shell to external binary | No external binary; only `call.py` re-invokes `sys.executable` for dispatch | | Writes outside skill root | Normal tools: no. `self_update.py`: yes, only for temporary extraction / backup / target skill replacement during explicit update fallback | | Reads OS credentials / env vars beyond declared ones | No; declared environment variables are `QUANT_BUDDY_API_KEY` and optional `BOCHA_API_KEY` | If any of the above statements is inaccurate, it is a bug and should be reported to the skill author. -
troubleshooting.md 11.8 KB
# 故障排查 > 汇总所有场景(量化选股、渲染图表、数据下载)的常见错误与解决方式。 --- ## 成败判定通则(先读这条) > ⚠️ **判断一次工具调用成败,看返回 body 里的 `code` / `success` 字段,不看 HTTP 状态码。** - **HTTP 200 ≠ 业务成功。** 平台/封装层即使返回 HTTP 200,body 里仍可能是 `"code": -1` / `"success": false` —— 这是**业务错误**,必须按失败处理。 - 命中 `code: -1` / `success: false` 时:读 `error` / `message` 字段判断原因,再决定动作(重试 / 改参 / 走对应排查表),**绝不能**因为「HTTP 通了」就当成功继续往下走。 - 常见 `code:-1` 业务错误类型:版本拦截(`error.type=SKILL_VERSION_OUTDATED`,见下方版本小节)、参数缺失/非法、task_id 不一致、数据为空等。 - 本地封装 `call.py` 的明确失败信号:`error: "INVALID_TOOL_NAME"`(工具名写错/缺失)、`error: "SKILL_VERSION_MISMATCH"`(会话与本地版本不一致),均需先自愈再继续。 --- ## 批量公式错误分类(`category`)—— 拿到 runMultiFormulaBatch 报错先看这条 > ⚠️ **不同错误处理方式完全不同,先看 `category` 再决定动作,别一律重试、也别一律改公式。** `runMultiFormulaBatchStream` 返回的错误(无论是整批 fatal `error.category`,还是单条失败 `errors[].category`)都带统一的 `category` 标签。按它决定动作: | `category` | 含义 | 能重试吗 | 你该做什么 | |------------|------|----------|------------| | `input_error` | 输入错(公式格式/函数名/数据名/代码错、参数缺失非法、公式数超限;`NOT_RESOLVED` 函数/资产名无法解析、`BATCH_DEPENDENCY_VALIDATION_FAILED` 依赖顺序不完整) | 否 | **改公式或参数再试**,别原样重试。按 `errors[].leftName` + message 定位是哪条、错在哪 | | `server_timeout` | 后端排队超时(`QUEUE_WAIT_TIMEOUT`)/ 执行超时(`EXECUTION_STALLED`) | 否 | **不是你公式的问题——别改公式、也别马上重试**(马上重试还是一样超时)。可:① 隔几分钟再试;② 把公式拆成更小批次;③ 改用异步/research 模式。连试两次仍超时就告诉用户后端繁忙 | | `server_error` | 后端崩(`WORKER_FATAL` / `SUBMIT_FAILED` / `TASK_NOT_FOUND`) | 否 | 后端异常,可隔一会儿试**一次**;仍失败就把情况告诉用户,别反复空转 | | `transport_recoverable` | 连接断了(`STREAM_INTERRUPTED`),但任务在后端**还活着** | **是** | 用 `resumeJob`(task_id + trace_id)断点续传接着读结果,**不要**重新提交整批 | **最常见的误区(务必避免):** - ❌ 看到「批量公式执行失败」就去**改公式**——若 `category=server_timeout`,公式没错,改了也没用。 - ❌ 看到超时就**立刻原样重试**——系统性超时短时间内重试必然又超时,纯属空转。 - ✅ 只有 `transport_recoverable` 才值得「续传重试」,因为任务还在后端跑、只是连接断了。 - ⚠️ **`NOT_RESOLVED` 会「整批受牵连」**:后端按批原子解析,只要有**一条**公式的函数名/资产名无法解析,**整批**都会被补成 `NOT_RESOLVED`(连本来正确的公式也显示 failed)。所以看到整批 `NOT_RESOLVED` 时,**不要以为每条都错**——逐条核对、找出真正无法解析的那条改掉即可,别整批重写或原样重试。 - 服务端没给 `category` 的旧返回,客户端会兜底贴标签;`retryable` 字段与上表一致,可直接读。 --- ## 公式 / 变量 | 问题现象 | 可能原因 | 处理方式 | |----------|----------|----------| | `公式变量未找到` | 同一批公式 task_id 不一致 | 确保所有公式用同一个 task_id | | `函数不存在` / 函数名报错 | 案例原文笔误,或抄写时引入差异 | 用 `searchFunctions` 找正确函数名 | | `数据名不存在` | 公式中名称与 confirmDataMulti 的 index_title 不一致 | 以 index_title 为准 | | `公式格式检测失败:MA(dataid(...),N)` | 平台**无 `MA()` 函数** | 简单均线用 `平均("数据名", N)`;指数均线用 `ema("数据名", N)` | | `函数不存在:取出最后值` | 该函数不存在于平台 | 替代:① `取前(数据, n, 返回数值)` 获取截面 TopN;② `readData(mode=last_day_stats)` 获取最新截面 | | PE 选股包含亏损股 | 未过滤负 PE | 加 `("PE">0)` 条件 | | 多因子选股数为 0 | 非季报日截面财务数据为空 | ① 检查结束日期是否临近季报日;② 用 `readData(mode=last_day_stats)`;③ 调整至最近季报日(3/31、6/30、9/30、12/31) | --- ## 工具参数 | 问题现象 | 可能原因 | 处理方式 | |----------|----------|----------| | `参数 xxx 必填` / `400` | 参数名错误 | 读对应 `tools/<tool>.md` 确认正确字段名 | | `readData` 返回 400 | 参数名用了 `variable_names` | 改为 `ids` 数组 | | `match_quality=low` | searchSimilarCases 未找到好模板 | 调整 query 关键词,**最多重试 1 次** | | confirmDataMulti 返回 404 | 查询的是单资产价格,不是平台聚合数据 | 用 `收盘价(资产名)` 函数,无需 confirmDataMulti | | 本地资产库未命中 | intentions 用了前缀格式如 `CMX-铜` | **优先方案**:先 `grep "铜" presets/assets_db/future.yaml` 拿到规范 name(如 `CMX-铜` / `沪铜主力合约`)直接用;**处理**:未唯一命中时停止并说明需要更明确的资产名称或代码,不再远程兜底 | --- ## 图表渲染 | 问题现象 | 可能原因 | 处理方式 | |----------|----------|----------| | renderChart 图中无曲线 | 数据为二维,renderChart 仅支持一维 | 确保公式输出一维时序 | | `参数 lines 必须是非空数组` | 误用了 `variable_names` 参数 | 改为 `lines: [{id, name}]`,id 来自 runMultiFormulaBatch 的 `_id` | | K线图报 `必须包含 open_id/high_id/low_id/close_id` | candlestick 参数缺少必填字段 | 传入完整 4 个 ID;或改用 `renderKLine` | | K线图日期对不齐 / 数据缺失 | OHLC 4 个 data ID 来自不同 task_id | 确保在同一个 runMultiFormulaBatch 中计算 | | renderKLine 报 ticker 不存在 | ticker 格式错误 | 使用 `SH`/`SZ` 前缀格式如 `SH600519` | | renderKLine indicators 无效 | 指标名拼写错误 | 参照 `tools/render_kline.md` 支持列表,全小写 | | 图表不知道在哪里 | 未找到输出文件 | `call.py` 已自动保存到 `output/` 并打开 | --- ## 认证 / 网络 | 问题现象 | 可能原因 | 处理方式 | |----------|----------|----------| | `401 Unauthorized` | api_key 无效或过期 | **立即停止**,提示用户重新认证 | | `402 Quota` | 配额耗尽 | **立即停止**,提示用户等待恢复或次日重置 | | 终端命令无输出 | 终端缓冲 stdout | `call.py` 已写 `/tmp/gzq_out.txt`,用 `cat /tmp/gzq_out.txt` 读取 | ## 业务错误(HTTP 200 + success: false) 部分错误走 HTTP 200 返回,通过 `success: false` + `error` 对象区分: ```json {"code": -1, "success": false, "error": {"message": "参数 ids 必须是非空数组"}} ``` | 判断方式 | 说明 | |----------|------| | `success === false` 或 `code !== 0` | 均可判定为错误 | | `error.message` | 错误描述 | > 业务错误与 429 配额超限使用相同的 `{ success: false, error: { message } }` 结构。 > 429/503 的 `error` 中额外包含 `code`(语义化字符串)和恢复时间字段。 > 调用方应先检查 HTTP status(429/503),再检查 body 的 `success` / `code`。 --- ## 配额限流(429 错误码) | 错误码 | 含义 | 处理方式 | |--------|------|----------| | `WINDOW_QUOTA_EXCEEDED` | 窗口 RU 已耗尽 | **停止调用**,告知用户等待时间(`error.nextResetIn` 秒后最早一批恢复) | | `DAILY_QUOTA_EXCEEDED` | 今日 RU 已耗尽 | **停止调用**,告知用户次日 00:00 重置(`error.resetIn` 秒) | | `DAILY_SCAN_EXCEEDED` | IC 扫描今日次数已满 | **停止调用**,告知用户次日 00:00 重置(free=2次/天,plus=10次/天)(`error.resetIn` 秒) | | `RATE_LIMIT_EXCEEDED` | 每分钟请求过于频繁 | **静默等待** `error.retryAfter` 秒后重试,不暴露给用户 | | `CONCURRENT_LIMIT` | 有计算任务正在执行 | **静默等待** `error.retryAfter` 秒后重试,不暴露给用户 | | `SERVICE_OVERLOADED` | 系统熔断(503) | **静默等待** `error.retryAfter` 秒后重试 1 次;若仍失败则告知用户"系统繁忙,请稍后重试" | --- ## 数据下载 | 问题现象 | 可能原因 | 处理方式 | |----------|----------|----------| | downloadData 返回 403 | 计算结果 `provider=dunhe`,无权限 | 改用 `readData(mode="range_data", start_date=..., end_date=...)` | | 下载到全量历史几千行 | 未传 begin_date | 调用前先问用户要哪段时间 | | 上传数据 NaN 率高 | CSV 列标题缺少交易所后缀 | 改为 `600519.SH` 格式 | --- ## 版本不匹配(SKILL_VERSION_MISMATCH / 服务端要求升级) | 问题现象 | 类型 | 处理方式 | |----------|------|----------| | 工具返回 `"error": "SKILL_VERSION_MISMATCH"` | 本地 session 与本地 SKILL.md 不一致 | 按 SKILL.md 硬规则 #8 (A):`newSession` + 重读 SKILL.md / workflow / tools,重跑任务 | | 工具返回 message 含 `[QBS:SKILL_UPDATE_REQUIRED]` / 『skill 版本过低』 | 服务端要求版本高于本地 | **第一步走 Python Zip**:先检查 `scripts/self_update.py` 是否存在——存在则 cd 到 SKILL_ROOT 执行 `python_zip_cmd` 字段命令;不存在则自行编写内联 Python 脚本(urllib 下载 → sha512 校验 → zipfile/shutil 解压 → 保留 config.json → 覆盖 SKILL_ROOT);Python 本身不可用时再按 `npx update` → `npx add` 顺序处理;成功后重读 SKILL.md / CHANGELOG 最新 5 条 / workflow / tools,再 `newSession` 重跑 | | `npx skills update` 成功 | 已安装老用户更新成功 | 重读 SKILL.md 确认版本,再 `newSession` 重跑 | | `update` 报 `not installed` / `skill not found` | 用户从未安装过本 skill | 改用 `npx skills add pseudo-longinus/quant-buddy-skills -g --all`;安装完成后,新 SKILL_ROOT 为输出中标注的路径(通常 `~\.agents\skills\quant-buddy-skill`);**若该路径与原始 SKILL_ROOT 不同,必须先读取原始 SKILL_ROOT/config.json 的 api_key 并写入新 SKILL_ROOT/config.json(保持其他字段不变),防止 API Key 因路径变更丢失**;完成后重读 SKILL.md,再 `newSession` 重跑 | | `update` / `add` 报 `command not found` / 无 Node/npx | Node 工具链不可用 | 若 message 协议块含 `python_zip_available=true`、`zip_url`、`zip_sha512`,进入 Python Zip Fallback | | `update` / `add` 报 `EACCES` / `EPERM` / symlink 权限 / 网络错误 | npx 更新链路不可用 | 进入 Python Zip Fallback;不要继续追加 `--copy` 或反复重试 npx | | Python Zip 的 SHA-512 不一致 | 下载包损坏或非服务端记录版本 | 放弃该 zip 包并提示重试,禁止解压或替换正式 skill 目录 | | Python Zip 内 `SKILL.md` 版本不等于 `required_version` | 下载包版本错误 | 放弃该 zip 包并输出原始错误 | | `newSession` 返回的 `skill_version` 旧于服务端提示版本 | 服务端要求版本高于本地 | 同「服务端要求升级」流程 | | 所有更新路径均失败 | 用户环境问题 | 把原始错误整段交给用户;**禁止**改本地版本号字符串或 session 文件凑数 | > ⚠️ **P0 红线**:任何情况下都禁止用编辑工具修改 `SKILL.md` / `config.json` / `scripts/*.py` / `CHANGELOG.md` 中的 `version` 字段,或改写 `.session.json` 的 `skill_version_at_creation` 来"伪装版本一致"。这是欺骗式自愈,本地工具签名仍是旧的,下一次调用必然继续报错;服务端审计日志也会记录真实上报版本,伪造无效。
-
-
scripts
-
call.py 95.9 KB
#!/usr/bin/env python3 """ 轻量调用封装器。通过环境变量传 JSON 参数,彻底绕过 PowerShell GBK 编码和双引号问题。 renderChart 自动保存 PNG 并打开(无需额外调 saveChart)。 用法(Claude 在终端执行): 方式1 —— 环境变量(推荐): GZQ_PARAMS='{"query":"收盘价"}' python scripts/call.py searchFunctions 方式2 —— @file 传参(跨平台备用): python scripts/call.py searchFunctions @params.json 方式3 —— 命令行传参(仅 bash/zsh): python scripts/call.py searchFunctions '{"query":"收盘价"}' 方式4 —— 管道传参(macOS/Linux): echo '{"query":"收盘价"}' | python scripts/call.py searchFunctions ❌ 常见错误用法(会被前置校验立即拒绝,不要这样用): ❌ python scripts/call.py @params.json # 缺工具名:把 @file 当成了工具名 ❌ python scripts/call.py '{"tool":"x","params":{}}' # 把“结果 JSON 的长相”误当成输入格式 ❌ python scripts/call.py fastquery @params.json # 工具名写错/大小写不符(正确为 fast_query) 正确:python scripts/call.py <工具名> @params.json,工具名必须排在最前,且为已注册工具。 原理: 1. 从 GZQ_PARAMS 环境变量读取 JSON(PowerShell 赋值字符串时不剥双引号) 2. 调用 executor.py <tool> @tmpfile 3. renderChart 额外处理:自动保存 PNG + 打开 4. 打印结果到 stdout + 写入临时目录下 gzq_out.txt 5. 清理临时文件 """ import base64 import io import json import os import re import subprocess import sys import tempfile import uuid from task_context import ( TaskContextError, build_new_session_context, build_turn_context, clear_turn_session_context, inject_or_validate_task_id, inject_or_validate_turn_context, may_replace_session_task_id, normalize_agent_intent, record_turn_tracking_diagnostic, session_context_fields, tracking_reason_code, tracking_result_outcome, turn_session_fields, ) try: import select as _select except Exception: # pragma: no cover _select = None SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) SKILL_ROOT = os.path.dirname(SCRIPT_DIR) EXECUTOR = os.path.join(SCRIPT_DIR, "executor.py") # newSession 的 config/api_key 解析复用 executor.py 的 load_config()/resolve_api_key(), # 不再自己重读一遍 config.json/config.local.json/QUANT_BUDDY_API_KEY(见下方 newSession 分支)。 if SCRIPT_DIR not in sys.path: sys.path.insert(0, SCRIPT_DIR) import executor as _ex # noqa: E402 # call.py 自身处理(不走 executor)的本地工具 _LOCAL_TOOLS = {"newSession", "beginTurn", "saveChart", "webSearch", "buildEventStudy"} def _known_tools(): """工具白名单 = executor.TOOL_ROUTES 的 key(网络工具,单一事实源) + call.py 本地处理的工具。 实现说明:**不 import executor**——executor.py 在模块级会重包裹 sys.stdout/stderr(TextIOWrapper over .buffer),import 会破坏 call.py 已配置的 stdio 并在解释器退出时抛 “I/O operation on closed file.”。 因此改为静态解析 executor.py 源码提取 TOOL_ROUTES / SAVE_CHART_TOOL, 既保持单一事实源、又零副作用。解析失败时退化为仅本地工具。 """ names = set(_LOCAL_TOOLS) try: with open(EXECUTOR, "r", encoding="utf-8") as handle: src = handle.read() lines = src.splitlines() in_routes = False for line in lines: stripped = line.strip() if not in_routes: if re.match(r"TOOL_ROUTES\s*=\s*\{", stripped): in_routes = True continue # 到达独立的右花括号即结束(避免被 "/data/{id}" 占位符误判) if stripped.startswith("}"): break km = re.match(r'["\']([A-Za-z0-9_]+)["\']\s*:', stripped) if km: names.add(km.group(1)) m2 = re.search(r'SAVE_CHART_TOOL\s*=\s*["\']([A-Za-z0-9_]+)["\']', src) if m2: names.add(m2.group(1)) except Exception: pass return names def _reject_bad_tool_name(tool_name): """工具名前置校验:@ 开头或未知工具立即报错退出, 把“静默挂死读 stdin”变成“瞬时明确报错”。 """ known = _known_tools() bad_reason = None if tool_name.startswith("@"): bad_reason = ( f"疑似把 @file 当成了工具名:'{tool_name}'。" "正确用法:python scripts/call.py <工具名> @params.json" ) elif tool_name not in known: bad_reason = ( f"未知工具名:'{tool_name}'。工具名必须排在最前,且为已注册工具。" ) if bad_reason is None: return err = { "code": 1, "success": False, "error": "INVALID_TOOL_NAME", "message": bad_reason, "known_tools": sorted(known), } out = json.dumps(err, ensure_ascii=False, indent=2) try: out_file = os.path.join(tempfile.gettempdir(), "gzq_out.txt") with open(out_file, "w", encoding="utf-8") as _f: _f.write(out) except Exception: pass print(out) sys.exit(1) def _read_stdin_nonblocking(): """只在 stdin 确有数据时读取,避免 agent/CI 空管道下无限阻塞。 - tty:直接返回 None(交互式不读) - POSIX:用 select 判定是否有数据; - Windows:select 对管道会报 WSAStartup,用 PeekNamedPipe 检查管道可读字节; 普通重定向文件直接读取。 返回 str 或 None。 """ try: if sys.stdin is None or sys.stdin.isatty(): return None except Exception: return None if sys.platform == "win32": try: import ctypes import msvcrt handle = msvcrt.get_osfhandle(sys.stdin.fileno()) kernel32 = ctypes.windll.kernel32 file_type = kernel32.GetFileType(ctypes.c_void_p(handle)) FILE_TYPE_DISK = 0x0001 FILE_TYPE_PIPE = 0x0003 if file_type == FILE_TYPE_PIPE: available = ctypes.c_ulong(0) ok = kernel32.PeekNamedPipe( ctypes.c_void_p(handle), None, 0, None, ctypes.byref(available), None, ) if not ok or available.value <= 0: return None elif file_type != FILE_TYPE_DISK: return None data = sys.stdin.buffer.read() return data.decode("utf-8", errors="replace").strip() except Exception: return None if _select is not None: try: ready, _, _ = _select.select([sys.stdin], [], [], 0) if not ready: return None except Exception: return None else: return None try: data = sys.stdin.buffer.read() return data.decode("utf-8", errors="replace").strip() except Exception: return None def _configure_parent_stdio(): """启动时把 sys.stdout/stderr 尽量重配为 UTF-8 + replace 模式, 防止 Windows GBK 终端在遇到 emoji 等字符时直接抛 UnicodeEncodeError。 只做 best-effort;失败不影响主流程。 """ for attr in ("stdout", "stderr"): stream = getattr(sys, attr) if stream is None: continue try: enc = getattr(stream, "encoding", None) or "utf-8" # 已是 utf-8 + replace,不需要重设 if enc.lower().replace("-", "") == "utf8" and getattr(stream, "errors", None) == "replace": continue buf = getattr(stream, "buffer", None) if buf is not None: new_stream = io.TextIOWrapper(buf, encoding="utf-8", errors="replace", line_buffering=True) setattr(sys, attr, new_stream) except Exception: pass def _safe_print(text: str, *, is_stderr: bool = False) -> None: """安全打印:先尝试正常 print;若遇到 UnicodeEncodeError, 用当前终端编码的 replace 策略写入 buffer; 若仍失败,至少打印一条纯 ASCII 提示,告知结果已存入 gzq_out.txt。 """ target = sys.stderr if is_stderr else sys.stdout try: print(text, end="", file=target) return except UnicodeEncodeError: pass # 第二层:buffer 直写 + replace try: enc = getattr(target, "encoding", None) or "utf-8" buf = getattr(target, "buffer", None) if buf is not None: buf.write(text.encode(enc, errors="replace")) buf.flush() return except Exception: pass # 第三层:纯 ASCII 提示 out_file = os.path.join(tempfile.gettempdir(), "gzq_out.txt") try: print( f"[call.py] Output contains unencodable characters; " f"full result saved to {out_file}", file=target, ) except Exception: pass def _resolve_session_file(): """按优先级解析 session 文件路径,支持多会话并行: 1) QBS_SESSION_FILE 环境变量直接指定路径(最高优先级) 2) QBS_SESSION_KEY 派生为 .session.<key>.json 3) 默认 .session.json(向后兼容单会话场景) """ explicit = os.environ.get("QBS_SESSION_FILE", "").strip() if explicit: return explicit key = os.environ.get("QBS_SESSION_KEY", "").strip() if key: # 仅允许字母数字、连字符、下划线,防止路径注入 safe_key = re.sub(r"[^A-Za-z0-9_\-]", "_", key)[:64] return os.path.join(SKILL_ROOT, "output", f".session.{safe_key}.json") return os.path.join(SKILL_ROOT, "output", ".session.json") SESSION_FILE = _resolve_session_file() # 部分工具需要比 900s 更长的 subprocess 超时(与 executor.TOOL_TIMEOUTS 对齐) # runMultiFormulaBatchStream 走 SSE 主路径,可能超过 5min,这里允许走完 30min _TOOL_SUBPROCESS_TIMEOUTS = { "runMultiFormulaBatchStream": 1800, } _DEFAULT_SUBPROCESS_TIMEOUT = 900 def _cleanup_stale_sessions(max_age_days: int = 7): """清理 output/ 下超过 max_age_days 的 .session.*.json,best-effort,失败不抛。""" try: import glob import time cutoff = time.time() - max_age_days * 86400 for path in glob.glob(os.path.join(SKILL_ROOT, "output", ".session.*.json")): try: if os.path.getmtime(path) < cutoff: os.remove(path) except Exception: pass except Exception: pass def _read_skill_version() -> str: """从 SKILL.md frontmatter 读取 version 字段;读取失败时返回空字符串。""" skill_md = os.path.join(SKILL_ROOT, "SKILL.md") try: with open(skill_md, "r", encoding="utf-8") as f: for line in f: line = line.strip() if line.startswith("version:"): return line.split(":", 1)[1].strip() except Exception: pass return "" def _parse_version_tuple(version): """把 4.21.2 / v4.21.2 解析为整数 tuple;解析失败返回 None。""" if not version: return None text = str(version).strip() if text.startswith(("v", "V")): text = text[1:] parts = text.split(".") if not parts: return None nums = [] for part in parts: if not re.fullmatch(r"\d+", part): return None nums.append(int(part)) return tuple(nums) def _compare_versions(left, right): """语义版本比较:left > right 返回 1;相等 0;left < right 返回 -1;未知返回 None。""" lv = _parse_version_tuple(left) rv = _parse_version_tuple(right) if lv is None or rv is None: return None width = max(len(lv), len(rv)) lv = lv + (0,) * (width - len(lv)) rv = rv + (0,) * (width - len(rv)) if lv > rv: return 1 if lv < rv: return -1 return 0 def _is_newer_version(target_version, current_version): relation = _compare_versions(target_version, current_version) if relation is None: return str(target_version or "") != str(current_version or "") return relation > 0 def _read_session(): """读取当前 session 的 task_id,不存在则返回 None。""" try: with open(SESSION_FILE, "r", encoding="utf-8") as f: return json.load(f).get("task_id") except Exception: return None def _read_session_full(): """读取当前 session 的全量字段,不存在则返回空 dict。""" try: with open(SESSION_FILE, "r", encoding="utf-8") as f: return json.load(f) or {} except Exception: return {} def _update_session_trace(task_id, trace_id): """在不丢失现有字段的前提下,将 SSE 返回的 trace_id 追加到 session。仅当 task_id 与 session 中一致或 session 原本为空时才写,避免污染别的 session。""" if not trace_id: return cur = _read_session_full() if cur.get("task_id") and task_id and cur["task_id"] != task_id: return if cur.get("trace_id") == trace_id: return cur["trace_id"] = trace_id if task_id and not cur.get("task_id"): cur["task_id"] = task_id if not cur.get("skill_version_at_creation"): cur["skill_version_at_creation"] = _read_skill_version() try: os.makedirs(os.path.dirname(SESSION_FILE), exist_ok=True) with open(SESSION_FILE, "w", encoding="utf-8") as f: json.dump(cur, f, ensure_ascii=False) except Exception: pass def _write_session(task_id, user_query=None, task_context=None, turn_context=None, clear_turn=False, agent_intent=None): """持久化 Session/Turn;未登记 Turn 时只保留真实业务上下文。""" os.makedirs(os.path.dirname(SESSION_FILE), exist_ok=True) previous = _read_session_full() same_task = str(previous.get("task_id") or "") == str(task_id or "") data = dict(previous) if same_task and not task_context else {} data.update({"task_id": task_id, "skill_version_at_creation": _read_skill_version()}) if user_query is not None: data["user_query"] = user_query # legacy compatibility if task_context: data.update(session_context_fields(task_context)) if turn_context: data.update(turn_session_fields(turn_context, previous if same_task else {})) data["user_query"] = turn_context.get("user_query") # legacy readers elif clear_turn: clear_turn_session_context(data, user_query=user_query, agent_intent=agent_intent) with open(SESSION_FILE, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False) def _post_turn(endpoint, api_key, channel, path, turn_context, agent_model=None, user_id=None): """Create a Turn and return the validated server response; never mutates local state.""" import urllib.request payload_dict = {key: value for key, value in turn_context.items() if value is not None} if agent_model: payload_dict["agent_model"] = agent_model if user_id: payload_dict["user_id"] = user_id headers = { "Content-Type": "application/json", "Authorization": f"Bearer {api_key}", "x-skill-name": "quant-buddy-skill", "x-task-id": turn_context["task_id"], "x-turn-id": turn_context["turn_id"], "x-skill-version": _read_skill_version(), } if channel: headers["x-skill-channel"] = channel req = urllib.request.Request( f"{endpoint}{path}", data=json.dumps(payload_dict, ensure_ascii=False).encode("utf-8"), headers=headers, method="POST", ) with urllib.request.urlopen(req, timeout=30) as response: body = json.loads(response.read().decode("utf-8")) if not isinstance(body, dict): raise RuntimeError("Turn tracking response is not an object") if body.get("code") == 0 and body.get("success") is not False and body.get("tracking_recorded") is not False: # message_id 幂等重试可能返回首次写入时的 canonical turn_id;客户端必须采用服务端值。 body["task_id"] = turn_context["task_id"] if body.get("turn_id") is not None: body["turn_id"] = str(body.get("turn_id") or "").strip() body["agent_intent"] = normalize_agent_intent( body.get("agent_intent") if "agent_intent" in body else turn_context.get("agent_intent") ) return body SELF_UPDATE_SCRIPT = os.path.join(SCRIPT_DIR, "self_update.py") # 自更新当日去重状态文件(output/ 受保护、跨版本共享) SELF_UPDATE_STATE_FILE = os.path.join(SKILL_ROOT, "output", ".self_update_state.json") VERSION_CHECK_STATE_FILE = os.path.join(SKILL_ROOT, "output", ".version_check_state.json") # 同一天、同一 target_version 的失败上限:一次触发内已含 3 次下载重试;失败后当日不再重复下载 SELF_UPDATE_DAILY_FAIL_CAP = 1 # self_update 子进程总超时:必须 >= 下载(5min) x 重试(3) + 安装余量,否则会被外层提前杀死 SELF_UPDATE_SUBPROC_TIMEOUT = 1200 # 20min # newSession 主动版本检查节流:默认 1 小时最多查一次;成功响应心跳只记录待更新 try: VERSION_CHECK_TTL_SECONDS = max(0, int(os.environ.get("QBS_VERSION_CHECK_TTL_SECONDS", "3600") or "3600")) except ValueError: VERSION_CHECK_TTL_SECONDS = 3600 def _today_str(): import time as _t return _t.strftime("%Y-%m-%d") def _read_self_update_state(): try: with open(SELF_UPDATE_STATE_FILE, "r", encoding="utf-8") as f: data = json.load(f) return data if isinstance(data, dict) else {} except Exception: return {} def _write_self_update_state(state): try: os.makedirs(os.path.dirname(SELF_UPDATE_STATE_FILE), exist_ok=True) with open(SELF_UPDATE_STATE_FILE, "w", encoding="utf-8") as f: json.dump(state, f, ensure_ascii=False, indent=2) except Exception: pass def _read_version_check_state(): try: with open(VERSION_CHECK_STATE_FILE, "r", encoding="utf-8") as f: data = json.load(f) return data if isinstance(data, dict) else {} except Exception: return {} def _write_version_check_state(skill_version, version_info=None, error=None): import time as _t state = { "checked_at": int(_t.time()), "skill_version": skill_version, "schema_version": (version_info or {}).get("schema_version"), "latest_version": (version_info or {}).get("latest_version"), "update_required": bool((version_info or {}).get("update_required")), "companions": (version_info or {}).get("companions") if isinstance((version_info or {}).get("companions"), list) else [], "error": error, } try: os.makedirs(os.path.dirname(VERSION_CHECK_STATE_FILE), exist_ok=True) with open(VERSION_CHECK_STATE_FILE, "w", encoding="utf-8") as f: json.dump(state, f, ensure_ascii=False, indent=2) except Exception: pass def _should_run_new_session_version_check(skill_version): """newSession 的主动版本检查只做节流补充;成功响应心跳负责记录 pending update。""" if os.environ.get("QBS_FORCE_VERSION_CHECK", "").strip() in ("1", "true", "TRUE", "yes", "YES"): return True, "forced" state = _read_version_check_state() try: import time as _t age = int(_t.time()) - int(state.get("checked_at") or 0) except Exception: age = VERSION_CHECK_TTL_SECONDS + 1 if state.get("skill_version") != skill_version: return True, "skill_version_changed" if age >= VERSION_CHECK_TTL_SECONDS: return True, "ttl_expired" return False, "ttl_active" # 会话内内存标记:本进程是否已对某 target_version 触发过更新(一个会话最多一次) _SELF_UPDATE_TRIED_THIS_RUN = set() def _self_update_target_version(self_update_info): """从 self_update / error 信息里尽量解析出目标版本号,作为去重键。""" if not isinstance(self_update_info, dict): return "" v = self_update_info.get("version") or self_update_info.get("target_version") if v: return str(v) args = self_update_info.get("args") or [] if isinstance(args, list): for i, a in enumerate(args): if a == "--version" and i + 1 < len(args): return str(args[i + 1]) return "" def _self_update_gate(target_version): """返回 (allowed: bool, short_circuit_reason: str|None)。 去重键 = 当日 + target_version。规则: - 本会话已对该 target_version 触发过 → 短路(会话内一次) - 同日 + 同 target_version + status=failed 且 attempts>=cap → 短路 - status=in_progress(充当软锁)→ 短路,避免并发重复触发 - target_version 变化或换天 → 放行 """ if not target_version: return True, None if target_version in _SELF_UPDATE_TRIED_THIS_RUN: return False, f"本会话已尝试更新到 {target_version},不再重复触发;如需更新请新开会话(newSession)。" state = _read_self_update_state() if state.get("date") == _today_str() and state.get("target_version") == target_version: status = state.get("status") attempts = int(state.get("attempts") or 0) if status == "in_progress": return False, "更新进行中,请稍后重试。" if status == "failed" and attempts >= SELF_UPDATE_DAILY_FAIL_CAP: return False, ( f"今日已自动更新 {attempts} 次失败(目标 {target_version}):{state.get('last_error')};" "当日不再重复下载,请手动执行 self_update 或检查网络。" ) return True, None def _self_update_mark(target_version, status, last_error=None): import time as _t state = _read_self_update_state() same = state.get("date") == _today_str() and state.get("target_version") == target_version attempts = int(state.get("attempts") or 0) if same else 0 prev_status = state.get("status") if same else None if status == "failed" and prev_status != "failed": attempts += 1 new_state = { "date": _today_str(), "target_version": target_version, "attempts": attempts, "status": status, "last_error": last_error, "ts": int(_t.time()), } _write_self_update_state(new_state) def _record_pending_self_update(self_update_info, source="heartbeat"): """记录待更新信息,但不安装激活新版。 长 session 中只能“发现并准备下一次 newSession 更新”,不能直接替换当前 SKILL_ROOT,否则下一次工具调用会因 session 版本与磁盘版本不一致被本地守卫拦住。 """ result = {"recorded": False, "skipped": False, "reason": None, "target_version": None} if not isinstance(self_update_info, dict) or not self_update_info.get("available"): result["reason"] = "self_update not available" return result target_version = _self_update_target_version(self_update_info) result["target_version"] = target_version or None if not target_version: result["reason"] = "self_update target version missing" return result current_version = _read_skill_version() if current_version and not _is_newer_version(target_version, current_version): result["skipped"] = True result["reason"] = ( f"target version {target_version} is not newer than current {current_version}; " "skip self_update to avoid downgrade." ) return result state = _read_self_update_state() if state.get("target_version") == target_version and state.get("status") == "pending": result["skipped"] = True result["reason"] = "pending update already recorded" return result import time as _t pending_state = { "date": _today_str(), "target_version": target_version, "attempts": int(state.get("attempts") or 0) if state.get("target_version") == target_version else 0, "status": "pending", "last_error": None, "ts": int(_t.time()), "source": source, "current_version": current_version, "activation": "next_newSession", "self_update": self_update_info, } _write_self_update_state(pending_state) result["recorded"] = True return result def _get_pending_self_update(): state = _read_self_update_state() if state.get("status") != "pending": return None self_update_info = state.get("self_update") if not isinstance(self_update_info, dict) or not self_update_info.get("available"): return None target_version = _self_update_target_version(self_update_info) current_version = _read_skill_version() if target_version and current_version and not _is_newer_version(target_version, current_version): _self_update_mark(target_version, "ok") return None return self_update_info def _attempt_self_update(self_update_info, timeout=None, skip_dedup=False): """根据服务端返回的 self_update 对象自动执行 scripts/self_update.py。 self_update_info 形如: {"available": True, "script": "scripts/self_update.py", "args": ["--version","4.20.15","--url","...","--sha512","...","--zip-skill-path","..."]} 返回 dict: {"attempted": bool, "ok": bool, "new_version": str|None, "stdout": str, "stderr": str, "error": str|None} """ result = { "attempted": False, "ok": False, "new_version": None, "stdout": "", "stderr": "", "error": None, "skipped": False, } if timeout is None: timeout = SELF_UPDATE_SUBPROC_TIMEOUT # ── 当日去重闸门:避免逢错必重下、并发重复触发 ── _target_ver = _self_update_target_version(self_update_info) _current_ver = _read_skill_version() if _target_ver and _current_ver and not _is_newer_version(_target_ver, _current_ver): result["skipped"] = True result["error"] = ( f"target version {_target_ver} is not newer than current {_current_ver}; " "skip self_update to avoid downgrade." ) return result if not skip_dedup: _allowed, _reason = _self_update_gate(_target_ver) if not _allowed: result["skipped"] = True result["error"] = _reason return result if not isinstance(self_update_info, dict): result["error"] = "self_update info missing" return result if not self_update_info.get("available"): result["error"] = "self_update not available" return result args = self_update_info.get("args") or [] if not isinstance(args, list) or not args: result["error"] = "self_update.args missing" return result if not os.path.exists(SELF_UPDATE_SCRIPT): result["error"] = f"self_update.py not found at {SELF_UPDATE_SCRIPT}" return result result["attempted"] = True if _target_ver: _SELF_UPDATE_TRIED_THIS_RUN.add(_target_ver) _self_update_mark(_target_ver, "in_progress") try: proc = subprocess.run( [sys.executable, SELF_UPDATE_SCRIPT, *[str(a) for a in args]], capture_output=True, timeout=timeout, env={**os.environ, "PYTHONIOENCODING": "utf-8"}, ) result["stdout"] = proc.stdout.decode("utf-8", errors="replace") if proc.stdout else "" result["stderr"] = proc.stderr.decode("utf-8", errors="replace") if proc.stderr else "" if proc.returncode == 0: # self_update.py 成功时输出 JSON:{"code":0,"success":true,"package_version":"X.Y.Z",...} try: payload = json.loads(result["stdout"]) if payload.get("success") and payload.get("code") == 0: result["ok"] = True result["new_version"] = payload.get("package_version") else: result["error"] = payload.get("error") or "self_update returned non-success payload" except Exception as e: result["error"] = f"failed to parse self_update output: {e}" else: result["error"] = f"self_update exited with code {proc.returncode}" except subprocess.TimeoutExpired: result["error"] = f"self_update timed out after {timeout}s" except Exception as e: result["error"] = f"self_update launch failed: {e}" if _target_ver: if result.get("ok"): _self_update_mark(_target_ver, "ok") else: _self_update_mark(_target_ver, "failed", last_error=result.get("error")) return result def _detect_skill_version_outdated(stdout_text): """解析 executor.py 的 stdout,判断是否被服务端版本拦截。 返回 (matched: bool, error_obj: dict|None)。 error_obj 含 self_update / latest_version 等结构化字段。 """ if not stdout_text: return False, None try: resp = json.loads(stdout_text) except Exception: return False, None if not isinstance(resp, dict): return False, None err = resp.get("error") if not isinstance(err, dict): return False, None if err.get("type") == "SKILL_VERSION_OUTDATED" or err.get("update_required") is True: return True, err return False, None def _run_executor(tool_name, param_arg): """调用 executor.py,返回 (returncode, stdout_bytes, stderr_bytes)。 对会输出流式进度的工具使用 Popen + 后台线程实时转发 stderr, 让用户在终端能逐条看到公式/后台任务完成进度;其余工具仍走 capture_output=True。 """ import signal as _signal import threading # Windows: 将 executor.py 放到新进程组,防止 console Ctrl+C 广播干扰子进程 extra_flags = subprocess.CREATE_NEW_PROCESS_GROUP if sys.platform == "win32" else 0 # 在父进程中临时忽略 SIGINT,防止 communicate() 被残留信号中断 try: old_handler = _signal.signal(_signal.SIGINT, _signal.SIG_IGN) except (OSError, ValueError): old_handler = None try: env = os.environ.copy() env["PYTHONIOENCODING"] = "utf-8" sub_timeout = _TOOL_SUBPROCESS_TIMEOUTS.get(tool_name, _DEFAULT_SUBPROCESS_TIMEOUT) # ── 流式进度工具:实时转发 stderr ────────────── stream_progress_tools = {"runMultiFormulaBatchStream", "resumeJob"} if tool_name in stream_progress_tools: proc = subprocess.Popen( [sys.executable, EXECUTOR, tool_name, param_arg], stdout=subprocess.PIPE, stderr=subprocess.PIPE, creationflags=extra_flags, env=env, ) stderr_chunks: list[bytes] = [] def _stream_stderr(): for raw in proc.stderr: stderr_chunks.append(raw) try: sys.stderr.write(raw.decode("utf-8", errors="replace")) sys.stderr.flush() except Exception: pass t = threading.Thread(target=_stream_stderr, daemon=True) t.start() try: stdout_bytes = proc.stdout.read() except Exception: stdout_bytes = b"" try: proc.wait(timeout=sub_timeout) except subprocess.TimeoutExpired: proc.kill() t.join(timeout=5) msg = ( f"[call.py] tool '{tool_name}' exceeded {sub_timeout}s timeout; " "subprocess killed. Consider splitting into smaller batches." ) return 124, b"", msg.encode("utf-8") t.join(timeout=10) # stderr 已由线程实时打印到终端,这里返回空 bytes 避免 main() 二次输出 return proc.returncode, stdout_bytes, b"" # ── 其余工具:原有 capture_output 路径 ────────────────────── result = subprocess.run( [sys.executable, EXECUTOR, tool_name, param_arg], capture_output=True, timeout=sub_timeout, creationflags=extra_flags, env=env, ) return result.returncode, result.stdout, result.stderr except subprocess.TimeoutExpired: # 子进程超时,返回明确错误码与提示,避免向上抛出导致客户端崩溃 msg = ( f"[call.py] tool '{tool_name}' exceeded {sub_timeout}s timeout; " "subprocess killed. Consider splitting into smaller batches." ) return 124, b"", msg.encode("utf-8") except KeyboardInterrupt: # 捕获并重试一次(处理极端情况) try: result = subprocess.run( [sys.executable, EXECUTOR, tool_name, param_arg], capture_output=True, timeout=_TOOL_SUBPROCESS_TIMEOUTS.get(tool_name, _DEFAULT_SUBPROCESS_TIMEOUT), creationflags=extra_flags, env=env, ) return result.returncode, result.stdout, result.stderr except subprocess.TimeoutExpired: msg = ( f"[call.py] tool '{tool_name}' exceeded subprocess timeout on retry; " "subprocess killed." ) return 124, b"", msg.encode("utf-8") finally: if old_handler is not None: try: _signal.signal(_signal.SIGINT, old_handler) except (OSError, ValueError): pass def _auto_save_chart(stdout_str, params): """renderChart 后自动保存 PNG,返回修改后的 stdout 供打印。支持 JSON 和 YAML 两种格式。""" # ── JSON 模式 ── try: data = json.loads(stdout_str) return _save_chart_from_json(data, params) except (json.JSONDecodeError, ValueError): pass # ── YAML 模式 ── return _save_chart_from_yaml(stdout_str, params) def _save_chart_from_json(data, params): """从 JSON dict 提取 base64 并保存。""" if data.get("code") != 0: return json.dumps(data, indent=2, ensure_ascii=False) b64 = (data.get("data") or {}).get("base64", "") if not b64: return json.dumps(data, indent=2, ensure_ascii=False) out_path = _save_b64_to_png(b64, params) data["data"]["base64"] = f"<{len(b64)} chars>" data["data"]["saved_to"] = out_path data["data"]["auto_opened"] = sys.platform == "win32" return json.dumps(data, indent=2, ensure_ascii=False) def _save_chart_from_yaml(stdout_str, params): """从 YAML 文本提取 base64 并保存。""" code_m = re.search(r'^code:\s*(\d+)', stdout_str, re.MULTILINE) if not code_m or code_m.group(1) != '0': return stdout_str # 在 YAML 行中查找 base64 字段 b64 = None for line in stdout_str.split('\n'): stripped = line.strip() if stripped.startswith('base64:'): b64 = stripped[len('base64:'):].strip().strip("'\"") break if not b64: return stdout_str out_path = _save_b64_to_png(b64, params) # 替换 YAML 中的 base64 为摘要 replacement = f"'<{len(b64)} chars, saved to {out_path}>'" stdout_str = stdout_str.replace(b64, replacement, 1) return stdout_str def _save_b64_to_png(b64, params): """解码 base64 保存 JPG,返回文件路径。""" b64_clean = b64.split(",", 1)[1] if b64.startswith("data:") else b64 # 修复 padding(base64 长度必须是 4 的倍数) b64_clean += "=" * (4 - len(b64_clean) % 4) name = params.get("title", params.get("name", "chart")) name = "".join(c if c.isalnum() or c in "-_" else "_" for c in name) output_dir = os.path.join(SKILL_ROOT, "output") os.makedirs(output_dir, exist_ok=True) out_path = os.path.join(output_dir, f"{name}.jpg") img_data = base64.b64decode(b64_clean) try: from io import BytesIO from PIL import Image img = Image.open(BytesIO(img_data)).convert("RGB") img.save(out_path, "JPEG", quality=90) except ImportError: # Pillow 未安装时直接写原始字节(保留服务端格式) with open(out_path, "wb") as f: f.write(img_data) if sys.platform == "win32": os.startfile(out_path) return out_path # 维度标准顺序(用于重算 overall_score 时保持一致) _DIM_ORDER = ["D1_估值", "D3_资金", "D4_波动率", "D5_宏观胜率", "D6_相关资产", "D7_技术形态", "D8_季节性", "D9_财务"] def _score_to_signal(score): for lo, hi, label in [ (80, 101, "强看多 ^^"), (65, 80, "偏多 ^"), (55, 65, "中性偏多 ->^"), (45, 55, "中性 ->"), (35, 45, "中性偏空 ->v"), (20, 35, "偏空 v"), (0, 20, "强看空 vv"), ]: if lo <= score < hi: return label return "中性 ->" def _auto_save_scan_dimensions(stdout_str, params): """scanDimensions 后**合并写入** output/ic_data/,返回精简摘要。 分维度调用时(dimensions=["D1_估值"] 等),新扫描的维度覆盖旧结果, 其他维度保留,并重算 overall_score / overall_signal / top_dimension / bottom_dimension。 全量一次性调用与单次效果相同。 """ try: resp = json.loads(stdout_str) except (json.JSONDecodeError, ValueError): return stdout_str if resp.get("code") != 0: return stdout_str data = resp.get("data", {}) # 提取资产名称(优先从请求参数取,其次从响应取) name = "" if isinstance(params.get("asset"), dict): name = params["asset"].get("name", "") if not name: name = (data.get("stock_name") or data.get("stock") or data.get("asset", {}).get("name") or "unknown") ic_dir = os.path.join(SKILL_ROOT, "output", "ic_data") os.makedirs(ic_dir, exist_ok=True) out_path = os.path.join(ic_dir, f"{name}_dimension_ic.json") # ── 合并已有文件 ───────────────────────────────────── merged = {} if os.path.exists(out_path): try: with open(out_path, "r", encoding="utf-8") as f: merged = json.load(f) except Exception: merged = {} # 顶层字段:新数据优先(dimensions 单独处理) for k, v in data.items(): if k not in ("dimensions", "overall_score", "overall_signal", "top_dimension", "bottom_dimension"): merged[k] = v # 合并 dimensions:只更新本次真正被计算的维度(indicators 非空), # 其余保留已有结果,避免后端返回全量默认值时覆盖已有好数据。 existing_dims = merged.get("dimensions", {}) new_dims = data.get("dimensions", {}) computed_dims = {k: v for k, v in new_dims.items() if v.get("indicators")} # 非空 indicators = 真正计算过 existing_dims.update(computed_dims) merged["dimensions"] = existing_dims # ── 重算综合数值 ───────────────────────────────────── raw_scores = [] dim_scores = [] for dim in _DIM_ORDER: if dim not in existing_dims: continue s = existing_dims[dim].get("score", 50) w = 0.5 if dim == "D8_季节性" else 1.0 dim_scores.append({"name": dim, "score": s}) raw_scores.extend([s] * round(w * 10)) if raw_scores: overall = round(sum(raw_scores) / len(raw_scores)) merged["overall_score"] = overall merged["overall_signal"] = _score_to_signal(overall) merged["top_dimension"] = max(dim_scores, key=lambda x: x["score"]) merged["bottom_dimension"] = min(dim_scores, key=lambda x: x["score"]) with open(out_path, "w", encoding="utf-8") as f: json.dump(merged, f, ensure_ascii=False, indent=2) # ── 精简摘要(仅打印本次新扫维度) ─────────────────── dim_summary = {} for dim_key in list(new_dims.keys()): dim_val = existing_dims.get(dim_key, {}) if isinstance(dim_val, dict): dim_summary[dim_key] = { "score": dim_val.get("score"), "signal": dim_val.get("signal"), "indicators": [ {"label": ind.get("label") or ind.get("name"), "current": ind.get("current_value"), "ic_ir": ind.get("ic_ir")} for ind in (dim_val.get("indicators") or [])[:3] ] } summary = { "code": 0, "data": { "asset": params.get("asset", {"name": name}), "scan_date": merged.get("scan_date"), "overall_score": merged.get("overall_score"), "overall_signal": merged.get("overall_signal"), "new_dimensions": sorted(new_dims.keys()), "file_has_dimensions": sorted(existing_dims.keys()), "new_dim_results": dim_summary, "saved_to": out_path, "note": ( f"新增/更新 {len(new_dims)} 个维度," f"文件已含 {len(existing_dims)}/8 个维度。" + ("" if len(existing_dims) >= 8 else f" 剩余未扫:{sorted(set(_DIM_ORDER) - set(existing_dims.keys()))}") ) }, "task_id": resp.get("task_id") } return json.dumps(summary, indent=2, ensure_ascii=False) def _auto_save_csv(stdout_str, params): """downloadData 后自动保存 CSV 到 output/,打印摘要而非原文。""" try: data = json.loads(stdout_str) except (json.JSONDecodeError, ValueError): # 后端直接返回 CSV 文本(Content-Type: text/csv)时 executor 已包一层 JSON return stdout_str if data.get("code") != 0: return json.dumps(data, indent=2, ensure_ascii=False) inner = data.get("data", {}) # format=json 路径:inner 包含 labels/values # format=csv 路径:inner 是 CSV 字符串 if isinstance(inner, str): # executor 把 CSV 文本放在 data 字段 csv_text = inner data_name = params.get("id", "download") total_rows = len(csv_text.strip().split('\n')) - 1 elif isinstance(inner, dict) and "labels" in inner: # json 路径:重新组装 CSV labels = inner.get("labels", []) values = inner.get("values", []) csv_text = "date,value\n" + "\n".join( f"{lbl},{'' if v is None else v}" for lbl, v in zip(labels, values) ) data_name = inner.get("data_name", params.get("id", "download")) total_rows = len(labels) else: return json.dumps(data, indent=2, ensure_ascii=False) # 保存到 output/ name = "".join(c if c.isalnum() or c in "-_" else "_" for c in str(data_name)) output_dir = os.path.join(SKILL_ROOT, "output") os.makedirs(output_dir, exist_ok=True) out_path = os.path.join(output_dir, f"{name}.csv") with open(out_path, "w", encoding="utf-8-sig", newline="") as f: f.write(csv_text) summary = { "code": 0, "data": { "saved_to": out_path, "total_rows": total_rows, "data_name": data_name, } } if isinstance(inner, dict): for k in ("provider", "dimension", "begin_date", "end_date"): if k in inner: summary["data"][k] = inner[k] return json.dumps(summary, indent=2, ensure_ascii=False) def _auto_summarize_read_data(stdout_str, params): """readData last_column_full 模式:为布尔掩码数据注入 matched_names 平铺列表。 触发条件:mode=last_column_full 且数据为布尔型(signature.is_bool=true 或值域仅含 0/1)。 效果:注入 matched_count + matched_names(value=1 的命中名单),便于 LLM 直接读取, 无需遍历 values 列表(如 5000 行只需读 matched_names 的 75 个名字)。 零值过滤由后端 allow_zero_values 参数控制,此处不再重复过滤: - allow_zero_values=false(默认):后端已过滤零值,values 仅含命中行 - allow_zero_values=true:后端保留零值,values 含全部行,matched_names 仍仅含命中名单 非布尔型数据原样返回,不做任何修改。 """ if params.get("mode") != "last_column_full": return stdout_str try: resp = json.loads(stdout_str) except (json.JSONDecodeError, ValueError): return stdout_str if resp.get("code") != 0: return stdout_str outer_data = resp.get("data", {}) if not isinstance(outer_data, dict): return stdout_str inner_list = outer_data.get("data", []) if not isinstance(inner_list, list) or not inner_list: return stdout_str modified = False for item in inner_list: if not isinstance(item, dict): continue lcf = item.get("last_column_full") if not isinstance(lcf, dict): continue values = lcf.get("values", []) if not isinstance(values, list) or not values: continue # 判断是否为布尔型:优先读 signature.is_bool,其次抽样检测值域 sig = item.get("signature") or {} is_bool = sig.get("is_bool", False) if not is_bool: sample = [v.get("value") for v in values[:200] if v.get("value") is not None] if sample and all(v in (0, 1, 0.0, 1.0) for v in sample): is_bool = True if not is_bool: continue # 布尔型:注入 matched_count + matched_names(仅统计命中=1的行) # values 列表本身不修改(已由后端按 allow_zero_values 控制) matched = [v for v in values if v.get("value") in (1, 1.0)] lcf["matched_count"] = len(matched) lcf["matched_names"] = [v.get("name", "") for v in matched if v.get("name")] modified = True if not modified: return stdout_str return json.dumps(resp, indent=2, ensure_ascii=False) def _process_run_multi_formula_batch(stdout_str): """runMultiFormulaBatchStream 后处理:把 SSE done 的 compact 负载归一化成文档信封。 SSE done 事件返回的是 compact 形态(顶层 status+summary+results、无 code/data 外壳、 字段叫 indexinfo_id),与 tools/run_multi_formula.md 承诺的同步版结构 ({code:0, success, data:{results:[{data_id,…}], summary, status}, task_id})不一致。 本函数兑现 executor 的「与同步版一致」契约,按三态处理: 1. fatal / 非SSE-JSON 错误(顶层有 code 且 != 0,如 INVALID_FORMULA)→ 原样透出。 2. compact 形态(顶层 status + results 列表、无 data 外壳)→ 包成文档信封: 注入 data_id = indexinfo_id(保留 indexinfo_id 向后兼容);失败/部分失败时在顶层 补 message 与 errors:[{leftName,formula,errorCode,message}]。 3. 旧同步 {code:0, data:{success,errors,…}} 形态(同步 fallback)→ 维持原失败提升逻辑。 """ try: resp = json.loads(stdout_str) except (json.JSONDecodeError, ValueError): return stdout_str if not isinstance(resp, dict): return stdout_str # ── 状态 1:fatal / 非 SSE JSON 错误,顶层 code 已是 -1(或其他非 0)→ 原样透出 if resp.get("code") not in (0, None): return stdout_str # ── 状态 2:compact 形态(顶层 status + results 列表、无 data 外壳) if ( "data" not in resp and isinstance(resp.get("results"), list) and resp.get("status") is not None and resp.get("status") != "deferred" ): status = resp.get("status") summary = resp.get("summary") or {} norm_results = [] for r in resp.get("results", []): if isinstance(r, dict): rr = dict(r) # 文档以 data_id 作为 readData 串联键;compact 里叫 indexinfo_id if "indexinfo_id" in rr and "data_id" not in rr: rr["data_id"] = rr.get("indexinfo_id") norm_results.append(rr) else: norm_results.append(r) envelope = { "code": 0, "success": (status == "ok"), "task_id": _read_session(), "data": { "status": status, "summary": summary, "results": norm_results, }, } # request_id 等顶层元信息透传(不含 results 内的 trace_id,由后续剥离块处理) if resp.get("request_id") is not None: envelope["request_id"] = resp["request_id"] if status != "ok": failed = [ r for r in norm_results if isinstance(r, dict) and r.get("status") != "success" ] total = summary.get("total", len(norm_results)) succ = summary.get("success", max(total - len(failed), 0)) fail = summary.get("failed", len(failed)) if succ == 0: envelope["message"] = ( f"runMultiFormulaBatchStream 全部失败({fail}/{total} 条),详见 errors 数组。" ) else: envelope["message"] = ( f"runMultiFormulaBatchStream 部分失败(成功 {succ}/{total},失败 {fail}/{total})," f"成功结果仍在 data.results 中,失败详情见 errors。" ) envelope["errors"] = [ { # 失败结果对象用 expression_id 存左变量名(compact 结果无 leftName/formula 字段), # 必须兜底到 expression_id,否则 errors[] 的 leftName 恒为 null,agent 认不出是哪条公式失败(错位) "leftName": r.get("leftName") or r.get("expression_id"), "formula": r.get("formula"), "errorCode": r.get("errorCode"), # 透传服务端的分类标签,让 agent 分清「该改公式 vs 后端问题」,别原样重试 "category": r.get("category"), "retryable": r.get("retryable"), "guidance": r.get("guidance"), "message": r.get("message"), } for r in failed ] return json.dumps(envelope, indent=2, ensure_ascii=False) # ── 状态 3:旧同步形态 {code:0, data:{success,errors,…}} data = resp.get("data", {}) if not isinstance(data, dict): return stdout_str # 仅在 success=false 时介入;success=true 或缺失(旧版本)时原样透传 if data.get("success") is not False: return stdout_str errors = data.get("errors") or [] dep = data.get("dependency_analysis") or {} total = data.get("total", len(errors)) error_count = data.get("errorCount", len(errors)) success_count = data.get("successCount", max(total - error_count, 0)) resp["success"] = False resp["errors"] = [ { "formula": e.get("formula"), "leftName": e.get("leftName"), "error": e.get("error"), "errorType": e.get("errorType"), } for e in errors ] if success_count == 0: resp["message"] = ( f"runMultiFormulaBatchStream 全部失败({error_count}/{total} 条),详见 errors 数组。" ) else: resp["message"] = ( f"runMultiFormulaBatchStream 部分失败(成功 {success_count}/{total},失败 {error_count}/{total})," f"成功结果仍在 data.data 中,失败详情见 errors。" ) can_retry = dep.get("can_incremental_retry", False) if can_retry: resp["can_incremental_retry"] = True if dep.get("incremental_retry_suggestion"): resp["incremental_retry_suggestion"] = dep["incremental_retry_suggestion"] return json.dumps(resp, indent=2, ensure_ascii=False) def _maybe_abort_on_client_validation(params): """若 _normalize_params 注入了客户端校验错误,立即输出并退出,避免无效远程调用。""" if isinstance(params, dict) and "__client_validation_error__" in params: msg = params.pop("__client_validation_error__") print(json.dumps({"code": 1, "message": msg}, ensure_ascii=False)) sys.exit(1) def _abort_on_run_multi_formula_missing_params(tool_name, params): if tool_name != "runMultiFormulaBatchStream": return if not isinstance(params, dict): print(json.dumps({"code": 1, "message": "runMultiFormulaBatchStream 参数必须是 JSON object。"}, ensure_ascii=False)) sys.exit(1) if not params.get("task_id"): print(json.dumps({ "code": 1, "message": ( "task_id 必填。请先调用 newSession 获取 task_id;" "若通过 call.py 调用,请确认 GZQ_PARAMS、@file 或命令行 JSON 中包含有效参数。" ), }, ensure_ascii=False)) sys.exit(1) formulas = params.get("formulas") if not isinstance(formulas, list) or not formulas: print(json.dumps({ "code": 1, "message": ( "formulas 必须是非空数组。" "请检查 GZQ_PARAMS、@file 或命令行 JSON 中是否包含至少一条公式字符串。" ), }, ensure_ascii=False)) sys.exit(1) def _normalize_params(tool_name, params): """常见参数名错误自动修正,减少 LLM 调用失败率。""" if not isinstance(params, dict): return params # runMultiFormulaBatchStream: formulas 元素必须是字符串,不能是对象 if tool_name == "runMultiFormulaBatchStream" and "formulas" in params: fixed = [] for item in params["formulas"]: if isinstance(item, str): fixed.append(item) elif isinstance(item, dict): # 尝试从对象中提取公式字符串 f = item.get("formula") or item.get("expression") or item.get("value") or "" if f: fixed.append(f) params["formulas"] = fixed # confirmDataMulti: 把常见错误参数名归一化到 data_desc # 错误形式:{"queries": ["A", "B"]} / {"query": "A"} / {"descriptions": [...]} # 正确形式:{"data_desc": "A,B"}(字符串,逗号分隔) if tool_name == "confirmDataMulti" and "data_desc" not in params: for alias in ("queries", "query", "descriptions", "description", "names", "data_descs"): if alias in params: v = params.pop(alias) if isinstance(v, list): params["data_desc"] = ",".join(str(x).strip() for x in v if str(x).strip()) else: params["data_desc"] = str(v) break # readData: 检测中文/变量名形式的 ids 误用,返回明确错误而非传到服务端 # 也把 variable_names / variable_name / index_title 这类常见错误参数名归一化 if tool_name == "readData": for alias in ("variable_names", "variable_name", "index_title", "index_titles", "name", "names"): if alias in params and "ids" not in params: v = params.pop(alias) if isinstance(v, list): params["ids"] = v else: params["ids"] = [v] break # 进一步校验 ids 必须是 hex data_id(24 字符 16 进制),否则提前拦截 ids = params.get("ids") if isinstance(ids, list) and ids: import re as _re bad = [x for x in ids if not (isinstance(x, str) and _re.fullmatch(r"[0-9a-fA-F]{24}", x))] if bad: # 直接把校验信息塞回参数,由 executor 后续上抛——不擅自调用,避免参数被 server 误吞 params["__client_validation_error__"] = ( "readData 的 ids 必须是 runMultiFormulaBatchStream 返回的 24 位 hex data_id;" f"以下值不是合法 data_id:{bad}。如需读取本批某个变量,请先在 runMultiFormulaBatchStream 返回的 results 中找到该变量的 _id,再传入 ids。" ) return params def main(): _configure_parent_stdio() if len(sys.argv) < 2: print(__doc__) sys.exit(1) tool_name = sys.argv[1] # 工具名前置校验:非法/缺失立即报错,杜绝“静默挂死读 stdin” _reject_bad_tool_name(tool_name) # ── beginTurn:同一 Session 中登记一条新的用户消息 ── if tool_name == "beginTurn": raw = os.environ.get("GZQ_PARAMS", "").strip() if not raw and len(sys.argv) >= 3: if sys.argv[2].startswith("@"): try: raw = open(sys.argv[2][1:], "r", encoding="utf-8").read() except Exception: raw = "{}" else: raw = " ".join(sys.argv[2:]) attempted_turn = None try: turn_params = json.loads(raw or "{}") session_data = _read_session_full() attempted_turn = build_turn_context(session_data, turn_params, uuid_factory=uuid.uuid4) response = {} trusted_turn = None tracking_recorded = False reason_code = None try: cfg = _ex.load_config() api_key = _ex.resolve_api_key(turn_params, cfg) endpoint = (cfg.get("endpoint") or "").rstrip("/") if not endpoint or not api_key: raise RuntimeError("缺少 endpoint 或 api_key") response = _post_turn( endpoint, api_key, cfg.get("_channel", ""), "/skill/session/turn", attempted_turn, agent_model=str(turn_params.get("agent_model") or "").strip() or None, user_id=turn_params.get("user_id"), ) tracking_recorded, canonical_turn_id, canonical_intent, reason_code = tracking_result_outcome( response, attempted_turn["task_id"], attempted_turn["turn_id"] ) if tracking_recorded: trusted_turn = { **attempted_turn, "turn_id": canonical_turn_id, "agent_intent": ( canonical_intent if "agent_intent" in response -
companion_manager.py 29.1 KB
#!/usr/bin/env python3 """Install and update optional QBS companion skills from a verified package. The module is intentionally standard-library only so packaged QBS installations can run it without adding dependencies. All failures are returned as structured soft results; callers decide how to expose them without blocking market-data workflows. """ from __future__ import annotations import contextlib import hashlib import json import os import re import shutil import subprocess import tempfile import time import urllib.request import uuid import zipfile from pathlib import Path, PurePosixPath from typing import Dict, Iterable, Optional, Tuple COMPANION_NAME = "quant-buddy-view" STATE_FILENAME = ".companion_state.json" LOCK_FILENAME = ".companion_update.lock" SHARED_LOCK_FILENAME = ".quant-buddy-view.update.lock" MANAGED_MARKER = ".managed-install.json" DEFAULT_PRESERVE = ("config.json", "config.local.json", "output", "logs") LOCK_STALE_SECONDS = 2 * 60 * 60 DOWNLOAD_TIMEOUT_SECONDS = 300 DOWNLOAD_ATTEMPTS = 3 KNOWN_AGENT_SKILL_CONTAINERS = frozenset({".agents", ".claude", ".cursor", ".codex"}) SEMVER_RE = re.compile( r"^v?(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)" r"(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$" ) class CompanionError(RuntimeError): pass class LockBusy(CompanionError): pass def _truthy(value: object) -> bool: return str(value or "").strip().lower() in {"1", "true", "yes", "on"} def _parse_semver(value: object) -> Optional[Tuple[Tuple[int, int, int], Tuple[str, ...]]]: match = SEMVER_RE.fullmatch(str(value or "").strip()) if not match: return None return ( (int(match.group(1)), int(match.group(2)), int(match.group(3))), tuple(match.group(4).split(".")) if match.group(4) else (), ) def _compare_identifier(left: str, right: str) -> int: left_numeric = left.isdigit() right_numeric = right.isdigit() if left_numeric and right_numeric: return (int(left) > int(right)) - (int(left) < int(right)) if left_numeric != right_numeric: return -1 if left_numeric else 1 return (left > right) - (left < right) def compare_semver(left: object, right: object) -> int: """Compare SemVer values. Returns -1, 0, or 1.""" a = _parse_semver(left) b = _parse_semver(right) if a is None or b is None: raise CompanionError(f"invalid semantic version: {left!r} or {right!r}") if a[0] != b[0]: return (a[0] > b[0]) - (a[0] < b[0]) a_pre, b_pre = a[1], b[1] if not a_pre and not b_pre: return 0 if not a_pre: return 1 if not b_pre: return -1 for index in range(max(len(a_pre), len(b_pre))): if index >= len(a_pre): return -1 if index >= len(b_pre): return 1 compared = _compare_identifier(a_pre[index], b_pre[index]) if compared: return compared return 0 def read_skill_version(skill_root: Path) -> str: try: with (skill_root / "SKILL.md").open("r", encoding="utf-8") as handle: for raw_line in handle: line = raw_line.strip() if line.startswith("version:"): return line.split(":", 1)[1].strip().strip('"\'') except Exception: return "" return "" def _has_git_ancestor(path: Path) -> bool: current = path while True: if (current / ".git").exists(): return True if current.parent == current: return False current = current.parent def _is_known_agent_skills_root(skills_root: Path) -> bool: return ( skills_root.name.lower() == "skills" and skills_root.parent.name.lower() in KNOWN_AGENT_SKILL_CONTAINERS ) def resolve_skills_root( qbs_root: Path, environ: Optional[Dict[str, str]] = None, canonical_root: Optional[Path] = None, ) -> Dict[str, object]: """Resolve the canonical directory that owns sibling skill installations. Explicit QBS_COMPANION_SKILLS_ROOT wins. Otherwise a real/canonical QBS path whose parent is named ``skills`` is preferred, which collapses Claude/Cursor links onto a shared ``~/.agents/skills`` target. A normal logical ``skills`` parent is the fallback. Unknown layouts are soft-skipped rather than guessed. """ env = os.environ if environ is None else environ logical_root = Path(qbs_root).absolute() explicit = str(env.get("QBS_COMPANION_SKILLS_ROOT", "") or "").strip() if explicit: explicit_path = Path(explicit) if not explicit_path.is_absolute(): return {"ok": False, "reason": "QBS_COMPANION_SKILLS_ROOT must be absolute"} skills_root = explicit_path.resolve(strict=False) return { "ok": True, "source": "environment", "logical_qbs_root": logical_root, "canonical_qbs_root": (canonical_root or logical_root.resolve(strict=False)), "skills_root": skills_root, "dev_checkout": False, } canonical = (canonical_root or logical_root.resolve(strict=False)) if canonical.parent.name.lower() == "skills": skills_root = canonical.parent source = "canonical_parent" elif logical_root.parent.name.lower() == "skills": skills_root = logical_root.parent source = "logical_parent" else: return { "ok": False, "reason": "cannot determine companion skills root safely", "logical_qbs_root": logical_root, "canonical_qbs_root": canonical, } resolved_skills_root = skills_root.resolve(strict=False) return { "ok": True, "source": source, "logical_qbs_root": logical_root, "canonical_qbs_root": canonical, "skills_root": resolved_skills_root, "dev_checkout": ( False if _is_known_agent_skills_root(resolved_skills_root) else _has_git_ancestor(canonical) ), } def _normalized_absolute(path: Path) -> str: return os.path.normcase(os.path.abspath(str(path))) def _is_directory_link(path: Path) -> bool: if path.is_symlink(): return True isjunction = getattr(os.path, "isjunction", None) return bool(isjunction and isjunction(path)) def _directory_link_points_to(link_path: Path, target_path: Path) -> bool: try: if link_path.exists() and target_path.exists() and os.path.samefile(link_path, target_path): return True except OSError: pass try: return _normalized_absolute(link_path.resolve(strict=False)) == _normalized_absolute( target_path.resolve(strict=False) ) except OSError: return False def _create_directory_link(link_path: Path, target_path: Path) -> None: """Create a directory symlink, falling back to a Windows junction.""" if not target_path.is_dir(): raise CompanionError(f"agent registration target is not a directory: {target_path}") if link_path.exists() or link_path.is_symlink() or _is_directory_link(link_path): raise FileExistsError(f"agent registration path already exists: {link_path}") link_path.parent.mkdir(parents=True, exist_ok=True) try: os.symlink(target_path, link_path, target_is_directory=True) return except OSError as symlink_error: if os.name != "nt": raise CompanionError(f"cannot create agent skill link: {symlink_error}") from symlink_error child_env = os.environ.copy() child_env["QBS_COMPANION_LINK_PATH"] = str(link_path) child_env["QBS_COMPANION_TARGET_PATH"] = str(target_path) command = ( "$ErrorActionPreference='Stop'; " "New-Item -ItemType Junction " "-Path $env:QBS_COMPANION_LINK_PATH " "-Target $env:QBS_COMPANION_TARGET_PATH | Out-Null" ) completed = subprocess.run( ["powershell.exe", "-NoLogo", "-NoProfile", "-NonInteractive", "-Command", command], capture_output=True, text=True, encoding="utf-8", errors="replace", check=False, env=child_env, creationflags=getattr(subprocess, "CREATE_NO_WINDOW", 0), ) if completed.returncode != 0: detail = (completed.stderr or completed.stdout or str(symlink_error)).strip() raise CompanionError(f"cannot create agent skill junction: {detail}") def ensure_agent_registration( resolved: Dict[str, object], canonical_qbv_root: Path, create: bool = True, ) -> Dict[str, object]: """Ensure the current Agent's logical skills root can discover canonical QBV. Existing real directories and links to another target are never replaced. """ logical_qbs_root = Path(resolved["logical_qbs_root"]).absolute() logical_skills_root = logical_qbs_root.parent canonical_qbv_root = Path(canonical_qbv_root).absolute() logical_qbv_root = logical_skills_root / COMPANION_NAME result = { "ok": True, "created": False, "logical_root": str(logical_qbv_root), "canonical_root": str(canonical_qbv_root), } if _normalized_absolute(logical_qbv_root) == _normalized_absolute(canonical_qbv_root): return {**result, "status": "already_visible"} if logical_skills_root.name.lower() != "skills": return { **result, "ok": False, "status": "agent_logical_root_unknown", "reason": "agent_logical_root_unknown", } path_present = logical_qbv_root.exists() or logical_qbv_root.is_symlink() or _is_directory_link(logical_qbv_root) if path_present: if _directory_link_points_to(logical_qbv_root, canonical_qbv_root): return {**result, "status": "already_registered"} if _is_directory_link(logical_qbv_root): return { **result, "ok": False, "status": "agent_link_target_mismatch", "reason": "agent_link_target_mismatch", } return { **result, "ok": False, "status": "agent_registration_conflict", "reason": "agent_registration_conflict", } if not create: return {**result, "status": "missing"} try: _create_directory_link(logical_qbv_root, canonical_qbv_root) except FileExistsError: # Another updater may have won the race. Accept only the intended target. if _directory_link_points_to(logical_qbv_root, canonical_qbv_root): return {**result, "status": "already_registered"} return { **result, "ok": False, "status": "agent_registration_conflict", "reason": "agent_registration_conflict", } except Exception as exc: return { **result, "ok": False, "status": "agent_registration_failed", "reason": "agent_registration_failed", "error": str(exc), } return {**result, "created": True, "status": "registered"} def _finish_without_package_update( result: Dict[str, object], resolved: Dict[str, object], target: Path, version_reason: str, activation: object, ) -> Dict[str, object]: registration = ensure_agent_registration(resolved, target, create=True) result.update({ "agent_registration": registration, "agent_link_path": registration.get("logical_root"), "canonical_root": registration.get("canonical_root"), "agent_link_created": bool(registration.get("created")), }) if not registration.get("ok"): result.update({ "attempted": False, "ok": False, "skipped": True, "reason": registration.get("reason") or "agent_registration_failed", "error": registration.get("error"), }) elif registration.get("created"): result.update({ "attempted": True, "ok": True, "skipped": False, "action": "register", "reason": "agent_registration_created", "reload_required": True, "activation": activation or "next_agent_reload", }) else: result.update({ "attempted": False, "ok": True, "skipped": True, "reason": version_reason, }) return result def _normalize_preserve_files(values: object) -> Tuple[str, ...]: raw_values: Iterable[object] = values if isinstance(values, list) else DEFAULT_PRESERVE normalized = [] for value in raw_values: text = str(value or "").strip().replace("\\", "/") path = PurePosixPath(text) if not text or path.is_absolute() or ".." in path.parts or len(path.parts) != 1: continue if text not in normalized: normalized.append(text) return tuple(normalized or DEFAULT_PRESERVE) def _write_state(qbs_root: Path, payload: Dict[str, object]) -> None: try: state_path = qbs_root / "output" / STATE_FILENAME state_path.parent.mkdir(parents=True, exist_ok=True) body = {"schema_version": 1, "updated_at": int(time.time()), **payload} temp_path = state_path.with_name(f"{state_path.name}.{uuid.uuid4().hex}.tmp") temp_path.write_text(json.dumps(body, ensure_ascii=False, indent=2), encoding="utf-8") os.replace(temp_path, state_path) except Exception: pass @contextlib.contextmanager def _exclusive_lock(path: Path): path.parent.mkdir(parents=True, exist_ok=True) try: if path.exists() and time.time() - path.stat().st_mtime > LOCK_STALE_SECONDS: path.unlink() except OSError: pass try: descriptor = os.open(str(path), os.O_CREAT | os.O_EXCL | os.O_WRONLY) except FileExistsError as exc: raise LockBusy(f"update lock is held: {path}") from exc try: os.write(descriptor, json.dumps({"pid": os.getpid(), "ts": int(time.time())}).encode("utf-8")) os.close(descriptor) yield finally: try: path.unlink() except OSError: pass def _sha512(path: Path) -> str: digest = hashlib.sha512() with path.open("rb") as handle: for block in iter(lambda: handle.read(1024 * 1024), b""): digest.update(block) return digest.hexdigest() def _download(url: str, destination: Path) -> None: last_error = None for attempt in range(DOWNLOAD_ATTEMPTS): try: request = urllib.request.Request( url, headers={"User-Agent": "quant-buddy-skill-companion-manager/1"}, ) with urllib.request.urlopen(request, timeout=DOWNLOAD_TIMEOUT_SECONDS) as response: with destination.open("wb") as handle: shutil.copyfileobj(response, handle) return except Exception as exc: last_error = exc if attempt + 1 < DOWNLOAD_ATTEMPTS: time.sleep((1, 3, 7)[attempt]) raise CompanionError(f"download failed after {DOWNLOAD_ATTEMPTS} attempts: {last_error}") def _safe_extract(zip_path: Path, destination: Path) -> None: root = destination.resolve() with zipfile.ZipFile(zip_path) as archive: for info in archive.infolist(): name = info.filename.replace("\\", "/") relative = PurePosixPath(name) if relative.is_absolute() or ".." in relative.parts or (relative.parts and ":" in relative.parts[0]): raise CompanionError(f"unsafe zip member: {info.filename}") unix_mode = (info.external_attr >> 16) & 0o170000 if unix_mode == 0o120000: raise CompanionError(f"zip symlink is not allowed: {info.filename}") target = (destination / Path(*relative.parts)).resolve() if target != root and root not in target.parents: raise CompanionError(f"zip member escapes staging: {info.filename}") if info.is_dir() or name.endswith("/"): target.mkdir(parents=True, exist_ok=True) continue target.parent.mkdir(parents=True, exist_ok=True) with archive.open(info) as source, target.open("wb") as output: shutil.copyfileobj(source, output) def _locate_source(staging: Path, zip_skill_path: str) -> Path: normalized = str(zip_skill_path or "").strip().replace("\\", "/").strip("/") relative = PurePosixPath(normalized) if not normalized or relative.is_absolute() or ".." in relative.parts: raise CompanionError("package.zip_skill_path is required and must be a safe relative path") source = staging.joinpath(*relative.parts).resolve() if staging.resolve() not in source.parents: raise CompanionError("package.zip_skill_path escapes staging") if not source.is_dir(): raise CompanionError(f"package skill path not found: {normalized}") return source def _copy_source(source: Path, prepared: Path) -> None: shutil.copytree(source, prepared, symlinks=False) def _merge_preserved(target: Path, prepared: Path, preserve_files: Tuple[str, ...]) -> None: if not target.exists(): return for name in preserve_files: source = target / name destination = prepared / name if not source.exists() and not source.is_symlink(): continue if destination.exists() or destination.is_symlink(): if destination.is_dir() and not destination.is_symlink(): shutil.rmtree(destination) else: destination.unlink() if source.is_dir() and not source.is_symlink(): shutil.copytree(source, destination) else: shutil.copy2(source, destination) def _write_managed_marker(prepared: Path, version: str) -> None: marker = { "schema_version": 1, "manager": "quant-buddy-skill", "channel": "companion", "installed_version": version, "source_repository": "pseudo-longinus/quant-buddy-view", "source_tag": f"v{version}", } (prepared / MANAGED_MARKER).write_text( json.dumps(marker, ensure_ascii=False, indent=2), encoding="utf-8" ) def _atomic_install(prepared: Path, target: Path, backup_root: Path) -> Optional[Path]: backup_root.mkdir(parents=True, exist_ok=True) target.parent.mkdir(parents=True, exist_ok=True) backup = None if target.exists() or target.is_symlink(): backup = backup_root / f"{COMPANION_NAME}-backup-{time.strftime('%Y%m%d%H%M%S')}-{uuid.uuid4().hex[:8]}" os.replace(target, backup) try: os.replace(prepared, target) except Exception: if backup is not None and backup.exists() and not target.exists(): os.replace(backup, target) raise return backup def _policy_allows(companion: Dict[str, object]) -> Tuple[bool, str]: if companion.get("manager_compatible") is False: return False, "manager_too_old" if "eligible" in companion: return bool(companion.get("eligible")), str(companion.get("eligibility_reason") or "server_eligibility") policy = str(companion.get("install_policy") or "off") rollout = int(companion.get("rollout_percent") or 0) if policy == "off": return False, "policy_off" if policy == "default_on" and rollout >= 100: return True, "default_on_100" return False, "server_eligibility_missing" def reconcile_after_qbs_check( companions: object, qbs_root: object, manager_version: str, qbs_update_required: bool = False, environ: Optional[Dict[str, str]] = None, ) -> Optional[Dict[str, object]]: """Apply the shared QBS-first companion state machine used by all clients.""" items = companions if isinstance(companions, list) else [] has_qbv = any( isinstance(item, dict) and item.get("name") == COMPANION_NAME for item in items ) if not has_qbv: return None if qbs_update_required: return { "name": COMPANION_NAME, "attempted": False, "ok": True, "skipped": True, "reason": "qbs_update_has_priority", "reload_required": False, } return reconcile_companions(items, qbs_root, manager_version, environ=environ) def reconcile_companions( companions: object, qbs_root: object, manager_version: str, environ: Optional[Dict[str, str]] = None, ) -> Dict[str, object]: """Reconcile QBV from a version/check companion payload. Returns a structured result and never raises. QBS callers may safely continue their business workflow when ``ok`` is false. """ env = os.environ if environ is None else environ qbs_path = Path(qbs_root).absolute() force = _truthy(env.get("QBS_FORCE_COMPANION_RECONCILE")) base = { "name": COMPANION_NAME, "attempted": False, "ok": True, "reload_required": False, } if _truthy(env.get("QBS_DISABLE_COMPANION_INSTALL")): result = {**base, "skipped": True, "reason": "disabled_by_environment"} _write_state(qbs_path, result) return result items = companions if isinstance(companions, list) else [] companion = next((item for item in items if isinstance(item, dict) and item.get("name") == COMPANION_NAME), None) if not companion: result = {**base, "skipped": True, "reason": "companion_not_declared"} _write_state(qbs_path, result) return result allowed, policy_reason = _policy_allows(companion) if not allowed: result = {**base, "skipped": True, "reason": policy_reason} _write_state(qbs_path, result) return result min_manager = str(companion.get("min_manager_version") or "").strip() try: if min_manager and compare_semver(manager_version, min_manager) < 0: result = {**base, "skipped": True, "reason": "manager_too_old", "required_manager_version": min_manager} _write_state(qbs_path, result) return result except CompanionError as exc: result = {**base, "ok": False, "skipped": True, "reason": str(exc)} _write_state(qbs_path, result) return result resolved = resolve_skills_root(qbs_path, env) if not resolved.get("ok"): result = {**base, "skipped": True, "reason": resolved.get("reason")} _write_state(qbs_path, result) return result if resolved.get("dev_checkout") and not force: result = {**base, "skipped": True, "reason": "development_checkout"} _write_state(qbs_path, result) return result skills_root = Path(resolved["skills_root"]) target = skills_root / COMPANION_NAME if qbs_path == target or qbs_path in target.parents: result = {**base, "ok": False, "skipped": True, "reason": "unsafe_target_path"} _write_state(qbs_path, result) return result package = companion.get("package") if isinstance(companion.get("package"), dict) else {} target_version = str(package.get("required_version") or companion.get("latest_version") or "").strip() zip_url = str(package.get("zip_url") or "").strip() expected_sha = str(package.get("zip_sha512") or "").strip().lower() zip_skill_path = str(package.get("zip_skill_path") or "").strip() if not target_version or not zip_url or not re.fullmatch(r"[0-9a-f]{128}", expected_sha) or not zip_skill_path: result = {**base, "ok": False, "skipped": True, "reason": "invalid_companion_package"} _write_state(qbs_path, result) return result current_version = read_skill_version(target) common = { **base, "current_version": current_version or None, "target_version": target_version, "skills_root": str(skills_root), "target_root": str(target), } try: compared = compare_semver(target_version, current_version) if current_version else 1 except CompanionError as exc: result = {**common, "ok": False, "skipped": True, "reason": str(exc)} _write_state(qbs_path, result) return result if compared <= 0 and not force: version_reason = "local_version_is_newer" if compared < 0 else "already_current" result = _finish_without_package_update( common, resolved, target, version_reason, companion.get("activation") ) _write_state(qbs_path, result) return result registration_preflight = ensure_agent_registration(resolved, target, create=False) if not registration_preflight.get("ok"): result = { **common, "ok": False, "skipped": True, "reason": registration_preflight.get("reason") or "agent_registration_failed", "error": registration_preflight.get("error"), "agent_registration": registration_preflight, "agent_link_path": registration_preflight.get("logical_root"), "canonical_root": registration_preflight.get("canonical_root"), "agent_link_created": False, } _write_state(qbs_path, result) return result preserve_files = _normalize_preserve_files(companion.get("preserve_files")) qbs_lock = qbs_path / "output" / LOCK_FILENAME shared_lock = skills_root / SHARED_LOCK_FILENAME result = { **common, "attempted": True, "ok": False, "action": "install" if not current_version else ("reconcile" if compared == 0 else "update"), } try: with _exclusive_lock(qbs_lock), _exclusive_lock(shared_lock): # Re-check inside the shared lock. Another QBS/QBV updater may have # completed after the optimistic check above; never overwrite it # with the now-stale target. Registration must still be reconciled. locked_current = read_skill_version(target) if locked_current: locked_compared = compare_semver(target_version, locked_current) if locked_compared < 0 or (locked_compared == 0 and not force): result.update({"current_version": locked_current}) version_reason = "local_version_is_newer" if locked_compared < 0 else "already_current" result = _finish_without_package_update( result, resolved, target, version_reason, companion.get("activation") ) _write_state(qbs_path, result) return result skills_root.parent.mkdir(parents=True, exist_ok=True) temp_root = Path(tempfile.mkdtemp(prefix=".qbs-companion-", dir=str(skills_root.parent))) try: zip_path = temp_root / "package.zip" _download(zip_url, zip_path) actual_sha = _sha512(zip_path) if actual_sha != expected_sha: raise CompanionError(f"zip sha512 mismatch: expected {expected_sha}, got {actual_sha}") staging = temp_root / "staging" staging.mkdir() _safe_extract(zip_path, staging) source = _locate_source(staging, zip_skill_path) actual_version = read_skill_version(source) if not actual_version or compare_semver(actual_version, target_version) != 0: raise CompanionError( f"package version mismatch: expected {target_version}, got {actual_version or '(missing)'}" ) prepared = temp_root / "prepared" _copy_source(source, prepared) _merge_preserved(target, prepared, preserve_files) _write_managed_marker(prepared, actual_version) backup_root = skills_root.parent / "skill-backups" backup = _atomic_install(prepared, target, backup_root) registration = ensure_agent_registration(resolved, target, create=True) result.update({ "agent_registration": registration, "agent_link_path": registration.get("logical_root"), "canonical_root": registration.get("canonical_root"), "agent_link_created": bool(registration.get("created")), "installed_version": actual_version, "backup_path": str(backup) if backup else None, "activation": companion.get("activation") or "next_agent_reload", }) if not registration.get("ok"): result.update({ "ok": False, "reason": registration.get("reason") or "agent_registration_failed", "error": registration.get("error"), }) else: if registration.get("created"): result["action"] = f"{result['action']}_and_register" result.update({"ok": True, "reload_required": True}) finally: shutil.rmtree(temp_root, ignore_errors=True) except LockBusy as exc: result.update({"attempted": False, "skipped": True, "reason": "update_lock_held", "error": str(exc)}) except Exception as exc: result.update({"error": str(exc)}) _write_state(qbs_path, result) return result __all__ = [ "COMPANION_NAME", "compare_semver", "read_skill_version", "resolve_skills_root", "ensure_agent_registration", "reconcile_companions", ] -
event_study_local.py 9.3 KB
"""本地辅助工具:通用外部事实搜索(Bocha 兜底)+ 事件研究公式生成。 供 call.py 直接调用,不经过 executor.py(不是平台 HTTP API)。 """ from __future__ import annotations import json import os import re from datetime import datetime, date from dateutil.relativedelta import relativedelta from pathlib import Path from typing import Any import requests SKILL_ROOT = Path(__file__).resolve().parents[1] # ── 配置 ────────────────────────────────────────────────────── # 事件研究专用配置(窗口别名、默认值等),内嵌即可 _EVENT_DEFAULTS: dict[str, Any] = { "default_mode": "single", "default_windows": [5, 21], "window_aliases": { "1周": 5, "一周": 5, "5日": 5, "2周": 10, "两周": 10, "10日": 10, "1月": 21, "一个月": 21, "21日": 21, "3月": 63, "三个月": 63, "63日": 63, "半年": 126, "6月": 126, "126日": 126, "1年": 252, "一年": 252, "252日": 252, }, } def _load_bocha_api_key() -> str: """读取博查 API key,优先级:环境变量 > config.local.json > config.json""" env_key = os.environ.get("BOCHA_API_KEY", "").strip() if env_key: return env_key for filename in ("config.local.json", "config.json"): path = SKILL_ROOT / filename if path.exists(): try: with path.open("r", encoding="utf-8") as f: cfg = json.load(f) key = cfg.get("bocha_api_key", "").strip() if key: return key except Exception: continue return "" # ── 通用外部事实 Web 搜索(Bocha 兜底)────────────────────── _BOCHA_BASE_URL = "https://api.bochaai.com/v1/web-search" _BOCHA_TIMEOUT = 15 _BOCHA_COUNT = 8 def _freshness_param(months_back: int = 36) -> str: today = date.today() start = today - relativedelta(months=months_back) return f"{start.strftime('%Y-%m-%d')}..{today.strftime('%Y-%m-%d')}" def bocha_web_search(query: str, freshness_months: int = 36, count: int | None = None) -> dict[str, Any]: """调用博查 web-search API,返回通用外部事实核验所需的结构化搜索结果。""" api_key = _load_bocha_api_key() if not api_key: return {"ok": False, "error": "BOCHA_API_KEY 未配置(环境变量 / config.local.json / config.json)", "results": []} headers = { "Content-Type": "application/json", "Authorization": f"Bearer {api_key}", } payload = { "query": query, "freshness": _freshness_param(freshness_months), "summary": True, "count": count or _BOCHA_COUNT, } try: resp = requests.post(_BOCHA_BASE_URL, headers=headers, json=payload, timeout=_BOCHA_TIMEOUT) resp.raise_for_status() data = resp.json() except Exception as exc: return {"ok": False, "error": str(exc), "results": []} raw: list[dict[str, Any]] = [] if "data" in data and "webPages" in data.get("data", {}) and "value" in data["data"]["webPages"]: raw = data["data"]["webPages"]["value"] elif "data" in data and isinstance(data["data"], list): raw = data["data"] items = [] for item in raw: items.append({ "title": item.get("name", ""), "url": item.get("url", item.get("link", "")), "snippet": item.get("summary", item.get("snippet", "")), "published": item.get("datePublished", "") or item.get("publishedTime", ""), }) return {"ok": True, "query": query, "count": len(items), "results": items} # ── 公式生成 ────────────────────────────────────────────────── def parse_dates(values: list[Any]) -> list[int]: parsed: list[int] = [] for value in values: if isinstance(value, int): parsed.append(value) continue text = str(value).strip().replace("-", "") if not text: continue parsed.append(int(text)) return parsed def parse_windows(values: list[Any] | None) -> list[int]: aliases = _EVENT_DEFAULTS["window_aliases"] raw_values = values or _EVENT_DEFAULTS["default_windows"] windows: list[int] = [] for value in raw_values: if isinstance(value, int): windows.append(value) continue text = str(value).strip() if text.isdigit(): windows.append(int(text)) continue if text in aliases: windows.append(int(aliases[text])) continue raise ValueError(f"不支持的窗口写法: {value}") return windows def int_to_date(value: int) -> datetime: return datetime.strptime(str(value), "%Y%m%d") def overlap_warning(dates: list[int], windows: list[int]) -> list[str]: if len(dates) < 2 or not windows: return [] sorted_dates = sorted(dates) min_gap = min((int_to_date(b) - int_to_date(a)).days for a, b in zip(sorted_dates, sorted_dates[1:])) max_window = max(windows) if min_gap < max_window: return [f"事件最小自然日间距为 {min_gap} 天,小于最大窗口 {max_window},结果可能出现窗口重叠。"] return [] def build_single_formulas(prefix: str, asset: str, dates: list[int], windows: list[int]) -> list[str]: formulas = [ f"{prefix}_事件日=选取日期({','.join(str(d) for d in dates)})", f"{prefix}_收盘=收盘价({asset})", f"{prefix}_日收益=涨跌幅(\"{prefix}_收盘\")", ] for window in windows: formulas.append( f"{prefix}_后{window}日路径=某天后累加(\"{prefix}_日收益\",\"{prefix}_事件日\",{window})" ) formulas.append(f"{prefix}_后{window}日收益=分段最终值(\"{prefix}_后{window}日路径\")") return formulas def build_compare_formulas( prefix: str, asset: str, group_a_name: str, group_a_dates: list[int], group_b_name: str, group_b_dates: list[int], windows: list[int], ) -> list[str]: formulas = [ f"{prefix}_{group_a_name}=选取日期({','.join(str(d) for d in group_a_dates)})", f"{prefix}_{group_b_name}=选取日期({','.join(str(d) for d in group_b_dates)})", f"{prefix}_收盘=收盘价({asset})", f"{prefix}_日收益=涨跌幅(\"{prefix}_收盘\")", ] for name in (group_a_name, group_b_name): for window in windows: formulas.append( f"{prefix}_{name}后{window}日路径=某天后累加(\"{prefix}_日收益\",\"{prefix}_{name}\",{window})" ) formulas.append( f"{prefix}_{name}后{window}日收益=分段最终值(\"{prefix}_{name}后{window}日路径\")" ) return formulas def sanitize_group_name(name: str, fallback: str) -> str: cleaned = re.sub(r"[^0-9A-Za-z\u4e00-\u9fff]", "", name or "") return cleaned or fallback def build_event_study(params: dict[str, Any]) -> dict[str, Any]: mode = str(params.get("mode") or _EVENT_DEFAULTS["default_mode"]) prefix = str(params.get("prefix") or "ES") windows = parse_windows(params.get("windows")) if mode == "single": dates = parse_dates(params.get("dates") or []) asset = str(params.get("asset") or "") if not dates: raise ValueError("single 模式需要 dates。") if not asset: raise ValueError("single 模式需要 asset。") return { "mode": "single", "asset": asset, "dates": dates, "windows": windows, "warnings": overlap_warning(dates, windows), "formulas": build_single_formulas(prefix=prefix, asset=asset, dates=dates, windows=windows), } if mode == "compare": asset = str(params.get("asset") or "") if not asset: raise ValueError("compare 模式需要 asset。") group_a_name = sanitize_group_name(str(params.get("group_a_name") or "A组"), "A组") group_b_name = sanitize_group_name(str(params.get("group_b_name") or "B组"), "B组") group_a_dates = parse_dates(params.get("group_a_dates") or []) group_b_dates = parse_dates(params.get("group_b_dates") or []) if not group_a_dates or not group_b_dates: raise ValueError("compare 模式需要 group_a_dates 和 group_b_dates。") warnings = [] warnings.extend(overlap_warning(group_a_dates, windows)) warnings.extend(overlap_warning(group_b_dates, windows)) return { "mode": "compare", "asset": asset, "group_a_name": group_a_name, "group_a_dates": group_a_dates, "group_b_name": group_b_name, "group_b_dates": group_b_dates, "windows": windows, "warnings": warnings, "formulas": build_compare_formulas( prefix=prefix, asset=asset, group_a_name=group_a_name, group_a_dates=group_a_dates, group_b_name=group_b_name, group_b_dates=group_b_dates, windows=windows, ), } raise ValueError(f"不支持的 mode: {mode}") -
executor.py 60.6 KB
#!/usr/bin/env python3 r""" 观照量化投研 Skill 执行器 Usage: python scripts/executor.py <tool_name> @<params_file.json> # 推荐,跨平台安全 python scripts/executor.py <tool_name> '<json_params>' # 仅 bash/zsh,不含中文时可用 PowerShell(Windows)— 必须用 @file 传参,中文字符在命令行参数中会被 GBK codepage 截断: # Step 1: 写参数文件 echo '{"query": "均线金叉回测", "top_k": 3}' > /tmp/p.json # Step 2: 调用(结果直接打印到终端,不要再管道到文件) python scripts/executor.py searchSimilarCases @/tmp/p.json # ❌ 禁止:以下两种方式均在 Windows 中文环境下必然失败 # python scripts\executor.py searchFunctions '{"query": "回测"}' # 中文 GBK 截断 # echo '...' | python scripts\executor.py searchFunctions # GBK 管道 bash/zsh: python scripts/executor.py searchFunctions '{"query": "回测", "top_k": 5}' """ import sys import io import json import os import time import re import urllib.request import urllib.error from datetime import datetime # ── 跳过 Windows 注册表代理检测(proxy_bypass_registry 在某些 Windows 环境极慢)── # 使用空 ProxyHandler() 完全绕过系统代理,量化 API 是内网地址无需代理 _NO_PROXY_OPENER = urllib.request.build_opener(urllib.request.ProxyHandler({})) def _read_skill_version() -> str: """从 SKILL.md frontmatter 读取 version 字段;读取失败时返回空字符串。""" skill_md = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "SKILL.md") try: with open(skill_md, "r", encoding="utf-8") as f: for line in f: line = line.strip() if line.startswith("version:"): return line.split(":", 1)[1].strip() except Exception: pass return "" def _read_skill_channel() -> str: """从 config.json 读取 _channel 字段(打包时注入);读取失败时返回空字符串。""" cfg = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "config.json") try: import json as _json with open(cfg, "r", encoding="utf-8") as f: return _json.load(f).get("_channel", "") except Exception: pass return "" SKILL_VERSION = _read_skill_version() SKILL_CHANNEL = _read_skill_channel() def _trace_headers(params=None, *, api_key=None, accept=None, content_type=None): params = params if isinstance(params, dict) else {} headers = { "x-skill-name": "quant-buddy-skill", "x-skill-version": SKILL_VERSION, } task_id = str(params.get("task_id") or "").strip() if task_id: headers["x-task-id"] = task_id turn_id = str(params.get("turn_id") or "").strip() if turn_id: headers["x-turn-id"] = turn_id if api_key: headers["Authorization"] = f"Bearer {api_key}" if accept: headers["Accept"] = accept if content_type: headers["Content-Type"] = content_type if SKILL_CHANNEL: headers["x-skill-channel"] = SKILL_CHANNEL return headers # ── Windows 下强制 stdout/stderr 使用 UTF-8,避免服务端返回 emoji 等字符时崩溃 ── # 不要用新的 TextIOWrapper 包装现有 stream.buffer:executor/common 在同一进程内依次 # import 时,旧 wrapper 析构会关闭共享 buffer,导致 pytest 报 ``I/O operation on # closed file``。原地 reconfigure 不转移 buffer 所有权,也兼容 pytest 捕获流。 def _configure_stdio_utf8(): for stream in (sys.stdout, sys.stderr): reconfigure = getattr(stream, "reconfigure", None) if not callable(reconfigure): continue try: reconfigure(encoding="utf-8", errors="replace", line_buffering=True) except (AttributeError, OSError, ValueError): # StringIO、已关闭流或宿主不允许重配时保持原状;输出路径另有 replace 兜底。 pass _configure_stdio_utf8() # scripts/ 目录的上一级即 skill 包根目录,config.json 在根目录 SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) SKILL_ROOT = os.path.dirname(SCRIPT_DIR) # ──────────────────────────────────────────────── # 错误类别(category)标签 —— 与 skill_server 用同一套词 # 服务端已为大多数错误返回 category / retryable / guidance;客户端只在两种情况下用到本表: # 1) 服务端没给(旧版服务端或兜底)时补默认值; # 2) 客户端自己产生的终态码(STREAM_INTERRUPTED / NO_READY_EVENT / NON_SSE_RESPONSE 等)贴标签。 # 客户端不自行判断业务错误,一律以服务端给的 category 为准(绝不覆盖)。 # input_error 输入错 —— 改公式/参数再试,别原样重试 # server_timeout 后端超时 —— 不是公式问题,别改公式也别马上重试;稍后/拆小批/异步 # server_error 后端崩 —— 后端异常,可隔一会儿试一次,仍失败告诉用户 # transport_recoverable 连接断了 —— 任务在后端还活着,用断点续传接着读(唯一真正可重试) # ──────────────────────────────────────────────── _CATEGORY_DEFAULTS = { "input_error": {"retryable": False, "guidance": "请检查并修改公式或请求参数后再试,不要原样重试。"}, "server_timeout": {"retryable": False, "guidance": "后端排队或执行超时,通常不是公式本身的问题;请勿修改公式、也不要立即重试,可稍后再试、拆成更小批次,或改用异步模式。"}, "server_error": {"retryable": False, "guidance": "后端发生异常;可隔一会儿再试一次,仍失败请把情况反馈给用户。"}, "transport_recoverable": {"retryable": True, "guidance": "与服务端的连接中断,但任务仍在后端执行,请使用断点续传继续读取结果。"}, } # 客户端自产终态码 → 类别(服务端不知道这些错误,由客户端用同一套词贴标签) _CLIENT_CODE_CATEGORY = { "STREAM_INTERRUPTED": "transport_recoverable", "NO_READY_EVENT": "input_error", "NON_SSE_RESPONSE": "input_error", "MISSING_PARAMS": "input_error", } def _enrich_error_category(err, fallback_category="server_error"): """给 error dict 补上 category / retryable / guidance。 服务端已给的字段优先,绝不覆盖;客户端自产码按 _CLIENT_CODE_CATEGORY 贴标签, 其余按 fallback_category 兜底。就地修改并返回同一个 dict。""" if not isinstance(err, dict): return err code = err.get("code") category = err.get("category") or _CLIENT_CODE_CATEGORY.get(code) or fallback_category defaults = _CATEGORY_DEFAULTS.get(category, _CATEGORY_DEFAULTS["server_error"]) err.setdefault("category", category) if err.get("retryable") is None: err["retryable"] = defaults["retryable"] err.setdefault("guidance", defaults["guidance"]) return err # ──────────────────────────────────────────────── # 工具名 → HTTP 方法 + 路径 映射表 # ──────────────────────────────────────────────── TOOL_ROUTES = { "fast_query": ("POST", "/fastQuery"), "fast_query_minute": ("POST", "/fastQueryMinute"), "fast_query_minute_range": ("POST", "/fastQueryMinuteRange"), "searchFunctions": ("POST", "/searchFunctions"), "searchSimilarCases": ("POST", "/searchSimilarCases"), "getCardFormulas": ("POST", "/getCardFormulas"), "confirmDataMulti": ("POST", "/confirmDataMulti"), "runMultiFormulaBatchStream": ("POST", "/runMultiFormulaBatch"), # 同步老接口(SSE fallback 用) "refreshSnapshotTime": ("POST", "/refreshSnapshotTime"), "readData": ("POST", "/readData"), "uploadData": ("POST", "/upload/preview"), # 两阶段:先 preview "uploadConfirm": ("POST", "/upload/confirm"), # 再 confirm "downloadData": ("GET", "/data/{id}"), # id 从 params 取 "renderChart": ("POST", "/renderChart"), "renderKLine": ("POST", "/renderKLine"), "stockProfile": ("POST", "/stockProfile"), "selectByComposition": ("POST", "/selectByComposition"), "listDimensionIndicators": ("POST", "/listDimensionIndicators"), "getIndicatorFormulas": ("POST", "/getIndicatorFormulas"), "getChartSpec": ("GET", "/chartSpec/{task_id}"), "reRenderChart": ("POST", "/reRenderChart"), "scanDimensions": ("POST", "/scanDimensions"), "resumeJob": ("GET", "/runMultiFormulaBatch/stream"), # deferred 任务续传 } # 部分工具需要覆盖默认超时(单位:秒) # 未在此表的工具统一使用 call_post/call_get 的默认值(900s) TOOL_TIMEOUTS = { "runMultiFormulaBatchStream": 1800, # SSE 主路径绕开网关 5min,整体上限 30min "resumeJob": 1800, # deferred 任务续传,同等超时 "scanDimensions": 900, "downloadData": 900, # call_get 默认仅 60s,大 CSV 下载需覆盖 "uploadData": 900, # call_multipart 原来硬编码 120s,大文件上传需覆盖 "renderChart": 900, "renderKLine": 900, "stockProfile": 900, "fast_query_minute_range": 120, # 历史分钟CSV元信息,客户端网络余量 "fast_query_minute": 120, # 单资产分钟行情,服务端总取数 deadline 30s "selectByComposition": 120, # 轻量 DB 查询,避免按公式任务长时间等待 "listDimensionIndicators": 120, # 同上,纯 DB 目录查询 "getIndicatorFormulas": 120, # 同上,DB 查询 + 服务端拓扑排序 "reRenderChart": 900, } # saveChart 不走 HTTP,本地处理 base64 → PNG SAVE_CHART_TOOL = "saveChart" # ──────────────────────────────────────────────── # Presets 层:零网络请求匹配常用资产/数据/函数 # ──────────────────────────────────────────────── # 策略:全量命中 → 返回合成响应;任一缺失 → fallthrough 到网络调用 # 只用 re + 字符串处理解析 YAML 子集,不引入 PyYAML 依赖 PRESETS_DIR = os.path.join(SKILL_ROOT, "presets") def _parse_yaml_list_of_dicts(text): """解析 YAML 列表项(- key: value 格式),返回 [dict, ...]。 只处理 presets 文件用到的子集:缩进 2-4 空格的 key: value,字符串值可带引号。""" items = [] current = None for line in text.splitlines(): stripped = line.strip() if not stripped or stripped.startswith('#'): continue # 新列表项 if stripped.startswith('- '): if current is not None: items.append(current) current = {} kv = stripped[2:].strip() if ':' in kv: k, v = kv.split(':', 1) current[k.strip()] = v.strip().strip('"').strip("'") elif current is not None and ':' in stripped: # 延续当前 dict 的 key: value k, v = stripped.split(':', 1) k = k.strip() v = v.strip().strip('"').strip("'") if v.lower() == 'true': v = True elif v.lower() == 'false': v = False current[k] = v if current is not None: items.append(current) return items def _load_presets_data_catalog(): """加载 presets/data_catalog.yaml,返回 {index_title_lower: {index_title, dimension, is_bool}} 映射""" fpath = os.path.join(PRESETS_DIR, "data_catalog.yaml") if not os.path.exists(fpath): return {} try: with open(fpath, 'r', encoding='utf-8') as f: text = f.read() items = _parse_yaml_list_of_dicts(text) mapping = {} for item in items: title = item.get('index_title', '') if title: mapping[title.lower()] = item return mapping except Exception: return {} def _load_presets_functions(): """加载 presets/functions.yaml,返回 {title_lower: {title, format}} 映射""" fpath = os.path.join(PRESETS_DIR, "functions.yaml") if not os.path.exists(fpath): return {} try: with open(fpath, 'r', encoding='utf-8') as f: text = f.read() items = _parse_yaml_list_of_dicts(text) mapping = {} for item in items: title = item.get('title', '') if title: mapping[title.lower()] = item return mapping except Exception: return {} # 延迟加载缓存(进程生命周期内只加载一次) _presets_cache = {} def _get_presets(kind): """获取指定类型的 presets 数据(首次调用时加载)""" if kind not in _presets_cache: if kind == 'data_catalog': _presets_cache[kind] = _load_presets_data_catalog() elif kind == 'functions': _presets_cache[kind] = _load_presets_functions() return _presets_cache.get(kind, {}) def _try_presets_confirm_data(params): """尝试从 presets 匹配 confirmDataMulti 请求。""" data_desc = params.get('data_desc', '') if not data_desc: return None catalog = _get_presets('data_catalog') if not catalog: return None # data_desc 可能是字符串或列表(MCP schema 允许两种格式) if isinstance(data_desc, list): queries = [str(q).strip() for q in data_desc if str(q).strip()] else: queries = [q.strip() for q in re.split(r'[,,\s]+', data_desc) if q.strip()] if not queries: return None results = [] for q in queries: key = q.lower() hit = catalog.get(key) if not hit: # 模糊匹配:查询词出现在 index_title 或 description 中 for k, v in catalog.items(): desc = str(v.get('description', '')).lower() if key in k or key in desc: hit = v break if not hit: return None results.append({ "query": q, "matched": True, "index_info": { "_id": "", # presets 没有 _id,调用方需注意 "index_title": hit.get('index_title', q), "dimension": hit.get('dimension', 'two'), "is_bool": hit.get('is_bool', False) if isinstance(hit.get('is_bool'), bool) else str(hit.get('is_bool', '')).lower() == 'true', "provider": "guanzhao", }, "_source": "presets" }) return {"code": 0, "data": {"results": results}, "_presets": True} def _try_presets_search_functions(params): """尝试从 presets 匹配 searchFunctions 请求。""" query = params.get('query', '').strip() if not query: return None funcs = _get_presets('functions') if not funcs: return None top_k = int(params.get('top_k', 5)) keywords = [kw.lower().strip() for kw in re.split(r'[|,,\s/]+', query) if kw.strip()] if not keywords: return None # 按关键词匹配打分 scored = [] for title_lower, item in funcs.items(): fmt = str(item.get('format', '')).lower() score = 0 for kw in keywords: if kw == title_lower: score += 10 # 精确匹配标题 elif kw in title_lower: score += 5 # 部分匹配标题 elif kw in fmt: score += 2 # 匹配 format if score > 0: scored.append((score, item)) if not scored: return None # 无任何命中,走网络 scored.sort(key=lambda x: -x[0]) top_items = scored[:top_k] functions = [] for _, item in top_items: functions.append({ "name": item.get('title', ''), "format": item.get('format', ''), "_source": "presets" }) return {"code": 0, "data": {"functions": functions}, "_presets": True} # ──────────────────────────────────────────────── # 日志模块:每个 task_id 一个 JSONL 文件,存 logs/ 目录 # ──────────────────────────────────────────────── def _get_log_path(params: dict, result=None) -> str: """按 task_id 决定日志文件路径;无 task_id 则按日期归入 general_YYYYMMDD.jsonl task_id 优先从服务端响应(result)里提取——因为 task_id 是服务端生成的, 第一次调用时请求里不带 task_id,只有响应里才有。 回退顺序:result.task_id → result.data.task_id → params.task_id → general_日期 """ log_dir = os.path.join(SKILL_ROOT, "logs") os.makedirs(log_dir, exist_ok=True) task_id = "" # 优先从响应里取 if isinstance(result, str): task_id = _extract_yaml_task_id(result) elif isinstance(result, dict): task_id = result.get("task_id", "") if not task_id and isinstance(result.get("data"), dict): task_id = result["data"].get("task_id", "") # 回退到请求参数里 if not task_id and isinstance(params, dict): task_id = params.get("task_id", "") filename = f"{task_id}.jsonl" if task_id else f"general_{datetime.now().strftime('%Y%m%d')}.jsonl" return os.path.join(log_dir, filename) def _sanitize_for_log(obj, _depth=0): """递归截断大字段,避免 base64 图片等撑爆日志文件。""" if _depth > 6: return "..." if isinstance(obj, str): # base64 图片 / 超长字符串 → 只保留前 120 字符 return obj[:120] + "...[truncated]" if len(obj) > 120 else obj if isinstance(obj, dict): return {k: _sanitize_for_log(v, _depth + 1) for k, v in obj.items()} if isinstance(obj, list): # 超过 20 个元素只保留前 10 + 尾部计数 if len(obj) > 20: return [_sanitize_for_log(x, _depth + 1) for x in obj[:10]] + [f"... ({len(obj) - 10} more)"] return [_sanitize_for_log(x, _depth + 1) for x in obj] return obj def _write_log(tool: str, params: dict, result, elapsed_ms: int): """追加一条 JSONL 记录,写失败静默忽略(不影响主流程)。 日志路径在拿到 result 之后才决定,这样能正确用服务端返回的 task_id 命名文件。 支持 result 为 dict(JSON)或 str(YAML 原文)。 """ log_path = _get_log_path(params, result) if isinstance(result, str): entry = { "ts": datetime.now().strftime("%Y-%m-%dT%H:%M:%S"), "tool": tool, "params": _sanitize_for_log(params), "elapsed_ms": elapsed_ms, "result_code": _extract_yaml_code(result), "result_yaml_preview": result[:500] + ("..." if len(result) > 500 else ""), } else: entry = { "ts": datetime.now().strftime("%Y-%m-%dT%H:%M:%S"), "tool": tool, "params": _sanitize_for_log(params), "elapsed_ms": elapsed_ms, "result_code": result.get("code") if isinstance(result, dict) else None, "result": _sanitize_for_log(result), } try: with open(log_path, "a", encoding="utf-8") as f: f.write(json.dumps(entry, ensure_ascii=False) + "\n") except Exception: pass # 日志写失败不中断主流程 def load_config(): config_path = os.path.join(SKILL_ROOT, "config.json") if not os.path.exists(config_path): raise FileNotFoundError(f"找不到配置文件: {config_path}") with open(config_path, "r", encoding="utf-8") as f: cfg = json.load(f) # config.local.json 覆盖(优先级高于 config.json) local_path = os.path.join(SKILL_ROOT, "config.local.json") if os.path.exists(local_path): try: with open(local_path, "r", encoding="utf-8") as f: local_cfg = json.load(f) for k, v in local_cfg.items(): if v not in (None, ""): cfg[k] = v except Exception: pass # 环境变量仅作最低优先级兜底:只在 config.json / config.local.json 都没有 api_key 时才生效, # 不作为常规覆盖手段(真正的临时覆盖走调用方 params 里的 api_key,见 main() 的 override_api_key)。 if not cfg.get("api_key"): env_key = os.environ.get("QUANT_BUDDY_API_KEY", "").strip() if env_key: cfg["api_key"] = env_key return cfg def _apply_api_key_override(override, cfg): """给定一个已经取出的 override 值(可能是空串),按"传入优先,否则用 config"决出最终 api_key; 最终仍为空则抛 ValueError。不关心 override 是怎么取出来的,供不同调用点在各自合适的时机复用同一条 判定规则,而不必都在同一处 pop 参数。 QBS_API_KEY 环境变量:跟现有的 QBS_SESSION_KEY/QBS_SESSION_FILE 同一档,是"这次调用要用哪个 key"的 显式覆盖通道,排在 override(调用方 params.api_key)之后、cfg['api_key'](config.json 解析结果) 之前——调用方拿到一份既有 @file 参数、不方便现改这份文件去塞 api_key 时可以直接用它,效果等价于在 顶层参数里传了 api_key,不会被 config.json 里已有的默认 key 悄悄盖掉。跟仅作最低优先级兜底的 QUANT_BUDDY_API_KEY(只在 config.json 也为空时才生效,见 load_config())是两回事,不要混用。 """ api_key = (override or "").strip() if not api_key: api_key = os.environ.get("QBS_API_KEY", "").strip() if not api_key: api_key = (cfg or {}).get("api_key", "") if not api_key: raise ValueError( "api_key 为空。请设置环境变量 QUANT_BUDDY_API_KEY," "或在 config.json / config.local.json 中填入 api_key 字段(从 https://www.quantbuddy.cn/login 获取)" ) return api_key def resolve_api_key(params, cfg): """本次调用实际用哪个 api_key:params.api_key(有传就用,原地 pop 掉,避免混进请求体/日志)> cfg['api_key'](load_config() 已经按 config.json / config.local.json / QUANT_BUDDY_API_KEY 兜底 解析好的结果)。不落盘、不设置进程环境变量,调用方(如 Playground)可放心每次传入不同会话的 key。 最终仍为空则抛 ValueError,沿用既有的用户引导文案(配置向导依赖这条消息触发)。""" override = "" if isinstance(params, dict): override = str(params.pop("api_key", "") or "").strip() return _apply_api_key_override(override, cfg) def call_multipart(endpoint, api_key, path, file_path, fields=None, timeout=900): """用 multipart/form-data 上传文件,附带额外表单字段 (fields dict)。""" import email.mime.multipart import uuid boundary = uuid.uuid4().hex body_parts = [] # 额外表单字段 for k, v in (fields or {}).items(): body_parts.append( f'--{boundary}\r\nContent-Disposition: form-data; name="{k}"\r\n\r\n{v}\r\n' .encode('utf-8') ) # 文件本身 with open(file_path, 'rb') as f: file_bytes = f.read() filename = os.path.basename(file_path) body_parts.append( f'--{boundary}\r\nContent-Disposition: form-data; name="file"; filename="{filename}"\r\nContent-Type: text/csv\r\n\r\n' .encode('utf-8') + file_bytes + b'\r\n' ) body_parts.append(f'--{boundary}--\r\n'.encode('utf-8')) body = b''.join(body_parts) url = f"{endpoint}{path}" req = urllib.request.Request( url, data=body, headers=_trace_headers(fields, api_key=api_key, content_type=f'multipart/form-data; boundary={boundary}'), method='POST', ) with _NO_PROXY_OPENER.open(req, timeout=timeout) as resp: return json.loads(resp.read().decode('utf-8')) def call_post(endpoint, api_key, path, params, accept_yaml=True, timeout=900): """发送 POST 请求。accept_yaml=True 时返回 YAML 原文(str),否则返回 JSON(dict)。""" url = f"{endpoint}{path}" data = json.dumps(params, ensure_ascii=False).encode("utf-8") headers = _trace_headers(params, api_key=api_key, content_type="application/json; charset=utf-8") if accept_yaml: headers["Accept"] = "text/yaml" req = urllib.request.Request(url, data=data, headers=headers, method="POST") with _NO_PROXY_OPENER.open(req, timeout=timeout) as resp: ct = resp.headers.get("Content-Type", "") raw = resp.read().decode("utf-8") if "yaml" in ct: return raw # YAML 原文,交给调用方直接输出 return json.loads(raw) # ──────────────────────────────────────────────── # runMultiFormulaBatch SSE 主路径 # spec: docs/runMultiFormulaBatch-sse-spec.md # 对外契约:返回 done 事件的 compact 负载({status,summary,results},字段 indexinfo_id) # 或 fatal/非SSE-JSON 错误 dict({code,success,error})。归一化成文档信封 # ({code,success,data:{...},task_id}、注入 data_id)由 call.py 的 # _process_run_multi_formula_batch 负责。trace_id 暂时保留在结果里,由 call.py # 写入 session 后由打印层剥离。 # ──────────────────────────────────────────────── class _StreamUnsupportedError(Exception): """SSE 端点未部署(404/405/406),允许调用方回退到同步老接口。""" def __init__(self, code): self.code = code super().__init__(f"stream endpoint not supported: HTTP {code}") def _derive_idempotency_key(params): """基于参数派生稳定的 Idempotency-Key,跨子进程重启仍一致。 入参 params 是 runMultiFormulaBatch 的请求体;只取影响业务结果的字段。 """ import hashlib canonical = json.dumps({ "task_id": params.get("task_id", ""), "formulas": params.get("formulas", []), "begin_date": params.get("begin_date", ""), "use_minute_data": params.get("use_minute_data", False), "force_reusable_array": params.get("force_reusable_array"), "execution_profile": params.get("execution_profile", "interactive_5m"), }, ensure_ascii=False, sort_keys=True) return hashlib.sha256(canonical.encode("utf-8")).hexdigest()[:32] def _consume_sse(url, method, body, headers, state, timeout, ignore_deferred=False): """逐行读 SSE。返回 dict(done/fatal);若连接异常断开返回 None 让上层走 resume。 state 由调用方维护,会在收到 ready 时被填上 task_id/trace_id;每收到一个有 id 的事件 会更新 state['last_event_id'],供 resume 时作为 since 参数。 """ req = urllib.request.Request(url, data=body, headers=headers, method=method) try: resp = _NO_PROXY_OPENER.open(req, timeout=timeout) except urllib.error.HTTPError as e: # 仅在主 POST 且尚未读到任何事件时允许回退到同步端点(spec §3.1) if ( method == "POST" and state.get("last_event_id") is None and e.code in (404, 405, 406) ): raise _StreamUnsupportedError(e.code) # 读出错误响应体,包成与同步版一致的 fatal dict try: body_txt = e.read().decode("utf-8", errors="replace") err = json.loads(body_txt) except Exception: err = {"message": getattr(e, "reason", str(e))} err_inner = err.get("error") if isinstance(err.get("error"), dict) else {} out_err = { "code": err_inner.get("code") or err.get("code", "HTTP_ERROR"), "message": err.get("message", str(e)), } # 服务端若已分类(如 429 RESEARCH_CONCURRENT_LIMIT 带 category)则原样带上 for k in ("category", "retryable", "guidance"): if err_inner.get(k) is not None: out_err[k] = err_inner[k] _enrich_error_category(out_err) return { "code": e.code, "success": False, "task_id": state.get("task_id"), "trace_id": state.get("trace_id"), "error": out_err, } except (urllib.error.URLError, TimeoutError, ConnectionError): # 连接级异常:交给上层 resume return None event_type = None data_buf = [] entry_id = None try: # ── 按 Content-Type 分流:后端对参数/格式错误用普通 JSON(HTTP 200)回复,不是 SSE 流。 # 若按 SSE 逐行解析,这条 JSON 匹配不到任何分支被当噪音丢弃,最后误报 STREAM_INTERRUPTED。 # 先看响应头,非 event-stream 直接当 fatal 原样透出后端 error(含 code/message/usage 用法提示)。 # 覆盖 TASK_ID_REQUIRED / FORMULAS_REQUIRED / INVALID_FORMULA / INVALID_FORCE_REUSABLE / # 公式数超限 等全部 200-JSON 校验错误。resume(GET) 是 SSE,不会误触此分支。 ctype = (resp.headers.get("Content-Type") or "").lower() if "text/event-stream" not in ctype: body_txt = resp.read().decode("utf-8", errors="replace") parsed = None try: obj = json.loads(body_txt) if isinstance(obj, dict): parsed = obj except Exception: parsed = None if parsed is not None: err_obj = parsed.get("error") if not isinstance(err_obj, dict): err_obj = { "code": parsed.get("code", "NON_SSE_RESPONSE"), "message": parsed.get("message", body_txt[:500]), } # 服务端 200-JSON 校验错误现已带 category(input_error);缺失时按 code 兜底 _enrich_error_category(err_obj, fallback_category="input_error") return { "code": parsed.get("code", -1), "success": bool(parsed.get("success", False)), "task_id": state.get("task_id"), "trace_id": state.get("trace_id"), "error": err_obj, } return { "code": -1, "success": False, "task_id": state.get("task_id"), "trace_id": state.get("trace_id"), "error": _enrich_error_category({ "code": "NON_SSE_RESPONSE", "message": (body_txt[:500] if body_txt.strip() else f"服务端返回了非 SSE 响应(Content-Type: {ctype or '未知'})"), }), } while True: try: raw_line = resp.readline() except (TimeoutError, ConnectionError, OSError): return None if not raw_line: # 连接被对端/中间设备关闭 return None line = raw_line.decode("utf-8", errors="replace").rstrip("\r\n") if line == "": # 一个 SSE 事件分隔 if event_type and data_buf: payload_text = "\n".join(data_buf) try: payload = json.loads(payload_text) except json.JSONDecodeError: payload = {"raw": payload_text} if entry_id: state["last_event_id"] = entry_id if event_type == "ready": state["trace_id"] = payload.get("trace_id") or state.get("trace_id") state["task_id"] = payload.get("task_id") or state.get("task_id") # 新 spec:ready 携带 job_id / execution_profile / queue / stream_url for k in ("job_id", "execution_profile", "queue", "stream_url"): if payload.get(k) is not None: state[k] = payload[k] elif event_type == "deferred": # research_24h 模式:后台已入队,关闭连接、返回 job ack deferred_result = { "code": 0, "success": True, "status": "deferred", "task_id": payload.get("task_id") or state.get("task_id"), "trace_id": payload.get("trace_id") or state.get("trace_id"), "job_id": payload.get("job_id") or state.get("job_id"), "execution_profile": payload.get("execution_profile") or state.get("execution_profile") or "research_24h", "queue": payload.get("queue") or state.get("queue"), "stream_url": payload.get("stream_url") or state.get("stream_url"), "message": payload.get("message") or "研究任务已进入后台队列,可稍后恢复查看进度", "_deferred": True, } if ignore_deferred: # resumeJob 场景:忽略 deferred,继续读到 done continue return deferred_result elif event_type == "done": # done.data 即完整同步版响应;保留 trace_id 给 call.py return payload elif event_type == "fatal": # 关键修复:服务端把错误码放在 errorCode,旧代码只读 code 永远拿到笼统 "FATAL", # 区分超时/崩溃/输入错的信息在客户端被丢掉。这里优先读 errorCode。 err = { "code": payload.get("errorCode") or payload.get("code") or "FATAL", "message": payload.get("message", "stream fatal"), } # 服务端已分类时原样带给 agent;未给则按 code 兜底 for k in ("category", "retryable", "guidance"): if payload.get(k) is not None: err[k] = payload[k] _enrich_error_category(err) return { "code": -1, "success": False, "task_id": payload.get("task_id") or state.get("task_id"), "trace_id": payload.get("trace_id") or state.get("trace_id"), "error": err, "partial": payload.get("partial", {}), "last_event_id": state.get("last_event_id"), } # progress / result / formula_error:仅累积 last_event_id,最终从 done 取 # 但把每条公式结果实时打印到 stderr,让用户在终端看到流式进度 elif event_type == "result": # 仅用 payload 自带身份标注左值,禁止用"提交顺序计数"猜名字: # SSE 事件按完成顺序到达(成功/失败交错),按计数取 _formula_names[idx] # 会把名字贴错(如成功的"好的"被贴成"坏资产")。 # leftName 缺失时回落到 expression_id(失败/预处理事件里它就是左值名)。 left = payload.get("leftName") or payload.get("expression_id") or "?" if isinstance(left, list): left = ", ".join(left) status = payload.get("status", "?") data_id = payload.get("indexinfo_id") or "—" symbol = "✓" if status == "success" else "✗" print(f" {symbol} {left} id={data_id}", file=sys.stderr, flush=True) elif event_type == "formula_error": left = payload.get("leftName") or payload.get("expression_id") or "?" if isinstance(left, list): left = ", ".join(left) msg = payload.get("message", "") print(f" ✗ {left} ERROR: {msg}", file=sys.stderr, flush=True) event_type = None data_buf = [] entry_id = None continue if line.startswith(":"): # 注释(keepalive) continue if line.startswith("id:"): entry_id = line[3:].strip() elif line.startswith("event:"): event_type = line[6:].strip() elif line.startswith("data:"): data_buf.append(line[5:].lstrip()) # 其他字段忽略 finally: try: resp.close() except Exception: pass def call_run_multi_formula_batch_stream(endpoint, api_key, params, timeout=1800): """以 SSE 主路径调用 runMultiFormulaBatch。 成功返回 done 的 compact 负载({status,summary,results});参数/格式错误(后端 200-JSON) 或 fatal 返回 {code,success,error} dict。归一化与 trace_id 剥离由 call.py 负责。 已握手后断线最多 3 次重连,遵循 1s/3s/9s 退避;从未握手(无 trace_id)则不续传, 返回 NO_READY_EVENT。若服务端尚未部署 SSE 端点(404/405/406),抛 _StreamUnsupportedError。 """ idem_key = _derive_idempotency_key(params) # 预建公式左侧名列表,供 _consume_sse 逐条打印进度时使用 _formula_names: list[str] = [] for _f in params.get("formulas", []): _lhs = _f.split("=")[0].strip() # "A,B=expr" → "A,B" _formula_names.append(_lhs) state = { "task_id": params.get("task_id"), "trace_id": None, "last_event_id": None, "_formula_names": _formula_names, # 有序左侧名,供进度打印 "_result_count": 0, # 已收到的 result 事件计数 } headers_post = _trace_headers( params, api_key=api_key, accept="text/event-stream", content_type="application/json; charset=utf-8", ) headers_post["Idempotency-Key"] = idem_key body = json.dumps(params, ensure_ascii=False).encode("utf-8") final = _consume_sse( url=f"{endpoint}/runMultiFormulaBatchStream", method="POST", body=body, headers=headers_post, state=state, timeout=timeout, ) # resume 循环:仅在已经拿到 trace_id 且未拿到 final 时进行 backoff = [1, 3, 9] attempt = 0 while final is None and state.get("trace_id") and attempt < len(backoff): time.sleep(backoff[attempt]) attempt += 1 url = ( f"{endpoint}/runMultiFormulaBatch/stream" f"?task_id={state['task_id']}&trace_id={state['trace_id']}" ) if state.get("last_event_id"): url += f"&since={state['last_event_id']}" headers_get = _trace_headers(state, api_key=api_key, accept="text/event-stream") final = _consume_sse( url=url, method="GET", body=None, headers=headers_get, state=state, timeout=timeout, ) if final is None: if not state.get("trace_id"): # 从未收到 ready 事件:续传循环根本没进(条件要求 trace_id)。 # 这通常不是传输问题,而是请求在握手前就结束(载荷/格式/网络)。别再假报“续传 N 次”。 return { "code": -1, "success": False, "task_id": state.get("task_id"), "trace_id": None, "error": _enrich_error_category({ "code": "NO_READY_EVENT", "message": "未收到 ready 事件即结束;请先检查公式格式/载荷与网络,再怀疑传输", }), "partial": {"last_event_id": state.get("last_event_id")}, } # 已握手但续传耗尽,用真实续传次数(任务可能仍在后端跑 → transport_recoverable,可续传) return { "code": -1, "success": False, "task_id": state.get("task_id"), "trace_id": state.get("trace_id"), "error": _enrich_error_category({ "code": "STREAM_INTERRUPTED", "message": f"SSE 流中断,{attempt} 次续传仍失败;可用 task_id+trace_id 后续手工续传", }), "partial": {"last_event_id": state.get("last_event_id")}, } return final def call_resume_job(endpoint, api_key, params, timeout=1800): """续传查询 deferred 任务进度,复用 _consume_sse。 params 须包含: - task_id (str) - trace_id (str) - since (str, 可选) — 上次断开时的 last_event_id,默认 "0" """ task_id = params.get("task_id") or "" trace_id = params.get("trace_id") or "" since = params.get("since", "0") if not task_id or not trace_id: return { "code": -1, "success": False, "error": _enrich_error_category({"code": "MISSING_PARAMS", "message": "resumeJob 需要 task_id 和 trace_id"}), } state = { "task_id": task_id, "trace_id": trace_id, "last_event_id": since if since != "0" else None, "_formula_names": [], "_result_count": 0, } url = ( f"{endpoint}/runMultiFormulaBatch/stream" f"?task_id={task_id}&trace_id={trace_id}&since={since}" ) headers = _trace_headers(params, api_key=api_key, accept="text/event-stream") backoff = [0, 3, 9] final = None tries = 0 for attempt, wait in enumerate(backoff): if wait: time.sleep(wait) tries += 1 final = _consume_sse(url=url, method="GET", body=None, headers=headers, state=state, timeout=timeout, ignore_deferred=True) if final is not None: break # 更新 since 以便断线续传 if state.get("last_event_id"): url = ( f"{endpoint}/runMultiFormulaBatch/stream" f"?task_id={task_id}&trace_id={trace_id}&since={state['last_event_id']}" ) if final is None: return { "code": -1, "success": False, "task_id": task_id, "trace_id": trace_id, "error": _enrich_error_category({ "code": "STREAM_INTERRUPTED", "message": f"SSE 流中断,{tries} 次续传仍失败", }), "partial": {"last_event_id": state.get("last_event_id")}, } return final def call_get(endpoint, api_key, path, params, timeout=900): """发送 GET 请求(downloadData / getChartSpec 等)""" # 用 params 中的值填充路径模板(支持 {id}、{task_id} 等) formatted_path = path.format(**params) if '{' in path else path _fmt = params.get("format", "") url = f"{endpoint}{formatted_path}" query_parts = [] if _fmt: query_parts.append(f"format={_fmt}") if params.get("begin_date"): query_parts.append(f"begin_date={params['begin_date']}") if params.get("end_date"): query_parts.append(f"end_date={params['end_date']}") if query_parts: url += "?" + "&".join(query_parts) req = urllib.request.Request( url, headers=_trace_headers(params, api_key=api_key, accept="text/yaml"), method="GET", ) with _NO_PROXY_OPENER.open(req, timeout=timeout) as resp: content_type = resp.headers.get("Content-Type", "") raw = resp.read() if "text/csv" in content_type: return {"code": 0, "data": raw.decode("utf-8")} raw_str = raw.decode("utf-8") if "yaml" in content_type: return raw_str # YAML 原文 return json.loads(raw_str) def upload_data(endpoint, api_key, params): """ 两阶段上传: 1. 读取本地 CSV 文件 2. POST /skill/upload/preview 获取 file_token 3. POST /skill/upload/confirm 完成上传 """ file_path = params.get("file_path") data_name = params.get("data_name", "") description = params.get("description", "") on_conflict = params.get("on_conflict", "error") if not file_path: return {"code": 1, "message": "缺少 file_path 参数"} if not os.path.exists(file_path): return {"code": 1, "message": f"文件不存在: {file_path}"} if not data_name: data_name = os.path.splitext(os.path.basename(file_path))[0] # Step 1: preview — multipart/form-data(后端用 multer upload.single('file')) _upload_timeout = TOOL_TIMEOUTS.get("uploadData", 300) preview_result = call_multipart(endpoint, api_key, "/skill/upload/preview", file_path=file_path, fields={"data_name": data_name}, timeout=_upload_timeout) if preview_result.get("code") != 0: return preview_result file_token = preview_result["data"]["file_token"] sample = preview_result["data"].get("sample_rows", []) name_conflict = preview_result["data"].get("name_conflict", False) print(f"[Preview OK] data_name={data_name}, rows={preview_result['data'].get('total_rows')}, " f"name_conflict={name_conflict}", file=sys.stderr) if sample: print(f" sample: {sample[:2]}", file=sys.stderr) # Step 2: confirm confirm_result = call_post(endpoint, api_key, "/skill/upload/confirm", { "file_token": file_token, "data_name": data_name, "description": description, "on_conflict": on_conflict, }, accept_yaml=False) return confirm_result def _decode_bytes(raw_bytes: bytes) -> str: """尝试多种编码解码字节流,优先 UTF-8,其次系统默认编码(应对 PowerShell GBK 管道)。""" for enc in ('utf-8-sig', 'utf-8'): try: return raw_bytes.decode(enc) except (UnicodeDecodeError, LookupError): pass # 回落系统编码(中文 Windows 通常是 gbk/cp936) import locale sys_enc = locale.getpreferredencoding(False) or 'gbk' try: return raw_bytes.decode(sys_enc) except (UnicodeDecodeError, LookupError): pass return raw_bytes.decode('utf-8', errors='replace') # ── YAML 响应解析辅助(零依赖,仅用正则提取关键字段)────────── _RE_YAML_CODE = re.compile(r'^code:\s*(-?\d+)', re.MULTILINE) _RE_YAML_TASK_ID = re.compile(r'^task_id:\s*(\S+)', re.MULTILINE) def _extract_yaml_code(yaml_text): """从 YAML 文本提取顶层 code 字段(int 或 None)。""" m = _RE_YAML_CODE.search(yaml_text) return int(m.group(1)) if m else None def _extract_yaml_task_id(yaml_text): """从 YAML 文本提取顶层 task_id 字段。""" m = _RE_YAML_TASK_ID.search(yaml_text) return m.group(1).strip("'\"") if m else "" def main(): if len(sys.argv) < 2: print(__doc__) sys.exit(1) tool_name = sys.argv[1] # 解析参数:支持三种方式 # 1. 命令行第2个参数(JSON 字符串,bash/zsh 适用) # 2. 命令行第2个参数以 @ 开头(从文件读取,PowerShell 推荐方式) # 3. 第2个参数为 "-" 或缺省且 stdin 非 tty(从 stdin 读取,自动检测编码) if len(sys.argv) >= 3 and sys.argv[2] == '-': raw_arg = _decode_bytes(sys.stdin.buffer.read()).strip() elif not sys.stdin.isatty() and len(sys.argv) < 3: raw_arg = _decode_bytes(sys.stdin.buffer.read()).strip() elif len(sys.argv) >= 3: raw_arg = sys.argv[2] else: raw_arg = '{}' if raw_arg.startswith('@'): # 从文件读取 JSON json_file = raw_arg[1:] try: with open(json_file, 'r', encoding='utf-8-sig') as f: json_str = f.read() params = json.loads(json_str) except FileNotFoundError: print(json.dumps({"code": 1, "message": f"参数文件不存在: {json_file}"}, ensure_ascii=False)) sys.exit(1) except json.JSONDecodeError as e: print(json.dumps({"code": 1, "message": f"参数文件 JSON 解析失败: {e}"}, ensure_ascii=False)) sys.exit(1) else: try: params = json.loads(raw_arg) except json.JSONDecodeError as e: print(json.dumps({ "code": 1, "message": ( f"参数 JSON 解析失败: {e}\n" "PowerShell 请用 @file 方式传参:\n" " echo '{\"key\": \"value\"}' > /tmp/p.json\n" f" python scripts/executor.py {tool_name} @/tmp/p.json" ) }, ensure_ascii=False)) sys.exit(1) # 调用方(如 Playground)可在 params 里附带 api_key,本次调用临时覆盖 config.json, # 优先级最高(高于 config.json,也高于仅作兜底用的 QUANT_BUDDY_API_KEY 环境变量); # 不落盘、不设置进程环境变量。这里先 pop 掉(早于下面任何工具专属校验/请求体拼装),避免它混进 # 发给后端的请求体/日志;真正"override 优先,否则用 config"的判定见下面 _apply_api_key_override() # (与 resolve_api_key() 共用同一条规则,供 formula_package.py / quant_api.py / call.py 复用)。 override_api_key = "" if isinstance(params, dict): override_api_key = str(params.pop("api_key", "") or "").strip() # ── 客户端必填参数前置校验(在网络调用前拦截,避免浪费 RU 配额)────── if tool_name == "runMultiFormulaBatchStream": if not params.get("task_id"): result = { "code": 1, "message": ( "task_id 必填。请先调用 newSession 获取 task_id," "再将其作为参数传入 runMultiFormulaBatchStream。" "若通过 call.py 调用,请确认 GZQ_PARAMS 环境变量已正确设置且包含 task_id 字段。" ), } print(json.dumps(result, ensure_ascii=False)) sys.exit(1) if not params.get("formulas"): result = { "code": 1, "message": ( "formulas 必须是非空数组。" "请检查 GZQ_PARAMS 中是否包含 formulas 字段,且数组中至少有一条公式字符串。" ), } print(json.dumps(result, ensure_ascii=False)) sys.exit(1) # ── 参数规范化:自动修正常见参数名错误 ────────────────────── if tool_name == "runMultiFormulaBatchStream" and "formulas" in params: fixed = [] for item in params["formulas"]: if isinstance(item, str): fixed.append(item) elif isinstance(item, dict): f = item.get("formula") or item.get("expression") or item.get("value") or "" if f: fixed.append(f) # 防御性修复:模型有时对公式中的引号做双重转义( \" → ") # 例如 Kimi 模型可能生成 \\\"A股营业收入\\\" 导致 HTTP 500 params["formulas"] = [f.replace('\\"', '"') for f in fixed] # begin_date 缺失/空值时默认取今天(YYYYMMDD 整数) if not params.get("begin_date"): from datetime import date as _date params["begin_date"] = int(_date.today().strftime("%Y%m%d")) # ── buildEventStudy 参数校验 ────────────────────────────── if tool_name == "buildEventStudy": def _is_weekday(d): """简易校验:YYYYMMDD 整数是否为工作日(排除周末)""" try: from datetime import date as _date s = str(d) dt = _date(int(s[:4]), int(s[4:6]), int(s[6:8])) return dt.weekday() < 5 # 0=周一 ... 4=周五 except Exception: return False # 校验 dates / group_a_dates / group_b_dates 中的周末日期 for date_key in ("dates", "group_a_dates", "group_b_dates"): date_list = params.get(date_key) if date_list and isinstance(date_list, list): weekends = [d for d in date_list if not _is_weekday(d)] if weekends: print(f"[warn] {date_key} 包含周末日期 {weekends},事件研究要求使用交易日", file=sys.stderr) # compare 模式:group_a / group_b 长度一致性 mode = params.get("mode", "single") if mode == "compare": ga = params.get("group_a_dates", []) gb = params.get("group_b_dates", []) if ga and gb and len(ga) != len(gb): print(f"[warn] compare 模式下 group_a_dates({len(ga)}) 与 group_b_dates({len(gb)}) 长度不一致", file=sys.stderr) # 特殊处理:saveChart(本地 base64 → PNG,不走 HTTP,无需配置文件) if tool_name == "saveChart": import base64 t0 = time.time() b64_str = params.get("base64", "") name = params.get("name", "latest_chart") if b64_str.startswith("data:image/"): b64_str = b64_str.split(",", 1)[1] if not b64_str: result = {"code": 1, "message": "缺少 base64 参数"} _write_log(tool_name, params, result, int((time.time()-t0)*1000)) print(json.dumps(result, ensure_ascii=False)) sys.exit(1) output_dir = os.path.join(SKILL_ROOT, "output") os.makedirs(output_dir, exist_ok=True) out_path = os.path.join(output_dir, f"{name}.png") with open(out_path, "wb") as f: f.write(base64.b64decode(b64_str)) if sys.platform == "win32": os.startfile(out_path) result = {"code": 0, "data": {"saved_to": out_path}} _write_log(tool_name, params, result, int((time.time()-t0)*1000)) print(json.dumps(result, indent=2, ensure_ascii=False)) return # 加载配置 try: cfg = load_config() except FileNotFoundError as e: print(json.dumps({"code": 1, "message": str(e)}, ensure_ascii=False)) sys.exit(1) endpoint = cfg["endpoint"].rstrip("/") try: api_key = _apply_api_key_override(override_api_key, cfg) except ValueError as e: print(json.dumps({"code": 1, "message": str(e)}, ensure_ascii=False)) sys.exit(1) # 特殊处理:uploadData(两阶段) if tool_name == "uploadData": t0 = time.time() result = upload_data(endpoint, api_key, params) _write_log(tool_name, params, result, int((time.time()-t0)*1000)) print(json.dumps(result, indent=2, ensure_ascii=False)) return # 查路由表 if tool_name not in TOOL_ROUTES: known = ", ".join(TOOL_ROUTES.keys()) print(json.dumps( {"code": 1, "message": f"未知工具: {tool_name}。支持的工具: {known}"}, ensure_ascii=False )) sys.exit(1) method, path = TOOL_ROUTES[tool_name] t0 = time.time() # ── Presets 层:尝试本地匹配,避免网络调用 ────────────────── presets_result = None if tool_name == "confirmDataMulti": presets_result = _try_presets_confirm_data(params) elif tool_name == "searchFunctions": presets_result = _try_presets_search_functions(params) if presets_result is not None: elapsed_ms = int((time.time() - t0) * 1000) print(f"[presets] {tool_name} 命中本地预设,跳过网络请求 ({elapsed_ms}ms)", file=sys.stderr) _write_log(tool_name, params, presets_result, elapsed_ms) print(json.dumps(presets_result, indent=2, ensure_ascii=False)) sys.stdout.flush() return # ── Presets miss → 走网络 ──────────────────────────────── # 日志记账用的工具名:默认与 LLM 看到的 tool_name 一致(即 runMultiFormulaBatchStream); # 仅在回退到同步老接口时改为 runMultiFormulaBatch,以区分两条路径(计费/审计统计需要)。 log_tool_name = tool_name try: _timeout = TOOL_TIMEOUTS.get(tool_name, 300) if tool_name == "runMultiFormulaBatchStream": # SSE 主路径;端点不存在时回退到同步老接口(spec §3.1:仅 404/405/406 可回退) try: result = call_run_multi_formula_batch_stream(endpoint, api_key, params, timeout=_timeout) except _StreamUnsupportedError: result = call_post(endpoint, api_key, path, params, timeout=_timeout) log_tool_name = "runMultiFormulaBatch" # 同步老接口,审计区分用 elif tool_name == "resumeJob": result = call_resume_job(endpoint, api_key, params, timeout=_timeout) elif method == "GET": result = call_get(endpoint, api_key, path, params, timeout=_timeout) else: result = call_post(endpoint, api_key, path, params, timeout=_timeout) except urllib.error.HTTPError as e: body = e.read().decode("utf-8") ct = e.headers.get("Content-Type", "") if hasattr(e, 'headers') else "" if "yaml" in ct: # 服务端返回了 YAML 格式的错误响应 _write_log(log_tool_name, params, body, int((time.time()-t0)*1000)) print(body, end="") sys.stdout.flush() sys.exit(1) try: err = json.loads(body) except Exception: err = {"message": body} result = {"code": e.code, "message": err.get("message", str(e))} # 透传服务端 error 结构(含 error.code / nextResetIn 等 429 分类信息) if "error" in err and isinstance(err["error"], dict): result["error"] = err["error"] if "success" in err: result["success"] = err["success"] -
fetch_fastquery_csv.py 13.3 KB
#!/usr/bin/env python3 # -*- coding: utf-8 -*- """Download and normalize ``fast_query`` CSV exports. When ``fast_query`` returns ``mode: csv``, this script consumes the returned ``csv_url`` values. It can either print the legacy JSON result to stdout, or write the full normalized series into a hash-bound artifact and print only a compact receipt. The artifact path is restricted to this skill's ``output`` directory so an Agent cannot use the helper as a general-purpose file writer. """ from __future__ import annotations import argparse import csv import hashlib import io import json import math import os import sys import tempfile import urllib.request from itertools import combinations from pathlib import Path from typing import Any, Callable, Iterable, Optional def _download(url: str, timeout: int = 60) -> str: req = urllib.request.Request(url, headers={"User-Agent": "quant-buddy-skill/csv-fetch"}) with urllib.request.urlopen(req, timeout=timeout) as resp: raw = resp.read() return raw.decode("utf-8-sig", errors="replace") def _to_float(value: Any) -> Optional[float]: if value is None: return None text = str(value).strip() if not text or text.lower() in {"nan", "null", "none"}: return None try: return float(text) except ValueError: return None def _parse_csv_text(text: str): """Parse the wide export: ticker,name,<date1>,<date2>...""" reader = csv.reader(io.StringIO(text)) header = next(reader, None) if not header or len(header) < 3: raise ValueError("CSV 表头异常:期望 ticker,name,<日期...> 至少 3 列") dates = [cell.strip() for cell in header[2:]] rows = [] for raw in reader: if not raw or len(raw) < 2: continue values = [_to_float(cell) for cell in raw[2:]] if len(values) < len(dates): values += [None] * (len(dates) - len(values)) elif len(values) > len(dates): values = values[: len(dates)] rows.append({"ticker": raw[0].strip(), "name": raw[1].strip(), "values": values}) return dates, rows def _summarize_row(dates: Iterable[str], values: Iterable[Optional[float]]) -> dict[str, Any]: pairs = [(date, value) for date, value in zip(dates, values) if value is not None] if not pairs: return {"count": 0, "note": "该资产在区间内无有效数据"} first_date, first_value = pairs[0] last_date, last_value = pairs[-1] min_date, min_value = min(pairs, key=lambda pair: pair[1]) max_date, max_value = max(pairs, key=lambda pair: pair[1]) result: dict[str, Any] = { "count": len(pairs), "first": {"date": first_date, "value": first_value}, "last": {"date": last_date, "value": last_value}, "min": {"date": min_date, "value": min_value}, "max": {"date": max_date, "value": max_value}, } if first_value != 0: result["period_return_pct"] = round((last_value / first_value - 1) * 100, 4) return result def _pearson(left: list[float], right: list[float]) -> Optional[float]: if len(left) < 2 or len(left) != len(right): return None left_mean = sum(left) / len(left) right_mean = sum(right) / len(right) numerator = sum((x - left_mean) * (y - right_mean) for x, y in zip(left, right)) left_sq = sum((x - left_mean) ** 2 for x in left) right_sq = sum((y - right_mean) ** 2 for y in right) denominator = math.sqrt(left_sq * right_sq) if denominator == 0: return None return round(numerator / denominator, 8) def _direction_agreement(left: list[float], right: list[float]) -> tuple[Optional[float], int]: comparable = 0 agreed = 0 for index in range(1, min(len(left), len(right))): left_delta = left[index] - left[index - 1] right_delta = right[index] - right[index - 1] # Flat/flat counts as agreement. Flat/non-flat is a disagreement. comparable += 1 if (left_delta > 0) == (right_delta > 0) and (left_delta < 0) == (right_delta < 0): agreed += 1 if comparable == 0: return None, 0 return round(agreed / comparable * 100, 4), comparable def _series_map(source: dict[str, Any]) -> dict[str, dict[str, float]]: mapped: dict[str, dict[str, float]] = {} for asset in source.get("assets") or []: key = str(asset.get("ticker") or asset.get("name") or "").strip() if not key: continue mapped[key] = { str(point["date"]): float(point["value"]) for point in asset.get("series") or [] if point.get("date") is not None and point.get("value") is not None } return mapped def _pairwise_analysis(sources: list[dict[str, Any]]) -> list[dict[str, Any]]: analyses: list[dict[str, Any]] = [] for left, right in combinations(sources, 2): if "error" in left or "error" in right: continue left_assets = _series_map(left) right_assets = _series_map(right) for asset_key in sorted(set(left_assets) & set(right_assets)): left_by_date = left_assets[asset_key] right_by_date = right_assets[asset_key] aligned_dates = sorted(set(left_by_date) & set(right_by_date)) left_values = [left_by_date[date] for date in aligned_dates] right_values = [right_by_date[date] for date in aligned_dates] agreement, direction_pairs = _direction_agreement(left_values, right_values) analyses.append({ "asset": asset_key, "left_label": left.get("label"), "right_label": right.get("label"), "aligned_points": len(aligned_dates), "aligned_date_range": [aligned_dates[0], aligned_dates[-1]] if aligned_dates else [], "level_correlation": _pearson(left_values, right_values), "direction_agreement_pct": agreement, "direction_pairs": direction_pairs, }) return analyses def build_result( urls: Iterable[str], labels: Optional[Iterable[str]] = None, include_full: bool = False, max_points: int = 2000, timeout: int = 60, downloader: Callable[..., str] = _download, ) -> dict[str, Any]: """Build a normalized result from one or more ``fast_query`` CSV URLs.""" url_list = [str(url).strip() for url in urls if str(url).strip()] if not url_list: raise ValueError("至少需要一个 csv_url") if max_points <= 0: raise ValueError("max_points 必须大于 0") label_list = [str(label).strip() for label in (labels or [])] result: dict[str, Any] = {"sources": []} total_data_points = 0 # Pairwise analysis needs complete aligned series even when legacy stdout # only asks for summaries. Internal series are removed before returning # when include_full=False. for index, url in enumerate(url_list): label = label_list[index] if index < len(label_list) and label_list[index] else f"field_{index + 1}" source: dict[str, Any] = {"label": label, "url": url} try: text = downloader(url, timeout=timeout) dates, rows = _parse_csv_text(text) except Exception as exc: # noqa: BLE001 - surface per-source failure source["error"] = f"{type(exc).__name__}: {exc}" result["sources"].append(source) continue source["date_range"] = [dates[0], dates[-1]] if dates else [] source["total_dates"] = len(dates) source["asset_count"] = len(rows) assets = [] for row in rows: asset = {"ticker": row["ticker"], "name": row["name"]} asset.update(_summarize_row(dates, row["values"])) full_series = [ {"date": date, "value": value} for date, value in zip(dates, row["values"]) if value is not None ] total_data_points += len(full_series) if len(full_series) > max_points: asset["series_truncated"] = True asset["series_shown"] = max_points asset["series"] = full_series[:max_points] else: asset["series"] = full_series assets.append(asset) source["assets"] = assets result["sources"].append(source) result["pairwise_analysis"] = _pairwise_analysis(result["sources"]) result["summary"] = { "source_count": len(result["sources"]), "successful_source_count": sum("error" not in source for source in result["sources"]), "error_count": sum("error" in source for source in result["sources"]), "total_data_points": total_data_points, } if not include_full: for source in result["sources"]: for asset in source.get("assets") or []: asset.pop("series", None) asset.pop("series_truncated", None) asset.pop("series_shown", None) return result def _default_output_root() -> Path: return Path(__file__).resolve().parents[1] / "output" def _is_under(path: Path, root: Path) -> bool: try: path.relative_to(root) return True except ValueError: return False def write_artifact(result: dict[str, Any], output_path: Any, output_root: Any = None) -> dict[str, Any]: """Atomically write an artifact below ``output_root`` and return its hash.""" root = Path(output_root or _default_output_root()).expanduser().resolve() target = Path(output_path).expanduser() if not target.is_absolute(): target = (Path.cwd() / target).resolve() else: target = target.resolve() if target == root or not _is_under(target, root): raise ValueError(f"artifact 仅允许写入 skill/output: {target}") target.parent.mkdir(parents=True, exist_ok=True) payload = json.dumps(result, ensure_ascii=False, indent=2).encode("utf-8") fd, temp_name = tempfile.mkstemp(prefix=f".{target.name}.", suffix=".tmp", dir=str(target.parent)) try: with os.fdopen(fd, "wb") as handle: handle.write(payload) handle.flush() os.fsync(handle.fileno()) os.replace(temp_name, target) finally: try: os.unlink(temp_name) except FileNotFoundError: pass return { "artifact_file": str(target), "artifact_sha256": "sha256:" + hashlib.sha256(payload).hexdigest(), "artifact_bytes": len(payload), } def _compact_receipt(result: dict[str, Any], artifact_receipt: dict[str, Any], code: int) -> dict[str, Any]: series_summaries = [] for source in result.get("sources") or []: label = source.get("label") for asset in source.get("assets") or []: summary = { "label": label, "ticker": asset.get("ticker"), "name": asset.get("name"), "count": asset.get("count", 0), } for field in ("first", "last", "min", "max", "period_return_pct", "note"): if field in asset: summary[field] = asset[field] series_summaries.append(summary) return { "code": code, **artifact_receipt, "summary": result.get("summary", {}), "source_summaries": [ { "label": source.get("label"), "date_range": source.get("date_range", []), "asset_count": source.get("asset_count", 0), "error": source.get("error"), } for source in result.get("sources") or [] ], "series_summaries": series_summaries, "pairwise_analysis": result.get("pairwise_analysis", []), } def main(argv: Optional[list[str]] = None) -> int: parser = argparse.ArgumentParser(description="下载并解析 fast_query 的 csv_url") parser.add_argument("urls", nargs="+", help="一个或多个 fast_query csv_url") parser.add_argument("--labels", default="", help="逗号分隔的字段名,按顺序对应各 url") parser.add_argument("--full", action="store_true", help="保留完整 date/value 序列") parser.add_argument("--max-points", type=int, default=2000, help="每个资产最多保留的数据点") parser.add_argument("--timeout", type=int, default=60) parser.add_argument("--output", help="完整 artifact 输出路径,必须位于 skill/output") args = parser.parse_args(argv) labels = [item.strip() for item in args.labels.split(",")] if args.labels else [] # An artifact is intended for QBV reuse, so it must contain the full series. include_full = bool(args.full or args.output) try: result = build_result( args.urls, labels=labels, include_full=include_full, max_points=args.max_points, timeout=args.timeout, downloader=_download, ) code = 2 if any("error" in source for source in result["sources"]) else 0 if args.output: artifact_receipt = write_artifact(result, args.output) print(json.dumps(_compact_receipt(result, artifact_receipt, code), ensure_ascii=False)) else: print(json.dumps(result, ensure_ascii=False, indent=2)) return code except (OSError, ValueError, urllib.error.URLError) as exc: print(json.dumps({"code": 1, "error": type(exc).__name__, "message": str(exc)}, ensure_ascii=False)) return 1 if __name__ == "__main__": raise SystemExit(main()) -
fetch_minute_range_csv.py 2.8 KB
#!/usr/bin/env python3 """Consume a saved fast_query_minute_range response; no API key and no extra query.""" import argparse import hashlib import json import os from pathlib import Path import tempfile from urllib.parse import urlsplit from fetch_fastquery_csv import _download from minute_range_csv import parse_minute_range_csv def main(argv=None): parser = argparse.ArgumentParser(description='下载历史分钟CSV长表,保留全部列及交易日/UTC时间戳') parser.add_argument('manifest', help='保存的完整工具JSON响应文件,可用@file') parser.add_argument('--output', required=True, help='完整数据JSON,必须位于本skill/output内') parser.add_argument('--timeout', type=int, default=60) args = parser.parse_args(argv) try: root = (Path(__file__).resolve().parents[1] / 'output').resolve() target = Path(args.output).resolve() target.relative_to(root) manifest_path = Path(args.manifest.lstrip('@')).resolve() manifest_path.relative_to(root) raw = json.loads(manifest_path.read_text(encoding='utf-8-sig')) if raw.get('code') not in (None, 0) or raw.get('success') is False: raise ValueError('取数响应未成功') manifest = raw.get('data') if isinstance(raw.get('data'), dict) else raw if manifest.get('csv_url'): url = urlsplit(manifest['csv_url']) if url.scheme not in ('http', 'https') or not url.netloc or url.username or url.password: raise ValueError('非法CSV URL') text = _download(manifest['csv_url'], timeout=args.timeout) if manifest.get('csv_url') else None data = parse_minute_range_csv(text, manifest) data.pop('csv_url', None) # 物化文件不传播签名地址。 content = json.dumps({'schema': 'qb_minute_range_artifact_v1', 'data': data}, ensure_ascii=False, allow_nan=False).encode('utf-8') target.parent.mkdir(parents=True, exist_ok=True) with tempfile.NamedTemporaryFile(dir=target.parent, delete=False) as out: tmp = out.name; out.write(content) try: os.replace(tmp, target) finally: if os.path.exists(tmp): os.unlink(tmp) print(json.dumps({'code': 0, 'artifact_file': str(target), 'sha256': hashlib.sha256(content).hexdigest(), 'shape': data['shape'], 'columns': data['columns'], 'empty': not data['rows'], 'warnings': data.get('warnings', [])}, ensure_ascii=False)) return 0 except Exception as exc: # 网络错误可能带签名URL,不原样打印。 print(json.dumps({'code': 1, 'error': type(exc).__name__, 'message': '历史分钟CSV解析/保存失败;检查manifest、链接有效期及skill/output输出路径,不改用单日数据替代。'}, ensure_ascii=False)) return 1 if __name__ == '__main__': raise SystemExit(main()) -
formula_package.py 18.7 KB
#!/usr/bin/env python3 r""" 公式任务包(Formula Package)客户端 —— 注册一组公式为任务包,凭包凭证流式取数。 对接接口文档:docs/formulaPackage 相关文档/对外接口文档.md 工具说明文档:tools/formula_package.md 两段式使用: 1. 注册(需 API Key):提交一组 formulas + 各产出读取模式,服务端执行校验后 返回 package_id + signature(signature 仅此一次明文返回,请妥善保存)。 2. 取数(无需 API Key):凭 package_id + signature 拉取数据,SSE 流式返回, 底层数据更新后自动重算,永远返回最新结果。 子命令: register 注册任务包(读 config.json 的 api_key + endpoint) query 取数(无需 api_key,凭 package_id + signature) list 列出我的任务包(需 api_key) revoke 撤销任务包(需 api_key) refresh 强制刷新/轮换签名(需 api_key) 参数传递(与 call.py / executor.py 同款,避免 PowerShell GBK 截断): 优先级:FP_PARAMS 环境变量 > @file > 命令行 JSON > stdin 用法示例: # 注册(推荐用 @file 传中文公式,Windows 必须) python scripts/formula_package.py register @params.json # 取数(package_id + signature 即可,不需要 api_key) FP_PARAMS='{"package_id":"pkg_xxx","signature":"a1b2..."}' \ python scripts/formula_package.py query # 管理 python scripts/formula_package.py list '{"page":1,"page_size":20}' python scripts/formula_package.py revoke '{"package_id":"pkg_xxx"}' python scripts/formula_package.py refresh '{"package_id":"pkg_xxx","rotate_signature":true}' 输出: 结果打印到 stdout(UTF-8),并写入临时目录下 fp_out.txt(防终端缓冲吞输出)。 register / query 成功时,包凭证额外落盘到 output/formula_packages/<package_id>.json, 方便后续取数与 HTML 页面引用(signature 服务端不可再取出,本地不存丢失即不可恢复)。 """ import json import os import sys import tempfile import urllib.request import urllib.error SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) SKILL_ROOT = os.path.dirname(SCRIPT_DIR) # 复用 executor.py 的配置加载、无代理 opener、skill 版本/渠道头,保持与其它工具一致 if SCRIPT_DIR not in sys.path: sys.path.insert(0, SCRIPT_DIR) import executor as _ex # noqa: E402 # 复用 call.py 的 session 读取与版本守卫,保证公式包与其它工具走同一套 # “先 newSession + 版本检查、每个请求带 task_id/user_query” 的统一流程。 # call.py 的 main() 受 __name__ 守卫,import 仅执行常量定义,无副作用。 import call as _call # noqa: E402 from task_context import TaskContextError, inject_or_validate_task_id # noqa: E402 # 公式包接口的统一前缀(与 fastQuery 等不同,固定带 /skill) _PATH = { "register": "/skill/registerFormulaPackage", "query": "/skill/queryFormulaPackage", "list": "/skill/listFormulaPackages", "revoke": "/skill/revokeFormulaPackage", "refresh": "/skill/refreshFormulaPackage", } # 取数(SSE)可能等待服务端重算,给足超时 _QUERY_TIMEOUT = 1800 _DEFAULT_TIMEOUT = 600 def _emit(obj): """打印结果(dict→JSON,或原样字符串),并写一份到临时文件防终端吞输出。""" text = obj if isinstance(obj, str) else json.dumps(obj, ensure_ascii=False, indent=2) out_file = os.path.join(tempfile.gettempdir(), "fp_out.txt") try: with open(out_file, "w", encoding="utf-8") as f: f.write(text) except Exception: pass try: print(text) except UnicodeEncodeError: enc = getattr(sys.stdout, "encoding", None) or "utf-8" sys.stdout.buffer.write((text + "\n").encode(enc, errors="replace")) sys.stdout.buffer.flush() def _read_params(argv): """按 FP_PARAMS > @file > 命令行 JSON > stdin 优先级解析参数 dict。""" raw = os.environ.get("FP_PARAMS", "").strip() if not raw and len(argv) >= 1: if argv[0].startswith("@"): with open(argv[0][1:], "r", encoding="utf-8-sig") as f: raw = f.read() else: raw = " ".join(argv) if not raw and not sys.stdin.isatty(): raw = sys.stdin.buffer.read().decode("utf-8", errors="replace").strip() raw = raw or "{}" try: return json.loads(raw) except json.JSONDecodeError as e: _emit({"code": 1, "message": f"参数 JSON 解析失败: {e}", "raw": raw[:200]}) sys.exit(1) def _config(require_key, params=None): """加载 endpoint(+api_key)。query 子命令 require_key=False。 require_key=True 时,本次调用实际用哪个 api_key 走与 executor.py 同一条规则(见 _ex.resolve_api_key):params 里传了 api_key 就用传入的(原地 pop 掉,不落盘/不混进请求体), 没传就用 config.json/config.local.json/QUANT_BUDDY_API_KEY 解析出的默认值;两者都没有则报错。 这样公式包的注册/查询/撤销/刷新也能像 executor.py 分发的工具一样接受调用方自带的 api_key, 不再被迫和其它并发会话共用 config.json 里的默认身份。 """ if require_key: cfg = _ex.load_config() api_key = _ex.resolve_api_key(params, cfg) else: # query 不需要 api_key;只取 endpoint,api_key 缺失也不报错 try: cfg = _ex.load_config() except ValueError: # 仅 endpoint 即可 path = os.path.join(SKILL_ROOT, "config.json") with open(path, "r", encoding="utf-8") as f: cfg = json.load(f) api_key = cfg.get("api_key", "") endpoint = (cfg.get("endpoint") or "").rstrip("/") if not endpoint: raise ValueError("config.json 缺少 endpoint") return endpoint, api_key def _headers(api_key=None, accept=None, task_id=None): return _ex._trace_headers( {"task_id": task_id} if task_id else {}, api_key=api_key, accept=accept, content_type="application/json; charset=utf-8", ) def _http_json(method, url, headers, body=None, timeout=_DEFAULT_TIMEOUT): data = json.dumps(body, ensure_ascii=False).encode("utf-8") if body is not None else None req = urllib.request.Request(url, data=data, headers=headers, method=method) try: with _ex._NO_PROXY_OPENER.open(req, timeout=timeout) as resp: return json.loads(resp.read().decode("utf-8")) except urllib.error.HTTPError as e: try: return json.loads(e.read().decode("utf-8")) except Exception: return {"code": e.code, "success": False, "error": {"message": getattr(e, "reason", str(e))}} except Exception as e: return {"code": 1, "success": False, "error": {"message": str(e)}} def _save_credential(reg): """注册/轮换成功后把 package_id + signature 落盘,供后续取数 / HTML 引用。""" pkg = reg.get("package_id") sig = reg.get("signature") if not pkg or not sig: return None out_dir = os.path.join(SKILL_ROOT, "output", "formula_packages") os.makedirs(out_dir, exist_ok=True) path = os.path.join(out_dir, f"{pkg}.json") record = { "package_id": pkg, "signature": sig, "outputs": reg.get("outputs"), "expires_at": reg.get("expires_at"), } with open(path, "w", encoding="utf-8") as f: json.dump(record, f, ensure_ascii=False, indent=2) return path # ──────────────────────────────────────────────── # 统一流程:session 上下文 + 版本守卫(与 call.py 同款) # ──────────────────────────────────────────────── # 需要 api_key 的管理类子命令,必须先 newSession(与 call.py 的“先建会话”一致) _SESSION_REQUIRED = {"register", "list", "revoke", "refresh"} def _session_context(): """读取当前 session 的 task_id / user_query,并做版本守卫。 复用 call.py 的 SESSION 解析(含 QBS_SESSION_FILE / QBS_SESSION_KEY 多会话), 保证公式包与其它工具读同一个 .session.json。 返回 (ctx, guard): ctx = {"task_id": str|None, "user_query": str|None} guard = None 或版本不匹配的错误 dict(命中即应中止并提示重新 newSession) """ data = _call._read_session_full() ctx = { "task_id": data.get("task_id"), "user_query": data.get("user_query"), "task_mode": data.get("task_mode"), "task_source": data.get("task_source"), "task_id_locked": data.get("task_id_locked") is True, } # 版本守卫:与 call.py 行为一致 —— 仅当已有 session 且版本变化时拦截 current = _call._read_skill_version() session_ver = data.get("skill_version_at_creation") guard = None if current and ctx["task_id"] and session_ver is not None and session_ver != current: guard = { "code": 1, "error": "SKILL_VERSION_MISMATCH", "current_version": current, "session_version": session_ver, "message": ( f"检测到 skill 版本不匹配(session 创建于 {session_ver},当前为 {current})。" "请立即调用 newSession 创建新 session,然后重读 SKILL.md 后再使用公式包。" ), } return ctx, guard def _inject_session(body, ctx): """把 session 的 task_id / user_query 注入请求体(已存在则不覆盖,空串视为未传)。""" inject_or_validate_task_id(ctx, body) if ctx.get("user_query") and not body.get("user_query"): body["user_query"] = ctx["user_query"] return body # ──────────────────────────────────────────────── # 子命令 # ──────────────────────────────────────────────── def cmd_register(params, ctx): endpoint, api_key = _config(require_key=True, params=params) formulas = params.get("formulas") reads = params.get("reads") if not isinstance(formulas, list) or not formulas: return {"code": 1, "message": "formulas 必须是非空数组(每条形如 \"变量名 = 表达式\")"} if not isinstance(reads, list) or not reads: return {"code": 1, "message": "reads 必须是非空数组,指定对外产出及其 read_mode"} if len(formulas) > 100: return {"code": 1, "message": f"单包公式数 ≤ 100,当前 {len(formulas)}"} if len(reads) > 20: return {"code": 1, "message": f"单包对外产出数 ≤ 20,当前 {len(reads)}"} body = {"formulas": formulas, "reads": reads} for k in ("intents", "begin_date", "ttl_days"): if params.get(k) is not None: body[k] = params[k] _inject_session(body, ctx) reg = _http_json("POST", endpoint + _PATH["register"], _headers(api_key, task_id=ctx.get("task_id")), body, timeout=_DEFAULT_TIMEOUT) if reg.get("code") == 0 and reg.get("package_id"): saved = _save_credential(reg) if saved: reg["_saved_credential"] = saved return reg def cmd_query(params, ctx): """取数:无需 api_key,凭 package_id + signature,逐条 SSE → 组装为 outputs dict。""" endpoint, _ = _config(require_key=False) pkg = params.get("package_id") sig = params.get("signature") # 允许只传 package_id,从本地凭证补全 signature if pkg and not sig: cred = os.path.join(SKILL_ROOT, "output", "formula_packages", f"{pkg}.json") if os.path.exists(cred): with open(cred, "r", encoding="utf-8") as f: sig = json.load(f).get("signature") if not pkg or not sig: return {"code": 1, "message": "query 需要 package_id + signature(signature 可由本地凭证补全)"} # 取数端点对第三方保持无凭证;从 skill 内调用时附带 session 上下文供服务端 audit _qbody = _inject_session({"package_id": pkg, "signature": sig}, ctx) body = json.dumps(_qbody).encode("utf-8") req = urllib.request.Request(endpoint + _PATH["query"], data=body, headers=_headers(accept="text/event-stream", task_id=ctx.get("task_id")), method="POST") outputs = {} progress = [] done = None err = None try: resp = _ex._NO_PROXY_OPENER.open(req, timeout=_QUERY_TIMEOUT) except urllib.error.HTTPError as e: try: return json.loads(e.read().decode("utf-8")) except Exception: return {"code": e.code, "success": False, "error": {"message": getattr(e, "reason", str(e))}} except Exception as e: return {"code": 1, "success": False, "error": {"message": str(e)}} event_type, data_buf = None, [] with resp: for raw_line in resp: line = raw_line.decode("utf-8", errors="replace").rstrip("\r\n") if line == "": if event_type and data_buf: try: payload = json.loads("\n".join(data_buf)) except json.JSONDecodeError: payload = {"raw": "\n".join(data_buf)} if event_type == "result": outputs[payload.get("output")] = { "read_mode": payload.get("read_mode"), "data_id": payload.get("data_id"), "data": payload.get("data"), } sys.stderr.write(f" ✓ {payload.get('output')} ({payload.get('read_mode')})\n") sys.stderr.flush() elif event_type == "progress": progress.append(payload) sys.stderr.write(f" … recomputing {payload.get('node')} " f"{payload.get('done')}/{payload.get('total')}\n") sys.stderr.flush() elif event_type == "done": done = payload elif event_type == "error": err = payload event_type, data_buf = None, [] continue if line.startswith(":"): continue if line.startswith("event:"): event_type = line[6:].strip() elif line.startswith("data:"): data_buf.append(line[5:].lstrip()) if err is not None: return {"code": 1, "success": False, "error": err, "outputs": outputs, "progress": progress} return {"code": 0, "success": True, "package_id": pkg, "outputs": outputs, "progress": progress, "done": done} def cmd_list(params, ctx): import urllib.parse as _up endpoint, api_key = _config(require_key=True, params=params) page = params.get("page", 1) page_size = params.get("page_size", 20) # task_id / user_query 走 query string,供服务端 audit 中间件落库 qs_pairs = [("page", page), ("page_size", page_size)] if ctx.get("task_id"): qs_pairs.append(("task_id", ctx["task_id"])) if ctx.get("user_query"): qs_pairs.append(("user_query", ctx["user_query"])) url = f"{endpoint}{_PATH['list']}?" + _up.urlencode(qs_pairs) return _http_json("GET", url, _headers(api_key, task_id=ctx.get("task_id"))) def cmd_revoke(params, ctx): endpoint, api_key = _config(require_key=True, params=params) if not params.get("package_id"): return {"code": 1, "message": "revoke 需要 package_id"} body = _inject_session({"package_id": params["package_id"]}, ctx) return _http_json("POST", endpoint + _PATH["revoke"], _headers(api_key, task_id=ctx.get("task_id")), body) def cmd_refresh(params, ctx): endpoint, api_key = _config(require_key=True, params=params) if not params.get("package_id"): return {"code": 1, "message": "refresh 需要 package_id"} body = {"package_id": params["package_id"], "rotate_signature": bool(params.get("rotate_signature", False))} _inject_session(body, ctx) res = _http_json("POST", endpoint + _PATH["refresh"], _headers(api_key, task_id=ctx.get("task_id")), body) # 轮换签名时更新本地凭证 if res.get("code") == 0 and res.get("signature"): cred = os.path.join(SKILL_ROOT, "output", "formula_packages", f"{params['package_id']}.json") if os.path.exists(cred): try: with open(cred, "r", encoding="utf-8") as f: rec = json.load(f) rec["signature"] = res["signature"] with open(cred, "w", encoding="utf-8") as f: json.dump(rec, f, ensure_ascii=False, indent=2) res["_credential_updated"] = cred except Exception: pass return res _COMMANDS = { "register": cmd_register, "query": cmd_query, "list": cmd_list, "revoke": cmd_revoke, "refresh": cmd_refresh, } def main(): # 注:executor.py 在 import 时已把 stdout/stderr 重配为 UTF-8,无需重复包裹 if len(sys.argv) < 2 or sys.argv[1] not in _COMMANDS: _emit({"code": 1, "message": f"用法: formula_package.py <{'|'.join(_COMMANDS)}> [params]", "doc": (__doc__ or "").strip()[:400]}) sys.exit(1) cmd = sys.argv[1] params = _read_params(sys.argv[2:]) # ── 统一流程:先做版本守卫,再带上 session 上下文 ───────────── ctx, guard = _session_context() if guard is not None: _emit(guard) sys.exit(1) # 管理类子命令必须先 newSession(与 call.py 的“先建会话”一致) if cmd in _SESSION_REQUIRED and not ctx.get("task_id"): _emit({ "code": 1, "error": "SESSION_REQUIRED", "message": ( f"`{cmd}` 需要先创建 session:请先运行 " "`python scripts/call.py newSession`(携带 user_query)," "再使用公式包。这样每个请求才会带上 task_id / user_query / 当前版本。" ), }) sys.exit(1) try: result = _COMMANDS[cmd](params, ctx) except TaskContextError as e: result = {"code": 1, "error": e.code, "message": e.message} except (FileNotFoundError, ValueError) as e: result = {"code": 1, "message": str(e)} _emit(result) sys.exit(0 if (isinstance(result, dict) and result.get("code") == 0) else 1) if __name__ == "__main__": main() -
live_page_routing.py 60.6 KB
#!/usr/bin/env python3 """QBS -> QBV live-page routing, handoff, and local job idempotency. This module is intentionally independent from the market-data execution path. A routing or registry failure must never turn a valid QBS answer into a hard business failure. """ from __future__ import annotations import argparse import hashlib import json import os import re import sys import tempfile import time import uuid from datetime import datetime, timedelta, timezone from pathlib import Path from typing import Any, Dict, Iterable, Optional from qbv_computation_capsule import ( ComputationCapsuleError, build_computation_capsule, build_computation_capsule_from_validated_roles, validate_computation_capsule, ) SCHEMA_VERSION = "qbs_qbv_handoff_v1" JOB_SCHEMA_VERSION = "qbs_qbv_job_v2" ROUTES = {"none", "suggest", "create", "existing_page"} HANDOFF_ROUTES = {"create", "existing_page"} TERMINAL_STATUSES = {"completed", "failed"} JOB_STATUSES = {"queued", "running", *TERMINAL_STATUSES} _ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$") _PAGE_ID_RE = re.compile(r"\bpage_[A-Za-z0-9_-]{6,}\b", re.IGNORECASE) _URL_RE = re.compile(r"https?://[^\s<>\]\[\"']+", re.IGNORECASE) _SKILL_ROOT = Path(__file__).resolve().parents[1] def _resolve_session_file() -> Path: explicit = os.environ.get("QBS_SESSION_FILE", "").strip() if explicit: return Path(explicit).expanduser().resolve() key = os.environ.get("QBS_SESSION_KEY", "").strip() if key: safe_key = re.sub(r"[^A-Za-z0-9_\-]", "_", key)[:64] return _SKILL_ROOT / "output" / f".session.{safe_key}.json" return _SKILL_ROOT / "output" / ".session.json" def _load_current_session() -> Dict[str, Any]: path = _resolve_session_file() try: payload = json.loads(path.read_text(encoding="utf-8-sig")) except FileNotFoundError as exc: raise LivePageRoutingError("QBS_SESSION_REQUIRED", f"缺少当前 QBS session: {path}") from exc except (OSError, json.JSONDecodeError) as exc: raise LivePageRoutingError("QBS_SESSION_INVALID", f"当前 QBS session 无法读取: {path}") from exc if not isinstance(payload, dict): raise LivePageRoutingError("QBS_SESSION_INVALID", f"当前 QBS session 不是 JSON object: {path}") return payload def _read_source_skill_version() -> str: skill_md = _SKILL_ROOT / "SKILL.md" try: for line in skill_md.read_text(encoding="utf-8-sig").splitlines(): if line.strip().startswith("version:"): return line.split(":", 1)[1].strip() except OSError: pass return "" def _normalize_agent_intent(value: Any) -> Optional[str]: if not isinstance(value, str): return None normalized = value.strip() return normalized[:300] or None def _resolve_prepare_lineage( task_id: Any, turn_id: Any, user_query: Any, agent_intent: Any = None, ) -> tuple[str, str, str, Optional[str]]: supplied = { "task_id": _compact_text(task_id), "turn_id": _compact_text(turn_id), "user_query": _compact_text(user_query), } supplied_intent = _normalize_agent_intent(agent_intent) if all(supplied.values()): if agent_intent is not None: return supplied["task_id"], supplied["turn_id"], supplied["user_query"], supplied_intent try: session = _load_current_session() except LivePageRoutingError: # Complete explicit lineage remains sufficient for legacy callers. # Intent enrichment from local session context is optional and fail-soft. return supplied["task_id"], supplied["turn_id"], supplied["user_query"], None session_matches = ( _compact_text(session.get("task_id")) == supplied["task_id"] and _compact_text(session.get("current_turn_id") or session.get("turn_id")) == supplied["turn_id"] and _compact_text(session.get("current_user_query") or session.get("user_query")) == supplied["user_query"] ) inherited_intent = _normalize_agent_intent(session.get("current_agent_intent")) if session_matches else None return supplied["task_id"], supplied["turn_id"], supplied["user_query"], inherited_intent session = _load_current_session() session_values = { "task_id": _compact_text(session.get("task_id")), "turn_id": _compact_text(session.get("current_turn_id") or session.get("turn_id")), "user_query": _compact_text(session.get("current_user_query") or session.get("user_query")), } session_intent = _normalize_agent_intent(session.get("current_agent_intent")) for field, explicit in supplied.items(): current = session_values[field] if explicit and current and explicit != current: raise LivePageRoutingError("QBS_LINEAGE_MISMATCH", f"显式 {field} 与当前 QBS session 不一致") resolved = {field: supplied[field] or session_values[field] for field in supplied} missing = [field for field, value in resolved.items() if not value] if missing: raise LivePageRoutingError("QBS_SESSION_LINEAGE_INCOMPLETE", f"当前 QBS session 缺少: {', '.join(missing)}") return ( resolved["task_id"], resolved["turn_id"], resolved["user_query"], supplied_intent if agent_intent is not None else session_intent, ) class LivePageRoutingError(ValueError): def __init__(self, code: str, message: str): super().__init__(message) self.code = code self.message = message _EXPLICIT_STATIC_PATTERNS = ( "只要png", "只要 png", "仅png", "仅 png", "png就行", "png 就行", "本地图片", "不要网页", "不要页面", "不需要网页", "不需要活页", "不要活页", "只发图片", "只要图片", "导出图片", "不要画图", "不用画图", "不需要图", "无需图", "只要表格", "仅要表格", "只看表格", ) _EXPLICIT_NO_VISUAL_RE = re.compile( r"(?:不要|不用|不需要|无需)[^。!?\n]{0,20}(?:画|绘制|生成|做)?[^。!?\n]{0,20}(?:图表|图|曲线)", re.IGNORECASE, ) _STRONG_VISUAL_PATTERNS = ( "k线图", "k 线图", "k线", "k 线", "分时图", "价格走势图", "行情走势图", "收益曲线", "收益对比曲线", "净值曲线", "基准对比", "回撤曲线", "多资产对比图", "资产对比图", "指标曲线", "排名图", "排行榜图", "热力图", "动态看板", "实时看板", "每日看板", "周度看板", "月度看板", "画个图", "画一张图", "生成图表", "交互图表", ) _STRONG_PAGE_CAPABILITIES = ( "可刷新", "自动刷新", "实时更新", "可交互", "交互式", "可分享", "公开链接", "持续跟踪", "长期跟踪", "每天更新", "每日更新", "定期更新", "看板", "活页", ) _WEAK_DURABLE_PATTERNS = ( "以后关注", "以后跟踪", "后续跟踪", "长期关注", "持续关注", "想做个页面", "想做一个页面", "整理成页面", "沉淀一下", "留着以后看", ) _HIGH_RISK_PATTERNS = ( "持仓", "仓位", "成本价", "持仓成本", "买入成本", "买入价", "股数", "持股数量", "止损", "止盈", "减仓", "加仓", "调仓", "自动化规则", "自动交易", "触发条件", ) _PAGE_REFERENCE_HINTS = ("page_id", "page id", "活页链接", "这个活页", "现有活页", "已有活页") _VISUAL_COMPOSITION_RES = ( re.compile(r"(?:画|绘制)(?:成|为)?(?:一张|同一张|同一个)?[^。!?\n]{0,48}(?:图表|图|曲线)", re.IGNORECASE), re.compile(r"(?:放在|放到|放进)[^。!?\n]{0,40}(?:一张|同一张|同一个)?图(?:里|中)?", re.IGNORECASE), re.compile(r"(?:做成|制成|生成|展示为)[^。!?\n]{0,32}(?:图表|图|曲线)", re.IGNORECASE), re.compile(r"(?:同图|同一张图|同一个图|一张图)(?:里|中)?[^。!?\n]{0,24}(?:对比|比较|展示|查看|看)", re.IGNORECASE), re.compile(r"(?:放在一起|放到一起|放一起)[^。!?\n]{0,24}(?:画|绘制|做成图|制成图)", re.IGNORECASE), ) _DURABLE_FACTOR_SCREEN_RES = ( # 高频且结构稳定的价值质量榜单:即使用户没有说“图/页面”,结果天然适合 # 排序、筛选、刷新和每日复用。显式“只要表格/不要网页”仍由上方静态规则拦截。 re.compile( r"(?=.*(?:选股|筛选|排行|排名))(?=.*(?:top\s*\d+|前\s*\d+))" r"(?=.*(?:低\s*(?:pe|市盈率)|低估值))(?=.*(?:高\s*roe|高净资产收益率|高盈利))", re.IGNORECASE, ), ) def _compact_text(value: Any) -> str: return re.sub(r"\s+", " ", str(value or "")).strip() def _match_any(text: str, patterns: Iterable[str]) -> bool: lowered = text.lower() return any(pattern.lower() in lowered for pattern in patterns) def _is_explicit_static_request(text: str) -> bool: return _match_any(text, _EXPLICIT_STATIC_PATTERNS) or bool(_EXPLICIT_NO_VISUAL_RE.search(text)) def _is_explicit_visual_request(text: str) -> bool: if _match_any(text, _STRONG_VISUAL_PATTERNS): return True return any(pattern.search(text) for pattern in _VISUAL_COMPOSITION_RES) def _is_durable_factor_screen(text: str) -> bool: return any(pattern.search(text) for pattern in _DURABLE_FACTOR_SCREEN_RES) def normalize_page_reference(value: Any) -> Optional[str]: text = _compact_text(value) if not text: return None page_match = _PAGE_ID_RE.search(text) if page_match: return page_match.group(0) url_match = _URL_RE.search(text) if url_match: return url_match.group(0).rstrip(".,;,。;))") return text def extract_page_reference(user_query: str) -> Optional[str]: query = _compact_text(user_query) page_match = _PAGE_ID_RE.search(query) if page_match: return page_match.group(0) url_match = _URL_RE.search(query) if url_match and _match_any(query, _PAGE_REFERENCE_HINTS + ("quantbuddy", "pages.")): return url_match.group(0).rstrip(".,;,。;))") return None def route_live_page( user_query: Any, page_reference: Any = None, persistence_confirmed: bool = False, ) -> Dict[str, Any]: """Return a deterministic QBS-side routing decision. The classifier is deliberately conservative: ordinary analysis remains in QBS; only explicit visual/page capabilities create a page automatically. """ query = _compact_text(user_query) reference = normalize_page_reference(page_reference) or extract_page_reference(query) if reference: return { "route": "existing_page", "route_reason": ["existing_page_reference"], "page_reference": reference, "requires_persistence_confirmation": False, } if _is_explicit_static_request(query): return { "route": "none", "route_reason": ["static_image_only"], "page_reference": None, "requires_persistence_confirmation": False, } high_risk = _match_any(query, _HIGH_RISK_PATTERNS) strong_visual = _is_explicit_visual_request(query) strong_page = _match_any(query, _STRONG_PAGE_CAPABILITIES) durable_factor_screen = _is_durable_factor_screen(query) # "看看走势/分析走势" is intentionally weak unless the user also names a # concrete chart/page capability caught above. weak_trend_only = ( ("走势" in query and not strong_visual and not strong_page) or query in {"看看走势", "看下走势", "分析一下走势", "最近走势", "走势怎么样"} ) if weak_trend_only: return { "route": "none", "route_reason": ["weak_visual_expression"], "page_reference": None, "requires_persistence_confirmation": False, } if strong_visual or strong_page or durable_factor_screen: reasons = [] if strong_visual: reasons.append("visualization_required") if strong_page: reasons.append("durable_interactive_page") if durable_factor_screen: reasons.append("structured_factor_screening_ranking") if high_risk and not persistence_confirmed: reasons.append("persistence_confirmation_required") return { "route": "suggest", "route_reason": reasons, "page_reference": None, "requires_persistence_confirmation": True, } return { "route": "create", "route_reason": reasons, "page_reference": None, "requires_persistence_confirmation": False, } if _match_any(query, _WEAK_DURABLE_PATTERNS): return { "route": "suggest", "route_reason": ["durable_value_but_schema_unclear"], "page_reference": None, "requires_persistence_confirmation": high_risk and not persistence_confirmed, } return { "route": "none", "route_reason": ["one_off_qbs_answer"], "page_reference": None, "requires_persistence_confirmation": False, } def _required_id(name: str, value: Any) -> str: text = _compact_text(value) if not text or not _ID_RE.fullmatch(text): raise LivePageRoutingError( f"INVALID_{name.upper()}", f"{name} 需要为 1-128 位字母、数字、点、下划线、冒号或短横线", ) return text def _required_text(name: str, value: Any) -> str: text = _compact_text(value) if not text: raise LivePageRoutingError(f"{name.upper()}_REQUIRED", f"{name} 不能为空") return text def _json_list(value: Any, name: str) -> list: if value is None: return [] if not isinstance(value, list): raise LivePageRoutingError(f"INVALID_{name.upper()}", f"{name} 必须是 JSON array") return value def _has_explicit_validation_receipt(validated_roles: Any, validation_receipts: Any) -> bool: if isinstance(validation_receipts, list) and validation_receipts: return True if not isinstance(validated_roles, list): return False for raw in validated_roles: if not isinstance(raw, dict): continue if raw.get("validation_receipt_file"): return True if isinstance(raw.get("validation_receipt_files"), list) and raw["validation_receipt_files"]: return True if isinstance(raw.get("validation_receipts"), list) and raw["validation_receipts"]: return True return False def _validated_role_data_ids(validated_roles: Any) -> set[str]: if not isinstance(validated_roles, list): return set() data_ids: set[str] = set() for item in validated_roles: if not isinstance(item, dict): continue data_id = item.get("data_id") if isinstance(data_id, str) and data_id.strip(): data_ids.add(data_id) plural = item.get("data_ids") if isinstance(plural, list): for candidate in plural: if isinstance(candidate, str) and candidate.strip(): data_ids.add(candidate) return data_ids def _validation_receipt_root() -> Path: override = _compact_text(os.environ.get("QBS_VALIDATION_RECEIPT_DIR")) if override: return Path(override).expanduser().resolve() return (_SKILL_ROOT / "output" / "validation_receipts").resolve() def _discover_formula_validation_receipt( *, task_id: str, validated_roles: Any, ) -> tuple[list[Dict[str, Any]], str, Optional[str]]: """Find one exact successful formula receipt for the current materialized roles. Automatic reuse is deliberately fail-closed: a candidate must belong to the same task and contain every materialized ``data_id``. Partial intersections are ignored because they cannot prove that one exact formula batch produced the complete page input. Multiple exact matches are allowed only when they carry the same runtime-contract fingerprint. """ expected_ids = _validated_role_data_ids(validated_roles) if not expected_ids: return [], "no_matching_receipt", None root = _validation_receipt_root() if not root.is_dir(): return [], "no_matching_receipt", None exact_matches = [] for path in sorted(root.glob("*.json")): try: payload = json.loads(path.read_text(encoding="utf-8-sig")) except (OSError, json.JSONDecodeError, UnicodeError): continue if not isinstance(payload, dict): continue if _compact_text(payload.get("task_id")) != task_id: continue if payload.get("tool_name") != "runMultiFormulaBatchStream": continue if payload.get("success") is not True or payload.get("status") != "completed": continue runtime_contract = payload.get("runtime_contract") if not isinstance(runtime_contract, dict): continue fingerprint = _compact_text(runtime_contract.get("contract_fingerprint")) if not fingerprint: continue output_ids = { str(item.get("data_id") or item.get("indexinfo_id") or "").strip() for item in payload.get("outputs", []) if isinstance(item, dict) } output_ids.discard("") if not expected_ids.issubset(output_ids): continue exact_matches.append((path.resolve(), payload, fingerprint)) if not exact_matches: return [], "no_matching_receipt", None fingerprints = {item[2] for item in exact_matches} if len(fingerprints) != 1: raise LivePageRoutingError( "AMBIGUOUS_VALIDATION_RECEIPT_CONTRACT", "同一 task_id 与 data_id 命中多个不同公式合同,禁止猜测后交给 QBV", ) selected_path, selected_payload, _ = sorted( exact_matches, key=lambda item: (_compact_text(item[1].get("created_at")), str(item[0])), reverse=True, )[0] receipt = dict(selected_payload) receipt["receipt_file"] = str(selected_path) receipt["receipt_sha256"] = "sha256:" + hashlib.sha256(selected_path.read_bytes()).hexdigest() return [receipt], "matched_by_task_and_data_ids", str(selected_path) def build_qbv_handoff( *, task_id: Any, turn_id: Any, source_skill_id: Any = None, source_skill_id_status: Any = None, source_skill_name: Any = "quant-buddy-skill", source_skill_version: Any = None, user_query: Any, agent_intent: Any = None, route: Any, route_reason: Any = None, page_reference: Any = None, validated_outputs: Any = None, validation_receipts: Any = None, computation_capsule: Any = None, requires_persistence_confirmation: bool = False, persistence_confirmed: bool = False, ) -> Dict[str, Any]: route_value = _compact_text(route) if route_value not in HANDOFF_ROUTES: raise LivePageRoutingError( "HANDOFF_ROUTE_NOT_ALLOWED", "只有 create 或 existing_page 可以生成 QBV Handoff", ) if requires_persistence_confirmation and not persistence_confirmed: raise LivePageRoutingError( "PERSISTENCE_CONFIRMATION_REQUIRED", "高风险状态未经确认,不得生成 QBV Handoff 或入队", ) reference = normalize_page_reference(page_reference) if route_value == "existing_page" and not reference: raise LivePageRoutingError( "PAGE_REFERENCE_REQUIRED", "existing_page 路由必须提供 page_reference", ) reasons = _json_list(route_reason, "route_reason") if not all(isinstance(item, str) and item.strip() for item in reasons): raise LivePageRoutingError("INVALID_ROUTE_REASON", "route_reason 只能包含非空字符串") source_id_text = _compact_text(source_skill_id) source_id = _required_id("source_skill_id", source_id_text) if source_id_text else None source_status = _compact_text(source_skill_id_status) or ("available" if source_id else "unavailable") if source_status not in {"available", "unavailable"}: raise LivePageRoutingError( "INVALID_SOURCE_SKILL_ID_STATUS", "source_skill_id_status 只能是 available 或 unavailable", ) if bool(source_id) != (source_status == "available"): raise LivePageRoutingError( "SOURCE_SKILL_ID_STATUS_MISMATCH", "source_skill_id 与 source_skill_id_status 不一致", ) source_name = _required_id("source_skill_name", source_skill_name or "quant-buddy-skill") source_version_text = _compact_text(source_skill_version) source_version = _required_id("source_skill_version", source_version_text) if source_version_text else None task_value = _required_id("task_id", task_id) turn_value = _required_id("turn_id", turn_id) query_value = _required_text("user_query", user_query) intent_value = _normalize_agent_intent(agent_intent) capsule = None if computation_capsule is not None: capsule = validate_computation_capsule( computation_capsule, expected_task_id=task_value, expected_turn_id=turn_value, expected_user_query=query_value, ) handoff = { "schema_version": SCHEMA_VERSION, "task_id": task_value, "turn_id": turn_value, "source_skill_id": source_id, "source_skill_id_status": source_status, "source_skill_name": source_name, "source_skill_version": source_version, "user_query": query_value, "agent_intent": intent_value, "route": route_value, "route_reason": [item.strip() for item in reasons], "page_reference": reference, "validated_outputs": _json_list(validated_outputs, "validated_outputs"), "validation_receipts": _json_list(validation_receipts, "validation_receipts"), "requires_persistence_confirmation": bool(requires_persistence_confirmation), "persistence_confirmed": bool(persistence_confirmed), } if capsule is not None: handoff["computation_capsule"] = capsule return handoff def validate_qbv_handoff(payload: Any) -> Dict[str, Any]: if not isinstance(payload, dict): raise LivePageRoutingError("INVALID_HANDOFF", "Handoff 必须是 JSON object") if payload.get("schema_version") != SCHEMA_VERSION: raise LivePageRoutingError("UNSUPPORTED_HANDOFF_SCHEMA", f"仅支持 {SCHEMA_VERSION}") forbidden = {"update_owned", "copy_to_owned", "direct", "fork", "unmatched"} if any(key in payload for key in forbidden): raise LivePageRoutingError("QBV_SOP_LEAKED_INTO_QBS", "QBS Handoff 不得包含页面归属或 QBV 路由动作") return build_qbv_handoff( task_id=payload.get("task_id"), turn_id=payload.get("turn_id"), source_skill_id=payload.get("source_skill_id"), source_skill_id_status=payload.get("source_skill_id_status"), source_skill_name=payload.get("source_skill_name", "quant-buddy-skill"), source_skill_version=payload.get("source_skill_version"), user_query=payload.get("user_query"), agent_intent=payload.get("agent_intent"), route=payload.get("route"), route_reason=payload.get("route_reason"), page_reference=payload.get("page_reference"), validated_outputs=payload.get("validated_outputs"), validation_receipts=payload.get("validation_receipts"), computation_capsule=payload.get("computation_capsule"), requires_persistence_confirmation=payload.get("requires_persistence_confirmation", False), persistence_confirmed=payload.get("persistence_confirmed", False), ) def idempotency_key_for(handoff: Dict[str, Any]) -> str: valid = validate_qbv_handoff(handoff) source = "\n".join(( valid["task_id"], valid["turn_id"], valid["route"], normalize_page_reference(valid.get("page_reference")) or "", )) return hashlib.sha256(source.encode("utf-8")).hexdigest() def _utc_now() -> str: return datetime.now(timezone.utc).isoformat().replace("+00:00", "Z") def _parse_utc(value: Any) -> datetime: text = _compact_text(value) if not text: raise LivePageRoutingError("INVALID_JOB_TIMESTAMP", "Job 时间戳不能为空") try: parsed = datetime.fromisoformat(text.replace("Z", "+00:00")) except ValueError as exc: raise LivePageRoutingError("INVALID_JOB_TIMESTAMP", f"无法解析 Job 时间戳: {text}") from exc if parsed.tzinfo is None: parsed = parsed.replace(tzinfo=timezone.utc) return parsed.astimezone(timezone.utc) def _worker_timeout_seconds(value: Any = None) -> int: raw = value if raw is None or raw == "": raw = os.environ.get("QBS_QBV_WORKER_TIMEOUT_SECONDS", "900") try: seconds = int(raw) except (TypeError, ValueError) as exc: raise LivePageRoutingError("INVALID_WORKER_TIMEOUT", "worker_timeout_seconds 必须是正整数") from exc if not 1 <= seconds <= 86400: raise LivePageRoutingError("INVALID_WORKER_TIMEOUT", "worker_timeout_seconds 必须在 1 到 86400 之间") return seconds def _job_root(job_dir: Any = None) -> Path: configured = _compact_text(job_dir) or os.environ.get("QBS_QBV_JOB_DIR", "").strip() root = Path(configured) if configured else Path(tempfile.gettempdir()) / "quant-buddy-qbv-jobs" root.mkdir(parents=True, exist_ok=True) return root def _atomic_write_json(path: Path, payload: Dict[str, Any]) -> None: temp_path = path.with_name(f".{path.name}.{os.getpid()}.{uuid.uuid4().hex}.tmp") temp_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8") os.replace(temp_path, path) class _RegistryLock: def __init__(self, path: Path, timeout: float = 5.0): self.path = path self.timeout = timeout self.fd = None def __enter__(self): deadline = time.monotonic() + self.timeout while True: try: self.fd = os.open(str(self.path), os.O_CREAT | os.O_EXCL | os.O_WRONLY) os.write(self.fd, f"{os.getpid()}\n".encode("ascii")) return self except FileExistsError: if time.monotonic() >= deadline: raise LivePageRoutingError("JOB_REGISTRY_LOCK_TIMEOUT", "QBV Job registry 正忙") time.sleep(0.05) def __exit__(self, exc_type, exc, tb): if self.fd is not None: os.close(self.fd) try: self.path.unlink() except FileNotFoundError: pass def prepare_qbv_job( handoff: Any, *, target_skill_id: Any = None, job_dir: Any = None, retry_failed: bool = False, ) -> Dict[str, Any]: valid = validate_qbv_handoff(handoff) key = idempotency_key_for(valid) root = _job_root(job_dir) job_path = root / f"{key}.job.json" handoff_path = root / f"{key}.handoff.json" lock_path = root / f"{key}.lock" now = _utc_now() with _RegistryLock(lock_path): existing = None if job_path.exists(): existing = json.loads(job_path.read_text(encoding="utf-8")) if existing: status = existing.get("status") if status == "failed" and existing.get("retryable") is True and retry_failed: existing.update({ "status": "queued", "failure_code": None, "retryable": False, "updated_at": now, "attempt": int(existing.get("attempt") or 1) + 1, "started_at": None, "expires_at": None, "completed_at": None, "failed_at": None, "published": False, "public_verified": False, "target_page_id": None, "public_url": None, }) _atomic_write_json(handoff_path, valid) _atomic_write_json(job_path, existing) return {**existing, "created": False, "reused": True, "should_spawn": True, "handoff_file": str(handoff_path), "job_file": str(job_path)} return {**existing, "created": False, "reused": True, "should_spawn": False, "handoff_file": str(handoff_path), "job_file": str(job_path)} target = _compact_text(target_skill_id) or None if target is not None: target = _required_id("target_skill_id", target) record = { "schema_version": JOB_SCHEMA_VERSION, "qbv_job_id": f"qbvjob_{key[:24]}", "idempotency_key": key, "task_id": valid["task_id"], "turn_id": valid["turn_id"], "source_skill_id": valid["source_skill_id"], "source_skill_id_status": valid["source_skill_id_status"], "source_skill_name": valid["source_skill_name"], "source_skill_version": valid["source_skill_version"], "target_skill_id": target, "route": valid["route"], "normalized_page_reference": normalize_page_reference(valid.get("page_reference")), "status": "queued", "delegation_tool": None, "delegation_id": None, "spawn_run_id": None, "child_session_key": None, "source_page_id": valid.get("page_reference") if valid["route"] == "existing_page" and str(valid.get("page_reference") or "").startswith("page_") else None, "target_page_id": None, "public_url": None, "failure_code": None, "retryable": False, "published": False, "public_verified": False, "worker_timeout_seconds": _worker_timeout_seconds(), "attempt": 1, "created_at": now, "updated_at": now, "started_at": None, "expires_at": None, "completed_at": None, "failed_at": None, } _atomic_write_json(handoff_path, valid) _atomic_write_json(job_path, record) return {**record, "created": True, "reused": False, "should_spawn": True, "handoff_file": str(handoff_path), "job_file": str(job_path)} def _fast_query_field_mapping(fields: list[str]) -> Dict[str, str]: mapping: Dict[str, str] = {"date": "date"} semantic_names = ( (("收盘", "股价", "价格", "close"), "price"), (("成交量", "volume"), "volume"), (("成交额", "amount", "turnover"), "amount"), (("市盈率", "pe", "pe_ttm"), "pe"), (("市净率", "pb"), "pb"), ) for field in fields: lowered = field.lower() for aliases, semantic in semantic_names: if any(alias.lower() in lowered for alias in aliases): mapping.setdefault(semantic, field) break return mapping def _artifact_summary(artifact_file: Path) -> Dict[str, Any]: try: payload = json.loads(artifact_file.read_text(encoding="utf-8-sig")) except (OSError, json.JSONDecodeError): return {"row_count": 0, "validation_receipts": []} summary = payload.get("summary") if isinstance(payload, dict) else None row_count = 0 if isinstance(summary, dict): try: row_count = max(0, int(summary.get("total_data_points") or 0)) except (TypeError, ValueError): row_count = 0 receipts = [] if isinstance(payload, dict) and isinstance(payload.get("pairwise_analysis"), list): receipts.append({ "kind": "fast_query_pairwise_analysis", "pairwise_analysis": payload["pairwise_analysis"], }) return {"row_count": row_count, "validation_receipts": receipts} def prepare_fast_query_page( *, task_id: Any, turn_id: Any, user_query: Any, source_skill_version: Any, agent_intent: Any = None, asset_id: Any, asset_name: Any, fields: Any, window_days: Any, artifact_file: Any, source_skill_id: Any = None, job_dir: Any = None, target_skill_id: Any = None, retry_failed: bool = False, ) -> Dict[str, Any]: """Create a hash-bound capsule, Handoff, and idempotent QBV Job in one call. This is the narrow adapter for a single-asset, multi-field ``fast_query`` visual request. It does not run any QBV page SOP and does not wait for page generation; it only prepares the independent Job for delegation. """ route_result = route_live_page(user_query) if route_result["route"] != "create": raise LivePageRoutingError( "FAST_QUERY_PAGE_REQUIRES_CREATE", f"prepare-fast-query-page 仅接受 create 路由,当前为 {route_result['route']}", ) if route_result.get("requires_persistence_confirmation"): raise LivePageRoutingError( "PERSISTENCE_CONFIRMATION_REQUIRED", "高风险持久状态未经确认,不得准备 QBV Job", ) if isinstance(fields, str): field_list = [item.strip() for item in fields.split(",") if item.strip()] elif isinstance(fields, list): field_list = [_compact_text(item) for item in fields if _compact_text(item)] else: raise LivePageRoutingError("INVALID_FAST_QUERY_FIELDS", "fields 必须是数组或逗号分隔字符串") if not 2 <= len(field_list) <= 4: raise LivePageRoutingError("INVALID_FAST_QUERY_FIELDS", "窄快路径只接受 2 到 4 个标准历史字段") try: days = int(window_days) except (TypeError, ValueError) as exc: raise LivePageRoutingError("INVALID_WINDOW_DAYS", "window_days 必须是正整数") from exc if days <= 0: raise LivePageRoutingError("INVALID_WINDOW_DAYS", "window_days 必须是正整数") artifact = Path(str(artifact_file or "")).expanduser().resolve() if not artifact.is_file(): raise LivePageRoutingError("ARTIFACT_FILE_MISSING", f"artifact_file 不存在: {artifact}") asset_id_text = _required_text("asset_id", asset_id) asset_name_text = _required_text("asset_name", asset_name) artifact_meta = _artifact_summary(artifact) validated_output = { "role": "main_series", "artifact_file": str(artifact), "row_count": artifact_meta["row_count"], "field_mapping": _fast_query_field_mapping(field_list), } contract = { "kind": "fast_query", "payload": { "assets": [asset_id_text], "query_type": "window", "fields": field_list, "window_days": days, }, } capsule = build_computation_capsule( task_id=task_id, turn_id=turn_id, user_query=user_query, page_intent={ "question_to_answer": _required_text("user_query", user_query), "recommended_page_type": "single_asset_multi_metric_timeseries", "primary_visualization": "price_volume_valuation_timeseries", "required_roles": ["main_series"], }, asset_resolution={ "query": asset_name_text, "canonical_name": asset_name_text, "canonical_id": asset_id_text, }, validated_contracts=[{ "role": "main_series", "kind": "fast_query", "contract": contract, }], validated_outputs=[validated_output], validated_insights=[], validation_receipts=artifact_meta["validation_receipts"], ) handoff = build_qbv_handoff( task_id=task_id, turn_id=turn_id, source_skill_id=source_skill_id, source_skill_version=source_skill_version, user_query=user_query, agent_intent=agent_intent, route="create", route_reason=route_result["route_reason"], validated_outputs=capsule["validated_outputs"], validation_receipts=capsule["validation_receipts"], computation_capsule=capsule, ) job = prepare_qbv_job( handoff, target_skill_id=target_skill_id, job_dir=job_dir, retry_failed=retry_failed, ) return { **job, "route": "create", "route_reason": route_result["route_reason"], "source_skill_id_status": handoff["source_skill_id_status"], "computation_coverage": "main_series", "artifact_file": str(artifact), "artifact_sha256": capsule["validated_outputs"][0]["data_hash"], } def prepare_validated_page( *, task_id: Any = None, turn_id: Any = None, user_query: Any = None, source_skill_version: Any = None, agent_intent: Any = None, page_intent: Any, validated_roles: Any, route: Any = None, page_reference: Any = None, asset_resolution: Any = None, validated_insights: Any = None, validation_receipts: Any = None, formula_runtime_contract: Any = None, source_skill_id: Any = None, target_skill_id: Any = None, job_dir: Any = None, retry_failed: bool = False, persistence_confirmed: bool = False, ) -> Dict[str, Any]: """Prepare Capsule + Handoff + idempotent Job from compact validated roles. This is the generic counterpart to ``prepare-fast-query-page``. It is used after QBS has already produced and validated structured artifacts for ranking, comparison, backtest, heatmap, or other page-worthy analysis. It deliberately stops before any QBV routing, ownership, rendering, publishing, or acceptance. """ task_id, turn_id, user_query, agent_intent = _resolve_prepare_lineage( task_id, turn_id, user_query, agent_intent ) source_skill_version = _compact_text(source_skill_version) or _read_source_skill_version() if not source_skill_version: raise LivePageRoutingError("SOURCE_SKILL_VERSION_REQUIRED", "无法从 SKILL.md 解析 source_skill_version") route_result = route_live_page( user_query, page_reference=page_reference, persistence_confirmed=bool(persistence_confirmed), ) requested_route = _compact_text(route) or route_result["route"] if route_result.get("requires_persistence_confirmation"): raise LivePageRoutingError( "PERSISTENCE_CONFIRMATION_REQUIRED", "高风险持久状态未经确认,不得准备 QBV Job", ) if requested_route not in HANDOFF_ROUTES: raise LivePageRoutingError( "VALIDATED_PAGE_REQUIRES_HANDOFF_ROUTE", f"prepare-validated-page 仅接受 create/existing_page,当前为 {requested_route}", ) if requested_route != route_result["route"]: raise LivePageRoutingError( "ROUTE_DECISION_MISMATCH", f"请求 route={requested_route} 与分类器 route={route_result['route']} 不一致", ) receipt_discovery = "explicit" if _has_explicit_validation_receipt( validated_roles, validation_receipts ) else "no_matching_receipt" discovered_receipt_file = None effective_validation_receipts = validation_receipts if receipt_discovery != "explicit": ( discovered_receipts, receipt_discovery, discovered_receipt_file, ) = _discover_formula_validation_receipt( task_id=task_id, validated_roles=validated_roles, ) if discovered_receipts: effective_validation_receipts = discovered_receipts capsule = build_computation_capsule_from_validated_roles( task_id=task_id, turn_id=turn_id, user_query=user_query, page_intent=page_intent, asset_resolution=asset_resolution, validated_roles=validated_roles, validated_insights=validated_insights, validation_receipts=effective_validation_receipts, formula_runtime_contract=formula_runtime_contract, ) handoff = build_qbv_handoff( task_id=task_id, turn_id=turn_id, source_skill_id=source_skill_id, source_skill_version=source_skill_version, user_query=user_query, agent_intent=agent_intent, route=requested_route, route_reason=route_result["route_reason"], page_reference=route_result.get("page_reference"), validated_outputs=capsule["validated_outputs"], validation_receipts=capsule["validation_receipts"], computation_capsule=capsule, requires_persistence_confirmation=False, persistence_confirmed=bool(persistence_confirmed), ) job = prepare_qbv_job( handoff, target_skill_id=target_skill_id, job_dir=job_dir, retry_failed=bool(retry_failed), ) artifacts = [ { "role": item["role"], "artifact_file": item.get("artifact_file"), "data_hash": item.get("data_hash"), "row_count": item.get("row_count"), } for item in capsule["validated_outputs"] ] return { **job, "route": requested_route, "route_reason": route_result["route_reason"], "source_skill_id_status": handoff["source_skill_id_status"], "computation_coverage": [item["role"] for item in capsule["validated_outputs"]], "artifacts": artifacts, "validation_receipt_count": len(capsule["validation_receipts"]), "validation_receipt_discovery": receipt_discovery, "formula_runtime_contract_attached": bool(capsule.get("formula_runtime_contract")), **( {"discovered_validation_receipt_file": discovered_receipt_file} if discovered_receipt_file else {} ), "handoff_ready_at": job.get("updated_at") or job.get("created_at"), } def prepare_industry_ranking_page( *, data_id: Any, index_title: Any, window_days: Any, asset_count: Any, as_of_date: Any = None, formula: Any = None, task_id: Any = None, turn_id: Any = None, user_query: Any = None, source_skill_version: Any = None, agent_intent: Any = None, source_skill_id: Any = None, target_skill_id: Any = None, job_dir: Any = None, retry_failed: bool = False, ) -> Dict[str, Any]: """Prepare a deterministic QBV handoff from an already materialized industry ranking. The command is intentionally narrow: QBS has already executed and validated the industry aggregation formula, so this function only binds the existing data_id, refresh formula, page intent, lineage, and idempotent QBV Job. It never renders a static chart or reruns the formula. """ task_id, turn_id, user_query, agent_intent = _resolve_prepare_lineage( task_id, turn_id, user_query, agent_intent ) data_id_value = _required_id("data_id", data_id) index_title_value = _required_text("index_title", index_title) try: window_days_value = int(window_days) except (TypeError, ValueError) as exc: raise LivePageRoutingError("INVALID_WINDOW_DAYS", "window_days 必须是正整数") from exc if not 1 <= window_days_value <= 2500: raise LivePageRoutingError("INVALID_WINDOW_DAYS", "window_days 必须在 1 到 2500 之间") try: asset_count_value = int(asset_count) except (TypeError, ValueError) as exc: raise LivePageRoutingError("INVALID_ASSET_COUNT", "asset_count 必须是正整数") from exc if not 1 <= asset_count_value <= 5000: raise LivePageRoutingError("INVALID_ASSET_COUNT", "asset_count 必须在 1 到 5000 之间") as_of_date_value = _compact_text(as_of_date) if as_of_date_value and not re.fullmatch(r"\d{8}", as_of_date_value): raise LivePageRoutingError("INVALID_AS_OF_DATE", "as_of_date 必须是 YYYYMMDD") formula_value = _compact_text(formula) or ( f'行业近{window_days_value}日涨跌幅=' f'成分平均汇总(涨跌幅("全市场每日收盘价",{window_days_value}),"申万资产所属指数")' ) role = { "role": "industry_aggregation_ranking", "data_id": data_id_value, "index_title": index_title_value, "description": ( f"申万一级行业成分股近{window_days_value}个交易日区间收益的算术平均排名" ), "asset_count": asset_count_value, "field_mapping": { "industry_code": "asset", "industry_name": "name", "return_value": "value", "as_of_date": "date", }, "formula": formula_value, "window_days": window_days_value, "aggregation": "arithmetic_mean_of_constituent_window_returns", "universe": "申万一级行业", "value_semantics": "decimal_return", } if as_of_date_value: role["date"] = as_of_date_value return prepare_validated_page( task_id=task_id, turn_id=turn_id, user_query=user_query, source_skill_version=source_skill_version, page_intent={ "question_to_answer": _compact_text(user_query), "title_hint": f"申万一级行业近{window_days_value}个交易日涨跌幅排名", "recommended_page_type": "industry_return_ranking", "primary_visualization": "horizontal_ranked_bar_chart", "required_roles": ["industry_aggregation_ranking"], "interaction_requirements": ["sort", "positive_negative_color", "hover_detail", "refresh"], "display_contract": { "positive_color": "red", "negative_color": "green", "zero_axis": True, "show_methodology": True, }, }, validated_roles=[role], asset_resolution={ "universe": "申万一级行业", "asset_count": asset_count_value, "classification": "SW_L1", }, source_skill_id=source_skill_id, target_skill_id=target_skill_id, job_dir=job_dir, retry_failed=bool(retry_failed), ) def _find_job_path(qbv_job_id: Any, job_dir: Any = None) -> Path: job_id = _required_id("qbv_job_id", qbv_job_id) root = _job_root(job_dir) matches = [] for path in root.glob("*.job.json"): try: payload = json.loads(path.read_text(encoding="utf-8")) except (OSError, json.JSONDecodeError): continue if payload.get("qbv_job_id") == job_id: matches.append(path) if not matches: raise LivePageRoutingError("QBV_JOB_NOT_FOUND", f"未找到 {job_id}") if len(matches) > 1: raise LivePageRoutingError("QBV_JOB_DUPLICATED", f"{job_id} 对应多个 Job 文件") return matches[0] def update_qbv_job( qbv_job_id: Any, *, status: Any, job_dir: Any = None, target_skill_id: Any = None, delegation_tool: Any = None, delegation_id: Any = None, spawn_run_id: Any = None, child_session_key: Any = None, source_page_id: Any = None, target_page_id: Any = None, public_url: Any = None, failure_code: Any = None, retryable: bool = False, published: Any = None, public_verified: Any = None, worker_timeout_seconds: Any = None, ) -> Dict[str, Any]: status_value = _compact_text(status) if status_value not in JOB_STATUSES: raise LivePageRoutingError("INVALID_JOB_STATUS", f"status 仅支持 {sorted(JOB_STATUSES)}") job_path = _find_job_path(qbv_job_id, job_dir) lock_path = job_path.with_suffix(job_path.suffix + ".lock") with _RegistryLock(lock_path): record = json.loads(job_path.read_text(encoding="utf-8")) current = record.get("status") allowed = { "queued": {"queued", "running", "failed"}, "running": {"running", "completed", "failed"}, "completed": {"completed"}, "failed": {"failed", "queued"}, } if status_value not in allowed.get(current, set()): raise LivePageRoutingError( "INVALID_JOB_TRANSITION", f"不允许 QBV Job 从 {current} 变为 {status_value}", ) updates = { "status": status_value, "updated_at": _utc_now(), } optional_ids = { "target_skill_id": target_skill_id, "delegation_tool": delegation_tool, "delegation_id": delegation_id, "spawn_run_id": spawn_run_id, "child_session_key": child_session_key, "source_page_id": source_page_id, "target_page_id": target_page_id, } for name, value in optional_ids.items(): text = _compact_text(value) if text: updates[name] = _required_id(name, text) url = _compact_text(public_url) if url: if not re.match(r"^https?://", url, re.IGNORECASE): raise LivePageRoutingError("INVALID_PUBLIC_URL", "public_url 必须是 http(s) URL") updates["public_url"] = url failure = _compact_text(failure_code) now = updates["updated_at"] if status_value == "running": timeout_seconds = _worker_timeout_seconds( worker_timeout_seconds if worker_timeout_seconds is not None else record.get("worker_timeout_seconds") ) started_at = record.get("started_at") or now expires_at = ( _parse_utc(started_at) + timedelta(seconds=timeout_seconds) ).isoformat().replace("+00:00", "Z") updates.update({ "worker_timeout_seconds": timeout_seconds, "started_at": started_at, "expires_at": expires_at, "failed_at": None, }) if status_value == "completed": target_page = updates.get("target_page_id") or record.get("target_page_id") target_skill = updates.get("target_skill_id") or record.get("target_skill_id") final_url = url or record.get("public_url") final_published = bool(published) if published is not None else bool(record.get("published")) final_verified = bool(public_verified) if public_verified is not None else bool(record.get("public_verified")) if not target_skill: raise LivePageRoutingError("TARGET_SKILL_ID_REQUIRED", "completed Job 必须记录真实 target_skill_id") if not target_page: raise LivePageRoutingError("TARGET_PAGE_ID_REQUIRED", "completed Job 必须记录 target_page_id") if not final_url: raise LivePageRoutingError("PUBLIC_URL_REQUIRED", "completed Job 必须记录 public_url") if not final_published: raise LivePageRoutingError("PUBLISHED_REQUIRED", "completed Job 必须确认 published=true") if not final_verified: raise LivePageRoutingError("PUBLIC_VERIFIED_REQUIRED", "completed Job 必须确认 public_verified=true") updates.update({ "published": True, "public_verified": True, "failure_code": None, "retryable": False, "completed_at": now, "failed_at": None, }) elif status_value == "failed": if not failure: raise LivePageRoutingError("FAILURE_CODE_REQUIRED", "failed Job 必须记录 failure_code") updates.update({ "failure_code": failure, "retryable": bool(retryable), "failed_at": now, "completed_at": None, }) record.update(updates) _atomic_write_json(job_path, record) return record def expire_stale_qbv_job( qbv_job_id: Any, job_dir: Any = None, *, max_age_seconds: Any = None, now: Any = None, ) -> Dict[str, Any]: """Fail a stale queued/running Job so it cannot remain non-terminal forever. A scheduler/parent callback still has to invoke this watchdog; the Skill repo does not pretend to own a production background queue. """ record = get_qbv_job(qbv_job_id, job_dir) if record.get("status") not in {"queued", "running"}: return {**record, "expired": False} timeout_seconds = _worker_timeout_seconds( max_age_seconds if max_age_seconds is not None else record.get("worker_timeout_seconds") ) anchor = record.get("started_at") or record.get("updated_at") or record.get("created_at") current = _parse_utc(now) if now is not None else datetime.now(timezone.utc) age_seconds = max(0.0, (current - _parse_utc(anchor)).total_seconds()) if age_seconds < timeout_seconds: return {**record, "expired": False, "age_seconds": age_seconds} failure_code = "QBV_WORKER_TIMEOUT" if record.get("status") == "running" else "QBV_QUEUE_TIMEOUT" failed = update_qbv_job( qbv_job_id, status="failed", job_dir=job_dir, failure_code=failure_code, retryable=True, ) return {**failed, "expired": True, "age_seconds": age_seconds} def mark_delegation_unavailable(qbv_job_id: Any, job_dir: Any = None) -> Dict[str, Any]: """Write the mandatory retryable terminal state when no delegation tool exists.""" return update_qbv_job( qbv_job_id, status="failed", job_dir=job_dir, failure_code="DELEGATION_UNAVAILABLE", retryable=True, ) def get_qbv_job(qbv_job_id: Any, job_dir: Any = None) -> Dict[str, Any]: path = _find_job_path(qbv_job_id, job_dir) return json.loads(path.read_text(encoding="utf-8")) def _read_params(args: list[str]) -> Dict[str, Any]: if not args: return {} token = args[0] if token.startswith("@"): return json.loads(Path(token[1:]).read_text(encoding="utf-8-sig")) return json.loads(token) def _read_route_params(args: list[str]) -> Dict[str, Any]: """Accept JSON/@file or a PowerShell-safe --user-query form.""" if not args or args[0] != "--user-query": return _read_params(args) if len(args) < 2 or not _compact_text(args[1]): raise LivePageRoutingError("USER_QUERY_REQUIRED", "--user-query 后必须提供用户本轮原话") params: Dict[str, Any] = {"user_query": args[1]} index = 2 while index < len(args): flag = args[index] if flag == "--persistence-confirmed": params["persistence_confirmed"] = True index += 1 continue if flag == "--page-reference": if index + 1 >= len(args): raise LivePageRoutingError("PAGE_REFERENCE_REQUIRED", "--page-reference 后必须提供页面引用") params["page_reference"] = args[index + 1] index += 2 continue raise LivePageRoutingError("INVALID_ROUTE_ARGUMENT", f"不支持的 route 参数: {flag}") return params def _read_job_id_params(args: list[str]) -> Dict[str, Any]: if not args or not args[0].startswith("--"): return _read_params(args) parser = argparse.ArgumentParser(add_help=False) parser.add_argument("--qbv-job-id", required=True) parser.add_argument("--job-dir") parser.add_argument("--max-age-seconds", type=int) try: namespace = parser.parse_args(args) except SystemExit as exc: raise LivePageRoutingError("INVALID_JOB_ARGUMENT", "必须提供 --qbv-job-id") from exc return vars(namespace) def _read_prepare_fast_query_params(args: list[str]) -> Dict[str, Any]: if not args or not args[0].startswith("--"): return _read_params(args) parser = argparse.ArgumentParser(add_help=False) parser.add_argument("--task-id", required=True) parser.add_argument("--turn-id", required=True) parser.add_argument("--user-query", required=True) parser.add_argument("--source-skill-version", required=True) parser.add_argument("--source-skill-id") parser.add_argument("--asset-id", required=True) parser.add_argument("--asset-name", required=True) parser.add_argument("--fields", required=True) parser.add_argument("--window-days", required=True, type=int) parser.add_argument("--artifact-file", required=True) parser.add_argument("--job-dir") parser.add_argument("--target-skill-id") parser.add_argument("--retry-failed", action="store_true") try: namespace = parser.parse_args(args) except SystemExit as exc: raise LivePageRoutingError("INVALID_FAST_QUERY_PAGE_ARGUMENT", "prepare-fast-query-page 参数不完整") from exc return vars(namespace) def _read_prepare_industry_ranking_params(args: list[str]) -> Dict[str, Any]: if not args or not args[0].startswith("--"): return _read_params(args) parser = argparse.ArgumentParser(add_help=False) parser.add_argument("--data-id", required=True) parser.add_argument("--index-title", required=True) parser.add_argument("--window-days", required=True, type=int) parser.add_argument("--asset-count", required=True, type=int) parser.add_argument("--as-of-date") parser.add_argument("--formula") parser.add_argument("--task-id") parser.add_argument("--turn-id") parser.add_argument("--user-query") parser.add_argument("--source-skill-version") parser.add_argument("--source-skill-id") parser.add_argument("--target-skill-id") parser.add_argument("--job-dir") parser.add_argument("--retry-failed", action="store_true") try: namespace = parser.parse_args(args) except SystemExit as exc: raise LivePageRoutingError( "INVALID_INDUSTRY_RANKING_PAGE_ARGUMENT", "prepare-industry-ranking-page 参数不完整", ) from exc return vars(namespace) def _emit(payload: Any) -> None: print(json.dumps(payload, ensure_ascii=False, indent=2)) def main(argv: Optional[list[str]] = None) -> int: argv = list(sys.argv[1:] if argv is None else argv) if not argv: _emit({"code": 1, "error": "COMMAND_REQUIRED", "message": "用法: live_page_routing.py route [JSON|@file|--user-query TEXT] | prepare-fast-query-page [flags|JSON|@file] | prepare-industry-ranking-page [flags|JSON|@file] | prepare-validated-page [JSON|@file] | mark-delegation-unavailable --qbv-job-id ID | expire-stale --qbv-job-id ID [--max-age-seconds N] | handoff|prepare|update|get [JSON|@file]"}) return 1 command = argv.pop(0) try: params = ( _read_route_params(argv) if command == "route" else _read_prepare_fast_query_params(argv) if command == "prepare-fast-query-page" else _read_prepare_industry_ranking_params(argv) if command == "prepare-industry-ranking-page" else _read_job_id_params(argv) if command in {"mark-delegation-unavailable", "expire-stale"} else _read_params(argv) ) if command == "route": result = route_live_page( params.get("user_query"), params.get("page_reference"), bool(params.get("persistence_confirmed", False)), ) elif command == "handoff": # Be idempotent for callers that accidentally pass an already-built # handoff file back to this command. Revalidate it instead of leaking # a Python signature error about ``schema_version``. result = ( validate_qbv_handoff(params) if params.get("schema_version") == SCHEMA_VERSION else build_qbv_handoff(**params) ) elif command == "prepare-fast-query-page": result -
minute_range_contract.py 2.4 KB
"""History-minute request contract; no I/O. Market-local today is checked by server.""" import calendar import datetime as dt import re PARAMS = {'asset', 'start_date', 'end_date', 'start_offset', 'end_offset'} def validate_request(payload): if not isinstance(payload, dict): return [('INVALID_PAYLOAD', '参数必须为对象')] errors = [] extra = sorted(set(payload) - PARAMS) if extra: errors.append(('UNSUPPORTED_PARAMETER', '不支持字段:' + ', '.join(extra))) if not isinstance(payload.get('asset'), str) or not payload['asset'].strip(): errors.append(('ASSET_REQUIRED', 'asset必须是单个非空字符串')) absolute = any(k in payload for k in ('start_date', 'end_date')) offset = any(k in payload for k in ('start_offset', 'end_offset')) if absolute and offset: errors.append(('DATE_MODE_CONFLICT', '绝对日期与offset互斥')) if absolute: if not all(k in payload for k in ('start_date', 'end_date')): errors.append(('DATE_PAIR_REQUIRED', 'start_date和end_date必须成对')) else: try: values = [payload[k] for k in ('start_date', 'end_date')] if any(not isinstance(v, str) or not re.fullmatch(r'[0-9]{4}-[0-9]{2}-[0-9]{2}', v) for v in values): raise ValueError() start, end = map(dt.date.fromisoformat, values) y, m = divmod(start.year * 12 + start.month - 1 + 3, 12) limit = dt.date(y, m + 1, min(start.day, calendar.monthrange(y, m + 1)[1])) if start > end: errors.append(('INVALID_DATE_RANGE', '起点不能晚于终点')) elif end >= limit: errors.append(('DATE_RANGE_TOO_LARGE', '终点必须早于起点加三个日历月')) except (ValueError, OverflowError): errors.append(('INVALID_DATE', '日期必须为YYYY-MM-DD真实日期')) if offset: for key in ('start_offset', 'end_offset'): if key in payload and (type(payload[key]) is not int or not -70 <= payload[key] <= -1): errors.append(('OFFSET_OUT_OF_RANGE', key + '必须为-70至-1整数自然日偏移')) start, end = payload.get('start_offset', payload.get('end_offset', -1)), payload.get('end_offset', -1) if type(start) is int and type(end) is int and start > end: errors.append(('INVALID_DATE_RANGE', 'offset起点不能晚于终点')) return errors -
minute_range_csv.py 3 KB
"""Full-column minute-range CSV reader. UTC seconds and trade_date stay separate.""" import copy import csv import datetime as dt import io import math _NULLS = {'', 'null', 'none', 'nan', 'inf', 'infinity', '-inf', '-infinity', '+inf', '+infinity'} _OHLCVA = {'open', 'high', 'low', 'close', 'volume', 'amount'} def parse_minute_range_csv(text, manifest): if not isinstance(manifest, dict) or manifest.get('query_type') != 'minute_range' or manifest.get('status') != 'ok': raise ValueError('历史分钟manifest类型或status不合法') shape = manifest.get('shape') if not isinstance(shape, list) or len(shape) != 2 or any(type(n) is not int or n < 0 for n in shape): raise ValueError('历史分钟shape不合法') if text is None: if shape != [0, 0] or manifest.get('csv_url') or manifest.get('empty') is not True: raise ValueError('仅显式无文件空结果可以省略CSV') return {**copy.deepcopy(manifest), 'columns': [], 'rows': [], 'source_mode': 'csv'} reader = csv.reader(io.StringIO(text.lstrip('\ufeff'), newline='')) header = next(reader, []) columns = [c.strip() for c in header] if len(columns) < 2 or columns[:2] != ['trade_date', 'timestamp'] or len(set(columns)) != len(columns) or any(not c for c in columns): raise ValueError('历史分钟CSV须以trade_date,timestamp开头,不能按日频宽表解析') if shape[1] != len(columns) or ('columns' in manifest and manifest['columns'] != columns): raise ValueError('历史分钟CSV表头与manifest/shape不一致') rows, previous = [], None for raw in reader: if len(raw) != len(columns): raise ValueError('历史分钟CSV行宽不一致') try: day = dt.datetime.strptime(raw[0], '%Y%m%d').date() if len(raw[0]) != 8: raise ValueError() stamp = float(raw[1]) if not math.isfinite(stamp) or (previous is not None and stamp < previous): raise ValueError() if manifest.get('start_date') and day < dt.date.fromisoformat(manifest['start_date']): raise ValueError() if manifest.get('end_date') and day > dt.date.fromisoformat(manifest['end_date']): raise ValueError() except (ValueError, TypeError): raise ValueError('历史分钟交易日/UTC时间戳非法或未升序') from None row = [int(raw[0]), stamp] for name, cell in zip(columns[2:], raw[2:]): cell = cell.strip() if cell.lower() in _NULLS: row.append(None); continue try: value = float(cell) row.append(value if math.isfinite(value) else None) except ValueError: if name in _OHLCVA: raise ValueError('历史分钟行情列含非数值') from None row.append(cell) # 保留上游额外字符串列,不猜列集合。 rows.append(row); previous = stamp if len(rows) != shape[0]: raise ValueError('历史分钟CSV行数与shape不一致') return {**copy.deepcopy(manifest), 'columns': columns, 'rows': rows, 'source_mode': 'csv'} -
qbv_computation_capsule.py 37.2 KB
#!/usr/bin/env python3 """Build and validate reusable QBS computation capsules for QBV handoff. The capsule carries both a verified result snapshot and the reproducible query or formula contract that produced it. It is deliberately page-implementation agnostic: QBV still owns routing, ownership, rendering, publishing, and public acceptance. """ from __future__ import annotations import hashlib import json import re import sys from pathlib import Path from typing import Any, Dict, Iterable, Optional SCHEMA_VERSION = "qbs_computation_capsule_v1" FORMULA_RUNTIME_SCHEMA_VERSION = "qbs_formula_runtime_contract_v1" _HASH_RE = re.compile(r"^(?:sha256:)?([0-9a-fA-F]{64})$") class ComputationCapsuleError(ValueError): def __init__(self, code: str, message: str): super().__init__(message) self.code = code self.message = message def _text(name: str, value: Any) -> str: text = re.sub(r"\s+", " ", str(value or "")).strip() if not text: raise ComputationCapsuleError(f"{name.upper()}_REQUIRED", f"{name} 不能为空") return text def _object(name: str, value: Any) -> Dict[str, Any]: if not isinstance(value, dict): raise ComputationCapsuleError(f"INVALID_{name.upper()}", f"{name} 必须是 JSON object") return dict(value) def _list(name: str, value: Any) -> list: if value is None: return [] if not isinstance(value, list): raise ComputationCapsuleError(f"INVALID_{name.upper()}", f"{name} 必须是 JSON array") return list(value) def canonical_json(value: Any) -> str: return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")) def fingerprint_contract(contract: Dict[str, Any]) -> str: return "sha256:" + hashlib.sha256(canonical_json(contract).encode("utf-8")).hexdigest() def _formula_left_names(formulas: list[str]) -> set[str]: names = set() for formula in formulas: if "=" not in formula: raise ComputationCapsuleError("INVALID_FORMULA_RUNTIME_CONTRACT", "formula 必须包含输出变量左值") name = formula.split("=", 1)[0].strip() if not name: raise ComputationCapsuleError("INVALID_FORMULA_RUNTIME_CONTRACT", "formula 输出变量不能为空") names.add(name) return names def _normalize_formula_runtime_contract(value: Any) -> Optional[Dict[str, Any]]: if value is None: return None item = _object("formula_runtime_contract", value) if item.get("schema_version") != FORMULA_RUNTIME_SCHEMA_VERSION: raise ComputationCapsuleError( "FORMULA_RUNTIME_SCHEMA_UNSUPPORTED", f"仅支持 {FORMULA_RUNTIME_SCHEMA_VERSION}", ) raw_formulas = _list("formula_runtime_contract.formulas", item.get("formulas")) if not raw_formulas: raise ComputationCapsuleError("FORMULA_RUNTIME_FORMULAS_REQUIRED", "formula runtime contract 至少需要一条公式") formulas = [] for raw in raw_formulas: if not isinstance(raw, str) or not raw.strip(): raise ComputationCapsuleError("INVALID_FORMULA_RUNTIME_FORMULA", "formulas 必须是非空字符串数组") formulas.append(raw) left_names = _formula_left_names(formulas) reusable = [] for raw in _list("formula_runtime_contract.force_reusable_array", item.get("force_reusable_array")): if not isinstance(raw, str) or not raw.strip(): raise ComputationCapsuleError("INVALID_FORCE_REUSABLE_ARRAY", "force_reusable_array 必须是非空字符串数组") output = raw.strip() if output not in left_names: raise ComputationCapsuleError("FORMULA_RUNTIME_OUTPUT_UNKNOWN", f"force_reusable_array 输出不存在于公式左值: {output}") if output not in reusable: reusable.append(output) reads = [] for index, raw in enumerate(_list("formula_runtime_contract.reads", item.get("reads"))): read = _object(f"formula_runtime_contract.reads[{index}]", raw) output = _text("formula_runtime_contract.reads.output", read.get("output")) if output not in left_names: raise ComputationCapsuleError("FORMULA_RUNTIME_OUTPUT_UNKNOWN", f"reads 输出不存在于公式左值: {output}") normalized_read = { "output": output, "read_mode": _text("formula_runtime_contract.reads.read_mode", read.get("read_mode")), } if "mode_params" in read: normalized_read["mode_params"] = _object("formula_runtime_contract.reads.mode_params", read.get("mode_params")) reads.append(normalized_read) normalized = { "schema_version": FORMULA_RUNTIME_SCHEMA_VERSION, "formulas": formulas, "include_description": item.get("include_description", False), "use_minute_data": item.get("use_minute_data", False), "force_reusable_array": reusable, "reads": reads, } for field in ("include_description", "use_minute_data"): if not isinstance(normalized[field], bool): raise ComputationCapsuleError("INVALID_FORMULA_RUNTIME_FLAG", f"{field} 必须是 boolean") if "begin_date" in item and item.get("begin_date") is not None: if isinstance(item.get("begin_date"), (dict, list, bool)): raise ComputationCapsuleError("INVALID_FORMULA_RUNTIME_BEGIN_DATE", "begin_date 必须是日期字符串或整数") normalized["begin_date"] = item.get("begin_date") actual = fingerprint_contract(normalized) supplied = _normalized_hash("formula_runtime_contract.contract_fingerprint", item.get("contract_fingerprint")) if supplied and supplied != actual: raise ComputationCapsuleError("FORMULA_RUNTIME_FINGERPRINT_MISMATCH", "formula runtime contract fingerprint 不一致") normalized["contract_fingerprint"] = actual return normalized def _runtime_contract_from_receipts(receipts: list[Dict[str, Any]]) -> Optional[Dict[str, Any]]: contracts = [] seen = set() for receipt in receipts: if not isinstance(receipt, dict) or receipt.get("runtime_contract") is None: continue contract = _normalize_formula_runtime_contract(receipt.get("runtime_contract")) fingerprint = contract["contract_fingerprint"] if fingerprint not in seen: contracts.append(contract) seen.add(fingerprint) if len(contracts) > 1: raise ComputationCapsuleError( "MULTIPLE_FORMULA_RUNTIME_CONTRACTS", "存在多个不同的公式运行合同,请显式提供本活页需要的 formula_runtime_contract", ) return contracts[0] if contracts else None def _formula_map(runtime_contract: Optional[Dict[str, Any]]) -> Dict[str, str]: if not isinstance(runtime_contract, dict): return {} result = {} for formula in runtime_contract.get("formulas", []): if not isinstance(formula, str) or "=" not in formula: continue output = formula.split("=", 1)[0].strip() if output: result[output] = formula return result def _receipt_data_bindings( receipts: list[Dict[str, Any]], runtime_contract: Optional[Dict[str, Any]], ) -> Dict[str, Dict[str, str]]: formulas_by_output = _formula_map(runtime_contract) bindings: Dict[str, Dict[str, str]] = {} for receipt in receipts: if not isinstance(receipt, dict): continue outputs = receipt.get("outputs") if not isinstance(outputs, list): continue for item in outputs: if not isinstance(item, dict): continue data_id = _compact_optional(item.get("data_id") or item.get("indexinfo_id")) if not data_id: continue index_title = _compact_optional(item.get("index_title") or item.get("leftName")) expression_id = _compact_optional(item.get("expression_id")) if index_title is None and expression_id in formulas_by_output: index_title = expression_id if index_title is None or index_title not in formulas_by_output: continue formula = formulas_by_output[index_title] supplied_formula = _compact_optional(item.get("formula")) if supplied_formula is not None and supplied_formula != formula: raise ComputationCapsuleError( "RECEIPT_FORMULA_BINDING_MISMATCH", f"validation receipt data_id={data_id} 的公式与 runtime contract 不一致", ) binding = {"index_title": index_title, "formula": formula} existing = bindings.get(data_id) if existing is not None and existing != binding: raise ComputationCapsuleError( "AMBIGUOUS_DATA_REFERENCE_BINDING", f"data_id={data_id} 命中多个不同公式输出,禁止猜测", ) bindings[data_id] = binding return bindings def _bind_materialized_outputs( outputs: list[Dict[str, Any]], receipts: list[Dict[str, Any]], runtime_contract: Optional[Dict[str, Any]], ) -> None: if not isinstance(runtime_contract, dict): return formulas_by_output = _formula_map(runtime_contract) readable_outputs = { str(item.get("output") or "").strip() for item in runtime_contract.get("reads", []) if isinstance(item, dict) and str(item.get("output") or "").strip() } receipt_bindings = _receipt_data_bindings(receipts, runtime_contract) for output in outputs: reference = output.get("data_reference") if not isinstance(reference, dict): continue data_id = _compact_optional(reference.get("data_id")) if not data_id: continue binding = receipt_bindings.get(data_id, {}) index_title = _compact_optional(reference.get("index_title")) or binding.get("index_title") formula = _compact_optional(reference.get("formula")) or binding.get("formula") if not index_title or not formula: raise ComputationCapsuleError( "DATA_REFERENCE_BINDING_REQUIRED", f"data_id={data_id} 缺少由 validation receipt/runtime contract 证明的 index_title 与 formula 绑定", ) expected_formula = formulas_by_output.get(index_title) if expected_formula != formula: raise ComputationCapsuleError( "DATA_REFERENCE_FORMULA_MISMATCH", f"data_id={data_id} 的 formula 与 runtime contract 不一致", ) if index_title not in readable_outputs: raise ComputationCapsuleError( "DATA_REFERENCE_READ_MISSING", f"data_id={data_id} 对应输出 {index_title} 不在 runtime reads 中", ) reference["index_title"] = index_title reference["formula"] = formula output["reference_hash"] = hash_inline_data(reference) def hash_file(path: Any) -> str: target = Path(path).expanduser().resolve() digest = hashlib.sha256() with target.open("rb") as handle: for chunk in iter(lambda: handle.read(1024 * 1024), b""): digest.update(chunk) return "sha256:" + digest.hexdigest() def hash_inline_data(value: Any) -> str: return "sha256:" + hashlib.sha256(canonical_json(value).encode("utf-8")).hexdigest() def _normalized_hash(name: str, value: Any) -> Optional[str]: text = str(value or "").strip() if not text: return None match = _HASH_RE.fullmatch(text) if not match: raise ComputationCapsuleError(f"INVALID_{name.upper()}", f"{name} 必须是 SHA256") return "sha256:" + match.group(1).lower() def _normalize_roles(values: Any, name: str = "required_roles") -> list[str]: roles = _list(name, values) normalized = [] for raw in roles: role = _text("role", raw) if role not in normalized: normalized.append(role) return normalized def _normalize_contracts(values: Any) -> list[Dict[str, Any]]: contracts = [] seen = set() for index, raw in enumerate(_list("validated_contracts", values)): item = _object(f"validated_contracts[{index}]", raw) role = _text("role", item.get("role")) if role in seen: raise ComputationCapsuleError("DUPLICATE_CONTRACT_ROLE", f"validated_contracts role 重复: {role}") kind = _text("kind", item.get("kind")) contract = _object("contract", item.get("contract")) actual = fingerprint_contract(contract) supplied = _normalized_hash("contract_fingerprint", item.get("contract_fingerprint")) if supplied and supplied != actual: raise ComputationCapsuleError("CONTRACT_FINGERPRINT_MISMATCH", f"{role} contract fingerprint 不一致") normalized = dict(item) normalized.update({"role": role, "kind": kind, "contract": contract, "contract_fingerprint": actual}) contracts.append(normalized) seen.add(role) return contracts def _normalize_outputs(values: Any, verify_artifacts: bool) -> list[Dict[str, Any]]: outputs = [] seen = set() for index, raw in enumerate(_list("validated_outputs", values)): item = _object(f"validated_outputs[{index}]", raw) role = _text("role", item.get("role")) if role in seen: raise ComputationCapsuleError("DUPLICATE_OUTPUT_ROLE", f"validated_outputs role 重复: {role}") artifact_text = str(item.get("artifact_file") or "").strip() has_inline = "data" in item has_reference = isinstance(item.get("data_reference"), dict) evidence_count = int(bool(artifact_text)) + int(has_inline) + int(has_reference) if evidence_count != 1: raise ComputationCapsuleError( "OUTPUT_EVIDENCE_REQUIRED", f"validated_outputs[{index}] 必须且只能包含 artifact_file、data 或 data_reference 之一", ) normalized = dict(item) if has_reference: reference = _object("data_reference", item.get("data_reference")) reference["schema_version"] = _text("data_reference.schema_version", reference.get("schema_version")) reference["provider"] = _text("data_reference.provider", reference.get("provider")) reference["data_id"] = _text("data_reference.data_id", reference.get("data_id")) reference["read_tool"] = _text("data_reference.read_tool", reference.get("read_tool")) if reference["schema_version"] != "quant_buddy_data_reference_v1" or reference["provider"] != "quant_buddy" or reference["read_tool"] != "readData": raise ComputationCapsuleError("INVALID_DATA_REFERENCE", f"{role} data_reference 不是受支持的 Quant Buddy 已物化数据引用") if item.get("data_hash"): raise ComputationCapsuleError("DATA_REFERENCE_MUST_NOT_USE_DATA_HASH", f"{role} 数据引用不得冒充完整数据快照 hash") actual_reference_hash = hash_inline_data(reference) supplied_reference_hash = _normalized_hash("reference_hash", item.get("reference_hash")) if supplied_reference_hash and supplied_reference_hash != actual_reference_hash: raise ComputationCapsuleError("DATA_REFERENCE_HASH_MISMATCH", f"{role} data reference hash 不一致") normalized["evidence_kind"] = "quant_buddy_data_id" normalized["data_reference"] = reference normalized["reference_hash"] = actual_reference_hash normalized.pop("data_hash", None) else: if artifact_text: artifact = Path(artifact_text).expanduser().resolve() if verify_artifacts and not artifact.is_file(): raise ComputationCapsuleError("ARTIFACT_FILE_MISSING", f"artifact_file 不存在: {artifact}") actual_hash = hash_file(artifact) if verify_artifacts else None normalized["artifact_file"] = str(artifact) else: actual_hash = hash_inline_data(item.get("data")) supplied_hash = _normalized_hash("data_hash", item.get("data_hash")) if actual_hash and supplied_hash and supplied_hash != actual_hash: raise ComputationCapsuleError("ARTIFACT_HASH_MISMATCH", f"{role} artifact hash 不一致") if actual_hash: normalized["data_hash"] = actual_hash elif supplied_hash: normalized["data_hash"] = supplied_hash else: raise ComputationCapsuleError("DATA_HASH_REQUIRED", f"{role} 缺少可验证 data_hash") if "row_count" in normalized: try: row_count = int(normalized["row_count"]) except (TypeError, ValueError) as exc: raise ComputationCapsuleError("INVALID_ROW_COUNT", f"{role} row_count 必须是非负整数") from exc if row_count < 0: raise ComputationCapsuleError("INVALID_ROW_COUNT", f"{role} row_count 必须是非负整数") normalized["row_count"] = row_count mapping = normalized.get("field_mapping", {}) if not isinstance(mapping, dict): raise ComputationCapsuleError("INVALID_FIELD_MAPPING", f"{role} field_mapping 必须是 JSON object") normalized["field_mapping"] = dict(mapping) outputs.append(normalized) seen.add(role) return outputs def _compact_optional(value: Any) -> Optional[str]: text = re.sub(r"\s+", " ", str(value or "")).strip() return text or None def _validate_receipt_lineage( receipt: Dict[str, Any], *, expected_task_id: str, expected_turn_id: str, expected_user_query: str, ) -> None: lineage = receipt.get("lineage") if isinstance(receipt.get("lineage"), dict) else {} expected = { "task_id": expected_task_id, "turn_id": expected_turn_id, "user_query": expected_user_query, } for field, expected_value in expected.items(): actual_values = [] if field in receipt: actual_values.append(receipt.get(field)) if field in lineage: actual_values.append(lineage.get(field)) for raw in actual_values: actual = _compact_optional(raw) if actual is not None and actual != expected_value: raise ComputationCapsuleError( "RECEIPT_LINEAGE_MISMATCH", f"validation receipt {field} 与当前 QBS Turn 不一致", ) def _normalize_validation_receipts( values: Any, *, expected_task_id: str, expected_turn_id: str, expected_user_query: str, receipt_file: Optional[Path] = None, ) -> list[Dict[str, Any]]: receipts = [] for index, raw in enumerate(_list("validation_receipts", values)): # Agent-authored prepare-page JSON naturally represents an existing # receipt as its file path. Accept that compact top-level form in # addition to an inline receipt object, while refusing nested file # references inside a receipt file to keep loading finite and explicit. if isinstance(raw, (str, Path)): if receipt_file is not None: raise ComputationCapsuleError( f"INVALID_VALIDATION_RECEIPTS[{index}]", "validation receipt 文件内部不得再引用其它 receipt 文件", ) receipts.extend(_load_validation_receipt_file( raw, expected_task_id=expected_task_id, expected_turn_id=expected_turn_id, expected_user_query=expected_user_query, )) continue item = _object(f"validation_receipts[{index}]", raw) _validate_receipt_lineage( item, expected_task_id=expected_task_id, expected_turn_id=expected_turn_id, expected_user_query=expected_user_query, ) if receipt_file is not None: item.setdefault("receipt_file", str(receipt_file)) item.setdefault("receipt_sha256", hash_file(receipt_file)) receipts.append(item) return receipts def _load_validation_receipt_file( path: Any, *, expected_task_id: str, expected_turn_id: str, expected_user_query: str, ) -> list[Dict[str, Any]]: target = Path(str(path or "")).expanduser().resolve() if not target.is_file(): raise ComputationCapsuleError("RECEIPT_FILE_MISSING", f"validation_receipt_file 不存在: {target}") try: payload = json.loads(target.read_text(encoding="utf-8-sig")) except json.JSONDecodeError as exc: raise ComputationCapsuleError("INVALID_RECEIPT_FILE", f"validation_receipt_file 不是有效 JSON: {target}") from exc if isinstance(payload, dict) and isinstance(payload.get("validation_receipts"), list): raw_receipts = payload["validation_receipts"] elif isinstance(payload, list): raw_receipts = payload elif isinstance(payload, dict): raw_receipts = [payload] else: raise ComputationCapsuleError("INVALID_RECEIPT_FILE", "validation_receipt_file 必须包含 JSON object 或 array") return _normalize_validation_receipts( raw_receipts, expected_task_id=expected_task_id, expected_turn_id=expected_turn_id, expected_user_query=expected_user_query, receipt_file=target, ) def _expand_validated_roles(values: Any) -> tuple[list[Dict[str, Any]], Dict[str, list[str]]]: roles = _list("validated_roles", values) expanded: list[Dict[str, Any]] = [] role_expansions: Dict[str, list[str]] = {} for index, raw in enumerate(roles): item = dict(_object(f"validated_roles[{index}]", raw)) role = _text("role", item.get("role")) if "data_ids" not in item: expanded.append(item) continue if _compact_optional(item.get("data_id")) is not None: raise ComputationCapsuleError( "AMBIGUOUS_DATA_IDS", f"validated_roles[{index}] 不得同时包含 data_id 和 data_ids", ) raw_ids = item.get("data_ids") if not isinstance(raw_ids, list) or not raw_ids: raise ComputationCapsuleError( "INVALID_DATA_IDS", f"validated_roles[{index}].data_ids 必须是非空 JSON array", ) expanded_roles = [] width = max(2, len(str(len(raw_ids)))) for position, raw_data_id in enumerate(raw_ids, start=1): if not isinstance(raw_data_id, str) or not raw_data_id.strip(): raise ComputationCapsuleError( "INVALID_DATA_ID", f"validated_roles[{index}].data_ids[{position - 1}] 必须是非空字符串", ) expanded_role = f"{role}__{position:0{width}d}" child = dict(item) child.pop("data_ids", None) child["role"] = expanded_role child["role_group"] = role # Keep the exact server-returned identifier. Do not trim, case-fold, # normalize, sort, or otherwise rewrite it. child["data_id"] = raw_data_id expanded.append(child) expanded_roles.append(expanded_role) role_expansions[role] = expanded_roles return expanded, role_expansions def _expand_required_roles(values: Any, role_expansions: Dict[str, list[str]]) -> list[str]: expanded = [] for role in _normalize_roles(values): candidates = role_expansions.get(role, [role]) for candidate in candidates: if candidate not in expanded: expanded.append(candidate) return expanded def build_computation_capsule_from_validated_roles( *, task_id: Any, turn_id: Any, user_query: Any, page_intent: Any, validated_roles: Any, asset_resolution: Any = None, validated_insights: Any = None, validation_receipts: Any = None, formula_runtime_contract: Any = None, ) -> Dict[str, Any]: """Build a full capsule from the compact role-oriented QBS handoff input. Agents provide one business-level entry per validated role. This helper expands those entries into the existing contract/output schema, binds every artifact and receipt by SHA256, and validates any lineage fields present in receipts before QBV is allowed to reuse them. """ task_value = _text("task_id", task_id) turn_value = _text("turn_id", turn_id) query_value = _text("user_query", user_query) roles, role_expansions = _expand_validated_roles(validated_roles) if not roles: raise ComputationCapsuleError("VALIDATED_ROLES_REQUIRED", "validated_roles 至少需要一个已验证 role") role_names = [_text("role", raw.get("role")) for raw in roles] if isinstance(page_intent, str): lowered_roles = " ".join(role_names).lower() if "industry" in lowered_roles and ("rank" in lowered_roles or "ranking" in lowered_roles): page_type = "industry_return_ranking" primary_visualization = "horizontal_ranked_bar_chart" elif "backtest" in lowered_roles or "net_value" in lowered_roles or "drawdown" in lowered_roles: page_type = "backtest_performance" primary_visualization = "net_value_benchmark_drawdown_chart" elif "heatmap" in lowered_roles: page_type = "ranking_heatmap" primary_visualization = "heatmap" elif "comparison" in lowered_roles or "compare" in lowered_roles: page_type = "multi_asset_comparison" primary_visualization = "multi_series_comparison_chart" else: page_type = "interactive_analysis" primary_visualization = "primary_chart" page_intent = { "question_to_answer": query_value, "title_hint": _text("page_intent", page_intent), "recommended_page_type": page_type, "primary_visualization": primary_visualization, "required_roles": role_names, } elif isinstance(page_intent, dict): page_intent = dict(page_intent) page_intent.setdefault("question_to_answer", query_value) page_intent.setdefault("recommended_page_type", "interactive_analysis") page_intent.setdefault("primary_visualization", "primary_chart") if page_intent.get("required_roles") is None: page_intent["required_roles"] = role_names else: page_intent["required_roles"] = _expand_required_roles( page_intent.get("required_roles"), role_expansions, ) contracts = [] outputs = [] receipts = _normalize_validation_receipts( validation_receipts, expected_task_id=task_value, expected_turn_id=turn_value, expected_user_query=query_value, ) seen_receipts = {canonical_json(item) for item in receipts} for index, raw in enumerate(roles): item = _object(f"validated_roles[{index}]", raw) role = _text("role", item.get("role")) data_id = _compact_optional(item.get("data_id")) if data_id: data_reference = { "schema_version": "quant_buddy_data_reference_v1", "provider": "quant_buddy", "data_id": data_id, "read_tool": "readData", } for field in ("index_title", "description", "date", "formula", "aggregation", "universe", "value_semantics"): value = _compact_optional(item.get(field)) if value is not None: data_reference[field] = value if item.get("window_days") is not None: try: window_days = int(item.get("window_days")) except (TypeError, ValueError) as exc: raise ComputationCapsuleError("INVALID_WINDOW_DAYS", f"{role} window_days 必须是正整数") from exc if window_days <= 0: raise ComputationCapsuleError("INVALID_WINDOW_DAYS", f"{role} window_days 必须是正整数") data_reference["window_days"] = window_days contract = dict(data_reference) contract["reuse_semantics"] = "materialized_result_only_no_formula_recompute" kind = "quant_buddy_materialized_data" else: contract = _object("contract", item.get("contract")) kind = _compact_optional(item.get("kind")) or _compact_optional(contract.get("kind")) or _compact_optional(contract.get("type")) or "validated_contract" role_group = _compact_optional(item.get("role_group")) contracts.append({ "role": role, "kind": kind, "contract": contract, **({"role_group": role_group} if role_group else {}), **({"contract_fingerprint": item.get("contract_fingerprint")} if item.get("contract_fingerprint") else {}), }) output = { "role": role, **({"role_group": role_group} if role_group else {}), } if data_id: output.update({ "evidence_kind": "quant_buddy_data_id", "data_reference": data_reference, "reference_hash": hash_inline_data(data_reference), }) elif str(item.get("artifact_file") or "").strip(): output["artifact_file"] = item.get("artifact_file") elif "data" in item: output["data"] = item.get("data") else: raise ComputationCapsuleError( "OUTPUT_EVIDENCE_REQUIRED", f"validated_roles[{index}] 必须包含 artifact_file、data、data_id 或 data_ids", ) if "asset_count" in item and "row_count" not in item: output["row_count"] = item.get("asset_count") for field in ("row_count", "field_mapping", "data_hash", "reference_hash"): if field in item: output[field] = item.get(field) outputs.append(output) inline_receipts = _normalize_validation_receipts( item.get("validation_receipts"), expected_task_id=task_value, expected_turn_id=turn_value, expected_user_query=query_value, ) receipt_files = [] if item.get("validation_receipt_file"): receipt_files.append(item.get("validation_receipt_file")) if item.get("validation_receipt_files") is not None: raw_files = item.get("validation_receipt_files") if not isinstance(raw_files, list): raise ComputationCapsuleError("INVALID_VALIDATION_RECEIPT_FILES", "validation_receipt_files 必须是 JSON array") receipt_files.extend(raw_files) file_receipts = [] for receipt_path in receipt_files: file_receipts.extend(_load_validation_receipt_file( receipt_path, expected_task_id=task_value, expected_turn_id=turn_value, expected_user_query=query_value, )) for receipt in [*inline_receipts, *file_receipts]: key = canonical_json(receipt) if key not in seen_receipts: receipts.append(receipt) seen_receipts.add(key) receipt_runtime_contract = _runtime_contract_from_receipts(receipts) explicit_runtime_contract = _normalize_formula_runtime_contract(formula_runtime_contract) if ( explicit_runtime_contract is not None and receipt_runtime_contract is not None and explicit_runtime_contract["contract_fingerprint"] != receipt_runtime_contract["contract_fingerprint"] ): raise ComputationCapsuleError( "FORMULA_RUNTIME_RECEIPT_MISMATCH", "显式 formula_runtime_contract 与成功执行 receipt 不一致,禁止改写公式后交给 QBV", ) runtime_contract = explicit_runtime_contract or receipt_runtime_contract _bind_materialized_outputs(outputs, receipts, runtime_contract) return build_computation_capsule( task_id=task_value, turn_id=turn_value, user_query=query_value, page_intent=page_intent, asset_resolution=asset_resolution if asset_resolution is not None else {}, validated_contracts=contracts, validated_outputs=outputs, validated_insights=validated_insights, validation_receipts=receipts, formula_runtime_contract=runtime_contract, ) def build_computation_capsule( *, task_id: Any, turn_id: Any, user_query: Any, page_intent: Any, asset_resolution: Any, validated_contracts: Any, validated_outputs: Any, validated_insights: Any = None, validation_receipts: Any = None, formula_runtime_contract: Any = None, ) -> Dict[str, Any]: intent = _object("page_intent", page_intent) for field in ("question_to_answer", "recommended_page_type", "primary_visualization"): intent[field] = _text(field, intent.get(field)) intent["required_roles"] = _normalize_roles(intent.get("required_roles") or []) capsule = { "schema_version": SCHEMA_VERSION, "task_id": _text("task_id", task_id), "turn_id": _text("turn_id", turn_id), "user_query": _text("user_query", user_query), "page_intent": intent, "asset_resolution": _object("asset_resolution", asset_resolution), "validated_contracts": _normalize_contracts(validated_contracts), "validated_outputs": _normalize_outputs(validated_outputs, verify_artifacts=True), "validated_insights": _list("validated_insights", validated_insights), "validation_receipts": _list("validation_receipts", validation_receipts), } normalized_runtime_contract = _normalize_formula_runtime_contract(formula_runtime_contract) if normalized_runtime_contract is not None: capsule["formula_runtime_contract"] = normalized_runtime_contract return capsule def validate_computation_capsule( payload: Any, *, expected_task_id: Any = None, expected_turn_id: Any = None, expected_user_query: Any = None, verify_artifacts: bool = True, ) -> Dict[str, Any]: if not isinstance(payload, dict): raise ComputationCapsuleError("CAPSULE_OBJECT_REQUIRED", "computation_capsule 必须是 JSON object") if payload.get("schema_version") != SCHEMA_VERSION: raise ComputationCapsuleError("CAPSULE_SCHEMA_UNSUPPORTED", f"仅支持 {SCHEMA_VERSION}") task_id = _text("task_id", payload.get("task_id")) turn_id = _text("turn_id", payload.get("turn_id")) user_query = _text("user_query", payload.get("user_query")) for name, actual, expected in ( ("task_id", task_id, expected_task_id), ("turn_id", turn_id, expected_turn_id), ("user_query", user_query, expected_user_query), ): if expected is not None and re.sub(r"\s+", " ", str(expected or "")).strip() != actual: raise ComputationCapsuleError("CAPSULE_LINEAGE_MISMATCH", f"computation_capsule {name} 与 Handoff 不一致") intent = _object("page_intent", payload.get("page_intent")) for field in ("question_to_answer", "recommended_page_type", "primary_visualization"): intent[field] = _text(field, intent.get(field)) intent["required_roles"] = _normalize_roles(intent.get("required_roles") or []) normalized = { **dict(payload), "schema_version": SCHEMA_VERSION, "task_id": task_id, "turn_id": turn_id, "user_query": user_query, "page_intent": intent, "asset_resolution": _object("asset_resolution", payload.get("asset_resolution")), "validated_contracts": _normalize_contracts(payload.get("validated_contracts")), "validated_outputs": _normalize_outputs(payload.get("validated_outputs"), verify_artifacts=verify_artifacts), "validated_insights": _list("validated_insights", payload.get("validated_insights")), "validation_receipts": _list("validation_receipts", payload.get("validation_receipts")), } if "formula_runtime_contract" in payload: normalized["formula_runtime_contract"] = _normalize_formula_runtime_contract(payload.get("formula_runtime_contract")) return normalized def _read_params(args: list[str]) -> Dict[str, Any]: if not args: return {} token = args[0] if token.startswith("@"): return json.loads(Path(token[1:]).read_text(encoding="utf-8-sig")) return json.loads(token) def main(argv: Optional[list[str]] = None) -> int: argv = list(sys.argv[1:] if argv is None else argv) if not argv: print(json.dumps({"code": 1, "error": "COMMAND_REQUIRED", "message": "用法: qbv_computation_capsule.py build|validate [JSON|@file]"}, ensure_ascii=False)) return 1 command = argv.pop(0) try: params = _read_params(argv) if command == "build": result = build_computation_capsule(**params) elif command == "validate": capsule = params.get("computation_capsule", params) result = validate_computation_capsule(capsule) else: raise ComputationCapsuleError("UNKNOWN_COMMAND", f"未知命令: {command}") print(json.dumps({"code": 0, "computation_capsule": result}, ensure_ascii=False, indent=2)) return 0 except (ComputationCapsuleError, OSError, json.JSONDecodeError, TypeError) as exc: code = exc.code if isinstance(exc, ComputationCapsuleError) else "CAPSULE_BUILD_FAILED" print(json.dumps({"code": 1, "error": code, "message": str(exc)}, ensure_ascii=False, indent=2)) return 1 if __name__ == "__main__": raise SystemExit(main()) -
quant_api.py 48 KB
#!/usr/bin/env python3 """ 观照量化 API —— Python 可编程接口 供业务 Skill 的脚本 import 使用,底层走 executor.py REST API,不依赖 MCP 协议。 所有工具名、参数格式、认证、session 管理均复用 quant-buddy-skill 的基础设施。 用法: import sys sys.path.insert(0, r"/path/to/quant-buddy-skill/scripts") from quant_api import QuantAPI api = QuantAPI() # 自动定位 skill root api.new_session() # 独立任务:生成新 task_id # 上游编排:api.new_session(task_id="...", task_mode="inherit", task_source="quant-buddy-view") r = api.run_multi_formula(formulas=["X=收盘价(贵州茅台)"], begin_date=20160101) ids = api.extract_obj_ids(r) # {"X": "60f..."} d = api.read_data(ids=list(ids.values()), mode="smart_sample") 依赖:仅 Python 标准库(json, os, re, sys, uuid)。 """ import base64 import io import json import os import re import sys import uuid from task_context import ( TaskContextError, build_new_session_context, build_turn_context, clear_turn_session_context, inject_or_validate_task_id, inject_or_validate_turn_context, may_replace_session_task_id, normalize_agent_intent, record_turn_tracking_diagnostic, session_context_fields, tracking_reason_code, tracking_result_outcome, turn_session_fields, ) __all__ = ["QuantAPI"] # 本文件所在目录 = quant-buddy-skill/scripts/ _SCRIPT_DIR = os.path.dirname(os.path.abspath(__file__)) _SKILL_ROOT = os.path.dirname(_SCRIPT_DIR) def _read_skill_version(skill_root: str = _SKILL_ROOT) -> str: """从 SKILL.md frontmatter 读取 version 字段;读取失败时返回空字符串。""" skill_md = os.path.join(skill_root, "SKILL.md") try: with open(skill_md, "r", encoding="utf-8") as f: for line in f: line = line.strip() if line.startswith("version:"): return line.split(":", 1)[1].strip() except Exception: pass return "" def _materialize_chart_artifact(raw: dict, params: dict, skill_root: str) -> dict: """Persist renderChart/renderKLine base64 as a real local artifact. The programmable QuantAPI path bypasses call.py, so it must perform the same materialization itself. Failures stay non-fatal for the data response but are made explicit through ``artifact_error``; a path is returned only after the file exists and is non-empty. """ if not isinstance(raw, dict) or raw.get("code") != 0: return raw data = raw.get("data") if not isinstance(data, dict): return raw encoded = data.get("base64") if not isinstance(encoded, str) or not encoded.strip(): return raw value = encoded.strip() mime = "" payload = value if value.startswith("data:") and "," in value: header, payload = value.split(",", 1) mime = header[5:].split(";", 1)[0].lower() if not mime: mime = str(data.get("mime_type") or "").strip().lower() source_format = str(data.get("format") or "").strip().lower().lstrip(".") extension = { "image/png": ".png", "image/jpeg": ".jpg", "image/jpg": ".jpg", "image/webp": ".webp", "image/gif": ".gif", }.get(mime, { "png": ".png", "jpeg": ".jpg", "jpg": ".jpg", "webp": ".webp", "gif": ".gif", }.get(source_format, ".png")) payload += "=" * ((4 - len(payload) % 4) % 4) requested_format = str(params.get("output_format") or "").strip().lower().lstrip(".") requested_format = {"jpeg": "jpg"}.get(requested_format, requested_format) if requested_format and requested_format not in {"png", "jpg", "webp"}: data["artifact_error"] = "CHART_ARTIFACT_FORMAT_UNSUPPORTED: output_format 仅支持 png/jpg/webp" return raw title = str(params.get("title") or params.get("name") or "chart").strip() safe_name = "".join(ch if ch.isalnum() or ch in "-_" else "_" for ch in title).strip("_") safe_name = safe_name[:120] or "chart" output_dir = os.path.abspath(os.path.join(skill_root, "output")) os.makedirs(output_dir, exist_ok=True) artifact_file = os.path.abspath(os.path.join(output_dir, safe_name + extension)) try: image_bytes = base64.b64decode(payload, validate=False) if not image_bytes: raise ValueError("decoded chart is empty") if requested_format: target_extension = {"png": ".png", "jpg": ".jpg", "webp": ".webp"}[requested_format] if extension != target_extension: try: from PIL import Image source = io.BytesIO(image_bytes) target = io.BytesIO() with Image.open(source) as image: if requested_format == "jpg" and image.mode not in {"RGB", "L"}: image = image.convert("RGB") image.save(target, format={"png": "PNG", "jpg": "JPEG", "webp": "WEBP"}[requested_format]) image_bytes = target.getvalue() except Exception as exc: raise RuntimeError(f"explicit {requested_format} conversion failed: {exc}") from exc extension = target_extension artifact_file = os.path.abspath(os.path.join(output_dir, safe_name + extension)) with open(artifact_file, "wb") as handle: handle.write(image_bytes) if not os.path.isfile(artifact_file) or os.path.getsize(artifact_file) <= 0: raise OSError("chart artifact was not written") except Exception as exc: data["artifact_error"] = f"CHART_ARTIFACT_SAVE_FAILED: {exc}" return raw data["base64"] = f"<{len(encoded)} chars>" final_format = {".png": "png", ".jpg": "jpg", ".webp": "webp", ".gif": "gif"}.get(extension, extension.lstrip(".")) data["format"] = final_format data["mime_type"] = {"png": "image/png", "jpg": "image/jpeg", "webp": "image/webp", "gif": "image/gif"}.get(final_format, data.get("mime_type")) data["artifact_file"] = artifact_file data["saved_to"] = artifact_file return raw def _resolve_session_file(skill_root: str) -> str: """按优先级解析 session 文件路径,支持多会话并行: 1) QBS_SESSION_FILE 环境变量直接指定路径 2) QBS_SESSION_KEY 派生为 .session.<key>.json 3) 默认 .session.json(向后兼容) """ explicit = os.environ.get("QBS_SESSION_FILE", "").strip() if explicit: return explicit key = os.environ.get("QBS_SESSION_KEY", "").strip() if key: safe_key = re.sub(r"[^A-Za-z0-9_\-]", "_", key)[:64] return os.path.join(skill_root, "output", f".session.{safe_key}.json") return os.path.join(skill_root, "output", ".session.json") def _normalize_compact_formula_payload(raw: dict, task_id: str) -> dict: """Normalize the SSE compact terminal payload to the documented envelope.""" if not isinstance(raw, dict): return raw if ( "data" not in raw and isinstance(raw.get("results"), list) and raw.get("status") is not None and raw.get("status") != "deferred" ): status = raw.get("status") results = [] for item in raw.get("results", []): if not isinstance(item, dict): results.append(item) continue normalized = dict(item) if normalized.get("indexinfo_id") is not None and normalized.get("data_id") is None: normalized["data_id"] = normalized.get("indexinfo_id") results.append(normalized) envelope = { "code": 0, "success": status == "ok", "task_id": str(task_id or raw.get("task_id") or "").strip(), "data": { "status": status, "summary": raw.get("summary") or {}, "results": results, }, } for field in ("request_id", "trace_id"): if raw.get(field) is not None: envelope[field] = raw[field] if status != "ok": failed = [item for item in results if isinstance(item, dict) and item.get("status") != "success"] envelope["message"] = "runMultiFormulaBatchStream 未全部成功,失败详情见 errors。" envelope["errors"] = [ { "leftName": item.get("leftName") or item.get("expression_id"), "formula": item.get("formula"), "errorCode": item.get("errorCode"), "category": item.get("category"), "retryable": item.get("retryable"), "guidance": item.get("guidance"), "message": item.get("message"), } for item in failed ] return envelope return dict(raw) def _finalize_formula_execution_payload( tool_name: str, params: dict, raw: dict, *, task_id: str = "", ) -> dict: """Persist successful formula receipts on the programmable QuantAPI path. ``agent/test_agent.py`` and other Python hosts call :class:`QuantAPI` directly and therefore bypass ``call.py``. This function keeps that path's terminal envelope, receipt, and summary-output contracts identical without making receipt failures block an otherwise valid market-data result. """ if tool_name not in {"runMultiFormulaBatchStream", "resumeJob"} or not isinstance(raw, dict): return raw payload = _normalize_compact_formula_payload(raw, task_id or params.get("task_id")) try: from validation_receipt import apply_output_mode, write_receipt # noqa: PLC0415 receipt_file = write_receipt(tool_name, params or {}, payload) if receipt_file: payload["validation_receipt_file"] = receipt_file return apply_output_mode(payload, (params or {}).get("output_mode", "full")) except Exception: return payload class QuantAPI: """观照量化平台的同步 Python 客户端。 Parameters ---------- skill_root : str, optional quant-buddy-skill 的根目录,默认自动检测(本文件上两级)。 timeout : int 每次 API 调用的最大等待秒数,默认 300。 api_key : str, optional 本次会话实际用哪个 api_key:传了就用传入的(覆盖 config.json),不传就用 config.json/config.local.json/QUANT_BUDDY_API_KEY 解析出的默认值。QuantAPI 是按会话 实例化的(一个会话一个实例),这里是它的"传入优先"入口——不是 executor.py 那种每次调用单独传 JSON 参数的 CLI,per-session 的 key 天然应该在构造实例时给一次,而不是每个方法都单独传。 """ def __init__(self, skill_root=None, timeout=300, api_key=None): self.skill_root = skill_root or _SKILL_ROOT self.timeout = timeout self._scripts_dir = os.path.join(self.skill_root, "scripts") self._session_file = _resolve_session_file(self.skill_root) self._api_key_override = str(api_key or "").strip() # in-memory task_id:一旦初始化后不再从文件重读,避免并发写入导致跨批次 task_id 漂移 self._task_id: str = "" self._turn_id: str = "" self._user_query: str = "" self._agent_intent = None # 配额累积器:跨工具调用累计本 session 消耗的 RU self._session_ru_cost: int = 0 self._last_quota: dict = {} if not os.path.isdir(self._scripts_dir): raise FileNotFoundError( f"找不到 scripts 目录: {self._scripts_dir}\n" f"请确认 skill_root 指向 quant-buddy-skill 根目录" ) def _resolve_api_key(self, cfg): """本实例(= 本会话)实际用哪个 api_key:构造时传入的 api_key 优先,否则用 cfg['api_key'] (load_config() 解析结果);两者都没有则抛 ValueError。复用 executor.py 同一条判定规则。""" if self._scripts_dir not in sys.path: sys.path.insert(0, self._scripts_dir) import executor as _ex # noqa: PLC0415 return _ex._apply_api_key_override(self._api_key_override, cfg) # ──────────────────────────────────────────── # Session 文件读写(与 call.py 共用同一个 .session.json) # ──────────────────────────────────────────── def _read_session(self) -> str: return self._read_session_full().get("task_id", "") def _read_session_full(self) -> dict: try: with open(self._session_file, "r", encoding="utf-8") as f: data = json.load(f) return data if isinstance(data, dict) else {} except Exception: return {} def _write_session(self, task_id: str, user_query: str = None, task_context: dict = None, turn_context: dict = None, clear_turn: bool = False, agent_intent: str = None): previous = self._read_session_full() same_task = str(previous.get("task_id") or "") == str(task_id or "") data = dict(previous) if same_task and not task_context else {} data.update({"task_id": task_id, "skill_version_at_creation": _read_skill_version(self.skill_root)}) if user_query is not None: data["user_query"] = user_query if task_context: data.update(session_context_fields(task_context)) if turn_context: data.update(turn_session_fields(turn_context, previous if same_task else {})) data["user_query"] = turn_context.get("user_query") elif clear_turn: clear_turn_session_context(data, user_query=user_query, agent_intent=agent_intent) os.makedirs(os.path.dirname(self._session_file), exist_ok=True) with open(self._session_file, "w", encoding="utf-8") as f: json.dump(data, f, ensure_ascii=False) self._task_id = task_id self._turn_id = str(data.get("current_turn_id") or "") self._user_query = str(data.get("current_user_query") or data.get("user_query") or "") self._agent_intent = normalize_agent_intent(data.get("current_agent_intent")) def _report_turn(self, path: str, turn_context: dict): if self._scripts_dir not in sys.path: sys.path.insert(0, self._scripts_dir) import executor as _ex # noqa: PLC0415 import urllib.request cfg = _ex.load_config() endpoint = (cfg.get("endpoint") or "").rstrip("/") api_key = self._resolve_api_key(cfg) if not endpoint or not api_key: raise RuntimeError("Turn 创建需要有效 endpoint 与 api_key") payload = {key: value for key, value in turn_context.items() if value is not None} body = json.dumps(payload, ensure_ascii=False).encode("utf-8") headers = _ex._trace_headers( turn_context, api_key=api_key, content_type="application/json" ) req = urllib.request.Request(f"{endpoint}{path}", data=body, headers=headers, method="POST") with urllib.request.urlopen(req, timeout=30) as response: result = json.loads(response.read().decode("utf-8")) if not isinstance(result, dict): raise RuntimeError("Turn tracking response is not an object") if result.get("code") == 0 and result.get("success") is not False and result.get("tracking_recorded") is not False: # message_id 幂等重试可返回首次写入的 canonical turn_id,后续上下文采用服务端值。 result["task_id"] = turn_context["task_id"] if result.get("turn_id") is not None: result["turn_id"] = str(result.get("turn_id") or "").strip() result["agent_intent"] = normalize_agent_intent( result.get("agent_intent") if "agent_intent" in result else turn_context.get("agent_intent") ) return result def _report_session_begin(self, turn_context: dict): return self._report_turn("/skill/session/begin", turn_context) def _report_turn_begin(self, turn_context: dict): return self._report_turn("/skill/session/turn", turn_context) def _check_skill_version(self) -> dict: """主动调用服务端 GET /skill/version/check,返回结构化升级元信息。 无配置/异常时返回空 dict。返回字段对齐服务端响应: update_required / latest_version / package / self_update / try_order / reload_files / message """ try: if self._scripts_dir not in sys.path: sys.path.insert(0, self._scripts_dir) import executor as _ex # noqa: PLC0415 import urllib.request cfg = _ex.load_config() endpoint = (cfg.get("endpoint") or "").rstrip("/") api_key = self._resolve_api_key(cfg) if not endpoint or not api_key: return {} headers = _ex._trace_headers( {"task_id": self._task_id or self._read_session()}, api_key=api_key, ) req = urllib.request.Request( f"{endpoint}/skill/version/check", headers=headers, method="GET", ) with urllib.request.urlopen(req, timeout=3) as resp: body = resp.read().decode("utf-8") data = json.loads(body) if data.get("code") == 0 and data.get("success"): return data return {} except Exception: return {} @staticmethod def _effective_update_required(version_info: dict, current_version: str) -> bool: """Ignore a stale server update flag when the local SemVer is already newer.""" requested = bool((version_info or {}).get("update_required")) if not requested: return False target = ( ((version_info or {}).get("package") or {}).get("required_version") or (version_info or {}).get("latest_version") ) if not current_version or not target: return requested try: from companion_manager import compare_semver # noqa: PLC0415 return compare_semver(current_version, target) < 0 except Exception: return requested # ──────────────────────────────────────────── # 底层调用(直接 HTTP,不走 subprocess) # ──────────────────────────────────────────── def _call(self, tool_name: str, params: dict = None) -> dict: """直接调用 executor.py 的 HTTP 函数,跳过 call.py / subprocess 两层开销。 返回 dict(JSON 响应)。出错时抛出 RuntimeError。 """ import uuid as _uuid params = dict(params or {}) # ── newSession:独立模式生成 UUID;上游编排模式继承 task_id ── if tool_name == "newSession": try: task_context = build_new_session_context(params, uuid_factory=_uuid.uuid4) except TaskContextError as exc: raise RuntimeError(f"{exc.code}: {exc.message}") from exc new_id = task_context["task_id"] _prev_session = self._read_session_full() _prev_ver = _prev_session.get("skill_version_at_creation") user_query = str(params.get("user_query") or "").strip() if not user_query: raise RuntimeError("USER_QUERY_REQUIRED: newSession 需要首轮用户原话 user_query") turn_params = dict(params) turn_params["task_id"] = new_id attempted_turn = None trusted_turn = None tracking_recorded = False reason_code = None should_adopt_upstream_turn = ( not task_context["report_session_begin"] and bool(str(params.get("turn_id") or "").strip()) ) if task_context["report_session_begin"] or should_adopt_upstream_turn: try: attempted_turn = build_turn_context({}, turn_params, uuid_factory=_uuid.uuid4) except TaskContextError as exc: raise RuntimeError(f"{exc.code}: {exc.message}") from exc if task_context["report_session_begin"] and attempted_turn: try: turn_result = self._report_session_begin(attempted_turn) tracking_recorded, canonical_turn_id, canonical_intent, reason_code = tracking_result_outcome( turn_result, new_id, attempted_turn["turn_id"] ) if tracking_recorded: trusted_turn = { **attempted_turn, "turn_id": canonical_turn_id, "agent_intent": ( canonical_intent if "agent_intent" in turn_result else attempted_turn.get("agent_intent") ), } else: record_turn_tracking_diagnostic( self._session_file, "newSession", new_id, attempted_turn.get("turn_id"), turn_result, ) except Exception as exc: reason_code = tracking_reason_code(exc, "TURN_TRACKING_REQUEST_FAILED") record_turn_tracking_diagnostic( self._session_file, "newSession", new_id, attempted_turn.get("turn_id"), exc, ) elif should_adopt_upstream_turn and attempted_turn: trusted_turn = attempted_turn tracking_recorded = True else: reason_code = "NO_TRUSTED_TURN" if trusted_turn: self._write_session( new_id, user_query=user_query, task_context=task_context, turn_context=trusted_turn ) else: self._write_session( new_id, user_query=user_query, task_context=task_context, clear_turn=True, agent_intent=params.get("agent_intent"), ) _cur_ver = _read_skill_version(self.skill_root) _changed = bool(_prev_ver and _prev_ver != _cur_ver) # 主动查询服务端版本,便于上层 Agent 在 newSession 时立即知道是否需要升级 _ver_info = self._check_skill_version() _qbs_update_required = self._effective_update_required(_ver_info, _cur_ver) _resp = { "code": 0, "task_id": new_id, "agent_intent": ( trusted_turn.get("agent_intent") if trusted_turn else normalize_agent_intent(params.get("agent_intent")) ), "task_mode": task_context["task_mode"], "task_id_source": task_context["task_id_source"], "task_source": task_context["task_source"], "skill_version": _cur_ver, "version_changed_from_last_session": _changed, "previous_skill_version": _prev_ver if _changed else None, "tracking_recorded": tracking_recorded, "blocking": False, "message": ( f"{'上游 task_id 已继承' if task_context['task_id_locked'] else '新 session 已创建'}(skill {_cur_ver})。" + (f"检测到 skill 从 {_prev_ver} 升级到 {_cur_ver}," "必须先重读 SKILL.md 再继续。" if _changed else ("可信 Turn 已保存到 session,后续业务工具会自动复用。" if tracking_recorded else "Turn 追踪未登记,后续业务工具将以无 Turn 模式继续。")) ), } if trusted_turn: _resp["turn_id"] = trusted_turn["turn_id"] if not tracking_recorded: _resp["reason_code"] = reason_code or "TURN_TRACKING_FAILED" if _qbs_update_required: _resp["update_required"] = True _resp["latest_version"] = _ver_info.get("latest_version") _resp["package"] = _ver_info.get("package") _resp["self_update"] = _ver_info.get("self_update") _resp["try_order"] = _ver_info.get("try_order") _resp["reload_files"] = _ver_info.get("reload_files") _resp["update_message"] = _ver_info.get("message") if isinstance(_ver_info.get("companions"), list) and _ver_info.get("companions"): try: from companion_manager import reconcile_after_qbs_check # noqa: PLC0415 companion_result = reconcile_after_qbs_check( _ver_info.get("companions"), self.skill_root, _cur_ver, qbs_update_required=_qbs_update_required, ) except Exception as exc: companion_result = { "name": "quant-buddy-view", "attempted": False, "ok": False, "reload_required": False, "error": str(exc), } if companion_result: _resp["companion_update"] = companion_result if companion_result.get("reload_required"): _resp["reload_required"] = True _resp["reload_reason"] = "quant-buddy-view installed, updated, or registered" _resp["message"] += ( " quant-buddy-view 已由 QBS 安装、更新或注册到当前 Agent;" "请重新加载 Agent 后再使用活页能力。" ) return _resp if tool_name == "beginTurn": session_data = self._read_session_full() if self._task_id: session_data["task_id"] = self._task_id try: attempted_turn = build_turn_context(session_data, params, uuid_factory=_uuid.uuid4) except TaskContextError as exc: raise RuntimeError(f"{exc.code}: {exc.message}") from exc tracking_recorded = False reason_code = None result = {} trusted_turn = None try: result = self._report_turn_begin(attempted_turn) tracking_recorded, canonical_turn_id, canonical_intent, reason_code = tracking_result_outcome( result, attempted_turn["task_id"], attempted_turn["turn_id"] ) if tracking_recorded: trusted_turn = { **attempted_turn, "turn_id": canonical_turn_id, "agent_intent": ( canonical_intent if "agent_intent" in result else attempted_turn.get("agent_intent") ), } else: record_turn_tracking_diagnostic( self._session_file, "beginTurn", attempted_turn.get("task_id"), attempted_turn.get("turn_id"), result, ) except Exception as exc: reason_code = tracking_reason_code(exc, "TURN_TRACKING_REQUEST_FAILED") record_turn_tracking_diagnostic( self._session_file, "beginTurn", attempted_turn.get("task_id"), attempted_turn.get("turn_id"), exc, ) if trusted_turn: self._write_session(trusted_turn["task_id"], turn_context=trusted_turn) else: self._write_session( attempted_turn["task_id"], user_query=attempted_turn["user_query"], clear_turn=True, agent_intent=attempted_turn.get("agent_intent"), ) response = { "code": 0, "success": True, "task_id": attempted_turn["task_id"], "created": bool(result.get("created")) if tracking_recorded else False, "user_query": attempted_turn["user_query"], "agent_intent": ( trusted_turn.get("agent_intent") if trusted_turn else attempted_turn.get("agent_intent") ), "tracking_recorded": tracking_recorded, "blocking": False, "message": ( "新 Turn 已登记;后续工具会自动复用当前可信 Turn。" if tracking_recorded else "Turn 追踪未登记,后续业务工具将以无 Turn 模式继续。" ), } if trusted_turn: response["turn_id"] = trusted_turn["turn_id"] else: response["reason_code"] = reason_code or "TURN_TRACKING_FAILED" return response # ── 版本守卫:检测旧会话与当前 skill 版本是否匹配 ───────────── _cur_ver = _read_skill_version(self.skill_root) if _cur_ver: try: with open(self._session_file, "r", encoding="utf-8") as _sf: _session_ver = json.load(_sf).get("skill_version_at_creation") if _session_ver is None or _session_ver != _cur_ver: raise RuntimeError( f"SKILL_VERSION_MISMATCH: session 创建于 {_session_ver}," f"当前为 {_cur_ver}。请调用 newSession 并重读 SKILL.md 后再继续。" ) except FileNotFoundError: pass # 尚未创建 session,不阻断 # ── 注入 task_id(优先用内存缓存,避免文件被并发覆盖导致跨批次漂移)── session_data = self._read_session_full() if self._task_id: session_data["task_id"] = self._task_id elif session_data.get("task_id"): self._task_id = session_data["task_id"] try: inject_or_validate_task_id(session_data, params) except TaskContextError as exc: raise RuntimeError(f"{exc.code}: {exc.message}") from exc turn_warnings = [] inject_or_validate_turn_context(session_data, params, warnings=turn_warnings) # ── 确保 executor 在 sys.path 里,然后 import ─────────────── if self._scripts_dir not in sys.path: sys.path.insert(0, self._scripts_dir) import executor as _ex # noqa: PLC0415 if tool_name not in _ex.TOOL_ROUTES: raise RuntimeError(f"未知工具: {tool_name},可用: {list(_ex.TOOL_ROUTES)}") cfg = _ex.load_config() method, path = _ex.TOOL_ROUTES[tool_name] try: api_key = self._resolve_api_key(cfg) except ValueError as e: raise RuntimeError(str(e)) from e try: if tool_name == "runMultiFormulaBatchStream": # SSE 主路径;服务端未部署时回退同步老接口 try: raw = _ex.call_run_multi_formula_batch_stream( cfg["endpoint"], api_key, params, timeout=self.timeout) except _ex._StreamUnsupportedError: raw = _ex.call_post(cfg["endpoint"], api_key, path, params, timeout=self.timeout) elif tool_name == "resumeJob": # deferred 任务续传是 SSE GET,必须带 Accept: text/event-stream; # 不能落入普通 call_get,否则服务端会返回 HTTP 406。 raw = _ex.call_resume_job( cfg["endpoint"], api_key, params, timeout=self.timeout) elif method == "GET": raw = _ex.call_get(cfg["endpoint"], api_key, path, params, timeout=self.timeout) else: raw = _ex.call_post(cfg["endpoint"], api_key, path, params, timeout=self.timeout) except Exception as e: raise RuntimeError(f"HTTP 调用失败 [{tool_name}]: {e}") from e # call_post 在服务端返回 YAML 时给回字符串,统一转 dict if isinstance(raw, str): try: raw = json.loads(raw) except json.JSONDecodeError: code_m = re.search(r"^code:\s*(-?\d+)", raw, re.MULTILINE) if code_m: return {"code": int(code_m.group(1)), "_raw_yaml": raw} return {"code": -1, "_raw": raw[:2000]} if tool_name in ("renderChart", "renderKLine"): raw = _materialize_chart_artifact(raw, params, self.skill_root) if tool_name in ("runMultiFormulaBatchStream", "resumeJob"): raw = _finalize_formula_execution_payload( tool_name, params, raw, task_id=self._task_id or params.get("task_id", "") ) # 服务端有时在响应里带新的 task_id: # - 只更新文件(供 call.py / 外部工具读取) # - 不覆盖 self._task_id 内存——内存 task_id 是本次扫描 8 个批次的"组号", # 中途被服务端改掉会导致跨批次变量引用失效("公式变量未找到") if isinstance(raw, dict) and raw.get("code") == 0: _data = raw.get("data") resp_tid = raw.get("task_id") or ((_data if isinstance(_data, dict) else {}).get("task_id")) session_data = self._read_session_full() if may_replace_session_task_id(session_data, resp_tid): # 只写文件,不动内存 os.makedirs(os.path.dirname(self._session_file), exist_ok=True) session_data["task_id"] = resp_tid with open(self._session_file, "w", encoding="utf-8") as _f: json.dump(session_data, _f, ensure_ascii=False) # ── 配额累积器:提取 _quota,累加 session 级总消耗 ────────── if isinstance(raw, dict) and "_quota" in raw: quota = raw["_quota"] self._session_ru_cost += quota.get("cost", 0) self._last_quota = dict(quota) # 注入 session_total_cost,方便模型从最后一次结果直接读取 raw["_quota"]["session_total_cost"] = self._session_ru_cost return raw @staticmethod def _unwrap(r: dict) -> dict: """将 {"code": 0, "data": {inner}} 展开为 inner dict。 后端 REST 响应总是包裹在 {"code": X, "data": {...}} 中, 而业务代码(ic_scan_dimensions.py 等)直接用 .get("data") / .get("errors") 访问内层字段。通过此展开保持向下兼容。 """ if isinstance(r, dict) and "code" in r and "data" in r: inner = r.get("data") if isinstance(inner, dict): return inner return r # ──────────────────────────────────────────── # Session 管理 # ──────────────────────────────────────────── def new_session( self, user_query: str = None, agent_intent: str = None, task_id: str = None, task_mode: str = "standalone", task_source: str = None, ) -> str: """初始化 session;可显式 inherit 已由上游创建的 task_id。""" params = {"task_mode": task_mode} if user_query is not None: params["user_query"] = user_query if agent_intent is not None: params["agent_intent"] = agent_intent if task_id is not None: params["task_id"] = task_id if task_source is not None: params["task_source"] = task_source r = self._call("newSession", params) task_id = r.get("task_id", "") if not task_id: raise RuntimeError(f"newSession 未返回 task_id: {r}") return task_id def begin_turn( self, user_query: str, turn_id: str = None, message_id: str = None, parent_turn_id: str = None, agent_intent: str = None, ) -> str: """Best-effort 切换 Turn;登记失败时返回 None,业务调用继续。""" params = {"user_query": user_query} if agent_intent is not None: params["agent_intent"] = agent_intent if turn_id is not None: params["turn_id"] = turn_id if message_id is not None: params["message_id"] = message_id if parent_turn_id is not None: params["parent_turn_id"] = parent_turn_id result = self._call("beginTurn", params) return result.get("turn_id") # ──────────────────────────────────────────── # 核心工具(与 SKILL.md 工具表一一对应) # ──────────────────────────────────────────── def search_functions(self, query: str, top_k: int = 3) -> dict: """搜索平台函数。""" return self._call("searchFunctions", {"query": query, "top_k": top_k}) def search_similar_cases(self, query: str) -> dict: """向量搜索相似案例(fallback 用途)。""" return self._call("searchSimilarCases", {"query": query}) def get_card_formulas(self, card_names: list) -> dict: """按卡片名称批量获取公式。""" return self._call("getCardFormulas", {"card_names": card_names}) def confirm_data_multi(self, data_desc: str) -> dict: """确认平台数据项。data_desc 是逗号分隔字符串,如 "换手率,市盈率"。""" return self._call("confirmDataMulti", {"data_desc": data_desc}) def run_multi_formula(self, formulas: list, begin_date: int = None, include_description: bool = False, **kwargs) -> dict: """执行公式批次。 Parameters ---------- formulas : list[str] 公式数组,如 ["X=收盘价(贵州茅台)", "Y=涨跌幅(\"X\",20)"] begin_date : int, optional 起始日期 YYYYMMDD,如 20160101。**不传则默认为今天**(YYYYMMDD)。 include_description : bool 是否在返回中包含数据描述,默认 False """ if begin_date is None: from datetime import date begin_date = int(date.today().strftime("%Y%m%d")) params = { "formulas": formulas, "include_description": include_description, "begin_date": begin_date, } params.update(kwargs) result = self._unwrap(self._call("runMultiFormulaBatchStream", params)) if isinstance(result, dict): result.setdefault("ids", self.extract_obj_id_list(result)) return result def refresh_snapshot_time(self, task_id: str = None) -> dict: """强制刷新指定 session 的分钟数据截止时间。""" task_id = task_id or self._task_id or self._read_session() if not task_id: raise ValueError( "refresh_snapshot_time 需要 task_id:请先调用 new_session()," "或显式传入 task_id 参数。" ) return self._unwrap(self._call("refreshSnapshotTime", {"task_id": task_id})) def read_data(self, ids: list, mode: str = "smart_sample", sample_points: int = None, **kwargs) -> dict: """读取数据。 Parameters ---------- ids : list[str] data_id 数组(来自 run_multi_formula 返回的 _id) mode : str smart_sample / last_day_stats / signature / precheck sample_points : int, optional 采样点数(mode=smart_sample 时有效) """ params = {"ids": ids, "mode": mode} if sample_points: params["sample_points"] = sample_points params.update(kwargs) return self._unwrap(self._call("readData", params)) def scan_dimensions(self, asset: dict, industry: str = None, dimensions=None, begin_date: int = 20160101, **kwargs) -> dict: """\u4e5d\u7ef4\u5ea6 IC \u626b\u63cf\u3002 Parameters ---------- asset : dict {"name": "\u4e2d\u63a7\u6280\u672f", "code": "688777.SH"} industry : str, optional \u884c\u4e1a\u6307\u6570\u540d\uff0c\u5982 "\u81ea\u52a8\u5316\u8bbe\u5907\uff08\u7533\u4e07\uff09" dimensions : list or "all", optional \u6307\u5b9a\u7ef4\u5ea6\u5217\u8868\uff0c\u5982 ["D1_\u4f30\u503c","D7_\u6280\u672f\u5f62\u6001"]\uff0c\u9ed8\u8ba4 None \u5373\u5168\u91cf begin_date : int, optional \u5386\u53f2\u8d77\u59cb\u65e5 YYYYMMDD\uff0c\u9ed8\u8ba4 20160101 """ params = {"asset": asset, "begin_date": begin_date} if industry: params["industry"] = industry if dimensions is not None: params["dimensions"] = dimensions params.update(kwargs) return self._call("scanDimensions", params) def render_chart(self, lines: list, title: str, **kwargs) -> dict: """渲染图表,自动保存 PNG 到 output/。 Parameters ---------- lines : list[dict] 如 [{"id": "data_id", "name": "图例名"}] title : str 图表标题 """ params = {"lines": lines, "title": title} params.update(kwargs) return self._call("renderChart", params) def render_kline(self, ticker: str, begin_date: int, title: str = "", indicators: list = None, **kwargs) -> dict: """K线图快捷渲染。""" params = {"ticker": ticker, "begin_date": begin_date} if title: params["title"] = title if indicators: params["indicators"] = indicators params.update(kwargs) return self._call("renderKLine", params) def download_data(self, data_id: str, begin_date: int = None, end_date: int = None, **kwargs) -> dict: """下载数据为 CSV(自动保存到 output/)。""" params = {"id": data_id} if begin_date: params["begin_date"] = begin_date if end_date: params["end_date"] = end_date params.update(kwargs) return self._call("downloadData", params) # ──────────────────────────────────────────── # 数据提取工具(纯本地,不调 API) # ──────────────────────────────────────────── @staticmethod def _formula_result_items(formula_result): """Yield formula result items in server order without normalizing IDs.""" seen_containers = set() def visit(value): if isinstance(value, list): identity = id(value) if identity in seen_containers: return seen_containers.add(identity) for item in value: if isinstance(item, dict): yield item yield from visit(item) elif isinstance(item, list): yield from visit(item) elif isinstance(value, dict): identity = id(value) if identity in seen_containers: return seen_containers.add(identity) for key in ("results", "data", "items"): nested = value.get(key) if isinstance(nested, (list, dict)): yield from visit(nested) if isinstance(formula_result, (dict, list)): yield from visit(formula_result) @staticmethod def _formula_item_obj_id(item): """Choose the read_data-compatible object ID, preserving exact server bytes.""" if not isinstance(item, dict): return None candidates = [ item.get("data_id"), item.get("indexinfo_id"), ] index_info = item.get("index_info") if isinstance(index_info, dict): candidates.extend([ index_info.get("data_id"), index_info.get("indexinfo_id"), index_info.get("_id"), index_info.get("id"), index_info.get("index_id"), ]) candidates.extend([ item.get("expression_id"), item.get("_id"), item.get("id"), item.get("index_id"), ]) for candidate in candidates: if isinstance(candidate, str) and candidate: return candidate return None @classmethod def extract_obj_ids(cls, formula_result: dict) -> dict: """从公式结果中提取 {leftName: obj_id},兼容顶层/嵌套 results。""" id_map = {} for item in cls._formula_result_items(formula_result): left_name = item.get("leftName") or item.get("index_title") if not left_name and isinstance(item.get("index_info"), dict): left_name = item["index_info"].get("index_title") obj_id = cls._formula_item_obj_id(item) if obj_id is not None and left_name: id_map[left_name] = obj_id return id_map @classmethod def extract_obj_id_list(cls, formula_result: dict) -> list: """Return de-duplicated read_data IDs in the exact order/bytes returned by the server.""" ordered = [] seen = set() for item in cls._formula_result_items(formula_result): obj_id = cls._formula_item_obj_id(item) if obj_id is not None and obj_id not in seen: ordered.append(obj_id) seen.add(obj_id) return ordered @staticmethod def extract_sample_values(item: dict): """从 read_data 返回的单条 item 中提取 (values, dates) 列表。 支持多种后端返回格式:sample_points / values / samples.values。 """ raw = [] for key in ("sample_points", "values"): candidate = item.get(key) if isinstance(candidate, list) and len(candidate) > 0: raw = candidate break if not raw: samples = item.get("samples") if isinstance(samples, dict): raw = samples.get("values", []) elif isinstance(samples, list): raw = samples values, dates = [], [] for sp in raw: if isinstance(sp, dict): v = sp.get("value") if "value" in sp else (sp.get("v") if "v" in sp else sp.get("y")) d = sp.get("date") if "date" in sp else (sp.get("x") if "x" in sp else sp.get("time")) else: v, d = sp, None try: fv = float(v) except (TypeError, ValueError): continue values.append(fv) dates.append(d) return values, dates @staticmethod def extract_latest_value(item: dict): """从 read_data 返回的单条 item 中获取最新值。""" lp = item.get("latest_point") if isinstance(lp, dict) and "value" in lp: try: return float(lp["value"]) except (TypeError, ValueError): pass return None -
self_update.py 23.4 KB
#!/usr/bin/env python3 """Self-update quant-buddy-skill from a verified zip package.""" import argparse import hashlib import json import os import re import shutil import sys import tempfile import time import urllib.request import zipfile from pathlib import Path, PurePosixPath, PureWindowsPath SCRIPT_DIR = Path(__file__).resolve().parent DEFAULT_SKILL_ROOT = SCRIPT_DIR.parent REQUIRED_PATHS = [ "SKILL.md", "CHANGELOG.md", "scripts/call.py", "scripts/executor.py", "workflows", "tools", ] def _json_exit(code, **payload): payload.setdefault("code", code) print(json.dumps(payload, ensure_ascii=False, indent=2)) sys.exit(0 if code == 0 else 1) def _read_skill_version(skill_md: Path) -> str: try: with skill_md.open("r", encoding="utf-8") as f: for line in f: stripped = line.strip() if stripped.startswith("version:"): return stripped.split(":", 1)[1].strip().strip('"\'') except OSError: pass return "" def _parse_version_tuple(version: str): if not version: return None text = str(version).strip() if text.startswith(("v", "V")): text = text[1:] parts = text.split(".") if not parts: return None nums = [] for part in parts: if not re.fullmatch(r"\d+", part): return None nums.append(int(part)) return tuple(nums) def _is_newer_version(target_version: str, current_version: str) -> bool: target = _parse_version_tuple(target_version) current = _parse_version_tuple(current_version) if target is None or current is None: return str(target_version or "") != str(current_version or "") width = max(len(target), len(current)) target = target + (0,) * (width - len(target)) current = current + (0,) * (width - len(current)) return target > current DOWNLOAD_TIMEOUT = 300 # 单次下载超时:5 分钟 DOWNLOAD_MAX_ATTEMPTS = 3 # 至少重试 3 次 DOWNLOAD_BACKOFF_BASE = 2 # 退避基数(秒):1s, 2s, 4s ... def _download_once(url: str, dest: Path) -> None: req = urllib.request.Request(url, headers={"User-Agent": "quant-buddy-skill-self-update"}) with urllib.request.urlopen(req, timeout=DOWNLOAD_TIMEOUT) as resp, dest.open("wb") as out: while True: chunk = resp.read(1024 * 1024) if not chunk: break out.write(chunk) def _download(url: str, dest: Path) -> None: """带退避重试的下载:单次 5min 超时,至少尝试 DOWNLOAD_MAX_ATTEMPTS 次。 每次失败按 1s/2s/4s 退避;全部失败时抛出最后一次异常,错误信息含尝试次数与原因类别。 """ last_exc = None for attempt in range(1, DOWNLOAD_MAX_ATTEMPTS + 1): try: _download_once(url, dest) return except Exception as exc: # noqa: BLE001 - 统一重试所有可恢复网络错误 last_exc = exc try: if dest.exists(): dest.unlink() except OSError: pass if attempt < DOWNLOAD_MAX_ATTEMPTS: time.sleep(DOWNLOAD_BACKOFF_BASE ** (attempt - 1)) raise RuntimeError( f"download failed after {DOWNLOAD_MAX_ATTEMPTS} attempts: " f"{type(last_exc).__name__}: {last_exc}" ) def _sha512(path: Path) -> str: h = hashlib.sha512() with path.open("rb") as f: for chunk in iter(lambda: f.read(1024 * 1024), b""): h.update(chunk) return h.hexdigest() def _is_unsafe_zip_name(name: str) -> bool: if not name or "\x00" in name: return True posix = PurePosixPath(name) win = PureWindowsPath(name) if posix.is_absolute() or win.is_absolute() or win.drive: return True return any(part == ".." for part in posix.parts) or any(part == ".." for part in win.parts) def _safe_extract(zip_path: Path, dest: Path) -> None: with zipfile.ZipFile(zip_path) as zf: bad = zf.testzip() if bad: raise RuntimeError(f"zip contains corrupt member: {bad}") for info in zf.infolist(): if _is_unsafe_zip_name(info.filename): raise RuntimeError(f"unsafe zip member path: {info.filename}") zf.extractall(dest) def _find_skill_source(staging: Path, zip_skill_path: str) -> Path: if zip_skill_path: source = staging / zip_skill_path if source.exists(): return source raise RuntimeError(f"zip skill path not found: {zip_skill_path}") direct = staging / "quant-buddy-skill" if direct.exists(): return direct candidates = [] for path in staging.rglob("SKILL.md"): candidates.append(path.parent) if len(candidates) == 1: return candidates[0] if not candidates: raise RuntimeError("cannot locate SKILL.md in zip") raise RuntimeError("multiple SKILL.md files found; pass --zip-skill-path") def _validate_source(source: Path, expected_version: str) -> str: for rel in REQUIRED_PATHS: path = source / rel if not path.exists(): raise RuntimeError(f"required path missing from package: {rel}") actual_version = _read_skill_version(source / "SKILL.md") if not actual_version: raise RuntimeError("cannot read version from package SKILL.md") if expected_version and actual_version != expected_version: raise RuntimeError(f"package version mismatch: expected {expected_version}, got {actual_version}") return actual_version SKILL_SLUG = "quant-buddy-skill" BACKUP_METADATA_FILENAME = "backup-metadata.json" BACKUP_METADATA_SCHEMA = "qbs_managed_backup_v1" BACKUP_RETENTION_COUNT = 3 BACKUP_RETENTION_SECONDS = 30 * 24 * 60 * 60 _MANAGED_BACKUP_NAME_RE = re.compile(r"^quant-buddy-skill-backup-\d{14}(?:-v[0-9A-Za-z._-]+)?(?:-\d+)?$") def _workbuddy_root(skill_root: Path): """Return the WorkBuddy root when this is a sibling inside <root>/skills/.""" parent = skill_root.parent return parent.parent if parent.name == "skills" else None def _default_backup_root(skill_root: Path) -> Path: """Keep managed QBS backups outside the active skills directory and root clutter.""" workbuddy_root = _workbuddy_root(skill_root) if workbuddy_root is not None: return workbuddy_root / "backups" / "skills" / SKILL_SLUG return skill_root.parent / "skill-backups" / SKILL_SLUG def _safe_version_fragment(version: str) -> str: value = re.sub(r"[^0-9A-Za-z._-]+", "_", str(version or "unknown")).strip("._-") return value or "unknown" def _next_backup_path(backup_root: Path, replaced_version: str, timestamp: str) -> Path: base = backup_root / f"{SKILL_SLUG}-backup-{timestamp}-v{_safe_version_fragment(replaced_version)}" candidate = base counter = 1 while candidate.exists(): candidate = backup_root / f"{base.name}-{counter}" counter += 1 return candidate def _read_json_dict(path: Path): try: data = json.loads(path.read_text(encoding="utf-8-sig")) return data if isinstance(data, dict) else {} except (OSError, ValueError, TypeError): return {} def _backup_metadata(path: Path): return _read_json_dict(path / BACKUP_METADATA_FILENAME) def _write_backup_metadata(backup_path: Path, *, replaced_version: str, source_skill_root: Path, created_at_epoch=None, migrated_from=None) -> dict: epoch = float(time.time() if created_at_epoch is None else created_at_epoch) payload = { "schema": BACKUP_METADATA_SCHEMA, "skill_slug": SKILL_SLUG, "replaced_version": str(replaced_version or ""), "created_at": time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime(epoch)), "created_at_epoch": epoch, "source_skill_root": str(source_skill_root), } if migrated_from: payload["migrated_from"] = str(migrated_from) (backup_path / BACKUP_METADATA_FILENAME).write_text( json.dumps(payload, ensure_ascii=False, indent=2) + "\n", encoding="utf-8" ) return payload def _copytree(src: Path, dst: Path) -> None: def ignore(_dir, names): return {name for name in names if name in {"output", "logs", "__pycache__"}} shutil.copytree(src, dst, ignore=ignore) def _create_managed_backup(skill_root: Path, backup_root: Path, timestamp: str) -> Path: replaced_version = _read_skill_version(skill_root / "SKILL.md") or "unknown" backup_root.mkdir(parents=True, exist_ok=True) backup_path = _next_backup_path(backup_root, replaced_version, timestamp) _copytree(skill_root, backup_path) _write_backup_metadata( backup_path, replaced_version=replaced_version, source_skill_root=skill_root, ) return backup_path def _validate_installed_skill(skill_root: Path, expected_version: str) -> None: actual_version = _validate_source(skill_root, expected_version) if actual_version != expected_version: raise RuntimeError( f"installed skill health check failed: expected {expected_version}, got {actual_version}" ) def _legacy_backup_metadata(path: Path): """Accept only an explicitly slugged historical SkillHub backup.""" managed = _backup_metadata(path) if managed.get("skill_slug") == SKILL_SLUG: return managed for filename in ("_skillhub_meta.json", "_meta.json"): legacy = _read_json_dict(path / filename) if legacy.get("slug") == SKILL_SLUG: return legacy return {} def _legacy_backup_candidates(skill_root: Path): workbuddy_root = _workbuddy_root(skill_root) if workbuddy_root is None or not workbuddy_root.is_dir(): return [] candidates = [] for child in workbuddy_root.iterdir(): if not child.is_dir() or child.is_symlink() or not _MANAGED_BACKUP_NAME_RE.fullmatch(child.name): continue if _legacy_backup_metadata(child): candidates.append(child) return candidates def _migrate_legacy_backups(skill_root: Path, backup_root: Path): """Move only verified legacy root backups after the new active install is healthy.""" migrated = [] backup_root.mkdir(parents=True, exist_ok=True) for source in _legacy_backup_candidates(skill_root): destination = backup_root / source.name counter = 1 while destination.exists(): destination = backup_root / f"{source.name}-{counter}" counter += 1 metadata = _legacy_backup_metadata(source) try: shutil.move(str(source), str(destination)) replaced_version = str(metadata.get("replaced_version") or metadata.get("version") or _read_skill_version(destination / "SKILL.md") or "unknown") created_at_epoch = metadata.get("created_at_epoch") if not isinstance(created_at_epoch, (int, float)): try: created_at_epoch = destination.stat().st_mtime except OSError: created_at_epoch = time.time() _write_backup_metadata( destination, replaced_version=replaced_version, source_skill_root=skill_root, created_at_epoch=created_at_epoch, migrated_from=source, ) migrated.append({"from": str(source), "to": str(destination)}) except Exception: # A failed migration must not affect the now-healthy active skill. continue return migrated def _managed_backup_created_at(path: Path, metadata: dict): value = metadata.get("created_at_epoch") if isinstance(value, (int, float)): return float(value) return None def _prune_managed_backups(backup_root: Path, now=None): """Delete only recognised metadata-bearing backups; leave all unknown folders alone.""" if not backup_root.is_dir(): return [] root = backup_root.resolve() now = time.time() if now is None else float(now) recognized = [] for child in backup_root.iterdir(): if not child.is_dir() or child.is_symlink(): continue # Defence in depth: never consider operational/artifact folders even if a user puts JSON inside. if child.name in {LOCK_DIRNAME, STAGING_DIRNAME, ".trash", "trash"} or child.name.startswith("."): continue if child.parent.resolve() != root: continue metadata = _backup_metadata(child) if metadata.get("schema") != BACKUP_METADATA_SCHEMA or metadata.get("skill_slug") != SKILL_SLUG: continue created_at = _managed_backup_created_at(child, metadata) if created_at is None: continue recognized.append((child, created_at)) recognized.sort(key=lambda item: item[1], reverse=True) remove = [] for index, (child, created_at) in enumerate(recognized): if now - created_at > BACKUP_RETENTION_SECONDS or index >= BACKUP_RETENTION_COUNT: remove.append(child) deleted = [] for child in remove: try: if child.parent.resolve() != root or child.is_symlink(): continue shutil.rmtree(child) deleted.append(str(child)) except OSError: continue return deleted # 随版本更新的“代码/文档”顶层项;其余(output/logs/config*)跨版本保留、绝不换名 PRESERVE_FROM_SWAP = {"config.json", "config.local.json", "output", "logs", "__pycache__"} STAGING_DIRNAME = ".staging" LOCK_DIRNAME = ".self_update.lock" LOCK_STALE_SECONDS = 1800 # 锁超过 30min 视为残留,可抢占 def _acquire_lock(skill_root: Path): """用 output/.self_update.lock 目录做并发互斥锁(mkdir 原子)。 返回锁路径;获取失败抛 RuntimeError。检测并抢占明显残留的旧锁。 """ output_dir = skill_root / "output" output_dir.mkdir(parents=True, exist_ok=True) lock_path = output_dir / LOCK_DIRNAME try: lock_path.mkdir() return lock_path except FileExistsError: try: age = time.time() - lock_path.stat().st_mtime if age > LOCK_STALE_SECONDS: shutil.rmtree(lock_path, ignore_errors=True) lock_path.mkdir() return lock_path except Exception: pass raise RuntimeError("another self_update is in progress (lock held)") def _release_lock(lock_path: Path): try: shutil.rmtree(lock_path, ignore_errors=True) except Exception: pass def _atomic_swap_item(staged_item: Path, target: Path, trash_dir: Path) -> None: """同卷原子换名:把已就位的 staged_item 换成 target。 - target 不存在:直接 os.replace(原子创建) - target 是文件/链接:os.replace 原子覆盖 - target 是目录:旧目录先移入 trash,再放新目录 Windows 对“有打开句柄/正在执行”的目录换名可能失败,调用方需保证更新时段无并发执行。 """ if not target.exists(): os.replace(staged_item, target) return if target.is_file() or target.is_symlink(): os.replace(staged_item, target) return trash_target = trash_dir / target.name os.replace(target, trash_target) try: os.replace(staged_item, target) except Exception: os.replace(trash_target, target) raise def _install(source: Path, skill_root: Path, backup_root: Path, expected_version: str) -> Path: """Install with a managed pre-update backup and rollback on swap or health-check failure. config*/output/logs remain in place. Historical migration and retention pruning are intentionally outside this function so they run only after this install returns healthy. """ lock_path = _acquire_lock(skill_root) timestamp = time.strftime("%Y%m%d%H%M%S") output_dir = skill_root / "output" staging_root = output_dir / STAGING_DIRNAME / timestamp trash_root = output_dir / STAGING_DIRNAME / f"{timestamp}.trash" swapped = [] try: # A backup failure is a safety failure: do not overwrite a working installation # without a rollback artifact. backup_path = _create_managed_backup(skill_root, backup_root, timestamp) items = sorted((it for it in source.iterdir() if it.name not in PRESERVE_FROM_SWAP), key=lambda item: item.name) if staging_root.exists(): shutil.rmtree(staging_root, ignore_errors=True) staging_root.mkdir(parents=True, exist_ok=True) trash_root.mkdir(parents=True, exist_ok=True) for item in items: dst = staging_root / item.name if item.is_dir() and not item.is_symlink(): shutil.copytree(item, dst) else: shutil.copy2(item, dst) for item in items: target = skill_root / item.name staged = staging_root / item.name had_target = target.exists() _atomic_swap_item(staged, target, trash_root) swapped.append((target, (trash_root / item.name) if had_target else None)) _validate_installed_skill(skill_root, expected_version) shutil.rmtree(trash_root, ignore_errors=True) shutil.rmtree(staging_root, ignore_errors=True) return backup_path except Exception: for target, trash_old in reversed(swapped): try: if trash_old is not None and trash_old.exists(): if target.exists(): if target.is_dir() and not target.is_symlink(): shutil.rmtree(target, ignore_errors=True) else: target.unlink() os.replace(trash_old, target) elif target.exists(): if target.is_dir() and not target.is_symlink(): shutil.rmtree(target, ignore_errors=True) else: target.unlink() except Exception: pass shutil.rmtree(trash_root, ignore_errors=True) shutil.rmtree(staging_root, ignore_errors=True) raise finally: _release_lock(lock_path) def _finalize_dedup_state(skill_root: Path, target_version: str, status: str, last_error=None): """更新 output/.self_update_state.json,让 call.py 的“当日去重/软锁”自愈: 后台更新结束(ok/failed)后清除 in_progress,避免同日其他进程被永久软锁。 与 call.py._self_update_mark 写同一文件、同一字段约定;best-effort,失败不抛。 """ if not target_version: return state_file = skill_root / "output" / ".self_update_state.json" try: today = time.strftime("%Y-%m-%d") prev = {} if state_file.exists(): try: prev = json.loads(state_file.read_text(encoding="utf-8")) or {} except Exception: prev = {} same = prev.get("date") == today and prev.get("target_version") == target_version attempts = int(prev.get("attempts") or 0) if same else 0 prev_status = prev.get("status") if same else None if status == "failed" and prev_status != "failed": attempts += 1 new_state = { "date": today, "target_version": target_version, "attempts": attempts, "status": status, "last_error": last_error, "ts": int(time.time()), } state_file.parent.mkdir(parents=True, exist_ok=True) state_file.write_text(json.dumps(new_state, ensure_ascii=False, indent=2), encoding="utf-8") except Exception: pass def main(): parser = argparse.ArgumentParser(description="Update quant-buddy-skill from a verified zip package.") parser.add_argument("--version", required=True, help="Expected SKILL.md version after update") parser.add_argument("--sha512", required=True, help="Expected SHA-512 hex digest for the zip package") parser.add_argument("--url", help="Zip package URL") parser.add_argument("--zip-path", help="Local zip package path") parser.add_argument("--zip-skill-path", default="", help="Path to skill directory inside the extracted zip") parser.add_argument("--skill-root", default=str(DEFAULT_SKILL_ROOT), help="Current skill root directory") parser.add_argument("--backup-root", default="", help="Directory outside skills/ for backups") parser.add_argument("--dry-run", action="store_true", help="Validate only; do not replace files") args = parser.parse_args() if not re.fullmatch(r"[0-9a-fA-F]{128}", args.sha512.strip()): _json_exit(1, success=False, error="sha512 must be a 128-character hex digest") if not args.url and not args.zip_path: _json_exit(1, success=False, error="one of --url or --zip-path is required") skill_root = Path(args.skill_root).resolve() if not (skill_root / "SKILL.md").exists(): _json_exit(1, success=False, error=f"skill root does not contain SKILL.md: {skill_root}") backup_root = Path(args.backup_root).resolve() if args.backup_root else _default_backup_root(skill_root).resolve() current_version = _read_skill_version(skill_root / "SKILL.md") if current_version and not _is_newer_version(args.version, current_version): _finalize_dedup_state(skill_root, args.version, "ok") _json_exit( 0, success=True, skipped=True, reason=f"target version {args.version} is not newer than current {current_version}; skip self_update to avoid downgrade.", package_version=current_version, skill_root=str(skill_root), ) with tempfile.TemporaryDirectory(prefix="qbs_self_update_") as tmp: tmpdir = Path(tmp) zip_path = Path(args.zip_path).resolve() if args.zip_path else tmpdir / "package.zip" try: if args.url: _download(args.url, zip_path) actual_sha = _sha512(zip_path) if actual_sha.lower() != args.sha512.lower(): _json_exit(1, success=False, error="zip sha512 mismatch", expected=args.sha512.lower(), actual=actual_sha.lower()) staging = tmpdir / "staging" staging.mkdir() _safe_extract(zip_path, staging) source = _find_skill_source(staging, args.zip_skill_path) package_version = _validate_source(source, args.version) if args.dry_run: _json_exit(0, success=True, dry_run=True, package_version=package_version, source=str(source), skill_root=str(skill_root)) backup_path = _install(source, skill_root, backup_root, package_version) migrated_legacy_backups = [] if not args.backup_root: migrated_legacy_backups = _migrate_legacy_backups(skill_root, backup_root) pruned_backups = _prune_managed_backups(backup_root) _finalize_dedup_state(skill_root, args.version, "ok") _json_exit( 0, success=True, package_version=package_version, skill_root=str(skill_root), backup_path=str(backup_path), backup_root=str(backup_root), migrated_legacy_backups=migrated_legacy_backups, pruned_backups=pruned_backups, ) except Exception as exc: _finalize_dedup_state(skill_root, args.version, "failed", last_error=str(exc)) _json_exit(1, success=False, error=str(exc), skill_root=str(skill_root)) if __name__ == "__main__": main() -
task_context.py 13.3 KB
"""Shared task/turn context rules for quant-buddy-skill entry points.""" import json import os import re import time import uuid TASK_MODE_STANDALONE = "standalone" TASK_MODE_INHERIT = "inherit" _TASK_ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$") _TURN_ID_RE = re.compile(r"^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$") _MAX_AGENT_INTENT_CHARS = 300 _TURN_SESSION_KEYS = ( "current_turn_id", "previous_turn_id", ) def record_turn_tracking_diagnostic(session_file, operation, task_id, turn_id, detail): """Best-effort local audit detail that is never returned to the Agent-facing response.""" try: target = os.environ.get("QBS_TRACKING_DIAGNOSTIC_FILE", "").strip() if not target: target = f"{session_file}.tracking.jsonl" parent = os.path.dirname(os.path.abspath(target)) if parent: os.makedirs(parent, exist_ok=True) if isinstance(detail, str): detail_text = detail else: try: detail_text = json.dumps(detail, ensure_ascii=False, default=str) except (TypeError, ValueError): detail_text = str(detail) payload = { "timestamp_ms": int(time.time() * 1000), "source": "quant-buddy-skill", "operation": str(operation or "turnTracking"), "task_id": str(task_id or "").strip() or None, "turn_id": str(turn_id or "").strip() or None, "detail": detail_text[:8000], } with open(target, "a", encoding="utf-8", newline="\n") as handle: handle.write(json.dumps(payload, ensure_ascii=False) + "\n") return True except (OSError, TypeError, ValueError): return False class TaskContextError(ValueError): def __init__(self, code, message): super().__init__(message) self.code = code self.message = message def _validate_task_id(value): task_id = str(value or "").strip() if not task_id or not _TASK_ID_RE.fullmatch(task_id): raise TaskContextError( "INVALID_INHERITED_TASK_ID", "inherit 模式需要合法 task_id(1-128 位字母、数字、点、下划线、冒号或短横线)", ) return task_id def build_new_session_context(params=None, uuid_factory=None): """Resolve standalone generation vs explicit upstream inheritance.""" params = params if isinstance(params, dict) else {} mode = str(params.get("task_mode") or TASK_MODE_STANDALONE).strip().lower() if mode not in {TASK_MODE_STANDALONE, TASK_MODE_INHERIT}: raise TaskContextError( "INVALID_TASK_MODE", "task_mode 仅支持 standalone 或 inherit", ) requested_task_id = params.get("task_id") if mode == TASK_MODE_INHERIT: task_id = _validate_task_id(requested_task_id) source = str(params.get("task_source") or "upstream").strip() or "upstream" return { "task_id": task_id, "task_mode": TASK_MODE_INHERIT, "task_id_source": "inherited", "task_source": source[:64], "task_id_locked": True, "report_session_begin": False, } if requested_task_id not in (None, ""): raise TaskContextError( "TASK_MODE_INHERIT_REQUIRED", "newSession 传入已有 task_id 时必须显式设置 task_mode=inherit", ) factory = uuid_factory or uuid.uuid4 return { "task_id": str(factory()), "task_mode": TASK_MODE_STANDALONE, "task_id_source": "generated", "task_source": "quant-buddy-skill", "task_id_locked": False, "report_session_begin": True, } def session_context_fields(context): return { key: context[key] for key in ("task_mode", "task_id_source", "task_source", "task_id_locked") if key in context } def inject_or_validate_task_id(session, params): """Inject the session id, and fail closed on inherited-session drift.""" session = session if isinstance(session, dict) else {} params = params if isinstance(params, dict) else {} session_task_id = str(session.get("task_id") or "").strip() request_task_id = str(params.get("task_id") or "").strip() if ( session.get("task_id_locked") is True and session_task_id and request_task_id and request_task_id != session_task_id ): raise TaskContextError( "TASK_ID_CONTEXT_MISMATCH", f"当前继承 session 已锁定 task_id={session_task_id},拒绝切换到 {request_task_id}", ) if session_task_id and not request_task_id: params["task_id"] = session_task_id return True return False def may_replace_session_task_id(session, response_task_id): session = session if isinstance(session, dict) else {} current = str(session.get("task_id") or "").strip() incoming = str(response_task_id or "").strip() if not incoming or incoming == current: return False return not (session.get("task_id_locked") is True and current) def _validate_turn_id(value): turn_id = str(value or "").strip() if not turn_id or not _TURN_ID_RE.fullmatch(turn_id): raise TaskContextError( "INVALID_TURN_ID", "turn_id 需要为 1-128 位字母、数字、点、下划线、冒号或短横线", ) return turn_id def normalize_agent_intent(value): """Normalize optional per-Turn intent without deriving it from user_query.""" if not isinstance(value, str): return None normalized = value.strip() return normalized[:_MAX_AGENT_INTENT_CHARS] or None def tracking_reason_code(detail, default="TURN_TRACKING_FAILED"): """Extract a stable tracking reason without exposing diagnostic text.""" if isinstance(detail, dict): direct = detail.get("reason_code") if isinstance(direct, str) and direct.strip(): return direct.strip() error = detail.get("error") if isinstance(error, dict): nested = error.get("code") if isinstance(nested, str) and nested.strip(): return nested.strip() if isinstance(error, str) and error.strip(): return error.strip() code = getattr(detail, "code", None) if isinstance(code, str) and code.strip(): return code.strip() return default def tracking_result_outcome(result, expected_task_id, attempted_turn_id): """Return (recorded, canonical_turn_id, canonical_intent, reason_code).""" if not isinstance(result, dict): return False, None, None, "TURN_TRACKING_RESPONSE_INVALID" if result.get("code") != 0 or result.get("success") is False or result.get("tracking_recorded") is False: return False, None, None, tracking_reason_code(result) if result.get("task_id") not in (None, "", expected_task_id): return False, None, None, "TURN_TASK_CONTEXT_MISMATCH" canonical_turn_id = str(result.get("turn_id") or "").strip() if not canonical_turn_id: return False, None, None, "TURN_ID_MISSING" canonical_intent = normalize_agent_intent( result.get("agent_intent") if "agent_intent" in result else None ) return True, canonical_turn_id, canonical_intent, None def build_turn_context(session, params=None, uuid_factory=None): """Build one immutable user-turn context without mutating the session.""" session = session if isinstance(session, dict) else {} params = params if isinstance(params, dict) else {} task_id = str(params.get("task_id") or session.get("task_id") or "").strip() if not task_id: raise TaskContextError("TASK_ID_REQUIRED", "beginTurn 前必须先调用 newSession") session_task_id = str(session.get("task_id") or "").strip() if session_task_id and task_id != session_task_id: raise TaskContextError( "TASK_ID_CONTEXT_MISMATCH", f"当前 session 的 task_id={session_task_id},拒绝为 {task_id} 创建 Turn", ) user_query = str(params.get("user_query") or "").strip() if not user_query: raise TaskContextError("USER_QUERY_REQUIRED", "beginTurn 需要本轮用户原话 user_query") factory = uuid_factory or uuid.uuid4 requested_turn_id = params.get("turn_id") turn_id = _validate_turn_id(requested_turn_id) if requested_turn_id else str(factory()) parent_turn_id = str( params.get("parent_turn_id") or session.get("current_turn_id") or "" ).strip() or None message_id = str(params.get("message_id") or "").strip() or None return { "task_id": task_id, "turn_id": turn_id, "user_query": user_query, "agent_intent": normalize_agent_intent(params.get("agent_intent")), "message_id": message_id, "parent_turn_id": parent_turn_id, } def turn_session_fields(turn_context, previous_session=None): """Return the non-overwriting session projection for a committed Turn.""" previous_session = previous_session if isinstance(previous_session, dict) else {} initial = previous_session.get("initial_user_query") if initial is None: initial = turn_context.get("user_query") if "initial_agent_intent" in previous_session: initial_agent_intent = normalize_agent_intent(previous_session.get("initial_agent_intent")) elif previous_session.get("current_turn_id"): # Legacy sessions did not record the first Turn intent; do not invent one from a follow-up. initial_agent_intent = None else: initial_agent_intent = normalize_agent_intent(turn_context.get("agent_intent")) return { "current_turn_id": turn_context.get("turn_id"), "current_turn_trusted": True, "current_user_query": turn_context.get("user_query"), "current_agent_intent": normalize_agent_intent(turn_context.get("agent_intent")), "previous_turn_id": turn_context.get("parent_turn_id"), "initial_user_query": initial, "initial_agent_intent": initial_agent_intent, } def clear_turn_session_context(session, user_query=None, agent_intent=None): """Remove any propagatable Turn while retaining the real business context.""" session = session if isinstance(session, dict) else {} had_initial_query = bool(session.get("initial_user_query")) for key in _TURN_SESSION_KEYS: session.pop(key, None) session["current_turn_trusted"] = False if user_query is not None: query = str(user_query).strip() session["user_query"] = query session["current_user_query"] = query if not had_initial_query: session["initial_user_query"] = query session["current_agent_intent"] = normalize_agent_intent(agent_intent) if "initial_agent_intent" not in session and not had_initial_query: session["initial_agent_intent"] = normalize_agent_intent(agent_intent) return session def inject_or_validate_turn_context(session, params, warnings=None, suppress_session_turn=False): """Best-effort Turn injection; tracing drift never blocks a business tool.""" session = session if isinstance(session, dict) else {} params = params if isinstance(params, dict) else {} warnings = warnings if isinstance(warnings, list) else [] turn_id = str(session.get("current_turn_id") or "").strip() turn_trusted = ( bool(turn_id) and session.get("current_turn_trusted") is not False and not suppress_session_turn ) current_query = str(session.get("current_user_query") or session.get("user_query") or "").strip() request_turn_id = str(params.get("turn_id") or "").strip() request_query = str(params.get("user_query") or params.get("userQuery") or "").strip() if not turn_trusted: changed = "turn_id" in params params.pop("turn_id", None) if request_query and "user_query" not in params: params["user_query"] = request_query params.pop("userQuery", None) changed = True elif current_query and not request_query: params["user_query"] = current_query changed = True return changed turn_mismatch = bool(request_turn_id and request_turn_id != turn_id) query_mismatch = bool(current_query and request_query and request_query != current_query) if turn_mismatch or query_mismatch: warnings.append({ "code": "TURN_CONTEXT_MISMATCH", "message": ( f"Turn 上下文不一致,已取消 Turn 关联并继续业务调用:" f"session_turn={turn_id or '-'}, request_turn={request_turn_id or '-'}" ), }) # Preserve the caller's real question, but never send a stale/mismatched # turn_id that could attach this tool call to another Session. changed = "turn_id" in params params.pop("turn_id", None) if request_query and "user_query" not in params: params["user_query"] = request_query params.pop("userQuery", None) changed = True return changed changed = False if turn_id and not request_turn_id: params["turn_id"] = turn_id changed = True if current_query and not request_query: params["user_query"] = current_query changed = True elif request_query and "user_query" not in params: params["user_query"] = request_query params.pop("userQuery", None) changed = True return changed -
validation_receipt.py 17.2 KB
import hashlib import json import os import re import tempfile from datetime import datetime, timezone VERSION = "qb_validation_receipt_v1" FORMULA_RUNTIME_SCHEMA_VERSION = "qbs_formula_runtime_contract_v1" CONTINUATION_CONTEXT_VERSION = "qbs_formula_continuation_context_v1" SUPPORTED_TOOLS = {"runMultiFormulaBatchStream", "resumeJob"} def _payload_data(payload): data = payload.get("data") if isinstance(payload, dict) else None return data if isinstance(data, dict) else payload _TOPN_RE = re.compile( r'^\s*取前\(\s*"([^"]+)"\s*,\s*(\d+)\s*(?:,\s*(返回数值)\s*)?\)\s*$' ) _TOPN_MINUS_REF_RE = re.compile( r'^\s*取前\(\s*"([^"]+)"\s*,\s*(\d+)\s*(?:,\s*(返回数值)\s*)?\)' r'\s*-\s*"([^"]+)"\s*$' ) _QUOTED_REF_RE = re.compile(r'"([^"]+)"') def _formula_assignments(formulas): assignments = {} for formula in formulas: if "=" not in formula: return None left, right = formula.split("=", 1) left = left.strip() if not left or left in assignments: return None assignments[left] = right.strip() return assignments def _bounded_output_sizes(formulas): """Infer only output cardinalities that are provably bounded by formula shape. Formula Package ``last_day_stats`` returns at most ten ranked values. It is therefore safe only when the QBS formula itself proves that an output has at most ten non-zero assets. The supported shapes deliberately stay narrow: direct ``取前`` outputs, a difference between nested prefixes from the same source (Top20 - Top10), and a metric multiplied by such a bounded mask. Unknown shapes remain without a runtime contract instead of guessing. """ assignments = _formula_assignments(formulas) if assignments is None: return {} topn = {} for name, right in assignments.items(): match = _TOPN_RE.fullmatch(right) if match: topn[name] = { "source": match.group(1), "size": int(match.group(2)), "returns_value": bool(match.group(3)), } memo = {} visiting = set() def infer(name): if name in memo: return memo[name] if name in visiting or name not in assignments: return None visiting.add(name) result = None direct = topn.get(name) if direct is not None: result = direct["size"] else: right = assignments[name] refs = _QUOTED_REF_RE.findall(right) inline_difference = _TOPN_MINUS_REF_RE.fullmatch(right) if inline_difference: larger = { "source": inline_difference.group(1), "size": int(inline_difference.group(2)), "returns_value": bool(inline_difference.group(3)), } smaller = topn.get(inline_difference.group(4)) if ( smaller is not None and larger["source"] == smaller["source"] and larger["returns_value"] == smaller["returns_value"] and larger["size"] >= smaller["size"] ): result = larger["size"] - smaller["size"] elif "-" in right and len(refs) == 2: larger = topn.get(refs[0]) smaller = topn.get(refs[1]) if ( larger is not None and smaller is not None and larger["source"] == smaller["source"] and larger["returns_value"] == smaller["returns_value"] and larger["size"] >= smaller["size"] ): result = larger["size"] - smaller["size"] if result is None and "*" in right: bounds = [infer(ref) for ref in refs] bounds = [bound for bound in bounds if isinstance(bound, int) and bound > 0] if bounds: result = min(bounds) visiting.remove(name) memo[name] = result return result for name in assignments: infer(name) return memo def _safe_runtime_reads(formulas, reusable): if not reusable: return None bounds = _bounded_output_sizes(formulas) if any(not isinstance(bounds.get(output), int) or not 1 <= bounds[output] <= 10 for output in reusable): return None return [{"output": output, "read_mode": "last_day_stats"} for output in reusable] def _formula_runtime_contract(tool_name, params): if tool_name != "runMultiFormulaBatchStream" or not isinstance(params, dict): return None raw_formulas = params.get("formulas") if not isinstance(raw_formulas, list) or not raw_formulas: return None formulas = [] for raw in raw_formulas: if not isinstance(raw, str) or not raw.strip(): return None formulas.append(raw) raw_reusable = params.get("force_reusable_array") if raw_reusable is None: raw_reusable = [] if not isinstance(raw_reusable, list): return None reusable = [] for raw in raw_reusable: if not isinstance(raw, str) or not raw.strip(): return None output = raw.strip() if output not in reusable: reusable.append(output) reads = _safe_runtime_reads(formulas, reusable) if reads is None: return None contract = { "schema_version": FORMULA_RUNTIME_SCHEMA_VERSION, "formulas": formulas, "include_description": bool(params.get("include_description", False)), "use_minute_data": bool(params.get("use_minute_data", False)), "force_reusable_array": reusable, "reads": reads, } if params.get("begin_date") is not None: contract["begin_date"] = params.get("begin_date") digest_source = json.dumps(contract, ensure_ascii=False, sort_keys=True, separators=(",", ":")) contract["contract_fingerprint"] = "sha256:" + hashlib.sha256(digest_source.encode("utf-8")).hexdigest() return contract def _runtime_formulas_by_output(runtime_contract): if not isinstance(runtime_contract, dict): return {} formulas = runtime_contract.get("formulas") if not isinstance(formulas, list): return {} result = {} for formula in formulas: if not isinstance(formula, str) or "=" not in formula: continue output = formula.split("=", 1)[0].strip() if output: result[output] = formula return result def _bind_receipt_outputs_to_runtime(receipt): runtime_contract = receipt.get("runtime_contract") if isinstance(receipt, dict) else None formulas_by_output = _runtime_formulas_by_output(runtime_contract) outputs = receipt.get("outputs") if isinstance(receipt, dict) else None if not formulas_by_output or not isinstance(outputs, list): return receipt formulas = runtime_contract.get("formulas") if isinstance(runtime_contract, dict) else None ordered_names = [] if isinstance(formulas, list): ordered_names = [ formula.split("=", 1)[0].strip() for formula in formulas if isinstance(formula, str) and "=" in formula ] positional_binding_allowed = len(ordered_names) == len(outputs) and len(ordered_names) == len(formulas_by_output) for index, item in enumerate(outputs): if not isinstance(item, dict): continue index_title = str(item.get("index_title") or "").strip() if not index_title: expression_id = str(item.get("expression_id") or "").strip() if expression_id in formulas_by_output: index_title = expression_id elif positional_binding_allowed: # QBS summary/resume responses may replace formula left names with # opaque expression IDs. The backend preserves submitted formula # order; bind only when the result count exactly matches the # runtime contract, mirroring QBV qbs_bridge's strict seam. index_title = ordered_names[index] if index_title: item["index_title"] = index_title formula = formulas_by_output.get(index_title) if formula: item["formula"] = formula digest_source = json.dumps(outputs, ensure_ascii=False, sort_keys=True, separators=(",", ":")) receipt["outputs_sha256"] = hashlib.sha256(digest_source.encode("utf-8")).hexdigest() return receipt def build_receipt(tool_name, params, payload): if tool_name not in SUPPORTED_TOOLS or not isinstance(payload, dict): return None data = _payload_data(payload) task_id = str(payload.get("task_id") or params.get("task_id") or data.get("task_id") or "").strip() results = data.get("results") if isinstance(data.get("results"), list) else [] status = str(data.get("status") or payload.get("status") or "").lower() success = payload.get("code") == 0 and payload.get("success") is True if status not in {"ok", "done", "completed"}: success = False if not task_id or not success or payload.get("errors"): return None if any(not isinstance(item, dict) or item.get("status") != "success" for item in results): return None outputs = [] for item in results: index_info = item.get("index_info") if isinstance(item.get("index_info"), dict) else {} index_title = str(item.get("leftName") or index_info.get("index_title") or "").strip() output = { "expression_id": item.get("expression_id") or item.get("leftName") or "", "data_id": item.get("data_id") or item.get("indexinfo_id") or "", "status": "success", } if index_title: output["index_title"] = index_title outputs.append(output) digest_source = json.dumps(outputs, ensure_ascii=False, sort_keys=True, separators=(",", ":")) receipt = { "version": VERSION, "task_id": task_id, "tool_name": tool_name, "status": "completed", "success": True, "failures": [], "outputs": outputs, "outputs_sha256": hashlib.sha256(digest_source.encode("utf-8")).hexdigest(), "created_at": datetime.now(timezone.utc).isoformat(timespec="seconds").replace("+00:00", "Z"), } runtime_contract = _formula_runtime_contract(tool_name, params) if runtime_contract is not None: receipt["runtime_contract"] = runtime_contract return _bind_receipt_outputs_to_runtime(receipt) def _receipt_root(): override = str(os.environ.get("QBS_VALIDATION_RECEIPT_DIR") or "").strip() if override: return os.path.abspath(override) skill_root = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) return os.path.join(skill_root, "output", "validation_receipts") def _continuation_root(): override = str(os.environ.get("QBS_FORMULA_CONTINUATION_DIR") or "").strip() if override: return os.path.abspath(override) return os.path.join(_receipt_root(), "_continuations") def _continuation_ids(params, payload): params = params if isinstance(params, dict) else {} payload = payload if isinstance(payload, dict) else {} data = _payload_data(payload) task_id = str(payload.get("task_id") or params.get("task_id") or data.get("task_id") or "").strip() trace_id = str(payload.get("trace_id") or params.get("trace_id") or data.get("trace_id") or "").strip() return task_id, trace_id def _continuation_path(task_id, trace_id): identity = json.dumps([task_id, trace_id], ensure_ascii=False, separators=(",", ":")) filename = hashlib.sha256(identity.encode("utf-8")).hexdigest() + ".json" return os.path.join(_continuation_root(), filename) def _write_json_atomic(path, value, prefix): root = os.path.dirname(path) os.makedirs(root, exist_ok=True) fd, temp_path = tempfile.mkstemp(prefix=prefix, suffix=".json", dir=root) try: with os.fdopen(fd, "w", encoding="utf-8", newline="\n") as handle: json.dump(value, handle, ensure_ascii=False, indent=2) handle.write("\n") os.replace(temp_path, path) except Exception: try: os.unlink(temp_path) except OSError: pass raise def _runtime_contract_is_valid(contract): if not isinstance(contract, dict) or contract.get("schema_version") != FORMULA_RUNTIME_SCHEMA_VERSION: return False fingerprint = str(contract.get("contract_fingerprint") or "") unsigned = {key: value for key, value in contract.items() if key != "contract_fingerprint"} digest_source = json.dumps(unsigned, ensure_ascii=False, sort_keys=True, separators=(",", ":")) expected = "sha256:" + hashlib.sha256(digest_source.encode("utf-8")).hexdigest() return fingerprint == expected def _is_deferred(payload): if not isinstance(payload, dict): return False data = _payload_data(payload) status = str(payload.get("status") or data.get("status") or "").strip().lower() return payload.get("_deferred") is True or status == "deferred" def _record_deferred_runtime_contract(tool_name, params, payload): if tool_name != "runMultiFormulaBatchStream" or not _is_deferred(payload): return None contract = _formula_runtime_contract(tool_name, params) task_id, trace_id = _continuation_ids(params, payload) if contract is None or not task_id or not trace_id: return None context = { "version": CONTINUATION_CONTEXT_VERSION, "task_id": task_id, "trace_id": trace_id, "runtime_contract": contract, "created_at": datetime.now(timezone.utc).isoformat(timespec="seconds").replace("+00:00", "Z"), } path = _continuation_path(task_id, trace_id) _write_json_atomic(path, context, ".continuation-") return path def _load_deferred_runtime_contract(tool_name, params, payload): if tool_name != "resumeJob": return None task_id, trace_id = _continuation_ids(params, payload) if not task_id or not trace_id: return None path = _continuation_path(task_id, trace_id) try: with open(path, "r", encoding="utf-8") as handle: context = json.load(handle) except (OSError, ValueError, TypeError): return None if not isinstance(context, dict) or context.get("version") != CONTINUATION_CONTEXT_VERSION: return None if context.get("task_id") != task_id or context.get("trace_id") != trace_id: return None contract = context.get("runtime_contract") return contract if _runtime_contract_is_valid(contract) else None def write_receipt(tool_name, params, payload): params = params or {} _record_deferred_runtime_contract(tool_name, params, payload) receipt = build_receipt(tool_name, params, payload) if receipt is None: return None if "runtime_contract" not in receipt: runtime_contract = _load_deferred_runtime_contract(tool_name, params, payload) if runtime_contract is not None: receipt["runtime_contract"] = runtime_contract _bind_receipt_outputs_to_runtime(receipt) root = _receipt_root() os.makedirs(root, exist_ok=True) task_hash = hashlib.sha256(receipt["task_id"].encode("utf-8")).hexdigest()[:16] filename = f"{task_hash}-{receipt['outputs_sha256'][:16]}.json" path = os.path.join(root, filename) _write_json_atomic(path, receipt, ".receipt-") return path def apply_output_mode(payload, mode="full"): if not isinstance(payload, dict): return payload data = payload.get("data") if isinstance(payload.get("data"), dict) else {} status = str(payload.get("status") or data.get("status") or "").strip().lower() is_deferred = payload.get("_deferred") is True or status == "deferred" if is_deferred: missing = [key for key in ("task_id", "trace_id") if not (payload.get(key) or data.get(key))] if missing: return { "code": 1, "success": False, "error": "DEFERRED_CONTINUATION_MISSING", "message": "deferred 响应缺少续传所需 task_id/trace_id,禁止重新提交原批次", "missing": missing, "status": "deferred", "task_id": payload.get("task_id") or data.get("task_id"), } if mode != "summary": return payload compact = { key: payload[key] for key in ( "code", "success", "status", "task_id", "trace_id", "job_id", "stream_url", "execution_profile", "queue", "_deferred", "message", "validation_receipt_file", ) if key in payload } results = data.get("results") if isinstance(data.get("results"), list) else [] compact["data"] = { "status": data.get("status"), "summary": data.get("summary") or {}, "results": [ {key: item.get(key) for key in ("expression_id", "data_id", "indexinfo_id", "status", "errorCode", "message") if item.get(key) is not None} for item in results if isinstance(item, dict) ], } if payload.get("errors"): compact["errors"] = payload["errors"] return compact
-
-
tools
-
confirm_data_multi.md 4 KB
# confirm_data_multi — 批量数据确认 > 确认数据项是否存在、维度是否正确、是否为掩码/季度财务数据。在写公式前必须先确认数据名。 ## 端点 `POST /skill/confirmDataMulti` ## 参数 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `data_desc` | string | ✅ | 数据描述,多个用逗号或空格分隔。如 `"收盘价, 市值, 净利润"` | | `dimension` | string | ❌ | 维度筛选:`one-row`(一维时序)/ `two`(二维矩阵)。不传则两种都返回 | | `is_bool` | boolean | ❌ | 是否布尔掩码数据(非ST等)。不传则不过滤 | | `content` | string | ❌ | 需求背景描述,帮助更准确匹配意图 | | `task_id` | string | ❌ | 任务ID(UUID) | ## 返回 ```json { "code": 0, "data": { "results": [ { "query": "收盘价", "matched": true, "index_info": { "_id": "60a1b2c3d4e5f6a7b8c9d0e1", "index_title": "全市场每日收盘价", "dimension": "two", "is_bool": false, "provider": "guanzhao", "description": "A股全市场复权收盘价" } } ] }, "task_id": "uuid-xxx" } ``` ## 维度说明 | 维度 | dimension 值 | 典型数据 | |------|-------------|----------| | 二维(资产×日期) | `two` | 收盘价、市值、成交量、财务指标 | | 一维(仅日期) | `one-row` | 宏观指标、指数净值、回测净值曲线 | | 0-1掩码 | `two` + `is_bool=true` | 非ST | ## 调用示例 ```bash # 确认基础市场数据 python scripts/executor.py confirmDataMulti '{"data_desc": "收盘价, 成交量, 市值"}' # 确认财务数据 python scripts/executor.py confirmDataMulti '{"data_desc": "净利润, 营业收入, 市盈率"}' # 只查一维数据(宏观/指数) python scripts/executor.py confirmDataMulti '{"data_desc": "GDP, 上证指数净值", "dimension": "one-row"}' # 查掩码数据 python scripts/executor.py confirmDataMulti '{"data_desc": "非ST", "is_bool": true}' # 带背景描述(提升匹配准确率) python scripts/executor.py confirmDataMulti '{ "data_desc": "换手率, 流通市值", "content": "构建小市值低换手因子选股" }' ``` ## ⚠️ 常见参数错误(生产日志高频错误) | 错误写法 | 正确写法 | 说明 | |---------|---------|------| | `{"queries": ["换手率", "市盈率"]}` | `{"data_desc": "换手率, 市盈率"}` | 参数名是 `data_desc`,不是 `queries`;类型是**逗号分隔字符串**,不是数组 | | `{"query": "收盘价"}` | `{"data_desc": "收盘价"}` | 参数名是 `data_desc`,不是 `query` | | `{"data_desc": "...", "dimension": "one-row"}` 且不确定维度 | `{"data_desc": "..."}` 不传 dimension | 不确定维度时不要传 dimension,让服务端返回所有匹配 | ## 重要规则 1. **优先使用通用数据名**:查"贵州茅台市盈率"→ 先查"A股市盈率",再用 `"市盈率"*取出(贵州茅台)` 取单股 2. **用返回的 `index_title` 写公式**(不要用原始查询词):返回 `"全市场每日收盘价"` → 公式写 `"全市场每日收盘价"` 3. **`intention` 与 `index_title` 可能不同**:查询"全市场每日涨跌幅"时,平台返回 `index_title: "全市场每日回报率"`,公式中必须写 `"全市场每日回报率"`,写"全市场每日涨跌幅"会报「数据名不存在」 4. **用返回的 `_id` 给 `readData`**:`readData` 的 `ids` 参数用 `_id` 字段 5. 季度财务数据(净利润等)是二维但按季度更新,计算时注意对齐日期 6. **根据 `dimension` 决定后续操作**: - `dimension: "one-row"` → 已是一维时间序列(单资产或宏观数据)→ **直接 `readData(ids=[_id])`,禁止再套 `取出()` 或 `runMultiFormulaBatchStream`** - `dimension: "two"` → 全市场二维截面 → 走规则 1(`runMultiFormulaBatchStream` + `取出()`),但公式左侧变量名与 `index_title` **不得相同**(否则循环依赖),命名规范:`{股票名}{指标名}` 或 `{指标名}_val` -
dimension_indicators.md 14.1 KB
# 维度指标库 — 列指标 / 取公式 > 工具名:`listDimensionIndicators`、`getIndicatorFormulas` > 用途:查平台**已建好的维度指标库**——有哪些维度、每个维度下有哪些指标、某个指标的口径公式到底怎么写的。 > > ⚠️ **这两个不是平台原生工具**,必须通过 `python scripts/call.py <工具名> '<JSON>'` 调用。 > 这属于硬规则 #2 的「平台明确不存在等价原生工具」情形,是许可路径,不算包装原生工具; > 同理不要去试 `listDimensionIndicators` 之类的原生工具名,会直接 unknown tool。 --- ## 一、先搞清楚「维度指标」是什么 平台维护着一套**分好类、已经算好的指标库**,三层结构: ``` 维度(dimension) ──包含──▶ 指标(indicator) ──定义于──▶ 公式组(formulas) 17 个 163 个 每个指标一组公式 趋势结构 20日高点接近突破 通用_前高20=最大(...) 动量与反转 均线多头排列 通用_20日高点接近突破比例=... 估值性价比 A股动量与反转 通用_20日高点接近突破得分=... ... ... ``` **维度**只是分组容器(趋势结构、动量与反转、资金流向、盈利能力、异动监控…),本身不带权重、不是一个可计算的东西。 **指标**分两类,这个区分很重要: | `indicator_type` | 是什么 | 典型用途 | |---|---|---| | `细分` | 单一口径的基础指标,如「20日高点接近突破」「均线多头排列」 | 取公式改口径、做变体;若已物化且当前可选,也可直接用于 `selectByComposition` | | `综合` | 该维度的**维度分**,由维度内多个细分指标按内在权重聚合而成,如「A股动量与反转」 | 可直接用于 `selectByComposition`,也可作为高层维度分做解释或组合 | 还有一个正交的属性 `output_type`:`score`(连续分,可加权排序)/ `screen`(0-1 布尔,可取集合)。`selectByComposition` 的请求位置由 `output_type` 决定,而不是由 `indicator_type` 决定。 **指标有两个名字,都能用来查**(这是最容易卡住的地方): | 字段 | 例子 | 谁在用 | |---|---|---| | `name` | `20日高点接近突破` | 指标库里的短名,`listDimensionIndicators` 返回的就是它 | | `index_title` | `通用_20日高点接近突破得分` | 公式里的变量名,也是这组公式最后一行的左侧。**只在 `getIndicatorFormulas` 的响应里**,列表默认不返回 | | `indicator_id` | `ind_20d_high_breakout_proximity` | 稳定语义键,`selectByComposition` 用它 | 三种写法 `getIndicatorFormulas` 都认,返回完全一致。用户如果直接报了 `通用_XXX得分` 这种全名,也能直接查。 ### 和 `presets/dimensions.yaml` 的关系 `presets/dimensions.yaml` 是本地候选快照,适合零网络开销地映射常见指标 ID;它不是服务端当前可选状态的证明,也不是 `selectByComposition` 的唯一指标来源。 `listDimensionIndicators` 是**在线全量目录,细分 + 综合都有**。两者分工如下: - 本地快照命中明确的常见指标 → 可直接按 `composition-select.md` 的角色规则调用选择器; - 用户指定细分指标、本地未命中,或需要确认物化/日期状态 → 用 `listDimensionIndicators` 窄查询,核对 `output_type`、`selection_ready`、`selection_mode`、`as_of` 与市场范围后再调用选择器; - 想知道指标口径、需要修改公式或构造自定义指标 → 用本组工具取得公式后走公式链路。 --- ## 二、什么时候用 ✅ 适用: - 「平台有哪些维度 / 指标」「趋势结构这个维度下面有什么指标」 - 「XX 指标是怎么算的」「XX 的公式是什么」「XX 的口径」 - 想在现成指标基础上改参数(如把 20 日改成 30 日)——先取公式再改,比从零写准得多 - 需要发现可直接用于 `selectByComposition` 的细分指标,或本地 `dimensions.yaml` 没有合适候选 ❌ 不适用: - 直接要 TopN 选股结果 → `composition-select.md` + `selectByComposition` - 要指标的历史序列 / 数值 → 本工具只给**定义**不给数据;要数值走 `runMultiFormulaBatchStream` - 要查平台有哪些**数据集**(`全市场每日收盘价` 之类)→ 那是 `presets/data_catalog.yaml` 和 `index_info_catalog/`,不是这里 --- ## 三、`listDimensionIndicators` — 按维度列指标 ### 调用 ```bash # 先看有哪些维度(约 1.6KB,最省) python scripts/call.py listDimensionIndicators '{"with_indicators": false}' # 看某个维度下的指标 python scripts/call.py listDimensionIndicators '{"dimension": "趋势结构"}' # 按市场 + 类型筛 python scripts/call.py listDimensionIndicators '{"asset_scope": "期货", "indicator_type": "综合"}' # 关键词找指标 python scripts/call.py listDimensionIndicators '{"keyword": "突破"}' ``` ### 参数(全部可选,不传 = 全量约 29KB) | 参数 | 类型 | 说明 | |---|---|---| | `dimension` | string \| string[] | 维度名或维度 `_id`,**精确匹配**(不是模糊搜索) | | `asset_scope` | string | `A股` / `港股` / `美股` / `期货` | | `indicator_type` | string | `细分` \| `综合` | | `output_type` | string | `score` \| `screen` | | `keyword` | string | 对指标名/说明/口径做包含匹配,不区分大小写 | | `with_indicators` | boolean | 默认 `true`;`false` 只返回维度目录 + 计数(约 1.6KB) | | `compact` | boolean | 默认 `false`;`true` 仅保留精简发现字段,适合只需名称和 ID 的目录浏览 | | `verbose` | boolean | 默认 `false`;`true` 补回 `indicator_type` / `index_title` / `calculation` / `window` / `formula_count` 及完整 `selection` 信息(约 58KB) | > 不传任何参数会一次拉回 29KB。**先用 `with_indicators:false` 看目录,再按维度取**,不要无脑全量拉。 > 想知道某指标口径**不要用 `verbose`**,直接 `getIndicatorFormulas`——那里有 `calculation` 和完整公式。 ### 返回要点 ```json { "code": 0, "data": { "dimensions": [{ "name": "趋势结构", "description": "价格是否已经进入可交易的强趋势结构,而不是震荡或假突破?", "indicator_count": 19, "indicators": [{ "indicator_id": "ind_20d_high_breakout_proximity", "name": "20日高点接近突破", "output_type": "score", "selection_ready": true, "selection_mode": "score", "as_of": 20260730, "description": "高分表示接近或突破短期高点", "asset_scope": ["A股","港股","美股","期货"], "last_date": 20260730 }] }], "total_dimensions": 17, "total_indicators": 163, "filters_applied": {} } } ``` 维度层返回名字 / 说明 / 指标数;默认指标条目可用于候选发现。要取得完整口径和公式,使用 `getIndicatorFormulas`。 > ⚠️ **选择器准入不由 `indicator_type:"综合"` 决定。** > 对选股,先按用户需要的请求角色使用 `output_type:"score"` 或 `output_type:"screen"` 缩小查询;只有用户明确要求细分或综合类别时才额外传 `indicator_type`。在线目录中的 `selection_ready`、`selection_mode`、`as_of` 用于预检,`selectByComposition` 才是启用、删除、物化和快照日期有效性的最终校验。 > ```bash > # 查询可作为 A 股 screen 条件的细分候选 > python scripts/call.py listDimensionIndicators '{"asset_scope": "A股", "indicator_type": "细分", "output_type": "screen"}' > ``` > ```bash > # 查询可作为 A 股排序/组合得分的 score 候选 > python scripts/call.py listDimensionIndicators '{"asset_scope": "A股", "output_type": "score"}' > ``` **异常字段与选择器预检字段**: | 字段 | 含义 | 怎么办 | |---|---|---| | `selection_ready` | 当前目录观察到的选择向量是否可用 | 仅 `true` 的候选可进入选择器预检;最终仍以选择器响应为准 | | `selection_mode` | 服务端选择向量角色,`score` 或 `screen` | 与 `output_type` 对齐后,放入正确请求位置 | | `as_of` | 当前选择向量快照日期 | 必须存在;可与其他参与指标不同,最终按选择器返回的 `date_alignment` 披露 | | `status`(值非 `"success"`) | 指标状态异常 | 不作为选择器候选;公式定义仍可能有效 | | `has_formula: false` | 该指标取不到公式(全库只有 2 个) | 别对它调 `getIndicatorFormulas`,会返回 `FORMULA_NOT_AVAILABLE` | 取公式直接用列表里的 `name` 即可(163 条内 `name` 唯一),不需要先拿到 `index_title`。 过滤后为空不是错误,看 `filters_applied` 判断是被哪个条件筛没的。 ### 错误码 | code | 含义 | |---|---| | `DIMENSION_NOT_FOUND` | `dimension` 一个都没匹配上,响应里带 `available_dimensions`,照着改重试一次 | | `INVALID_INDICATOR_TYPE` / `INVALID_OUTPUT_TYPE` / `INVALID_DIMENSION_FILTER` | 参数值写错,按上表改 | --- ## 四、`getIndicatorFormulas` — 按指标名取公式 ### 调用 ```bash # 三种写法等价,返回完全一致 python scripts/call.py getIndicatorFormulas '{"indicators": ["20日高点接近突破"]}' python scripts/call.py getIndicatorFormulas '{"indicators": ["ind_20d_high_breakout_proximity"]}' python scripts/call.py getIndicatorFormulas '{"indicators": ["通用_20日高点接近突破得分"]}' # 一次取多个(≤10) python scripts/call.py getIndicatorFormulas '{"indicators": ["20日高点接近突破", "均线多头排列"]}' ``` ### 参数 | 参数 | 类型 | 必填 | 说明 | |---|---|---|---| | `indicators` | string[] | ✅ | 1~10 个;`name` / `ind_id` / `index_title` 三种写法均可混用 | | `merge` | boolean | | 默认 `false`;多个指标时额外返回一份去重合并的公式组 | ### 返回 ```json { "code": 0, "data": { "indicators": [{ "query": "20日高点接近突破", "matched_by": "name", "indicator_id": "ind_20d_high_breakout_proximity", "name": "20日高点接近突破", "index_title": "通用_20日高点接近突破得分", "dimension_name": "趋势结构", "indicator_type": "细分", "output_type": "score", "target": "通用_20日高点接近突破得分", "formula_count": 3, "formulas": [ "通用_前高20=最大(前几天(\"全市场每日最高价\",1),20)", "通用_20日高点接近突破比例=\"全市场每日收盘价\"/\"通用_前高20\"", "通用_20日高点接近突破得分=按市场排序分(\"通用_20日高点接近突破比例\")" ], "external_datasets": ["全市场每日最高价", "全市场每日收盘价"] }] } } ``` **`formulas` 可以原样传给 `runMultiFormulaBatchStream`**: - 顺序已经排好——每个中间变量都在被引用之前定义,最后一行就是最终产出(`target`); - 表达式一个字符都没改,就是平台公式引擎的原始输入; - `external_datasets` 是公式引用的底层数据集(如 `全市场每日收盘价`),它们不是公式行,平台已有这些数据,**不要为它们编造定义**。 ### 公式超 20 条时 单批公式上限 20 条,库里有 19 个指标超过(最长 83 条)。这些指标会额外返回 `batch_hint`: ```json "batch_hint": { "exceeds_batch_limit": true, "layers": [ { "batch_no": 1, "formulas": ["..."], "force_reusable_array": ["通用_1日涨幅"] }, { "batch_no": 2, "formulas": ["..."], "force_reusable_array": [] } ] } ``` 按 `batch_no` 顺序分批调 `runMultiFormulaBatchStream`,**沿用同一个 `task_id`**,每批把该批的 `force_reusable_array` 原样传进去。层内互不依赖,不要自己重排。 ### 错误码 请求级(整个请求失败):`INDICATORS_REQUIRED`(没传)、`INDICATORS_LIMIT_EXCEEDED`(超 10 个)、`INVALID_INDICATORS`(类型不对)。 项级(只影响那一项,其余照常返回,落在 `data.indicators[i].error`): | code | 处理 | |---|---| | `INDICATOR_NOT_FOUND` | 名字对不上。先用 `listDimensionIndicators` 的 `keyword` 找准确名字,**不要换着花样重试同一个词** | | `INDICATOR_AMBIGUOUS` | 命中多条,响应里给了 `candidates`(含三种名字)。从中选一个准确名重试一次;**不许自己猜一个** | | `FORMULA_NOT_AVAILABLE` | 该指标没有关联公式(库里有 2 个这样的),换指标或退回 `quant-standard.md` 自己写公式 | | `FORMULA_HIDDEN` | 公式被标记为不公开,不要试图绕过 | 模糊匹配命中、`status != success` 等情况会进 `warnings`,不影响返回。 --- ## 五、典型串联 **「趋势结构维度里有什么指标?20日高点接近突破怎么算的?」** ```bash python scripts/call.py listDimensionIndicators '{"dimension": "趋势结构"}' python scripts/call.py getIndicatorFormulas '{"indicators": ["20日高点接近突破"]}' ``` 直接把 `calculation`(人话口径)+ `formulas`(公式)讲给用户。 **「按 20日高点接近突破的口径,但改成 30 日,选出 A 股 Top20」** ```bash # 1. 取现成公式作骨架 python scripts/call.py getIndicatorFormulas '{"indicators": ["20日高点接近突破"]}' # 2. 把 20 改成 30、变量名改掉,走 quant-standard.md 的公式路径执行 # 3. runMultiFormulaBatchStream → readData ``` 比从零写公式准得多——函数名、数据集名、嵌套写法都是平台验证过的。 **「A股动量与反转分数最高的10只」** → 不用本工具,直接 `composition-select.md` 读 `presets/dimensions.yaml` 走 `selectByComposition`。 --- ## 六、输出要求 把指标口径讲给用户时: - 优先用 `calculation`(人话)解释,公式作为佐证,不要甩一堆公式了事; - 带上 `dimension_name` 和 `indicator_type`,让用户知道这是哪个维度的、是细分还是维度分; - 涉及数值时带 `last_date`;`status != "success"` 要说明数据可能不是最新; - 声明:指标口径只代表该指标定义,不构成投资建议。 -
download_data.md 2.7 KB
# download_data — 下载数据 > 通过数据ID下载一维时序数据,支持 CSV 文件格式和 JSON 格式返回。CSV 结果自动保存到 `output/` 目录。 ## 端点 `GET /skill/data/:id?format=<csv|json>&begin_date=YYYYMMDD&end_date=YYYYMMDD` ## 参数 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `id` | string | ✅ | 数据ID(`indexInfo` 的 `_id`,来自 `runMultiFormulaBatchStream` / `confirmDataMulti` / `uploadData`) | | `format` | string | ❌ | `csv`(默认)或 `json`(结构化数据) | | `begin_date` | integer | ❌ | 开始日期 YYYYMMDD(含),不传则从数据最早日期起 | | `end_date` | integer | ❌ | 结束日期 YYYYMMDD(含),不传则到数据最新日期止 | | `task_id` | string | ❌ | 任务ID(UUID) | ## 返回(format=json) ```json { "code": 0, "data": { "data_name": "我的动量因子.csv", "provider": "mydata", "dimension": "one-row", "total_rows": 250, "begin_date": 20250303, "end_date": 20260228, "labels": [20150104, 20150105, 20150106], "values": [0.0123, -0.0045, 0.0087] } } ``` ## 返回(format=csv,call.py 自动保存) `call.py` 调用后自动保存到 `output/<data_name>.csv`,终端打印摘要: ```json { "code": 0, "data": { "saved_to": "output/贵州茅台收盘价.csv", "total_rows": 250, "data_name": "贵州茅台收盘价", "begin_date": 20250303, "end_date": 20260228 } } ``` ## 调用示例 ```bash # 下载最近一年,CSV 自动保存到 output/ python scripts/call.py downloadData '{"id": "60a1b2c3d4e5f6a7b8c9d0e1", "format": "csv", "begin_date": 20250303}' # JSON 格式(获取结构化数据) python scripts/call.py downloadData '{"id": "60a1b2c3d4e5f6a7b8c9d0e1", "format": "json", "begin_date": 20250303}' ``` ## 权限说明 | provider | 访问条件 | |----------|----------| | `mydata` | 只能下载自己上传的数据 | | `guanzhao` | 所有 API Key 用户均可访问 | | `dunhe` | 需要后台开通 `access_dunhe` 权限 | ## 注意事项 - 仅支持**一维时序数据**(dimension=one-row)。公式计算的二维矩阵结果(如选股信号)请用 `readData` 读取 - `runMultiFormulaBatchStream` 的计算结果 provider 通常为 `dunhe`,普通 Skill 用户无 `access_dunhe` 权限,**会返回 403**;需读取公式计算结果请改用 `readData(mode="range_data", start_date=..., end_date=...)` - `begin_date` / `end_date` 在服务端做裁剪,CSV 只含该区间的行 - `call.py` 下载后自动保存到 `output/` 并打印摘要,无需手动处理 CSV 文本 - 返回的 `labels` 为 YYYYMMDD 整数数组,`values` 为对应数值数组 -
fast_query.md 16.1 KB
# fast_query — 快速查询(单次合并接口) 一次调用完成资产解析 + 字段解析 + 公式执行 + 取值。 适用:≤1000 资产,标准字段,行情/估值/财务标量、A 股所属行业基础信息、固定区间序列或窗口序列;期货仅尝试行情字段,是否可得以工具返回为准。 **不适用**:选股、回测、行业聚合、事件研究、K线、开放式个股指标画像或全维度指标概览(此类走 `stockProfile`);用户要求画像指标的“上一值 / 前值 / 前后两期对比”时也走 `stockProfile`,由其返回 `previous_value` / `previous_date`,不要用 `fast_query` 临时拼接;期货估值/财务/K线不在本接口承诺范围内。 ## 参数 | 参数 | 必填 | 说明 | |------|------|------| | `assets` | ✅ | ≤1000 个资产,中文名/代码均可 | | `query_type` | ✅ | `"snapshot"`最新行情 / `"window"`近N日或固定区间序列 / `"report"`最近报告期财务(A/US/HK 部分字段,以返回为准) | | `fields` | ✅ | 字段意图数组,见下方白名单 | | `window_days` | ❌ | 1~2500,`window` 模式可用;与 `start_date`/`end_date` 二选一(同时传时优先使用日期范围) | | `start_date` | ❌ | 固定日期范围起始日,格式 `YYYYMMDD` / `YYYY-MM-DD`;三种 `query_type` 均可用 | | `end_date` | ❌ | 固定日期范围结束日,同格式;不传时默认当天 | | `result_mode` | ❌ | `"value"` / `"series"`,默认 `"value"`;`snapshot`/`report` 固定日期范围需要完整序列时传 `"series"`,`window` 固定返回序列无需传 | `options.partial_ok` 默认 true(部分失败仍返回其余结果)。 日期范围规则: - 三种 `query_type` 均可传 `start_date` + `end_date`;`window` 模式中与 `window_days` 二选一,同时传时优先使用日期范围。 - `snapshot`/`report` 仅传 `end_date` 时自动补齐 `start_date = end_date`;`result_mode=series` 且未传 `start_date` 时报 `MISSING_START_DATE`。 - 显式 `start_date`/`end_date` 没有独立的 2,500 交易日跨度拦截;仍校验日期合法性、顺序及最早数据日期。 - `start_date > end_date` 会返回 `INVALID_DATE_RANGE`。 - 日期早于系统最早数据日期 `20050104` 会返回 `DATE_BEFORE_SYSTEM_LIMIT`。 - `result_mode="value"` 默认返回指定区间内的最后有效值;`result_mode="series"` 返回区间完整序列。 - **日频行情/估值的显式日期对齐**:当 `result_mode="value"` 且同时传入 `start_date`、`end_date` 时,服务端会从 `start_date` 向前回看 10 个自然日。先严格选择指定区间内的数据;若某个日频字段(如 PE/PB)因刷新晚于行情而尚未覆盖该区间,才返回该字段最近有效值,并用字段自身日期标识。该行为是正常的刷新时点差异,不是 `DATA_UNAVAILABLE`。 - 不支持月采样或其他采样频率参数;需要月度序列应走完整公式链路。 ## 日内刷新行为 - **`snapshot` 模式(不传 `start_date`)**:行情字段(收盘价/涨跌幅/成交额等)自动启用日内刷新(等效后端 `use_minute_data: true`)。 - 盘中:返回最后一分钟更新值(实时行情) - 收盘后:返回当日收盘价(与日线一致) - 历史数据不受影响 - **`snapshot` + `start_date` 模式** / **`window` 模式**:不启用分钟刷新,只取日线数据。 - 估值字段(PE/PB/市值等)及财务字段始终为日线口径,不受分钟刷新影响。 ## fields 白名单(直接命中,零额外开销) **行情**(snapshot/window):`收盘价` `开盘价` `最高价` `最低价` `收盘价(不复权)` `涨跌幅` `成交额` `成交量` 英文:`close` `open` `high` `low` `pct_change` `回报率` `amount` `volume` **估值**(snapshot/window): - A/US/HK 均支持(日频估值):`PE` `PE_TTM` `市盈率TTM` `PB` `市净率` `PS_TTM` `市销率` `股息率` `PCF` `市现率` `PCF_现金净流量`(港美股按字段自动映射;PB 不带 TTM;精确数据名与 ID 沿用 presets 数据层) - 仅 A 股:`总市值`(英文:`market_cap`,亿元)`流通市值` `换手率`(英文:`turnover`)——港股/美股查询这些字段返回 `FIELD_MARKET_MISMATCH` - 历史兼容入口:`PE_单季` `PB_单季` `PS_单季` `股息率_单季` 在港美股实际映射日频估值,不代表季频;新调用使用 `PE_TTM` / `PB` / `PS_TTM` / `股息率`。 - **兼容字段元信息冲突**:若返回 `LEGACY_FIELD_UPGRADED`,仅对其 `upgraded` 显式列出的字段按映射后的标准字段解释;例如 `PE_单季 → PE_TTM` 是日频 TTM 估值,即使旧字段仍标为 `date_type="report_period"`,也不得称为报告期序列。日期仍取字段自身 `d` / `dates`,否则取与值数组对应的共享日期轴;单值无自身日期时取映射后标准字段的日期类型对应的公共日期,缺失则说明日期未提供,不猜测。已返回可用结果时不因元信息冲突重查。未明确升级的字段继续遵循原日期类型;用户真正要求单季口径时,须说明当前返回的日频标准估值不满足单季要求。 - PE(静态):A 股用静态 PE,港美股自动映射到 TTM 版(HK/US 无静态 PE) **所属行业基础信息**(仅 `snapshot` + `value`): - 仅 A 股股票:`所属行业`。资产必须满足 `type=stock` 且 `market_id=1/2`;港股、美股、指数及其他资产返回暂不支持。 - 返回结构化分类:`swl1`(申万一级)、`swl2`(申万二级)、`jqc`(概念列表)。无单位、无交易日/报告期;直接按 `swl1.name`、`swl2.name`、`jqc[].name` 展示,不能将整个对象作为文本或数值格式化。 **财务**(report,所有财务字段统一返回**单季**数据,A/US/HK 一致): - A/US/HK 均支持:`营业收入` `净利润` `归母净利润` `营业成本` `总资产` `净资产` `ROE`(`roe`)`净利率` `毛利率`(`gross_margin`);现金流:`经营现金流`(`operating_cashflow`)`投资活动现金流`(`investing_cashflow`)`筹资活动现金流`(`financing_cashflow`);英文:`revenue` `net_profit` `cogs` `total_assets` `equity` **派生**(服务端自动计算,A/US/HK 均支持):`资产负债率`(英文:`debt_ratio`,公式:`(总资产 - 净资产) / 总资产 × 100`) **资金流向 / 南北向持股**(snapshot/window,股票×日期 日频;用 `snapshot` 或 `window`,**不是 `report`**): - 仅 A 股:`主力资金净额`(`主力净额`/`主力资金净流入`,万元)`主力资金净占比`(%)`超大单净额` `超大单净占比` `大单净额` `大单净占比` `中单净额` `中单净占比` `小单净额` `小单净占比`(主力 = 超大单 + 大单;净占比分母为当日全股成交额,各档之和≠100%) - 仅 A 股:`北向持股比例`(`陆股通持股比例`,%)`北向持股市值`(亿元,RMB)——2024-08 后季频/稀疏,末值常落在季末 - 仅港股:`南向持股比例`(`港股南向持股比例`,%)`南向持股市值`(亿元,HKD)——日频 - A 股资金流向/北向字段查港美股返回 `FIELD_MARKET_MISMATCH`;港股南向字段查 A 股返回 `DATA_UNAVAILABLE` - 以上为白名单专用短别名,仅白名单内命中(全称如 `A股中单资金当日流量净额`) - **不支持**:北向/南向「资金成交额、成交量、净买入」(市场级一维序列,无个股维度,需走 confirmDataMulti + readData);北向/南向「十大活跃股成交额」暂走动态解析(+2s,需用全称) **商品期货:行情 / 现货价格 / 库存**(snapshot/window,按品种单位,仅 A 股期货品种,约 60 个商品): - 期货行情 `收盘价`/`开盘价`/`最高价`/`最低价`:单位**按品种**(白银元/千克、螺纹元/吨、黄金元/克、原油元/桶、鸡蛋元/500千克…);成交额仍为元 - `现货价格`(`商品现货价格`/`现货价`):基差/现货,多为元/吨,约 50 品种有数据 - `商品库存`(`库存`)/ `库存按发布日`:单位**按品种推测**(万吨/吨/千克/克/万重量箱/万立方米/头),带 `STOCK_UNIT_INFERRED` 警告 - 直接用期货 ticker(如 `RB.SHF`、`AG.SHF`)查询即可;查港美股返回 `FIELD_MARKET_MISMATCH` > **单位按品种下沉**:同一字段多品种单位不一致时,`fields_meta[字段].unit_per_asset=true`,单位下沉到每个资产值:value 模式 `{v, unit}`,series 模式 `{unit, values}`(主轴)。单位一致时仍在 `fields_meta`、值为标量/数组。读值时优先看资产内联 `unit`,没有再读 `fields_meta.unit`。 除 `所属行业` 这一专用 A 股资产元数据字段外,不在白名单 → 服务端自动调 confirmDataMulti 解析(+2s);无法解析则 FIELD_UNRESOLVABLE。 ## 限流 | 维度 | 上限 | 错误码 | |------|------|--------| | 最大资产数 | 1,000 | `ASSETS_EXCEED_LIMIT` | | `window_days` 最大交易日数 | 2,500 | `WINDOW_DAYS_EXCEED_LIMIT` | | 单次最大数据点 | 200,000 | `DATA_POINTS_EXCEED_LIMIT` | | 每日数据点(用户级) | 1,000,000 | `DAILY_DATA_POINTS_EXCEEDED` | | 每日 CSV 下载 | 50 次 | `DAILY_CSV_DOWNLOADS_EXCEEDED` | 数据点 = 资产数 × (日频字段数 × 日频日期数 + 季频字段数 × 季频日期数)。显式日期范围的日频日期数按 UTC 自然日差 × 0.7 估算;超过 500 数据点自动切换 CSV 格式返回,但港股财报因服务端多来源合并仍返回 JSON。 ## 返回结构(compact 格式,默认) 顶层公共信息: ``` success / query_type fields_meta: { 字段名: {unit, date_type} } ← 每个字段的单位和日期类型,只声明一次 meta: query_time_ms / latest_trade_date / partial_ok ``` > **单位按品种下沉**:当同一字段多资产单位不一致(商品按品种)时,`fields_meta[字段]` 改为 `{unit_per_asset:true, date_type}`(无统一 unit),单位下沉到每资产值:value 模式 `字段:{v, unit}`,series 模式主轴字段 `字段:{unit, values}`、非主轴 `{dates, values, unit}`。读值优先看资产内联 `unit`,没有再读 `fields_meta.unit`。 `result_mode="value"`(默认)— 表格化字典: ``` dates: { trade_date: "YYYY-MM-DD", report_period: "YYYY-MM-DD" } ← 公共日期按 date_type 提升 results: { "资产名": { ticker, 字段1: 值, 字段2: 值, ... } ← 日期已提升时直接是数字 } ``` 若字段日期与公共日期不同(例如日频行情/估值刷新错位触发 `DATE_RANGE_FALLBACK`),该字段值为 `{v: 数值, d: "日期", fallback: true}`。此时使用 `v`,并按该字段的 `d` 展示真实日期;不要因其晚于公共日期而报缺失、重试或升级到完整链路。 `result_mode="series"` 或 `query_type="window"` — 列式存储: ``` results: { "资产名": { ticker, dates: ["YYYY-MM-DD", ...], ← 共享日期轴(多数字段共用) 字段1: [值, 值, ...], ← 与 dates 等长的值数组 字段2: [值, 值, ...], 字段3: { dates: [...], values: [...] } ← 日期轴不同的字段各自带 dates+values } } ``` 非空时才出现的字段:`asset_errors[]` / `field_errors[]` / `warnings[]`(空时省略)。 ## 返回结构 — CSV 模式(数据点 > 500 时自动触发) 当数据点超过 500,服务端自动切换 CSV 格式;但港股财报因服务端多来源合并仍返回 JSON。返回结构变为: ``` success: true query_type / mode: "csv" csv_fields: [ { intent: "收盘价", index_title: "...", csv_url: "https://...", csv_expires_at: "...", tickers: [...], unit: "元" }, // 商品多品种:unit:null + unit_per_asset:true + units:{ "RB.SHF":"元/吨", "AG.SHF":"元/千克" } ... ] summary: { total_data_points: 156468, assets: ["贵州茅台", ...], fields: ["收盘价", ...], csv_count: 5 } asset_errors / field_errors / warnings ``` 处理规则: - 检查 `mode` 字段:若为 `"csv"`,按 CSV 模式处理 - 向用户汇报 `summary`(资产数、字段数、总数据点数) - 展示 `csv_fields[].csv_url` 供用户下载(链接有过期时间) - **禁止**在对话中逐行展开 CSV 内容 - 若用户追问具体数值,建议下载 CSV 查看 ## 错误处理 | Layer | 触发 | 处理 | |---|---|---| | 1 | 参数不合法(整体拒绝) | 退出 fast path,走完整链路 | | 1 ASSETS_EXCEED_LIMIT | 资产数超过 1000 | 告知用户分批查询 | | 1 WINDOW_DAYS_EXCEED_LIMIT | `window_days` 超过 2500 | 告知用户缩小范围 | | 1 DATA_POINTS_EXCEED_LIMIT | 预估数据点超过 200,000 | 告知用户减少资产/字段/日期 | | 1 DAILY_DATA_POINTS_EXCEEDED | 今日累计数据点超过 1,000,000 | 告知用户明天再试 | | 1 DAILY_CSV_DOWNLOADS_EXCEEDED | 今日 CSV 下载次数超过 50 | 告知用户明天再试或缩小查询 | | 1 MISSING_START_DATE | `result_mode=series`(非 window)且未传 `start_date` | 补传 `start_date` 或改用 `window` 模式 | | 1 INVALID_DATE_RANGE | `start_date > end_date` | 告知用户日期范围无效 | | 1 DATE_BEFORE_SYSTEM_LIMIT | 日期早于 `20050104` | 告知用户调整日期范围 | | 1 INVALID_RESULT_MODE | `result_mode` 非 `value/series` | 修正为合法值后再调用 | | 2 | 资产无法识别 | 告知,其余资产继续 | | 3 FIELD_UNRESOLVABLE | 字段不可解析 | 见下方恢复策略 | | 3 FIELD_MARKET_MISMATCH | 字段不支持该市场(如港/美股请求总市值/流通市值/换手率等仅 A 股字段) | 告知用户该字段仅支持 A 股;其余字段继续 | | Warning DATE_RANGE_FALLBACK | 日频字段在指定日期/范围内暂未更新,已返回最近有效值 | 按字段 `d` 标注日期并正常作答,**不得**把它当作 `DATA_UNAVAILABLE`、重试或升级完整链路 | | 4 DATA_UNAVAILABLE | 字段经过日期对齐回看后仍无数据,或公式/派生字段计算失败(`DERIVED_COMPUTE_FAILED`) | 告知该字段暂无数据;按当前 workflow 的恢复规则处理 | **FIELD_UNRESOLVABLE 恢复**(partial_ok: true):保留已成功字段,仅对失败字段补 `confirmDataMulti` → `runMultiFormulaBatchStream`(公式:`"字段全名"*取出(资产名)`,**禁止 LAST() 语法**),不得重读任何 workflow .md。若 field_error 带 `fallback_hint`,按其操作。 ## ⚠️ 注意 - **须先建立 Turn**:首次问题调 `newSession`;同一 Session 的追问调 `beginTurn`。后续 `fast_query` 自动复用当前 `turn_id/user_query` - **涨跌幅**:返回值已是百分比数(如 `-2.74`),直接加 `%`,不再乘 100 - **总市值/流通市值**:单位已是亿元 - **result_mode**:默认 `value`;需要固定区间完整序列时才传 `result_mode="series"` - **window series**:已按日期升序;`window` 可传 `window_days`,也可传 `start_date/end_date` 固定日期范围(二选一)。`window` 固定返回序列,不需要传 `result_mode`。 ## 连续期货附加返回(4.25.38) 不增加请求字段,原 fields 保留。服务结果可能增加 `future_context`,按连续ticker分组;verbose、compact和CSV均须保留该元信息。 - window/series:`future_context[ticker].roll_events` 是实际返回窗口内事件;JSON按最终序列日期,CSV按导出日期轴(scope=csv_date_axis),不是为了取数扩大的begin_date。 - snapshot/value:`future_context[ticker].contract_info.contracts[]` 按各字段**实际有效值日期**分别匹配具体合约,含历史fallback日期。不要把某个字段较新的合约套给较旧值。status=inferred 是事件推导,unavailable/null是不确定,不是无合约。 - 事件 trade_date 是生效交易日;old_close/new_close 为当日新旧具体合约各自未复权日频收盘价,不是分钟换月价。字段仅辅助解释,不能据此自行改写报价或宣称全历史已复权。 - `future_context[ticker].error` / warnings 仅表示附加信息缺失;即使 partial_ok=false 也不应丢掉已成功行情。主行情错误仍按原规则处理。 - 内部合约检索按需回看6/12/24个月,不从1900年全量查、不暴露 lookup_start_date;Agent无需额外请求。日内当前分钟用 fast_query_minute,历史跨日分钟用 fast_query_minute_range,不把日频窗口误当分钟数据。 -
fast_query_minute.md 4.6 KB
# fast_query_minute — 单资产日内分钟行情 > **真实工具名**:`fast_query_minute` > **服务端**:`POST /skill/fastQueryMinute` > **适用**:用户明确要某一只资产的分钟、分时、1 分钟、每分钟或日内 OHLCVA 序列。 ## 调用前 1. 使用 `Grep` 在 `presets/assets_db/` 的对应资产库确认**唯一资产**;优先传确认后的 ticker/code。 2. 每个新问题先完成 `newSession`;追问先完成 `beginTurn`。task/turn 会由执行器自动注入,**不要**作为工具参数传入。 3. 只在用户需要完整分钟序列时使用。只问最新价、当前价或一个最新标量时,使用 `fast_query(query_type="snapshot")`。 ## 市场覆盖 调用前按 [分钟行情支持范围](../references/minute-data-coverage.md) 确认市场与资产类型:美国/香港指数及期货暂不支持分钟行情,不能因日频可查就调用本工具。本工具仍只查询当前/最近完整交易日;用户指定历史日期时转 `fast_query_minute_range` 并执行对应最早日期提示,不能向本工具添加历史日期参数。 ## 参数 ```json { "asset": "SH600000", "fields": ["close", "high", "low", "volume"] } ``` | 参数 | 必填 | 说明 | |---|---:|---| | `asset` | 是 | 单个资产名称、ticker 或 code。不能是数组。| | `fields` | 是 | 非空字段数组;按请求顺序返回,服务端会去重。| ### 支持字段 | canonical | 中文别名 | |---|---| | `open` | 开盘价 | | `high` | 最高价 | | `low` | 最低价 | | `close` | 收盘价、最新价、现价、当前价 | | `volume` | 成交量 | | `amount` | 成交额、money | ## 严格边界 - 仅支持**单资产**。 - 不传、也不支持 `assets`、`trade_date`、`date`、`start_date`、`end_date`、`window_days` 或任意历史日期/时间区间。 - 不做分钟聚合;返回原始分钟 bar。 - 服务端根据 `tkrsInfo.market_id` 与市场 `trade_period` 自动选择: - `current_session`:当前盘中分钟数据; - `latest_completed`:最近完整交易日数据。 用户要求指定历史日/跨日原始分钟时,改用 `fast_query_minute_range`,不能偷换为本工具或日频。多资产、分钟聚合不属于两个单资产分钟工具的能力;按实际需求另行确认支持的路径。 ## 返回结构 ```json { "code": 0, "data": { "success": true, "query_type": "minute", "interval": "1min", "asset_name": "浦发银行", "ticker": "SH600000", "trade_date": 20260807, "data_scope": "current_session", "timezone": "Asia/Shanghai", "dates": ["2026-08-07 09:31", "2026-08-07 09:32"], "fields": { "close": [10.01, 10.02], "high": [10.02, 10.03], "volume": [123456, 234567] } } } ``` `dates[i]` 与每一个 `fields.<field>[i]` 是同一根分钟 bar;数组严格对齐,遇到空值保留 `null`,不得单独过滤某字段的空值。时间已经按资产市场时区格式化;回答时保留 `trade_date`、`data_scope` 和 `timezone` 的语义。 正常空结果会返回 `dates: []` 及每个请求字段对应的空数组;这不等同于资产解析失败。 ## 错误处理 - `ASSET_NOT_FOUND`:重新核对本地资产库名称/ticker/code。 - `INVALID_MINUTE_FIELD`:只使用上述 OHLCVA 字段及别名。 - `UNSUPPORTED_MINUTE_MARKET`:该 `market_id` 尚未接入分钟行情。 - `UPSTREAM_MINUTE_QUERY_FAILED` / `MINUTE_QUERY_TIMEOUT`:说明分钟数据源暂不可用;如仍需要结果,向用户说明并考虑后续重试。 成功取得分钟序列后直接分析/回答并停止,不再额外调用 fast_query、readData 或公式链路重复取数。 ## 连续期货附加返回(4.25.38) 主/次主连在行情 data 中增加 `contract_info: {"contract":"C2611.DCE","status":"inferred"}`。它对应**顶层 trade_date**(含夜盘归属或最近完整日回退),不是墙上“今天”。status=inferred 表示依据已有事件推导,不能宣称独立实时核验。 单日分钟不返回 roll_events、多日期 contracts、lookup_start_date,也无需 Agent 手工查换月。内部按需回看6→12→24个日历月,找到即停,共用最多2秒附加预算;这不是期货固定三个月换月的保证。无法确认时 contract=null、status=unavailable 并带 reason;附加查询失败另有 warnings。保留已成功 dates/fields,不因附加警告重查或降级行情,不把未知合约说成没有合约。 历史/跨日原始分钟改用 [fast_query_minute_range](fast_query_minute_range.md),不要往本工具追加日期参数。**本工具仍需要 fields**。 -
fast_query_minute_range.md 5.5 KB
# fast_query_minute_range — 单资产历史跨日分钟 CSV 真实工具名 `fast_query_minute_range`,后端 `POST /skill/fastQueryMinuteRange`。原生工具可用时直接调用;没有原生绑定时才按现有 CLI fallback 使用 `scripts/call.py fast_query_minute_range @params.json`。新增声明不会自动更新宿主已经加载的工具列表;服务端也须包含该路由。 ## 什么时候用 用户要某个资产历史日期/跨交易日的原始1分钟数据、历史分时或分钟CSV下载。不要把它降级为日频 fast_query,也不要用单日 fast_query_minute 冒充历史区间。 | 场景 | 工具 | |---|---| | 当前/最近完整交易日分钟 | fast_query_minute,保留 fields | | 单资产历史分钟窗口 | fast_query_minute_range,不提供 fields | | 最新标量、日频窗口 | fast_query snapshot/window,保留 fields | 先按资产库规则确认唯一资产。接口不支持多资产、日内时分筛选、5分钟聚合或自动复权。 ## 分钟历史覆盖(调用前必查) 先阅读 [分钟行情支持范围与越界提示](../references/minute-data-coverage.md)。A股/美股股票与国内期货最早 **2026-05-13**,港股股票 **2026-05-20**,国内指数 **2026-08-13**;美国/香港指数及期货暂不支持。请求早于对应起点时必须先提示:全窗口越界不调用,部分重叠说明缺失区间、保留原请求窗口并如实交付可用部分。不得将返回的首条数据夸大为完整历史覆盖。 ## 参数 | 参数 | 规则 | |---|---| | asset | 唯一资产名称/ticker,非数组 | | start_date + end_date | 成对的 YYYY-MM-DD,双端包含;end_date 必须早于起点加三个日历月(夹紧月末) | | start_offset / end_offset | 与绝对日期互斥;-70至-1整数**自然日**偏移;默认昨天。仅start_offset时终点-1,仅end_offset时为该单日 | 禁止 fields、format、remove_nan、window_days、trade_date、assets。返回固定CSV全列、全行,不按缺失值过滤;不能只改manifest假装已经裁列。原两个工具的 fields 不受此变化影响。 日期不得包含**市场当地今天或未来**(即使今天已经收盘);最终时区/日期边界以服务端为准。休市日可返回空,不自动改查更早交易日。task_id/turn_id/user_query 由既有追踪通道透传,不参与行情或缓存键。 ```json {"asset":"SH600519","start_date":"2026-06-01","end_date":"2026-06-30"} ``` ```json {"asset":"C.DCE","start_offset":-5,"end_offset":-1} ``` ## 返回与CSV消费 ```json { "code":0, "data":{ "status":"ok","query_type":"minute_range","interval":"1min", "data_scope":"historical","mode":"csv","ticker":"C.DCE","timezone":"Asia/Shanghai", "start_date":"2026-06-01","end_date":"2026-06-30", "shape":[100,8],"csv_url":"https://example.invalid/history.csv", "csv_expires_at":"2026-09-16T02:00:00Z" } } ``` 示例仅表达结构。`columns` 可缺省,以实际CSV首行表头为准,不猜固定六列。**CSV是长表 `trade_date,timestamp,open,...`,不是日频的 `ticker,name,<dates...>` 宽表**;不能用 fetch_fastquery_csv.py 的宽表模式解析它。 下载文件即可满足纯导出请求;需读取完整数据时,把工具JSON响应存为本地文件,使用专用受控读取器: ```bash python scripts/fetch_minute_range_csv.py @output/minute-manifest.json --output output/minute-data.json ``` 在 skill 根目录运行;manifest和输出均放在本skill/output内,可为这些文件使用绝对路径。该脚本只消费返回的签名URL,不会重新取数;输出完整数据文件与SHA256收据,不把整份分钟矩阵刷到终端。保留所有列、全空行情行、null及扩展列;不复权、不补0。此产物为 `qb_minute_range_artifact_v1`,不是日频series capsule,不能冒充旧日频胶囊交给活页构建器。 `timestamp` 是UTC秒;显示时按 timezone 转换。`trade_date` 是交易日归属,期货夜盘自然日可能不同,换月必须按 trade_date 匹配。 ## 附加信息与错误 - 连续期货附 `roll_events`,包含窗口内生效交易日、新旧合约和两条合约生效日各自原始**日频**收盘价;不把它解释为换月瞬间价格,不改变分钟价。 - 股票 adjustment 仅窗口内 split 事件,不承诺全历史复权链。 - 换月查询失败/超时/格式异常只附 warnings/roll_events_error;主行情成功则继续使用CSV,不把警告当作工具失败,也不伪造“无换月”。窗口查询不需要手工回看6/12/24个月;该策略仅供服务端确认单日具体合约。 - 明确空结果:empty=true、csv_url=null、shape=[0,0],不是系统错误,也不是0价格。 - 直接调用每个认证用户滚动12小时最多10次,缓存命中也计次;Data Grant试跑/刷新/页面查询不计该专属次数。超限按 Retry-After 等待,不换key或拆请求规避。 - 行情CSV结果缓存12小时;附加事件成功短缓存60秒、失败不缓存。链接失效先通过原工具/Grant重新取得有效manifest,不能延长字符串中的到期时间或循环访问旧URL。 - 404表示后端路由/版本接入未到位,不能声称该资产无数据;CSV_EXPORT_FAILED需核对真实链接寿命;这些与仅附加换月失败不同。 要做实时取数活页:交给 quant-buddy-view 的 `data_grant`,kind=`fast_query_minute_range`。公开页面用Grant签名,不嵌API Key,不把一次性CSV URL当成长期数据源。绝对窗口保持固定,offset授权每天按市场日期滚动;本工具始终不包含今天。 -
formula_package.md 7.3 KB
# formula_package — 公式任务包(注册一组公式 → 凭包凭证取数) > 把一组公式注册成一个「任务包」,得到 `package_id` + `signature`;之后**无需 API Key**, > 凭这两个凭证就能反复取数。底层数据更新后服务端自动重算,取数永远拿最新结果。 > 适合给**前端页面 / 第三方**做只读数据接入(如本地 HTML 直接渲染)。 > > ⚠️ 本工具**不是**平台原生 MCP 工具,通过本地脚本 `scripts/formula_package.py` 调用, > 走的是公式包专用 REST 端点(`/skill/registerFormulaPackage` 等),与 `runMultiFormulaBatchStream` > 的执行/计费池**不同**。何时用它 vs 用 `runMultiFormulaBatchStream` 见文末「选型」。 ## 端点 | 操作 | 方法 + 路径 | 认证 | |------|-------------|------| | 注册 | `POST /skill/registerFormulaPackage` | `Authorization: Bearer <api_key>` | | 取数 | `POST /skill/queryFormulaPackage` | **无需**,凭 `package_id`+`signature`(SSE 流式) | | 列表 | `GET /skill/listFormulaPackages?page=&page_size=` | Bearer | | 撤销 | `POST /skill/revokeFormulaPackage` | Bearer | | 刷新 | `POST /skill/refreshFormulaPackage` | Bearer | > `endpoint` / `api_key` 读 `config.json`(与其它工具同源)。`signature` 仅在**注册响应中明文返回一次**, > 服务端不可再取出;脚本会自动落盘到 `output/formula_packages/<package_id>.json` 以防丢失。 ## 调用方式(本地脚本,参数传递与 call.py 同款,避免 PowerShell GBK 截断) ```bash # 注册(Windows 用 @file 传中文公式) python scripts/formula_package.py register @params.json # 取数(只需 package_id,signature 可由本地凭证自动补全) FP_PARAMS='{"package_id":"pkg_xxx"}' python scripts/formula_package.py query # 管理 python scripts/formula_package.py list '{"page":1,"page_size":20}' python scripts/formula_package.py revoke '{"package_id":"pkg_xxx"}' python scripts/formula_package.py refresh '{"package_id":"pkg_xxx","rotate_signature":true}' ``` ## 注册参数 | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `formulas` | `string[]` | ✅ | 1~100 条公式(可含中间变量),每条形如 `变量名 = 表达式`。语法同 `runMultiFormulaBatchStream`(引用数据/变量用双引号,资产名不加引号)| | `reads` | `object[]` | ✅ | 1~20 个**对外产出**及其读取模式,见下 | | `begin_date` | `number` | ❌ | 公式计算起始日(裸整数 `YYYYMMDD`) | | `ttl_days` | `number` | ❌ | 有效期(天),默认 365 | | `intents` | `string[]` | ❌ | 可选意图描述,透传给执行引擎 | ### `reads[]` 元素 | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `output` | `string` | ✅ | 产出标识 = 某条公式左侧变量名(如 `AG_ret = ...` 的 `AG_ret`)| | `read_mode` | `string` | ✅ | 该产出读取模式,见下表 | | `mode_params` | `object` | ❌ | 模式参数;`last_day_stats` / `last_column_full` 可选 `date` 或 `offset`,`range_data` 必填 `lookback_days` | > - 未列入 `reads` 的公式 = 中间变量,只计算不对外。 > - **每个 `output` 只能指定一个 `read_mode`**;同一产出写两条会导致注册失败。 > - 不同产出可用不同模式与参数。 ### 读取模式与 `result.data` 结构 | read_mode | 适用 | `mode_params` | 取数返回的 `data` 关键字段 | |-----------|------|---------------|---------------------------| | `last_day_stats` | 截面(2维)/ 单序列(1维)皆可 | `date` 或 `offset`(互斥,可选) | 2维:`last_day_stats.{date,top_values[],valid_count,coverage_rate,...}`;**1维序列:`last_value.{date,value}`** | | `last_column_full` | 完整截面列(2维)/ 截止日序列(1维) | `date` 或 `offset`(互斥,可选)、`max_rows`、`allow_zero_values` | 2维:`last_column_full.{date,values[],returned_rows,valid_rows,is_truncated,...}`;1维:截至目标日的有效时间序列 | | `last_valid_per_asset` | 2维截面 | `max_rows`(默认8000) | 每个资产最后一个有效值(跨市场对齐);不支持 `date`/`offset` | | `range_data`(滚动窗口)| 1维序列 / 2维 | `lookback_days`✅(回溯天数,区间=`[今天-N, 今天]`)、`assets`、`max_cells`、`nan_handling`(`keep`/`fill_forward`/`drop_rows`) | `range_data.{dates[],values[],series_name}`(非交易日为 `null`);不支持 `date`/`offset` | > **单列日期参数**:`date` 接受 `YYYYMMDD` 或 `YYYY-MM-DD`,读取不晚于该日的最近有效数据;`offset` 是相对每次查询当天的自然日偏移(`0`=截至今天,`1`=截至昨天),不是第 N 个交易日,也不是相对注册日。两者不能同时传。不传时继续按原模式读取最新数据,旧请求与返回结构不变。 > > `range_data` 是**滚动窗口**:注册时只给 `lookback_days`(如近一年=365、近半年=180),取数时服务端现算成 `[今天-lookback_days, 今天]`。任务包是 live 看板数据源,这样窗口才会随时间滚动到最近 N 天,而不是停在注册那天。旧版 `start_date`+`end_date` 仍兼容(按跨度滚到今天),但**不要再指定绝对 `end_date`**——那对 live 场景是错误用法。 ## 取数(SSE) `POST /skill/queryFormulaPackage`,body 仅 `{package_id, signature}`,永远返回 `text/event-stream`: | event | 含义 | |-------|------| | `result` | 单个产出数据(每产出一条):`{output, read_mode, data_id, data}` | | `progress` | 重算进度(仅数据陈旧时出现) | | `done` | 全部完成:`{code,stale,recomputed,summary}` | | `error` | 出错并关闭流:`{code,message}` | `scripts/formula_package.py query` 已封装 SSE 解析,返回组装好的 `{code, outputs:{<output>:{read_mode,data_id,data}}, progress, done}`。 **浏览器直连**(取数无需 api_key、已支持跨域,用 `fetch` 读流,**勿用 `EventSource`**): 见 `recipes/formula-package.md` 的 HTML 示例与对外接口文档 §3。 ## 错误码(节选) | code | 场景 | |------|------| | `REGISTER_FAILED` | 参数非法 / 公式执行失败 / 产出未生成 | | `PARAMS_REQUIRED` | 取数缺 `package_id` 或 `signature` | | `PACKAGE_NOT_FOUND` / `PACKAGE_EXPIRED` / `PACKAGE_INACTIVE` | 包不存在 / 过期 / 已撤销 | | `SIGNATURE_INVALID` | 签名校验失败 | | `OWNER_QUOTA_EXCEEDED` | 所有者配额耗尽 | ## 计费 注册按公式条数计费;取数计基础读取费,触发重算时按实际重算条数追加。**取数费用计入任务包所有者配额**,取数方不消耗自己配额、也无需 API Key。 ## 选型:何时用公式包 vs `runMultiFormulaBatchStream` | 用公式包(本工具) | 用 `runMultiFormulaBatchStream` | |--------------------|-------------------------------| | 要把一组结果做成**长期、可重复、对外只读**的取数接口(前端页面、看板、第三方接入)| 一次性问答 / 即时计算,会话内拿到 `data_id` 即用 `readData` | | 取数方**不该持有 api_key**(如浏览器本地 HTML 直连)| 调用方在本 skill 内、已有 session 与 api_key | | 需要「数据更新→自动重算→永远最新」的语义 | 手动重跑公式即可 | > 详见对外接口文档:`docs/formulaPackage 相关文档/对外接口文档.md`;端到端示例:`recipes/formula-package.md`。 -
get_card_formulas.md 3.1 KB
# getCardFormulas — 按卡片名称批量获取完整公式组 ## 用途 根据 `presets/cases_index.yaml` 中找到的卡片名称,从平台数据库拉取一张或多张卡片的完整公式组。 支持批量拉取(1-10 张),方便对比多个参考卡片后选最贴合的公式骨架。 ## 调用方式 **批量(推荐)** ```bash python scripts/call.py getCardFormulas '{"card_names": ["高波动率多因子筛选", "优质低估值选股"]}' ``` **单张** ```bash python scripts/call.py getCardFormulas '{"card_names": ["高波动率多因子筛选"]}' ``` ## 参数 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `card_names` | string[] | ✅ | 卡片名称数组,1-10 个,从 `presets/cases_index.yaml` 中找到的名称 | ### 名称匹配规则(服务端) - **大小写不敏感**:`"roe分析"` 可匹配 `"ROE分析"` - **子串匹配**:`"ROE"` 可匹配 `"ROE分析"` 和 `"ROE杜邦拆解"` - **每个名称最多返回 2 张**匹配卡片 - 多名称结果自动去重 > ⚠️ 传入已知存在的卡片名称或其明确子串,不要传泛化关键词(如 `"分析"`、`"指标"` 等)。 ## 返回 ```json { "code": 0, "cards": [ { "card_id": "691467e9acdb52784932c7a9", "card_name": "高波动率多因子筛选", "insight": "全A股多因子评分策略...", "total_steps": 7, "total_formulas": 62, "action_steps": [{ "step_no": 1, "purpose": "计算60日滚动年化波动率", "formulas": [...] }], "all_formulas": ["波动率_60日 = ...", "..."] }, { "card_id": "691467fbacdb52784932c80e", "card_name": "优质低估值选股", "..." : "..." } ], "warnings": ["名称 'xxx' 无匹配结果"] } ``` ## 字段说明 | 字段 | 说明 | |------|------| | `card_name` | 卡片名称 | | `insight` | 卡片整体洞察/策略说明(来自卡片 insight 字段) | | `total_steps` | 步骤数量 | | `total_formulas` | 公式总条数 | | `action_steps` | 按步骤分组的公式列表(step_no, purpose, formulas) | | `all_formulas` | 所有公式的扁平化列表,可直接传给 `runMultiFormulaBatchStream` 的 formulas 参数 | > ⚠️ **严禁将 `action_steps` 数组直接传给 `runMultiFormulaBatchStream` 的 `formulas` 参数!** > `action_steps[i]` 是对象 `{step_no, purpose, formulas}`,不是字符串。 > 传给 `runMultiFormulaBatchStream` 时**只能用 `all_formulas`**(字符串数组),或从 `action_steps[i].formulas` 中提取字符串。 > 错误示例:`formulas: card.action_steps` → 会产生 `[object Object]` 错误。 ## 典型用法 ``` Step 1a:读 presets/cases_index.yaml(~105 行,一次读完),按 tags 找 1-3 张最相关卡片 Step 1b:getCardFormulas(card_names=["卡片名称1","卡片名称2"]) → 对比多张卡片的 all_formulas,选最贴合的作为公式骨架 Step 5: runMultiFormulaBatchStream(formulas=选定卡片的 all_formulas, task_id=...) → 替换资产名后执行 ``` ## 错误码 | code | 含义 | |------|------| | 0 | 成功 | | -1 | 所有名称均无匹配结果 | | 401/402 | API Key 无效或过期 | -
read_data.md 7.6 KB
# read_data — 读取/验证计算结果 > 读取 `runMultiFormulaBatchStream` 生成的数据(indexInfo / matrixInfo),用于签名检查、采样、统计、表格读取,或按日期区间读取完整连续数据。 ## 端点 `POST /skill/readData` ## 数据类型 接口自动识别数据类型: | 类型标识 | 来源 | 说明 | |---|---|---| | `two_dimensional` | indexInfo(dimension = `two`) | 二维矩阵(assets × dates),如行情、因子、选股信号 | | `one_dimensional` | indexInfo(dimension = `one-row`) | 一维时间序列,如净值、指数序列 | | `matrix_table` | matrixInfo(含 table_json) | 表格数据,如选股结果表 | ## 通用参数 | 参数 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|------|------| | `ids` | string[] | ✅ | — | 数据 ID 数组(indexInfo / matrixInfo 的 `_id`),最多 10 个,超过须分批调用 | | `mode` | string | ❌ | `signature` | 查询模式,见下方 mode 说明 | | `start_date` | number | ❌ | — | 起始日期,格式 `YYYYMMDD` | | `end_date` | number | ❌ | — | 结束日期,格式 `YYYYMMDD` | | `decimal_places` | number | ❌ | `4` | 小数位数,最大 10 | | `include_distribution` | boolean | ❌ | `false` | 是否额外计算分布信息 | | `align_samples` | boolean | ❌ | `true` | 多数据采样时是否返回顶层 `aligned_comparison` | | `anchor_index` | number | ❌ | `0` | 多数据对齐锚点索引 | | `task_id` | string | ❌ | 自动注入 | 任务 ID。通过 `scripts/call.py` 调用时通常无需传入 | > `align_samples` 不支持 `per_asset_sample`、`precheck`、`table_data` 模式。 ## mode 说明 | mode | 用途 | 支持数据类型 | 专用参数 | |------|------|-------------|----------| | `signature`(默认) | 返回数据签名、轻量预览采样和最后一列统计 | 二维 / 一维 / 表格 | `include_samples`、`preview_assets`、`preview_points`、`sparse_threshold` | | `smart_sample` | 智能采样查看具体数值 | 二维 / 一维 / 表格(表格走 `table_data`) | `top_assets`、`sample_points` | | `per_asset_sample` | 每个资产单独采样 | 二维 | `top_assets`、`sample_points` | | `last_day_stats` | 最后一个有效交易日的截面统计 | 二维 / 一维 | — | | `last_column_full` | 最后一个有效列的完整截面;一维数据返回时间序列 | 二维 / 一维 | `max_rows`、`allow_zero_values` | | `last_valid_per_asset` | 每个资产取最后一个有效值,适合跨市场或不同更新频率数据 | 二维 / 一维 | `max_rows` | | `range_data` | 指定日期区间内的完整连续原始数据,不采样 | 二维 / 一维 | `start_date`、`end_date`、`assets`、`max_cells`、`nan_handling` | | `precheck` | 数据质量预检查,可配合 `expected` 做断言式校验 | 二维 / 一维 | `expected` | | `table_data` | 读取 matrixInfo 表格数据,支持排序和分页 | 表格 | `top_n_assets`、`last_m_columns`、`sort_by`、`sort_order` | ## mode = `range_data` 返回指定日期区间内的**完整连续数据**,不做任何采样。适用于需要读取原始连续数据做本地合成计算、区间收益复核、图表补齐或导出片段的场景。 ### 参数 | 参数 | 类型 | 必填 | 默认值 | 说明 | |------|------|------|------|------| | `start_date` | number | ✅ | — | 起始日期,格式 `YYYYMMDD` | | `end_date` | number | ✅ | — | 结束日期,格式 `YYYYMMDD` | | `assets` | string[] | ❌ | 全部资产 | 仅二维数据有效;只返回指定资产子集 | | `max_cells` | number | ❌ | `500000` | 单元格数量安全上限,最大 200 万;超限时截断日期轴 | | `nan_handling` | string | ❌ | `keep` | NaN 处理策略:`keep` / `fill_forward` / `drop_rows` | `nan_handling` 选项: | 值 | 说明 | |---|---| | `keep` | 保留 NaN,响应中以 `null` 返回 | | `fill_forward` | 向前填充;序列开头无法填充的 NaN 仍为 `null` | | `drop_rows` | 去掉全 NaN 的资产行,仅二维数据有效 | ### 二维响应结构 ```json { "id": "6651a...", "data_type": "two_dimensional", "signature": { "shape": { "assets": 5000, "dates": 244 } }, "range_data": { "dates": [20240101, 20240102, 20240103], "assets": ["000001", "600519"], "names": ["平安银行", "贵州茅台"], "values": [ [10.5, 10.8, 10.6], [1800.0, 1795.5, 1810.0] ], "shape": { "assets": 2, "dates": 3 }, "nan_handling": "keep", "is_truncated": false, "warning": null } } ``` > `values[i][j]` 对应 `assets[i]` 在 `dates[j]` 的值。 ### 一维响应结构 ```json { "id": "6651a...", "data_type": "one_dimensional", "signature": { "shape": { "dates": 244 } }, "range_data": { "dates": [20240101, 20240102, 20240103], "series_name": "沪深300指数", "values": [3450.5, 3462.1, 3448.8], "shape": { "dates": 3 }, "nan_handling": "keep", "is_truncated": false, "warning": null } } ``` ### 截断响应 ```json { "range_data": { "shape": { "assets": 5000, "dates": 100 }, "is_truncated": true, "warning": "数据量过大(1220000 > 500000),日期轴已截断至 100 个交易日" } } ``` ### 调用示例 ```bash # 读取一维/二维数据的完整区间,不采样 python scripts/executor.py readData '{ "ids": ["6651a0b1c2d3e4f5a6b7c8d9"], "mode": "range_data", "start_date": 20240101, "end_date": 20241231 }' # 只读取指定资产,并向前填充 NaN python scripts/executor.py readData '{ "ids": ["6651a0b1c2d3e4f5a6b7c8d9"], "mode": "range_data", "start_date": 20240101, "end_date": 20241231, "assets": ["000001", "600519", "000858"], "nan_handling": "fill_forward" }' # 控制返回规模 python scripts/executor.py readData '{ "ids": ["6651a0b1c2d3e4f5a6b7c8d9"], "mode": "range_data", "start_date": 20200101, "end_date": 20241231, "max_cells": 1000000 }' ``` ## 常见参数错误 | 错误写法 | 正确写法 | 说明 | |---------|---------|------| | `{"variable_names": ["趋势放量背景"]}` | `{"ids": ["60a1b2c3d4e5f6a7b8c9d0e1"]}` | 参数名是 `ids`,不是 `variable_names`;值必须是 data_id,不是中文变量名 | | `{"ids": ["NAV", "收盘价"]}` | `{"ids": ["60a1b2...", "60a1b3..."]}` | ids 不接受变量名,只接受数据 ID | | `{"ids": [...], "mode": "table_data", "sample_points": 12}` | `{"ids": [...], "mode": "table_data"}` | `table_data` 不支持 `sample_points` 和 `align_samples` | | `{"ids": [...], "mode": "range_data"}` | `{"ids": [...], "mode": "range_data", "start_date": 20240101, "end_date": 20241231}` | `range_data` 必须指定起止日期 | ## 注意事项 - `ids` 使用 `runMultiFormulaBatchStream` 返回的 **`data_id`**(非 `expression_id`),或 `confirmDataMulti` 返回的 `_id`。 > ⚠️ **高频错误**:`runMultiFormulaBatchStream` 每条结果同时含 `expression_id` 和 `data_id` 两个字段,两者相邻但含义不同。`ids` 必须传 **`data_id`**;若误传 `expression_id`,接口会返回 `"error": "IndexInfo {id}"` 并 `status: failed`,此时不要重试相同 id,应重新检查返回体取正确的 `data_id` 字段。 - 仅验证结果是否合理时,优先用 `signature` / `smart_sample` / `precheck`,避免读取大体量完整数据。 - 需要完整连续原始数据时,用 `range_data`,不要用采样结果做精确计算。 - `range_data` 可能返回大数组;跨多年、全市场二维数据必须设置较窄日期区间、`assets` 子集或 `max_cells`。 - 需要最新截面排名时用 `last_column_full`;需要每个资产最新有效值时用 `last_valid_per_asset`。 - `table_data` 仅适用于 matrixInfo 表格数据;二维矩阵和一维时间序列不要使用 `table_data`。 -
refresh_snapshot_time.md 1.5 KB
# refresh_snapshot_time — 强制刷新分钟数据截止时间 ## 端点 `POST /skill/refreshSnapshotTime` ## 参数 | 参数 | 类型 | 必填(HTTP 层) | 说明 | |------|------|------|------| | `task_id` | string | ✅ | 当前 session 的 task_id | > **调用方注意**:通过 `scripts/call.py` 调用时,`task_id` 会从 `output/.session.json` 或 `output/.session.<key>.json`(设置 `QBS_SESSION_KEY` 时)**自动注入**,**LLM/调用方传 `{}` 即可**(与 `runMultiFormulaBatchStream` 等其他工具一致)。仅在脱离 call.py 直接打 HTTP 时才需手动带 task_id。 ## 调用场景 同一天同一 session 内,多次执行盘中公式且需要推进分钟数据截止时间时,先调用本工具,再调用 `runMultiFormulaBatchStream`。 **通过 call.py 的标准调用(推荐)**: ```bash python scripts/call.py refreshSnapshotTime '{}' ``` **等价的底层 HTTP payload**(call.py 自动补好 task_id): ```json { "task_id": "550e8400-e29b-41d4-a716-446655440000" } ``` ## 返回 ```json { "code": 0, "data": { "snapshot_time": 202604271345 }, "task_id": "550e8400-e29b-41d4-a716-446655440000" } ``` ## 注意事项 - `snapshot_time` 是 **12 位整数,格式为 YYYYMMDDHHMM**(如 `202604271345` 表示 2026-04-27 13:45),**不是** Unix 时间戳 - 本工具会强制获取并覆盖该 session 当天的快照时间 - ⛔ 不要向 `runMultiFormulaBatchStream` 传 `refresh_snapshot_time`;该参数已移除 -
render_chart.md 7.3 KB
# render_chart — 渲染图表 > 将计算结果渲染成图表,返回 base64 编码的 PNG 图片。支持折线、柱状、面积、K线图。图表 spec 自动保存,可通过 task_id 检索和重新渲染。 ## 端点 `POST /skill/renderChart` ## 参数 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `chart_type` | string | ❌ | 图表类型:`line`(默认)/ `bar` / `area` / `candlestick` / `candlestick_volume` | | `lines` | object[] | ✅* | 曲线配置数组,**line/bar/area 类型必填**,详见下方 | | `candlestick` | object | ✅* | K线数据配置,**candlestick/candlestick_volume 类型必填**,详见下方 | | `title` | string | ❌ | 图表标题 | | `start_date` | number | ❌ | 显示起始日期(格式 20150101),不传则从数据最早日期开始 | | `width` | number | ❌ | 图片宽度(像素),默认 1200 | | `height` | number | ❌ | 图片高度(像素),默认 600 | | `task_id` | string | ❌ | 任务ID(UUID),用于保存和检索图表 spec | > `*` 互斥必填:`lines` 和 `candlestick` 根据 `chart_type` 二选一。 ### chart_type 说明 | chart_type | 说明 | 数据输入 | |-----------|------|----------| | `line` | 折线图(默认) | `lines` 数组 | | `bar` | 柱状图 | `lines` 数组 | | `area` | 面积图(带填充) | `lines` 数组 | | `candlestick` | K线图(纯K线) | `candlestick` 对象 | | `candlestick_volume` | K线+成交量 | `candlestick` 对象(含 `volume_id`) | ### lines 数组元素(line / bar / area 用) | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `id` | string | ✅ | 数据ID(来自 `runMultiFormulaBatchStream` 的 `data_id`) | | `name` | string | ✅ | 曲线图例名称 | | `axis` | string | ❌ | `left`(默认)或 `right`(右轴,适合量纲不同的数据) | > ℹ️ 仅支持一维数据。二维数据需先通过公式提取为一维(如 `收盘价(贵州茅台)` 返回的就是一维时序)。 ### ⚠️ 常见参数错误 | 错误写法 | 正确写法 | 说明 | |---------|---------|------| | `{"variable_names": ["金银比价"]}` | `{"lines": [{"id": "60a1b2...", "name": "金银比价"}]}` | 参数名是 `lines`,不是 `variable_names` | | `{"lines": [{"name": "NAV"}]}` | `{"lines": [{"id": "60a1b2...", "name": "NAV"}]}` | `id` 必填,必须是 `runMultiFormulaBatchStream` 返回的 24位 hex data_id | | `{"chart_type":"bar","x_data":[...],"series":[...]}` | `{"chart_type":"bar","lines":[{"id":"hex","name":"X"}]}` | 柱状图也用 `lines` 数组,不是 `x_data`/`series` | ### candlestick 对象(K线图用) | 字段 | 类型 | 必填 | 说明 | |------|------|------|------| | `open_id` | string | ✅ | 开盘价数据ID | | `high_id` | string | ✅ | 最高价数据ID | | `low_id` | string | ✅ | 最低价数据ID | | `close_id` | string | ✅ | 收盘价数据ID | | `volume_id` | string | ❌ | 成交量数据ID(`candlestick_volume` 类型必填) | > ℹ️ 所有 ID 必须是一维数据。通过 `开盘价(资产名)` 等公式计算得到的就是一维时序。 ## 返回 ```json { "code": 0, "data": { "base64": "data:image/png;base64,iVBORw0KGgo...", "lines_count": 2, "width": 1200, "height": 600, "chart_type": "line", "errors": [] }, "task_id": "uuid-xxx" } ``` - `base64`:完整 data URI,可直接用作 `<img src="...">` 或解码保存为 PNG - 图表 spec 自动按 `task_id` 持久化到 MongoDB,后续可用 `getChartSpec` 检索或 `reRenderChart` 重新渲染 ## 调用示例 ### 折线图(默认) ```bash # 单条净值曲线 python scripts/call.py renderChart '{"title": "策略净值", "lines": [{"id": "nav_id", "name": "MA金叉策略"}]}' # 多策略对比 python scripts/call.py renderChart '{"title": "策略对比", "chart_type": "line", "lines": [{"id": "id1", "name": "策略A"}, {"id": "id2", "name": "策略B"}]}' ``` ### 柱状图 ```bash python scripts/call.py renderChart '{"title": "月度收益", "chart_type": "bar", "lines": [{"id": "monthly_ret_id", "name": "月度收益"}]}' ``` ### 面积图 ```bash python scripts/call.py renderChart '{"title": "累计收益", "chart_type": "area", "lines": [{"id": "cum_ret_id", "name": "累计收益"}]}' ``` ### K线图 需要先通过 `runMultiFormulaBatchStream` 计算 OHLC 数据,然后传入 4 个 data_id: ```bash # Step 1: 计算 OHLC(同一 task_id) python scripts/call.py runMultiFormulaBatchStream '{"task_id": "kline-001", "formulas": ["O=开盘价(贵州茅台)", "H=最高价(贵州茅台)", "L=最低价(贵州茅台)", "C=收盘价(贵州茅台)", "V=成交量(贵州茅台)"]}' # Step 2: 渲染K线图 python scripts/call.py renderChart '{ "chart_type": "candlestick_volume", "title": "贵州茅台K线", "start_date": 20250101, "candlestick": { "open_id": "<O的data_id>", "high_id": "<H的data_id>", "low_id": "<L的data_id>", "close_id": "<C的data_id>", "volume_id": "<V的data_id>" }, "task_id": "kline-001" }' ``` ### 左右双轴 ```bash python scripts/call.py renderChart '{"lines": [{"id": "nav_id", "name": "策略净值", "axis": "left"}, {"id": "factor_id", "name": "换手率因子", "axis": "right"}]}' ``` --- ## 图表 Spec 持久化 每次 `renderChart` 调用成功后,图表的 echarts option 和原始参数会自动保存到 MongoDB,关联 `task_id`。 ### 获取 Chart Spec `GET /skill/chartSpec/{task_id}` ```bash python scripts/call.py getChartSpec '{"task_id": "kline-001"}' ``` 返回: ```json { "code": 0, "data": { "specs": [ { "_id": "mongo_id", "task_id": "kline-001", "chart_type": "candlestick_volume", "title": "贵州茅台K线", "render_params": {...}, "width": 1200, "height": 600, "lines_count": 2, "created_at": "2026-03-05..." } ], "total": 1 } } ``` ### 重新渲染 `POST /skill/reRenderChart` 从已保存的 spec 重新渲染(可覆盖 width/height/title),**不重新读取数据**: ```bash python scripts/call.py reRenderChart '{"task_id": "kline-001", "width": 1600, "height": 800}' ``` | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `spec_id` | string | ✅* | spec 的 MongoDB _id(精确定位) | | `task_id` | string | ✅* | 取该 task_id 下最新一条 spec | | `width` | number | ❌ | 覆盖原始宽度 | | `height` | number | ❌ | 覆盖原始高度 | | `title` | string | ❌ | 覆盖原始标题 | > `*` spec_id 和 task_id 二选一,优先 spec_id。 ## 注意事项 - 涉跌颜色遵循中国习惯:红涨绿跌 - 仅支持一维数据。二维数据需先通过公式提取为一维(如 `收盘价(贵州茅台)` 返回的就是一维时序) with open("chart.png", "wb") as f: f.write(base64.b64decode(b64)) ``` ## 完整示例 见 [`examples/04_render_chart.py`](../examples/04_render_chart.py)——执行两个策略回测 + 渲染对比图 + 自动保存 PNG。 ## 注意事项 - **一维净值曲线**(`runMultiFormulaBatchStream` 里 `回测()` 的结果):`ticker` 不传 - **二维数据**(收盘价矩阵等):必须传 `ticker` 指定要画哪只资产 - 图片默认 2x 分辨率(高清),1200x600 像素实际输出 2400x1200 - `errors` 字段记录部分曲线加载失败的情况(不影响其他曲线渲染) -
render_kline.md 5.7 KB
# render_kline — 渲染 K 线图 > 通过 ticker 直接渲染 K 线图(OHLCV),**无需提前 runMultiFormulaBatchStream**。只需传资产代码和起始日期,后端自动获取 OHLC 行情数据并渲染。支持叠加技术指标(MA / MACD / BOLL / RSI),自动处理指标预热期。 ## 端点 `POST /skill/renderKLine` ## 参数 ### 基础参数 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `ticker` | string | ✅ | 资产 ticker 代码,如 `SH600519`、`SZ300750`、`SH000300` | | `begin_date` | number | ❌ | 起始日期(格式 YYYYMMDD),不传返回全量历史 | | `title` | string | ❌ | 图表标题,不传默认显示 ticker | | `show_volume` | boolean | ❌ | 是否显示成交量子图,默认 `true` | | `width` | number | ❌ | 图片宽度(像素),默认 1200 | | `height` | number | ❌ | 图片高度(像素),默认 600(有子面板时自动升至 800) | | `task_id` | string | ❌ | 任务ID,用于保存和检索图表 spec | ### 技术指标参数 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `indicators` | string[] | ❌ | 技术指标列表(见下方支持列表),不传则渲染纯 K 线 | | `annotations` | object[] | ❌ | 图表标注(预留),如买卖点标记 | > **ticker 格式说明**:使用 `SH`/`SZ` 前缀 + 6位代码,如 `SH600519`(贵州茅台)、`SZ000858`(五粮液)、`SH000300`(沪深300)。 ### ⚠️ 常见参数错误 | 错误写法 | 正确写法 | 说明 | |---------|---------|------| | `{"ticker": "600519.SH"}` | `{"ticker": "SH600519"}` | 前缀格式,不是点号后缀 | | `{"ticker": "贵州茅台"}` | `{"ticker": "SH600519"}` | 必须是代码,不接受中文名 | | `{"asset": "国电南瑞", "code": "600406.SH"}` | `{"ticker": "SH600406"}` | 参数名是 `ticker`,不是 `asset`/`code` | ### 支持的技术指标 | 指标名 | 说明 | 面板 | |--------|------|------| | `ma5` | 5日均线 | 主图叠加 | | `ma10` | 10日均线 | 主图叠加 | | `ma20` | 20日均线 | 主图叠加 | | `ma30` | 30日均线 | 主图叠加 | | `ma60` | 60日均线 | 主图叠加 | | `ma120` | 120日均线 | 主图叠加 | | `ma250` | 250日均线(年线) | 主图叠加 | | `ema5` ~ `ema250` | 指数移动均线,周期同上 | 主图叠加 | | `boll` / `boll20` | 布林带(20日) | 主图叠加(上/中/下三条线 + 填充区) | | `boll10` / `boll30` | 布林带(10日 / 30日) | 主图叠加 | | `macd` | MACD(12,26,9) | 独立子面板(DIF / DEA / 柱状) | | `rsi` / `rsi14` | RSI(14日) | 独立子面板(含 70/30 参考线) | | `rsi6` / `rsi24` | RSI(6日 / 24日) | 独立子面板 | | `vol` / `volume` | 成交量柱状图 | 独立子面板(与 `show_volume` 等效) | **面板布局**:主图(K线 + MA/BOLL)始终在顶部;子面板(VOL / MACD / RSI)按出现顺序纵向排列,每个面板自动分配高度。 ### 预热机制 当传入 `indicators` 且指定了 `begin_date` 时,后端会**自动**将数据请求的起始日期向前推 `warmupDays × 1.5` 个自然日(例如 MA60 需要预热 60 个交易日 ≈ 90 个自然日),确保指标从用户指定的 begin_date 开始就有完整数值。最终图表只显示 begin_date 之后的数据,预热区间不可见。 ## 返回 ```json { "code": 0, "data": { "base64": "data:image/png;base64,iVBORw0KGgo...", "width": 1200, "height": 800, "lines_count": 2, "ticker": "SH600519", "data_points": 243, "indicators": ["ma5", "ma20", "macd"], "warmup_days": 26 }, "task_id": "uuid-xxx" } ``` - 图表 spec 自动按 `task_id` 持久化,后续可用 `getChartSpec` 检索或 `reRenderChart` 重新渲染 ## 调用示例 ### 基础 K 线 ```bash # 贵州茅台近一年 K 线图(含成交量) python scripts/call.py renderKLine '{"ticker": "SH600519", "begin_date": 20240101, "title": "贵州茅台K线"}' ``` ```bash # 沪深300 纯K线(不显示成交量) python scripts/call.py renderKLine '{"ticker": "SH000300", "begin_date": 20250101, "show_volume": false}' ``` ### 带技术指标 ```bash # 茅台 K 线 + MA5/MA20 + MACD(自动 800 高度) python scripts/call.py renderKLine '{"ticker": "SH600519", "begin_date": 20240101, "title": "贵州茅台技术分析", "indicators": ["ma5", "ma20", "macd"]}' ``` ```bash # 宁德时代 布林带 + RSI python scripts/call.py renderKLine '{"ticker": "SZ300750", "begin_date": 20250101, "title": "宁德时代", "indicators": ["boll", "rsi"]}' ``` ```bash # 沪深300 全套技术分析:MA5/MA10/MA20 + BOLL + MACD + RSI python scripts/call.py renderKLine '{"ticker": "SH000300", "begin_date": 20240701, "title": "沪深300技术分析", "indicators": ["ma5", "ma10", "ma20", "boll", "macd", "rsi"]}' ``` ## 与 renderChart(candlestick 模式)的区别 | | renderKLine | renderChart (candlestick) | |---|---|---| | 需要 runMultiFormulaBatchStream | ❌ 不需要 | ✅ 需要(先算出 4 个 data_id) | | 步骤数 | 1步 | 2步 | | 技术指标 | ✅ 内置 MA/MACD/BOLL/RSI | ❌ 需自行计算 | | 适用场景 | 直接查看某资产的行情 K 线、技术分析 | 用自定义公式处理后的数据画 K 线 | | 成交量 | 内置,`show_volume=true` 开启 | 需传 volume_id | ## 注意事项 - ticker 格式必须是 `SH`/`SZ` + 6位代码,不支持 `600519.SH` 等点号分隔格式 - 不支持港股、期货等非 A 股资产(由 getKLineDataByTicker 底层决定) - 渲染颜色遵循中国习惯:红涨绿跌 - 含子面板(MACD/RSI)时高度自动升至 800px,也可手动指定更大值 - `indicators` 中的重复项会自动去重 - 无需手动计算预热期——传 `begin_date` + `indicators` 后端自动处理 -
resume_job.md 3.4 KB
# resumeJob — 续传查询 deferred 后台任务 > 用于在 `runMultiFormulaBatchStream` 返回 `status:"deferred"` 后,重新连接 SSE 流查看任务进度并获取最终结果。 ## 使用场景 当 `runMultiFormulaBatchStream` 以 `execution_profile:"research_24h"` 提交任务,或服务端自动将任务升级为后台队列模式时,会立即返回: ```json { "status": "deferred", "task_id": "...", "trace_id": "...", "job_id": "...", "message": "研究任务已进入后台队列,可稍后恢复查看进度" } ``` 此时调用 `resumeJob` 即可续传,等待并获取完整结果(与 `runMultiFormulaBatchStream` 同步模式返回结构完全一致)。 ## 调用方式 ```bash GZQ_PARAMS='{"task_id":"<task_id>","trace_id":"<trace_id>"}' python scripts/call.py resumeJob ``` ## 参数 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `task_id` | string | ✅ | 来自 deferred 响应的 `task_id` | | `trace_id` | string | ✅ | 来自 deferred 响应的 `trace_id` | | `since` | string | ❌ | 上次断连时的 `last_event_id`(默认 `"0"`,从头重放) | ## 返回结构 任务完成时返回与 `runMultiFormulaBatchStream` 同步模式完全一致的结构: ```json { "status": "ok", "summary": { "total": N, "success": N, "failed": 0, "skipped": 0 }, "results": [ ... ] } ``` 任务仍在队列中(未完成)时会阻塞等待,直到收到 `done` 事件或超时(最长 30 分钟)。 ## 错误情况 | 情况 | 返回 | |------|------| | 缺少 `task_id` 或 `trace_id` | `code:-1, error.code:"MISSING_PARAMS"`, `category:"input_error"` | | SSE 流中断且 3 次重连均失败 | `code:-1, error.code:"STREAM_INTERRUPTED"`, `category:"transport_recoverable"`,`partial.last_event_id` 可用于下次 `since` 参数 | > ⚠️ **`resumeJob` 只对 `category:"transport_recoverable"`(连接断了、任务还活着)有意义。** > 若返回的是 `category:"server_timeout"`(`QUEUE_WAIT_TIMEOUT` / `EXECUTION_STALLED`),说明任务在后端已超时/判死,**续传读不到结果**——别反复 `resumeJob`,应改为稍后重试、拆小批或换异步模式(见 `references/troubleshooting.md`)。 ### STREAM_INTERRUPTED 恢复(硬规则) 收到 `STREAM_INTERRUPTED`(`category:"transport_recoverable"`)**不等于任务失败**——服务端任务仍在运行,只是 SSE 连接断了。必须按以下步骤恢复: 1. 从响应 `partial.last_event_id` 取出上次断点 2. 以 `since` 参数**再次调用** `resumeJob`: ```bash GZQ_PARAMS='{"task_id":"<task_id>","trace_id":"<trace_id>","since":"<last_event_id>"}' python scripts/call.py resumeJob ``` 3. 最多额外重试 2 次(合计 3 次 `resumeJob` 调用),仍 `STREAM_INTERRUPTED` 才可向用户报告连接异常 4. **禁止**:直接放弃 → 跳到下一批 → 或向用户说"任务失败" ## 注意事项 - `resumeJob` 不额外扣 RU;任务提交接受后断线不退款 - 实时进度(每条公式完成)会打印到 stderr,与 `runMultiFormulaBatchStream` 体验一致 - 如果任务已完成,SSE 流会从 `since=0` 重放所有历史事件并返回 `done` 结果 - **多批任务中,必须每批 `deferred` 后都调 `resumeJob` 等到 `done`,才可提交下一批**——否则上一批变量未计算完,下一批引用会空跑。详见 `tools/run_multi_formula.md`「deferred 响应与 resumeJob 续传」 -
run_multi_formula.md 23 KB
> ⛔ **LLM 调用工具名必须是 `runMultiFormulaBatchStream`**。调用任何其他名称均报 `未知工具`;收到此错误后**禁止以任何名称变体重试**。 # run_multi_formula — 执行公式批次 > 核心工具。在全市场数据上执行一组公式,生成因子、选股信号、回测净值等。同一批公式**必须使用同一 `task_id`**,以保证变量可互相引用。 ## 端点 `POST /skill/runMultiFormulaBatchStream`(SSE 主路径);回退时自动走同步 `/skill/runMultiFormulaBatch` > - 计费规则与配额池与旧端点共享(`run_multi_formula` 池,7 RU / 公式)。 ## 参数 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `formulas` | string[] | ✅ | 公式数组,按顺序执行。后面的公式可引用前面的变量。**每个元素必须是纯字符串**(如 `"A=平均(...)"` ),不能是对象 | | `task_id` | string | ❌ | 任务ID(UUID)。**同一批必须相同**,不传则自动生成并返回 | | `begin_date` | number | ❌ | 计算起始日期(格式 20240101,**裸整数**)。**业务侧硬要求显式传入**,不再依赖服务端默认值;选择规则见下方「begin_date 分档方案」 | | `intents` | string[] | ❌ | 每条公式的意图说明(与 formulas 等长),便于理解日志 | | `actions` | string[] | ❌ | 每条公式的阶段说明(与 formulas 等长) | | `include_description` | boolean | ❌ | 是否返回描述信息,默认 true。设为 false 可减少响应体积 | | `use_minute_data` | boolean | ❌ | 启用盘中刷新模式,API 与业务默认均为 false。仅当用户明确要求盘中、当前分钟或当日未收盘数据时设为 true;历史日频、回测、净值、回撤、波动率和财务计算传 false 或省略。详见下方「盘中刷新模式」| | `force_reusable_array` | string[] | ❌ | 需要保留、后续复用或查看的公式**左侧变量名**数组。传入后未列出的变量自动标记为不复用;不传时默认全部复用。详见下方「中间变量复用标记」 | | `execution_profile` | string | ❌ | 仅 `"research_24h"` 一个可选值,触发服务端长任务后台模式(24h 队列、断线 SSE 续传、不退款)。**任务级判定**:在切批之前先判断"整个任务"是否属于策略/回测/PnL/批量研究;若是,则**该任务下所有相关批次**(含前置准备、修复/重试 FIX 批次)都必须显式传入。详见下方「任务级 research_24h 传播规则」 | | `user_query` | string | ❌ | 当前用户原始问题/意图(中文或英文)。建议每个批次都附带 | | `output_mode` | string | ❌ | `full`(默认)或 `summary`。completed summary 保留状态、计数和 `expression_id/data_id`;deferred summary 保留 `task_id/trace_id/job_id/stream_url/_deferred`,省略冗长描述 | 成功完成且每条结果均为 `success` 时,客户端额外返回脱敏的 `validation_receipt_file`。`deferred` 只是排队态,必须用同一 `task_id + trace_id` 调 `resumeJob` 直到完成;失败、部分失败和 deferred 均不会生成收据。 ## 单次公式数限制(所有 tier 一律 20 条) | 用户层级 (`_quota.tier`) | 服务端硬上限 | **本 skill 实际必须遵守** | |:---:|:---:|:---:| | free | 20 | **20** | | plus | 20 | **20** | | pro | 20 | **20** | | ultra | 20 | **20** | > ⚠️ **硬规则**:服务端对单次公式数有 20 条硬上限,超出即 `code=-1`,本 skill 与服务端对齐,切批阈值同为 **20 条/批**,原因: > 1. 每批公式越少,跨批保活的变量越清晰,`force_reusable_array` 误填概率更低 > 2. 在 20 条服务端上限内切批,兼顾吞吐量与超时风险 > 3. 单批失败时损失更小,重试成本更低 > > 超过 20 条的请求必须拆分为多次调用(保持同一 `task_id`),并通过 `force_reusable_array` 列出前置批次需保留的变量名,复用缓存避免重复计算。 > 层级信息(`_quota.tier`)仅用于配额池区分,不影响切批阈值。 ## begin_date 分档方案(业务侧默认值) > ⚠️ **大原则:begin_date 越晚,工具返回越快**。按下方流程取最小够用的起点,避免拉过长历史。 **判定流程(顺序匹配,第一条命中即停):** 1. **用户/卡片明确给出起止日期** → 直接用,**禁止扩大** 2. **需要长历史的场景**: - 公式含 `回测(`、`区间统计量(`、`上行区间(`、`下行区间(`、`累计最大(`、`年度累计`、`月度累计`、`从头平均`、`累乘` → `20210101` - 财务报告期查询要看 N 个连续报告期 → `T-(N+1) 年`(默认 N=4 → `T-3 年`) 3. **公式含明确滚动窗口 N**(`平均(…,N)` / `涨跌幅(…,N)` / `最大(…,N)` / `最小(…,N)` / `标准差(…,N)` / `EMA(…,N)` / `相关系数(…,…,N)` / `MACD(…)` 默认 N=35 / `同比` 取 N=365 等)→ `begin_date = T - N 自然日`(N 取公式中最大窗口) 4. **无法判定 N / 单点快照 / 仅取最新值** → 直接 `T` > 📌 `T` = 今日 YYYYMMDD 整数(如 20260514),由 LLM 在调用前根据当前日期换算。 > 📌 多档冲突取较早档(如同时含 `回测` 和 `平均(…,60)` → `20210101`)。 > 📌 如果结果为空或不完整,再放宽到 `T - N×2 自然日`。 ## 返回 > ⚠️ **`data_id` vs `expression_id`**:每条结果同时包含这两个字段,切勿混用。 > - **`data_id`**(即 `indexinfo_id`)→ 传给 `readData` 的 `ids` 参数 > - **`expression_id`** → 内部标识,**不能传给 `readData`**;若误传,接口返回 `"error": "IndexInfo {id}"` ```json { "code": 0, "data": { "results": [ { "formula": "A=平均(\"全市场每日收盘价\", 20)", "variable_name": "A", "expression_id": "60a1b2c3d4e5f6a7b8c9d0e0", "data_id": "60a1b2c3d4e5f6a7b8c9d0e1", "dimension": "two", "description": "20日均线,二维矩阵(资产×日期)", "status": "success" }, { "formula": "NAV=回测(\"Signal\")", "variable_name": "NAV", "expression_id": "60a1b2c3d4e5f6a7b8c9d0e1", "data_id": "60a1b2c3d4e5f6a7b8c9d0e2", "dimension": "one-row", "description": "回测净值曲线", "status": "success" } ] }, "task_id": "550e8400-e29b-41d4-a716-446655440000" } ``` > `expression_id`(如 `...e0`)= 内部标识,**不传给 `readData`**。 > `data_id`(如 `...e1`)= 计算结果存储 ID,**传给 `readData` 的 `ids` 参数**。 ## 公式语法规则(必须遵守) | 规则 | 正确 ✅ | 错误 ❌ | |------|---------|---------| | 引用数据/变量名用双引号 | `"全市场每日收盘价"` | `全市场每日收盘价` | | 函数参数为板块名不加引号 | `板块(万得全A)` | `板块("万得全A")` | | AND 用乘法 | `("A")*("B")` | `A and B`、`A与B` | | 不等式参与运算加括号 | `("A">"B")*("C"<0.05)` | `"A">"B"*"C"<0.05` | | 取并集用 `或()` | `或(A, B)` | `A+B` | | 一维条件转二维 | `("条件")*板块(万得全A)` | 直接用一维条件做掩码 | | 一维0-1数据相加前填零 | `缺失填零("掩码A")+"掩码B"` | `"掩码A"+"掩码B"` | ## 常用公式函数速查 > 完整函数列表见 `presets/functions.yaml`,有疑问时调 `searchFunctions` 获取详细说明。 > **平台无 `MA()` 函数**,均线请用 `平均("数据", N)`。 | 类别 | 函数 | 用法 | 返回维度 | |------|------|------|----------| | 行情获取 | `收盘价` | `收盘价(沪深300)`、`收盘价(贵州茅台)` — 资产名不加引号 | 1维 | | 行情获取 | `涨跌幅` | `涨跌幅("数据名")` 或 `涨跌幅("数据名",n)` | 同输入 | | 统计计算 | `平均` | `平均("数据名", 20)` | 同输入 | | 统计计算 | `最大` | `最大("数据名", 60)` — 滚动60日最大 | 同输入 | | 统计计算 | `累计最大` | `累计最大("数据名")` — 历史累计最大值 | 同输入 | | 统计计算 | `回撤` | `回撤("数据名", 60)` — 60日滚动回撤 | 同输入 | | 日期范围 | `日期范围` | `日期范围(20160101,20201231)` — 返回0-1掩码 | 1维 | | 区间识别 | `上行区间` | `上行区间(收盘价(沪深300), 60, 0)` | 1维 | | 区间识别 | `下行区间` | `下行区间(收盘价(沪深300), 60, 0)` | 1维 | | 区间识别 | `区间统计量` | `区间统计量("日收益率", "上行区间", 求和, 平均, 计数)` — 统计函数名不加引号 | 表格 | | 选股板块 | `板块` | `板块(万得全A)` — 板块名不加引号 | 2维掩码 | | 选股板块 | `资产池构造` | `资产池构造(沪深300, 1, 中证500, 1)` | 2维掩码 | ### 公式链示例 ```python # 示例1:指数收盘价 + 回撤计算(1维) HS300=收盘价(沪深300) HS300_MAX=累计最大("HS300") HS300_DD=("HS300"/"HS300_MAX"-1)*100 # 示例2:指数区间收益对比 HS300_RET=涨跌幅(收盘价(沪深300)) 消费_RET=涨跌幅(收盘价(食品饮料(申万))) 超额="消费_RET"-"HS300_RET" # 示例3:上行区间统计 上行=上行区间(收盘价(沪深300), 60, 0) 中证1000日收益=涨跌幅(收盘价(中证1000)) 统计表=区间统计量("中证1000日收益", "上行", 求和, 平均, 计数) ``` ## 调用示例 ```bash # 均线策略(基础示例) python scripts/executor.py runMultiFormulaBatchStream '{ "formulas": [ "MA5=平均(\"全市场每日收盘价\", 5)", "MA20=平均(\"全市场每日收盘价\", 20)", "Signal=(\"MA5\">\"MA20\")*板块(万得全A)*\"非ST\"", "NAV=回测(\"Signal\")" ], "task_id": "550e8400-e29b-41d4-a716-446655440000" }' # 低市盈率选股 python scripts/executor.py runMultiFormulaBatchStream '{ "formulas": [ "PE=取出(\"A股市盈率\")", "Signal=(\"PE\">0)*(\"PE\"<20)*板块(万得全A)*\"非ST\"", "NAV=回测(\"Signal\")" ], "task_id": "uuid-pe-strategy" }' # 指定起始日期 python scripts/executor.py runMultiFormulaBatchStream '{ "formulas": ["A=收盘价(贵州茅台)/平均(\"全市场每日收盘价\", 20)"], "task_id": "uuid-xxx", "begin_date": 20200101 }' ``` ## 标准回测写法 > 数据名必须来自 `presets/data_catalog.yaml` 或 `presets/index_info_catalog/` 的精确 `index_title`。不要把用户口语词直接写进公式。 ### 单因子 TopN 回测 ```json { "formulas": [ "过滤=板块(万得全A)*缺失填零(\"非ST股\")", "排序分=\"A股净资产收益率ROE\"*\"过滤\"", "Top20=取前(\"排序分\",20,返回掩码)", "NAV=回测(\"Top20\",当天收盘买入,返回复利净值,信号按列归一)" ], "begin_date": 20210101, "force_reusable_array": ["NAV"], "execution_profile": "research_24h", "user_query": "单因子 ROE Top20 等权回测" } ``` ### 多因子综合分 TopN 回测 ```json { "formulas": [ "过滤=板块(万得全A)*缺失填零(\"非ST股\")", "ROE分=按天归一(\"A股净资产收益率ROE\")", "成长分=按天归一(同比(\"A股营业收入〔报告期利润表〕\"))", "估值惩罚=按天归一(\"A股市盈率(PE, TTM)〔估值数据〕\")", "综合分=(\"ROE分\"+\"成长分\"-\"估值惩罚\")*\"过滤\"", "Top30=取前(\"综合分\",30,返回掩码)", "NAV=回测(\"Top30\",当天收盘买入,返回复利净值,信号按列归一)" ], "begin_date": 20210101, "force_reusable_array": ["NAV"], "execution_profile": "research_24h", "user_query": "多因子综合分 Top30 等权回测" } ``` ### 等权基准 ```json { "formulas": [ "基准池=板块(万得全A)*缺失填零(\"非ST股\")", "等权基准NAV=回测(\"基准池\",当天收盘买入,返回复利净值,信号按列归一)" ], "begin_date": 20210101, "force_reusable_array": ["等权基准NAV"], "execution_profile": "research_24h", "user_query": "万得全A非ST等权基准回测" } ``` ### 指数基准/超额净值 ```json { "formulas": [ "过滤=板块(万得全A)*缺失填零(\"非ST股\")", "排序分=\"A股净资产收益率ROE\"*\"过滤\"", "Top20=取前(\"排序分\",20,返回掩码)", "超额NAV=回测(\"Top20\",当天收盘买入,返回复利超额净值,信号按列归一,收盘价(沪深300))", "沪深300NAV=设定基准(收盘价(沪深300),1)" ], "begin_date": 20210101, "force_reusable_array": ["超额NAV", "沪深300NAV"], "execution_profile": "research_24h", "user_query": "ROE Top20 相对沪深300超额回测" } ``` ### 含交易成本版本 当前 `presets/functions.yaml` 暴露的 `回测` 签名没有 `费率` 参数;不要凭旧记忆写 `回测(..., 费率)`。用户明确要求交易成本时,先调用 `searchFunctions`: ```json {"query":"回测 费率 交易成本", "top_k":3, "detail_level":"full"} ``` 只有返回的现行签名明确包含交易成本/费率参数时,才按返回格式补参数;否则应向用户说明“当前函数签名未暴露交易成本参数”,并先交付无成本回测与指数/等权基准对比。 ### 中间变量复用标记(多公式必填) `force_reusable_array` 用于告诉计算框架哪些公式结果需要保留、后续复用或被读取。它是公式**左侧变量名**的字符串数组——只把需要保活的变量名列进去即可,**未列出的变量自动视为中间量、不写入复用缓存**。 **触发规则(硬要求)**: - 单公式(`formulas.length == 1`)→ 可不传 - 多公式(`formulas.length ≥ 2`)→ **必须主动评估并显式传入**,不得直接省略让所有公式默认复用(会浪费计算配额、污染缓存) **判定方法(逐条公式问一句话)**: > 这条公式左侧的变量名,**会被 `readData` 或最终业务步骤读取吗?** > - 不会,仅被本批后续公式引用 → 不要写进 `force_reusable_array` > - 会被读取,或它就是最终输出 → 写进 `force_reusable_array` **典型可识别的「中间量」命名**:`*_中间`、`*_条件`、`*_掩码`、`*_排序值`、`*_因子矩阵`、`*_筛选`、`MA5` / `MA20` / `Signal` / 各种打分/分位中间步骤——几乎全部不该写进 `force_reusable_array`。 **示例 1:均线金叉 TopN** ```json { "formulas": [ "MA5=平均(\"全市场每日收盘价\", 5)", "MA20=平均(\"全市场每日收盘价\", 20)", "Signal=(\"MA5\">\"MA20\")*板块(万得全A)", "TOP10=取前(\"Signal\", 10, 从大到小)" ], "force_reusable_array": ["TOP10"] } ``` **示例 2:盘中振幅 + 换手率筛选(多中间量典型场景)** ```json { "formulas": [ "高点涨幅=(最大值(\"全市场每日最高价\", 1)/延迟(\"全市场每日收盘价\", 1) - 1)", "低点涨幅=(最小值(\"全市场每日最低价\", 1)/延迟(\"全市场每日收盘价\", 1) - 1)", "振幅=\"高点涨幅\" - \"低点涨幅\"", "振幅条件=(\"振幅\" > 0.05)", "换手率=取出(\"全市场每日换手率\")", "TOP20=取前(\"换手率\" * \"振幅条件\" * 板块(万得全A) * 缺失填零(\"非ST股\"), 20, 返回数值)" ], "force_reusable_array": ["TOP20"] } ``` > 6 条公式中前 5 条全是中间量,仅 `TOP20` 需要 `readData` 读取——只把这 1 个变量名写进数组。 **回填语义说明**: - 未出现在 `force_reusable_array` 的变量只表示「不写入复用缓存」,**不影响同批后续公式继续引用它** - 不传 `force_reusable_array` 时,所有变量默认全部复用(多公式场景请勿依赖此默认值) - 数组元素必须严格匹配 `formulas` 中某条公式的左侧变量名(前后空格自动忽略,大小写敏感);不存在的变量名服务端会直接返回 `code: -1` - 多输出公式(如 `"净值, 持仓 = 回测(...)"`)任写其中一个变量名即可标记整条公式复用 - 当前 `formulas` 不允许出现重复左值变量名,否则同样返回 `code: -1` - 最终要传给 `readData` 的变量必须出现在 `force_reusable_array` 中 ## task_id 生命周期 ``` 同一批公式(顺序依赖) → 必须同一 task_id 下一批新策略 → 新 UUID(防止变量污染) 修复/重试同一批(名称不变) → 复用原 task_id(命中缓存跳过重算) 不传 → 服务端自动生成,从响应中取出给 readData ``` ## ⚠️ 常见参数错误 | 参数 | ✅ 正确 | ❌ 常见错误 | 后果 | |------|--------|-----------|------| | `formulas` | `["A=收盘价(贵州茅台)", "B=涨跌幅(\"A\")"]`(**字符串数组**) | `[{"formula": "A=收盘价(贵州茅台)"}]`(对象数组);使用 `MA()` 等平台不存在的函数 | 400/500 参数校验失败 | | `formulas` | 每个元素是一条完整公式,平均字符数 ≥ 10 | 多行公式字符串被逐字符拆成 `["个","股","近","5","日",...]`(95+ 元素,每元素 1 字符)——通常由多行字符串误拆所致 | **每次浪费 600+ RU**,所有元素报"公式缺少左值赋值";**提交前必须检查:若 formulas.length > 15 且平均每元素字符数 < 3,禁止提交,重新构造公式后再调用** | | `begin_date` | `20160101`(**裸整数**) | `"2016-01-01"`、`"20160101"`(字符串) | 400 类型错误 | ## 注意事项 1. **`task_id` 绑定整批**:`runMultiFormulaBatchStream` 返回的 `task_id` 需传给后续 `readData` 的 `ids`(或用返回的 `data_id`) 2. **`begin_date` 必传**:业务侧不再依赖服务端默认值;按上方「begin_date 分档方案」选择最小够用的起点,禁止默认 `20150101` 3. **回测公式**:`回测()` 第一参数必须是二维矩阵(信号掩码),不能是一维 4. **单批公式数**:硬上限 **20 条/批**,超时时减少数量分批执行 --- ## 盘中刷新模式 > **按用户需要选择频率,禁止把 `use_minute_data: true` 当成全局默认。** > > 仅当用户明确要求**盘中、当前分钟或当日未收盘**行情时设为 `true`。历史区间收益、回测、净值、最大回撤、波动率或历史日线必须使用 `use_minute_data: false` 或省略;财务报告期同样必须为 false 或省略。开启后,7 个日频行情数据名的**最新一列**会自动替换为“最后一分钟更新版”,因此它只服务于需要当前盘中截面的场景。 ### 机制 1. **数据名自动映射**:以下 7 个日频行情数据名会被服务端自动替换为分钟频数据源,其他数据名(如 PE、换手率、财务指标)不受影响,走正常解析。 | 数据名 | 分钟频数据 ID | |--------|---------------| | 全市场每日收盘价 | `69ca3df24f760ff70564dc10` | | 全市场每日开盘价 | `69ca3df24f760ff70564dc11` | | 全市场每日最高价 | `69ca3df24f760ff70564dc12` | | 全市场每日最低价 | `69ca3df24f760ff70564dc13` | | 全市场每日成交量 | `69ca3df24f760ff70564dc14` | | 全市场每日成交额 | `69ca3df24f760ff70564dc15` | | 全市场每日收盘价(不复权) | `69ca3df24f760ff70564dc20` | 2. **Snapshot Time 管理**:首次调用时自动获取分钟数据截止时间并缓存当天。同一天内多次执行默认复用当天缓存。若确实需要强制刷新截止时间,先调用独立工具 `refreshSnapshotTime`,再执行 `runMultiFormulaBatchStream`。 ### 调用示例 ```json // 盘中实时:当前涨幅前10 { "formulas": ["日内涨幅=涨跌幅(\"全市场每日收盘价\")", "TOP10=取前(\"日内涨幅\", 10, 从大到小)"], "task_id": "uuid-xxx", "use_minute_data": true } ``` ### 注意 - 公式中的数据名写法**不变**——仍写 `"全市场每日收盘价"`,映射由服务端透明完成 - 非映射表内的数据(PE、换手率、财务指标等)不受影响,日频和分钟频可混合使用 - `use_minute_data` 的 API 默认值是 false。只有盘中、当前分钟或当日未收盘请求传 `true`;历史日频分析、历史区间收益、回测、净值、最大回撤、波动率、财务报告期传 `false` 或省略。图表请求本身不会自动开启分钟模式 - ⛔ **禁止向 `runMultiFormulaBatchStream` 传 `refresh_snapshot_time`**——该参数已移除;需要强制刷新时调用独立工具 `refreshSnapshotTime` - ⛔ **禁止在公式中使用原生分钟数据名**(如 `"全市场1分钟收盘价"`、`"全市场1分钟成交额"` 等)——这些是平台底层数据源,不在映射表中,使用会导致维度不匹配或 HTTP 500 错误 - ⛔ **禁止使用 `变频到分钟()` 函数**——`use_minute_data` 模式下数据名映射由服务端完成,不需要手动变频 --- ## 任务级 research_24h 传播规则 `execution_profile` 是**任务级**标签,不是批次级。拆批前先对整条公式链做一次判定,命中任一条即 `is_research_job = true`: 1. `user_query` 含 `回测`/`策略`/`PnL`/`净值`/`超额收益`/`重算`/`批量`/`研究`(中英文等价) 2. 完整公式计划中任一条含 `回测(` 3. 全部批次累计公式数 > 20 4. 拆批后 `runMultiFormulaBatchStream` 调用次数 ≥ 3 判定为 true 时,**该 task_id 下所有批次**(含前置准备批、FIX/重试批)都必须显式传 `execution_profile: "research_24h"` + `user_query`。 --- ## deferred 响应与 resumeJob 续传 `research_24h` 模式下,提交后立即返回 `status:"deferred"`。**必须**用 `resumeJob` 续传到 `done`/`fatal` 才算该批完成: 1. 提交 → 收到 `deferred` → 保存 `task_id` + `trace_id` 2. 立即调用 `resumeJob`(传 `task_id` + `trace_id`),等 `done` 拿完整结果 3. 若返回 `STREAM_INTERRUPTED` → 用 `partial.last_event_id` 作 `since` 参数再调 `resumeJob`,最多额外重试 2 次 若 deferred 响应缺少 `task_id` 或 `trace_id`,客户端返回 `DEFERRED_CONTINUATION_MISSING`。此时不得原样重提整批,以免创建孤儿任务或重复计算。 4. 拿到 `done` 后才可进入下一批 **禁止**:`deferred` 当完成收尾、跳过续传直接下一批、`STREAM_INTERRUPTED` 后放弃。详见 `tools/resume_job.md`。 --- ## 出错时怎么办:先看 `category`,别误把后端超时当公式错 报错(整批 `error.category`,或单条 `errors[].category`)都带统一标签,**处理方式按 `category` 分四类**: | `category` | 能重试 | 动作 | |------------|--------|------| | `input_error` | 否 | 改公式/参数再试;按 `errors[].leftName` + message 定位错在哪条 | | `server_timeout` | 否 | **不是公式问题**——别改公式、别马上重试;隔几分钟再试 / 拆小批 / 改 `research_24h` 异步模式 | | `server_error` | 否 | 隔一会儿试一次,仍失败告诉用户 | | `transport_recoverable` | 是 | **唯一该续传重试的**:用 `resumeJob`(task_id+trace_id)接着读,别重提整批 | > 关键区分:`server_timeout`(任务超时、多半已死,重试/续传都没用,要拆小批或异步)≠ `transport_recoverable`(任务还活着、只是连接断了,该 `resumeJob` 续传)。两者过去长得一样,是误导的根源。完整对照见 `references/troubleshooting.md`。 > > ⚠️ **`NOT_RESOLVED`(函数/资产名无法解析)属 `input_error`,且会「整批受牵连」**:只要批里有一条解析不了,整批都会被标 `NOT_RESOLVED`(含本来正确的公式)。看到整批 `NOT_RESOLVED` 别以为每条都错——逐条核对、改掉真正出错那条即可。 -
scan_dimensions.md 4.5 KB
# scanDimensions — 九维度 IC 扫描 对单只股票执行八大维度的 IC 预测力全量扫描,返回每个维度的核心指标当前值、IC_IR(历史预测力)、综合评分及季节性统计。 **结果自动保存到** `output/ic_data/{股票名}_dimension_ic.json`,同时打印精简摘要。 --- ## 请求参数 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `asset` | object | ✅ | `{"name": "中控技术", "code": "688777.SH"}` | | `industry` | string | 可选 | 行业指数名,如 `"自动化设备(申万)"` | | `dimensions` | string\[\] \| `"all"` | 可选 | 指定要扫描的维度;缺省或 `null` = 全量 8 维度 | | `begin_date` | number | 可选 | 历史计算起始日 YYYYMMDD,默认 `20160101` | | `task_id` | string | 可选 | 由 `call.py` 自动从 `.session.json` 注入 | **`dimensions` 合法值**: `D1_估值` / `D3_资金` / `D4_波动率` / `D5_宏观胜率` / `D6_相关资产` / `D7_技术形态` / `D8_季节性` / `D9_财务` --- ## 调用规范 > **🚫 禁止全量调用**(`dimensions` 参数缺省、`null` 或 `"all"`)——会触发所有 8 维度并行计算,耗时 3-5 分钟,体验极差。 > **✅ 必须每次只传一个维度**,扫完立即读取结果,再扫下一个。结果自动合并到同一 JSON,不覆盖已有维度。 --- ## 标准调用顺序(每次一个维度,执行后立即读取结果) ```bash # ① 技术形态(~40 RU,最快) python scripts/call.py scanDimensions '{"asset":{"name":"中控技术","code":"688777.SH"},"dimensions":["D7_技术形态"]}' # ② 估值(需要 industry 参数) python scripts/call.py scanDimensions '{"asset":{"name":"中控技术","code":"688777.SH"},"industry":"自动化设备(申万)","dimensions":["D1_估值"]}' # ③ 资金 python scripts/call.py scanDimensions '{"asset":{"name":"中控技术","code":"688777.SH"},"dimensions":["D3_资金"]}' # ④ 波动率 python scripts/call.py scanDimensions '{"asset":{"name":"中控技术","code":"688777.SH"},"dimensions":["D4_波动率"]}' # ⑤ 宏观胜率 python scripts/call.py scanDimensions '{"asset":{"name":"中控技术","code":"688777.SH"},"dimensions":["D5_宏观胜率"]}' # ⑥ 相关资产 python scripts/call.py scanDimensions '{"asset":{"name":"中控技术","code":"688777.SH"},"dimensions":["D6_相关资产"]}' # ⑦ 财务(~90 RU,公式最多) python scripts/call.py scanDimensions '{"asset":{"name":"中控技术","code":"688777.SH"},"dimensions":["D9_财务"]}' # ⑧ 季节性(纯统计,最快) python scripts/call.py scanDimensions '{"asset":{"name":"中控技术","code":"688777.SH"},"dimensions":["D8_季节性"]}' ``` > **合并机制**:每次调用结束后,`call.py` 自动读取已有 JSON → 合并新维度 → 重算 `overall_score` / `overall_signal` / `top_dimension` / `bottom_dimension` → 写回文件。摘要输出中的 `file_has_dimensions` 字段显示文件当前已积累几个维度,`note` 字段提示还剩哪些未扫。 --- ## 响应结构(精简摘要,完整数据见 JSON 文件) ```json { "code": 0, "data": { "asset": {"name": "中控技术", "code": "688777.SH"}, "scan_date": 20260317, "overall_score": 52, "signal": "中性 ->", "dimensions": { "D1_估值": { "score": 48, "signal": "中性", "indicators": [ {"name": "PE水位", "current": 0.91, "ic_ir": -0.45}, {"name": "PB水位", "current": 0.76, "ic_ir": -0.38} ] }, "D7_技术形态": { "score": 63, "signal": "偏强", "indicators": [ {"name": "MACD金叉", "current": 1, "ic_ir": 2.10}, {"name": "站上MA20", "current": 1, "ic_ir": 1.85} ] } }, "saved_to": "output/ic_data/中控技术_dimension_ic.json", "note": "完整结果已保存到文件,直接使用以上数据构建报告" }, "task_id": "..." } ``` --- ## RU 成本 | 扫描范围 | 估算 RU | |----------|---------| | 全量 8 维度 | ~320 RU | | 单维度(如 D7) | ~68 RU | | 自定义 3 维度 | ~150 RU | 计费公式:`base(26) + Σdim_ru`,各维度 RU:D1=36 / D3=48 / D4=30 / D5=30 / D6=24 / D7=42 / D8=6 / D9=84 --- ## 注意事项 - 扫描期间**不要并发执行其他 API 调用**,避免 `.session.json` 并发写入导致 task_id 漂移 - **执行前先检查缓存**:若 `output/ic_data/{股票名}_dimension_ic.json` 已存在且为今日,直接读取,跳过调用 - 结果 JSON 包含所有维度的完整指标数据,Step 4 写报告直接从中取值,无需再调 `readData` -
search_functions.md 2.8 KB
# search_functions — 函数检索 > 当不确定某个函数是否存在、或其调用格式/参数时使用。返回函数的中文名、调用格式、示例和说明。 ## 端点 `POST /skill/searchFunctions` ## 参数 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `query` | string | ✅ | 检索关键词(中英文均可),支持空格/逗号/\|分隔多关键词 | | `top_k` | number | ❌ | 返回条数,默认 8,最大 20 | | `detail_level` | string | ❌ | `brief`(更短)/ `normal`(默认)/ `full`(top_k≤3时建议用) | | `task_id` | string | ❌ | 任务ID(UUID) | ## 返回 ```json { "code": 0, "data": { "functions": [ { "name": "回测", "internal_name": "backtest", "format": "回测(信号矩阵, [基准], [费率])", "short_instruction": "按列归一化信号,等权买入,返回复利净值曲线", "example": "NAV=回测(\"Signal\")", "notes": "第一参数必须是二维矩阵(资产×日期)" } ] }, "task_id": "uuid-xxx" } ``` ## 调用示例 ```bash # 查回测函数 python scripts/executor.py searchFunctions '{"query": "回测", "top_k": 3, "detail_level": "full"}' # 查均线相关 python scripts/executor.py searchFunctions '{"query": "均线 MA rolling"}' # 查延迟/移位函数 python scripts/executor.py searchFunctions '{"query": "延迟 shift lag"}' # 查缺失值处理 python scripts/executor.py searchFunctions '{"query": "缺失填零 fillna"}' # 多关键词 python scripts/executor.py searchFunctions '{"query": "波动率|标准差|std"}' ``` ## 常用函数速查(高频) | 函数名 | 格式 | 说明 | |--------|------|------| | `平均` | `平均("数据", N)` | N日均线;平台无 `MA()` 函数 | | `回测` | `回测("Signal")` | 等权回测,返回净值 | | `延迟` | `延迟("数据", N)` | 按自然日延迟 N 天 | | `缺失填零` | `缺失填零("数据")` | NaN → 0 | | `取前` | `取前("数据", N)` | 截取前 N 条 | | `板块` | `板块(万得全A)` | 生成板块掩码(注:板块名不加引号) | | `或` | `或(A, B)` | 取并集(不用 A+B) | ## ⚠️ 常见参数错误 | 错误写法 | 正确写法 | 说明 | |---------|---------|------| | `{"keyword": "MA均线"}` | `{"query": "MA均线"}` | 参数名是 `query`,不是 `keyword` | | `{"intent": "移动平均"}` | `{"query": "移动平均"}` | 参数名是 `query`,不是 `intent` | | `{}` (空 body) | `{"query": "回测"}` | `query` 是必填参数 | ## 注意事项 - 函数参数为**资产/板块名**时不加引号:`板块(万得全A)`、`收盘价(贵州茅台)` - 引用变量/数据名必须用双引号:`"全市场每日收盘价"`、`"Signal"` - `延迟` 按**自然日**计算(不是交易日) -
search_similar_cases.md 2.2 KB
# search_similar_cases — 搜索相似案例 > **必须首先调用此工具**。在设计任何选股/回测策略前,先搜索已验证的公式模板,可显著提高公式编写准确率。 ## 端点 `POST /skill/searchSimilarCases` ## 参数 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `query` | string | ✅ | 业务场景描述,如 "均线金叉"、"回测收益曲线"。用通用描述,避免过于具体 | | `top_k` | number | ❌ | 返回案例数,默认 3,最大 5 | | `task_id` | string | ❌ | 任务ID(UUID),不传则自动生成 | ## 返回 ```json { "code": 0, "data": { "cases": [ { "description": "案例描述", "formulas": ["A=公式1", "B=公式2", "..."], "syntax_tips": ["注意事项1", "注意事项2"], "match_quality": "high | medium | low" } ] }, "task_id": "uuid-xxx" } ``` ### match_quality 处理规则 | 值 | 处理方式 | |----|----------| | `high` | 直接参考,可直接套用公式模板 | | `medium` | 参考模板结构,适当修改 | | `low` | **必须**换一个 query 重试(用核心函数名或通用场景描述),最多重试 2 次 | ## 调用示例 ```bash # 搜索均线策略 python scripts/executor.py searchSimilarCases '{"query": "均线金叉选股回测", "top_k": 3}' # 搜索因子类策略 python scripts/executor.py searchSimilarCases '{"query": "价值因子低市盈率选股"}' # 搜索相关性分析 python scripts/executor.py searchSimilarCases '{"query": "商品价格与股票涨幅相关性分析"}' ``` ## ⚠️ 常见参数错误 | 错误写法 | 正确写法 | 说明 | |---------|---------|------| | `{"params": {}, "body": {"query": "均线金叉"}}` | `{"query": "均线金叉"}` | 禁止多层嵌套,直接平铺参数 | | `{"keyword": "均线"}` | `{"query": "均线"}` | 参数名是 `query`,不是 `keyword` | ## 注意事项 - query 建议使用**通用业务场景描述**,避免包含具体资产名(如"贵州茅台"、"铜期货") - 搜索技巧:通用场景 → 核心函数组合 → 技术细节(逐步细化) - 返回的 `formulas` 数组可直接传给 `runMultiFormulaBatchStream`,是已验证的公式 -
select_by_composition.md 6.7 KB
# selectByComposition 工具说明 > 工具名:`selectByComposition` > 用途:读取已物化且当前可选的千维指标末列,做横截面组合选股或筛选。不走公式引擎。 ## 何时使用 当用户的问题可以映射到一个或多个已物化的 score / screen 指标时,优先使用本工具。例如: - “A股动量与反转分数最高的10只股票”; - “短期高低点抬升、RSI强而不过热的10只 A 股”; - “今日交易异动名单”。 以下情况改走 `quant-standard.md`: - 用户指定了临时公式口径,如“近60日涨幅减近20日涨幅”; - 需要回测、净值曲线、IC、历史时序、下载公式结果; - 本地快照和在线目录均找不到已物化、可选且角色匹配的指标; - 用户要求盘中/实时分钟级横截面排名,而不是已物化日频指标。 `indicator_type` 的“综合 / 细分”是分类信息;两个分类都可以进入本工具。是否可执行取决于服务端的启用、删除、状态、物化、市场范围及快照日期是否已知,指标放在何处则取决于 `output_type`。 ## 必要前置 1. 先调用 `newSession`。 2. 本地 `presets/dimensions.yaml` 可用于快速发现已知候选;它不是实时可选性的证明,不能据此自造 ID。 3. 细分指标、本地未命中项或可选性错误时,使用 `listDimensionIndicators` 在线确认候选的 `indicator_id`、`output_type`、`selection_ready`、`selection_mode`、`as_of` 和 `asset_scope`。 4. 按 `output_type` 组织请求: - `score`:放入 score 模式的 `composition` 或 `score_filters`;在 screen 模式中仅能作为 `sort_by`; - `screen`:放入 `screens`。 ## 参数 ### score 模式:分数排名,可叠加 screen 交集和 score 阈值 ```json { "mode": "score", "universe": { "asset_scope": "A股" }, "composition": [ { "indicator_id": "ind_a_share_momentum_reversal", "weight": 1 } ], "screens": [ { "indicator_id": "<screen-indicator-id>" } ], "score_filters": [ { "indicator_id": "ind_a_share_relative_strength", "op": ">=", "value": 0.8 } ], "top_n": 10, "with_breakdown": true, "breakdown_format": "compact", "task_id": "<newSession 返回的 task_id>", "user_query": "<用户原问题>" } ``` ### screen 模式:筛选集合,可选按 score 排序 ```json { "mode": "screen", "universe": { "asset_scope": "A股" }, "screens": [ { "indicator_id": "ind_a_share_daily_trading_abnormal" } ], "sort_by": { "indicator_id": "ind_a_share_trend_structure", "order": "desc" }, "top_n": 10, "task_id": "<newSession 返回的 task_id>", "user_query": "<用户原问题>" } ``` 字段说明: - `mode`:`score` 或 `screen`,默认 `score`。 - `universe.asset_scope`:目标市场,使用 `A股` / `港股` / `美股` / `期货`。服务端将它用于指标范围校验,也会限制返回的实际证券范围。 - `composition`:score 模式必填。每项为一个 score 指标及正权重;权重默认 1,服务端会归一化。 - `screens`:screen 模式必填;score 模式可选,用于与排名对象求交集。 - `score_filters`:仅 score 模式可用。每项是 score 指标与 `>=`、`>`、`<=`、`<` 之一,阈值范围为 `[0, 1]`,比较的是服务端归一化后的数值。 - `sort_by`:仅 screen 模式可用,必须为 score 指标;`order` 可取 `asc`、`desc` 或 `abs_desc`,默认 `desc`。 - `top_n`:返回数量,默认 30,最大 500;`composition`、`screens`、`score_filters` 各最多 8 项。 - `with_breakdown`:仅 score 模式生效,控制是否返回贡献拆解。 - `breakdown_format`:`compact`(默认)或 `verbose`。compact 的 `top_contributors[].composition_index` 对应顶层 `composition_used` 的数组下标。 同一请求中的所有指标都必须有已知的 `as_of`,但允许不同日期的已物化指标组合。响应中的 `date_alignment` 会逐项列出实际快照;`date_alignment_status` 为 `aligned` 时全局 `as_of` / `last_date` 有值,为 `mixed` 时两个全局字段均为 `null`,不得将结果描述为截至某个统一日期。 ## 典型调用 ### A股动量与反转 Top10 ```powershell $env:GZQ_PARAMS='{"mode":"score","universe":{"asset_scope":"A股"},"composition":[{"indicator_id":"ind_a_share_momentum_reversal","weight":1}],"top_n":10,"with_breakdown":true,"task_id":"<task_id>","user_query":"A股中选出动量与反转分数最高的10个股票"}' python scripts/call.py selectByComposition ``` ### 短期高低点抬升且 RSI 强而不过热的 A 股 Top10 ```json { "mode": "score", "universe": { "asset_scope": "A股" }, "composition": [ { "indicator_id": "ind_a_share_rsi_strong_not_overheated", "weight": 1 } ], "screens": [ { "indicator_id": "ind_a_share_short_term_high_low_lift" } ], "score_filters": [ { "indicator_id": "ind_a_share_rsi_strong_not_overheated", "op": ">", "value": 0 } ], "top_n": 10 } ``` 该请求使用细分 screen 指标限制“短期高低点抬升”,以细分 score 指标筛掉 RSI 非正样本并完成排序;提交前仍需用在线目录确认两项指标处于可选、具有已知快照日期的物化状态。 ## 输出要点 最终回答必须展示: - 数据时点:优先使用返回的 `as_of`、`date_alignment_status` 与 `date_alignment`;当状态为 `mixed` 时逐项披露快照日期,不能写为单一“数据截至”日期; - 组合口径:来自 `composition_used` 或 `screens_used`; - TopN 表格:排名、名称、代码、score,或 screen 的命中状态与 `sort_score`; - 若有 `top_contributors`,可用一列或简短说明解释贡献; - 声明:已物化指标只代表对应指标口径,不构成投资建议。 ## 失败处理 - `INDICATOR_NOT_FOUND`:请求的指标 ID 不存在。使用在线目录找到准确 ID,不要原样重试。 - `INDICATOR_NOT_SELECTABLE`:指标已知但当前不可执行。读取响应中的 `reasons`,例如 `disabled`、`deleted`、`status_not_success`、`not_materialized`、`vector_not_ready`、`unsupported_scope`;换用新的已确认候选或退回公式路径。 - `INDICATOR_DATE_MISMATCH`:至少一个参与指标缺少可用的 `as_of`;换用具有已知快照日期的候选。不同但有效的快照日期可以组合,须按 `date_alignment` 披露。 - `INVALID_SCORE_INDICATOR`、`INVALID_SCREEN_INDICATOR`、`INVALID_SCORE_FILTER`、`INVALID_SORT_INDICATOR`:指标放错了 `output_type` 对应的请求位置,按本文件“必要前置”重新组织。 - 404 / Not Found / Unknown tool:当前服务端或工具 schema 未部署本接口。可公式复刻的需求退回 `quant-standard.md`,否则输出受控失败。 -
stock_profile.md 7.1 KB
# stockProfile — 个股指标画像 > **实际工具名:`stockProfile`**。禁止调用 `stock_profile` / `getStockProfile` / `stockProfileQuery` 等名称变体。 一次调用返回单只股票在评级表系统中的预计算及千维动态指标画像。预计算指标来自 `smartstock_indicator_latest`,千维最新值来自 `qw_indicator`,千维上一有效值来自 `qw_indicator_history`;按维度分组返回最新值、上一有效值、日期和单位。本工具是纯 DB 查询,不执行自定义公式,不做 IC 扫描。 ## 适用 / 不适用 | 适用 | 不适用 | |---|---| | 分析一下 XX 个股 | 最新价、PE、ROE 等单字段快查 → `fast_query` | | 个股画像、指标概览 | 最近 N 日序列 / 窗口统计 → `fast_query` | | 估值、财务、资金、波动率、走势综合看一下 | 多股票批量比较、选股、回测、自定义公式 → 对应量化 workflow | | 单只股票全维度预计算指标概览 | IC / 预测力 / 哪个维度有效 → `scanDimensions` | | 最新值 + 上一有效值对比 | 投资建议、目标价、价格预测 → safe-fail | ## 参数 | 参数 | 必填 | 说明 | |---|---|---| | `asset` | ✅ | 资产名称或代码,如 `贵州茅台` / `600519.SH` / `苹果` / `AAPL` / `小米` / `01810.HK` | | `user_query` | ✅ | 用户原始问题,用于 trace;调用时携带原文 | | `task_id` | 自动注入 | 当前 session 的 task_id,由调用层注入,不需要手写 | 调用示例: ```json { "asset": "贵州茅台", "user_query": "分析一下贵州茅台这只股票" } ``` ## 返回结构 响应经过压缩:维度内统一的 `latest_date`、`unit` 提升到维度层级;同一基础指标的衍生变体(百分位、builder 变体等)折叠到 `variants` 子对象;数值已按精度 round,不返回 `data_id` 和 `decimals`。 ```text code: 0 task_id data: asset: code / name / ticker computed_at indicators_count dimensions: 维度名: latest_date ← 维度内统一时出现,省略则看各指标自身 unit ← 维度内统一时出现 indicators: base_id: name latest_value latest_date ← 与维度层不同时出现 unit ← 与维度层不同时出现 description ← 千维动态指标可能出现 previous_value ← 预计算上一期值或千维上一有效值 previous_date ← 对应的上一期 / 上一有效日期 variants: ← 有衍生变体时出现 suffix: value date ← 与父指标不同时出现 pre_value ← period_change 类变体 pre_date ← 与父指标不同时出现 ``` ### 维度层字段 | 字段 | 说明 | |---|---| | `latest_date` | 维度内所有指标日期一致时提升到此层(YYYYMMDD) | | `unit` | 维度内所有指标单位一致时提升到此层(倍、%、元、港元、美元) | ### 指标字段(`indicators[base_id]`) | 字段 | 说明 | |---|---| | `name` | 指标展示名 | | `description` | 千维动态指标的口径说明;预计算指标通常不返回 | | `latest_value` | 最新值;预计算数值已 round,布尔型千维指标命中时可能返回 description 文本 | | `latest_date` | 最新值日期(YYYYMMDD),与维度层一致时省略;财务指标可能是报告期 | | `unit` | 单位,与维度层一致时省略 | | `previous_value` | 预计算指标的上一期值,或千维动态指标严格早于 `latest_date` 的上一有效值 | | `previous_date` | 对应的上一期 / 上一有效日期(YYYYMMDD);历史不足时与 `previous_value` 一并省略 | | `variants` | 衍生变体子对象,key 为变体后缀(如 `pctrank:1Y`、`quarter_yoy`、`ret:20`) | ### 变体字段(`variants[suffix]`) | 字段 | 说明 | |---|---| | `value` | 该变体最新值 | | `date` | 日期(YYYYMMDD),仅当与父指标 `latest_date` 不同时出现 | | `pre_value` | 上一期值,仅 `period_change` 类变体 | | `pre_date` | 上一期日期,仅当与父指标 `previous_date` 不同时出现 | ## 维度口径 | 维度 | 常见指标 | |---|---| | 估值 | `pe_ttm`、`pb_ratio`、`ps_ttm`、`dividend_yield` 及百分位(A股/港股/美股均覆盖) | | 财务分析 | 毛利率、净利率、营业利润率、收入增速、ROE、ROIC、经营现金流等及 builder 变体(`:quarter_yoy`/`:ttm_level`/`:annual_yoy` 等) | | 资金流向 | 成交额占比、换手均线、卖空比例、基金持仓等 | | 波动率 | 年化波动率、标准差及百分位 | | 宏观胜率背景 | 行业估值、情绪、换手等背景指标 | | 资产走势 | `close_price` 及 `:ret:20/60/120/250` | | 千维动态维度 | 后端 `qw_dimension` 返回的市场动态维度,例如相对强度、趋势结构、量能与流动性 | | 其他 | 新增但尚未映射到固定维度的指标 | ## 千维上一有效值 - 数值型千维指标的 `previous_value` 来自同一指标严格早于 `latest_date` 的最近有效历史值,`previous_date` 是该值对应日期。 - 历史同日快照不会作为前值;空值或无效值会继续向更早日期查找,`0` 是有效值。 - 布尔型千维指标当前未命中时不会出现在结果中;其 `previous_*` 表示上一次命中及日期。 - 没有有效历史时只展示 `latest_*`,不要把字段缺失解释为 0,也不要再调用 `fast_query` 或公式链补算。 ## 日内刷新注意 `close_price` 支持分钟级刷新。若后端 `realtime_value` 存在且 `realtime_computed_at` 距今小于 2 小时,返回的 `latest_value` 可能是分钟刷新值;否则是日频管线值。其他指标始终使用日频管线值。 回答时不要自行判断是否实时,除非返回字段能直接证明。可表述为“接口返回的最新值为……”。 ## 错误处理 | 返回情况 | 处理 | |---|---| | `TASK_ID_REQUIRED` | 当前 session 异常;重新 `newSession` 后再试 | | `ASSET_REQUIRED` | 参数缺失,修正为只传一个明确资产 | | `ASSET_NOT_FOUND` | 告知资产无法识别,不用相似股票替代 | | `indicators_count = 0` 或 `warnings` | 告知该资产暂无预计算指标数据,不改用模型常识补写 | | 某维度 `indicators` 为空 | 说明该维度本轮无返回指标,跳过该维度 | | HTTP 500 / 网络错误 | 受控失败,不改用外部网页或训练知识猜测 | ## 输出规则 1. 首句直接给指标画像结论,不说”已成功获取”。 2. 只引用返回的维度和指标,不补写未返回字段。 3. 对用户关注的维度优先展开;未明确关注时按”资产走势 → 估值 → 财务分析 → 资金流向 → 波动率 → 宏观胜率背景 → 千维动态维度”排序。 4. 每个指标尽量包含:指标名、最新值、日期、上一期 / 上一有效值。日期和单位可能在维度层级,注意合并读取。 5. `variants` 中的变体按需展开:用户问估值时展开百分位,问财务时展开 yoy/qoq/ttm 等口径。不必列出全部变体。 6. 不提供买卖建议、目标价或后市价格预测。 -
upload_data.md 3.3 KB
# upload_data — 上传自有因子数据 > 将本地 CSV 格式的一维时序数据(如自研因子、宏观指标)上传到平台,上传后可在公式中直接引用。 > `executor.py` 的 `uploadData` 工具自动处理两阶段(preview + confirm),无需手动调用两次。 ## 端点 - **第一阶段(Preview)**:`POST /skill/upload/preview` - **第二阶段(Confirm)**:`POST /skill/upload/confirm` (使用 `executor.py uploadData` 时自动完成两阶段) ## CSV 格式要求 ```csv date,value 20240101,1.23 20240102,1.45 20240103,1.67 ``` - 必须有 2 列:第一列日期(`YYYYMMDD` 格式整数),第二列数值 - 编码:UTF-8 或 GBK(自动检测) - 文件名即为数据名(可用 `data_name` 参数覆盖) ## 参数(executor.py uploadData) | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | `file_path` | string | ✅ | 本地 CSV 文件绝对路径 | | `data_name` | string | ❌ | 数据名称,默认取文件名(不含扩展名) | | `description` | string | ❌ | 数据描述 | | `on_conflict` | string | ❌ | 同名冲突处理:`error`(默认,报错)/ `overwrite`(覆盖)/ `rename`(自动重命名) | ## 返回 ```json { "code": 0, "data": { "index_id": "60a1b2c3d4e5f6a7b8c9d0e1", "data_name": "我的动量因子.csv", "rows_written": 1250, "message": "上传成功" } } ``` - `index_id`:上传后的数据ID,可用于 `downloadData` 或 `readData` - 上传成功后可通过 `confirmDataMulti` 按数据名查找到该数据 ## 调用示例 ```bash # 基础上传 python scripts/executor.py uploadData '{ "file_path": "C:/data/momentum_factor.csv", "data_name": "20日动量因子", "description": "过去20个交易日收益率" }' # 覆盖同名数据 python scripts/executor.py uploadData '{ "file_path": "C:/data/my_factor.csv", "data_name": "我的因子", "on_conflict": "overwrite" }' # 自动重命名(避免覆盖) python scripts/executor.py uploadData '{ "file_path": "C:/data/my_factor.csv", "on_conflict": "rename" }' ``` ## 上传后在公式中使用 ```bash # 先确认数据名(获取 index_title) python scripts/executor.py confirmDataMulti '{"data_desc": "我的动量因子"}' # 在公式中引用(用 index_title) python scripts/executor.py runMultiFormulaBatchStream '{ "formulas": [ "MyFactor=\"我的动量因子.csv\"", "Signal=(\"MyFactor\">0)*板块(万得全A)*\"非ST\"" ], "task_id": "uuid-xxx" }' ``` ## 注意事项 - 上传依赖后端 Celery Worker 异步处理,正常 2-10 秒内完成(超过 20s 超时报错) - `provider` 自动设为 `mydata`,只有上传者本人可查看/下载 - 数据名在系统中以 `.csv` 后缀存储(`我的因子` → `我的因子.csv`) - 单次上传建议不超过 10MB ## 手动两阶段上传(高级用法) 如需在 preview 后检查数据再决定是否 confirm: ```bash # 第一步:预览 python scripts/executor.py uploadData '{"file_path": "data.csv", "data_name": "测试"}' # → 只传 preview 参数时手动处理 # 第二步:确认(使用 preview 返回的 file_token) python scripts/executor.py uploadConfirm '{ "file_token": "abc123:1709000000:sig...", "data_name": "测试", "on_conflict": "error" }' ```
-
-
workflows
-
composition-select.md 5.3 KB
# Composition Select Workflow — 已物化指标组合选股 > 目标:对已物化、当前可选的指标做当前截面 TopN、排名或名单筛选,调用 `selectByComposition`。本流程不写公式、不调 `confirmDataMulti`、不调 `runMultiFormulaBatchStream`。 ## 1. 命中条件 同时满足: - 用户要的是当前截面 TopN / 排名 / 名单 / 推荐; - 用户条件可映射为已物化的 `score` 或 `screen` 指标; - 不要求回测、历史曲线、IC、下载、自定义公式、盘中分钟级实时排名。 不能满足任一条件时,退回 `quant-standard.md`。 > `presets/dimensions.yaml` 是本地候选快照,不是服务端可选状态的权威来源。它适合零网络开销的常见指标映射;细分指标、本地未命中的指标,或快照失效后的替代候选,必须通过 `listDimensionIndicators` 在线确认后才能调用选择器。 ## 2. 指标可选性与角色 `indicator_type`(`细分` / `综合`)只描述指标分类,**不是** `selectByComposition` 的准入条件。服务端在执行时校验:指标已启用、未删除、`status:"success"`、已物化且向量可用、支持 `universe.asset_scope`,并要求每个参与指标具有已知的 `as_of`;不同有效快照可以组合。 请求位置由 `output_type` 决定: | `output_type` | 可用位置 | |---|---| | `score` | score 模式的 `composition`、`score_filters`;screen 模式的 `sort_by` | | `screen` | `screens` | 不能把 screen 指标放入 `composition`,也不能把 score 指标放入 `screens`。 ## 3. 执行步骤 1. 调 `newSession`。 2. 先从 `presets/dimensions.yaml` 解析明确的常见候选;禁止凭名称自造 `indicator_id`。 3. 出现以下任一情形时,使用 `listDimensionIndicators` 做窄查询:用户指定细分指标、本地快照无匹配项、或上次选择器返回可选性错误。 - 按实际需要组合 `asset_scope`、`keyword`、`output_type` 和 `indicator_type`;`indicator_type` 仅用于缩小检索范围。 - 读取候选的 `output_type`、`selection_ready`、`selection_mode`、`as_of`、`asset_scope` 和异常 `status` 字段。 - 在线目录用于预检;`selectByComposition` 才是启用、删除、物化和快照日期有效性的最终权威。 4. 按角色构造请求: - 需要按分数排名时使用 `mode:"score"`,`composition` 至少包含一个 score 指标;可同时传 `screens` 求交集,及用 `score_filters` 限制 score 指标的归一化数值。 - 需要返回满足条件的集合时使用 `mode:"screen"`,将条件放入 `screens`;可选的 score 指标放入 `sort_by` 仅用于排序。screen 模式不得传 `composition` 或 `score_filters`。 - `universe.asset_scope` 使用目标市场(`A股` / `港股` / `美股` / `期货`);它会限制最终返回的实际证券范围。 - `top_n` 使用用户指定数量,未指定时取 10;每个 `composition`、`screens`、`score_filters` 最多 8 项。 - 需要贡献解释时在 score 模式传 `with_breakdown:true`;默认返回 `breakdown_format:"compact"`,需要旧式完整贡献对象时显式传 `"verbose"`。 5. 输出结果时使用服务端返回的 `as_of`、`date_alignment_status`、`date_alignment`、`composition_used` 或 `screens_used`,不要自行推断数据日期。若 `date_alignment_status:"mixed"`,逐项披露 `date_alignment`,且不得把结果写成截至单一日期。 6. 失败处理: - `INDICATOR_NOT_FOUND`:指标 ID 不存在;重新通过在线目录定位准确候选。 - `INDICATOR_NOT_SELECTABLE`:读取服务端返回的 `reasons`(如 `disabled`、`not_materialized`、`unsupported_scope`);禁止原参数重试,可换已确认候选或退回公式路径。 - `INDICATOR_DATE_MISMATCH`:至少一个参与指标缺少可用的 `as_of`;换用具有已知快照日期的候选。不同但有效的快照日期允许组合,须按 `date_alignment` 披露。 - `INVALID_SCORE_INDICATOR`、`INVALID_SCREEN_INDICATOR`、`INVALID_SCORE_FILTER`、`INVALID_SORT_INDICATOR`:按 `output_type` 重新分配请求位置。 - 404 / Unknown tool / Not Found:服务端或工具 schema 未部署;对可公式复刻的需求退回 `quant-standard.md`,否则输出受控失败。 ## 4. 默认映射 | 用户说法 | indicator_id | `output_type` | |---|---|---| | A股动量与反转 / 动量反转分数 | `ind_a_share_momentum_reversal` | score | | A股趋势结构 | `ind_a_share_trend_structure` | score | | A股相对强度 | `ind_a_share_relative_strength` | score | | A股当日交易异动 / 异动名单 | `ind_a_share_daily_trading_abnormal` | screen | | A股当日异动评分 | `ind_a_share_daily_abnormal_score` | score | 本表仅为常见映射;以本地快照或在线目录实际返回的 `indicator_id` 与 `output_type` 为准。 ## 5. 输出格式 日期对齐时,首句可直接给结论或标题,例如:`A股动量与反转 Top10(数据截至 YYYYMMDD)`;`date_alignment_status:"mixed"` 时应改为“指标快照日期见下方说明”,并逐项展示 `date_alignment`。 表格至少包含: - 排名; - 名称(代码); - score,或 screen 的命中状态与可选排序分; - 主要贡献/口径(服务端返回且用户需要时展示)。 结尾声明:已物化指标只代表对应指标口径,不构成投资建议。 -
daily-financial-screen.md 2.9 KB
# 日行情与季度财务联合筛选 ## 保留问题与口径 任务记录分别保存 `user_query` 原文和 `resolved_criteria`,不要用解析结果覆盖原文。后者至少包含市场、排除项、目标交易日、MA 周期、突破窗口、利润指标及阈值,并标注哪些由用户指定、哪些采用默认值。 本次历史回归采用:沪深主板及创业板、非 ST,收盘价 > MA5 > MA10 > MA20,收盘价突破此前 60 个交易日最高价,单季归母净利润同比 > 20%。这是样本的明确口径,不是所有“均线多头/利润增长”问题的唯一解释;用户指定时以原文为准。实际交付说明采用的默认值。 ## 对齐顺序 1. 财务序列选“按发布日”,而非按报告期。报告期 20260630 不代表该日已知财报。 2. 将已发布财务数值映射到交易日轴,向后续交易日前向填充。不能使用未来披露的数据填充过去。 3. 在同一日轴上与日行情计算条件相乘,最后读取目标交易日;不要把最后财报发布日 20260831 当成当前筛选日期。 历史合同中的公式原样为: ```text 利润日c=缺失填充("A股归属母公司股东的净利润同比增长率:单季:按发布日〔财务指标〕") 利润达标c=("利润日c">20) 筛选条件c="基本面过滤c"*"利润达标c" ``` `缺失填充` 在此使用向后续日期延续前值的默认语义。先确认返回输出的日期,再解读数值。固定历史样本必须得到 20260914 的 12 只股票;20260630 或 20260831 均应报告日期不匹配。线上结果应按实际目标交易日核验,不固定为这 12 只。 完整脱敏合同及结果位于相邻 QBV 的 `tests/fixtures/screen_4116.json`,测试覆盖错误报告期、错误最后发布日和正确目标交易日。该样本是历史响应回放,不是修改计算引擎的依据。 ## QBS → QBV 合同边界 QBS 批次收据证明该批计算执行结果;QBV 包级收据绑定完整 `{formulas, reads, begin_date}` 与 task/turn、子收据哈希。批次数量不应改变调用方接口。不要扩展公式数量来绕过单批注册失败。 注册和恢复使用同一份完整合同,不能丢掉 reads,也不能把不同日期或不同读取模式的计算收据视为同一证明。已有包与授权通过 `static_page.py bind_runtime_route` 绑定,复用计算和登记结果;全市场范围无需虚构单一股票。 ## 桥接进程输出 stdout 只承载一个 JSON;进度和日志写 stderr。Python 调用示例: ```python completed = subprocess.run(argv, capture_output=True, text=True, encoding="utf-8", check=True) payload = json.loads(completed.stdout) # completed.stderr 单独记录为进度,不拼入 payload。 ``` Shell 可使用 `> result.json 2> progress.log`。禁止将 `2>&1` 合并结果当成单个 JSON 解析;JSON 解码失败要报告通道错误,不重跑已成功的计算。 -
event-study.md 49.6 KB
# 事件研究 > **适用范围**:给定或可搜索到的历史事件日期,研究事件发生后资产在各时间窗口的表现。 > 本流程定义本场景的专用执行步骤;全局执行合同、证据分级、禁语与回答边界仍以 `workflows/global-rules.md` 为准。 > ⛔ **硬前置条件**:进入本流程前 **必须** 已读取 `workflows/global-rules.md`。 > 未读取 = 本流程所有步骤无效,不得继续。 ### 内联核心红线(来自 global-rules,无需额外读取即刻生效) 1. **evidence-only**:最终答案只输出工具结果支持的数值/日期/排名/口径说明;**禁止**输出"新冠疫情冲击""市场环境""利好出尽""政策宽松预期""OPEC+减产谈判"等无工具证据的归因——即使你认为这些是"常识"也不行 2. **去过程化**:禁止「已成功获取」「让我来」「按照流程」「Step 1/2/3」「数据已获取」等过程性话术;禁止泄露 `_working/` 路径/checkpoint/workflow 名 3. **事件定义逐字冻结**:用户说"2015年12月、2022年3月、2022年5月三次"就只查这三次,不得泛化为"2015-2022年区间内所有加息";用户说"过去10年"就严格按10年筛选,不得扩大到更早 4. **资产口径冻结**:`assets_db` 本地资产库 返回与用户原口径不一致时,禁止静默替代 5. **输出前自检**:输出最终答案前,必须执行 global-rules 的 ⛔ 最终答案自检清单(删过程话术 → 删无证据归因 → 校验汇总数值 → 核对 Working State) ### 单事件单资产窗口收益 Fast Path(优先级高于完整事件研究链) 若用户已给出明确事件日期、单一资产/指数、单一区间窗口(如 T+2 到 T+20),且只要求该窗口累计涨跌幅: 1. `newSession` 2. `grep presets/assets_db/{类型}.yaml` 确认资产 ticker 3. 用一次 `fast_query(query_type="window", fields=["收盘价"], start_date=事件日, end_date=足够覆盖窗口的自然日)` 拉取事件日后收盘价序列 4. 在返回的 `series` 内定位 T+N 到 T+M 对应的有效交易日(**N/M 偏移按下方「偏移口径判定」确定**:含「排除事件当天/宣布后/事件后 第a个交易日」时,第a个交易日 = T+a,不是 T+(a-1)) 5. 按窗口内首末有效交易日收盘价计算累计涨跌幅并直接回答(取到即停,禁止 CSV/Bash 旁路重算) 本 Fast Path 禁止使用 `buildEventStudy`、`runMultiFormulaBatchStream`、`runMultiFormulaBatch`、`readData`、`force_reusable_array`、`force_reusable_flags`。仅当存在多事件、多窗口、阈值触发或分组比较时,才进入完整事件研究链。 ### 最小执行链(完整事件研究;不得跳过任何一步) ``` 1. read_skill_file("workflows/global-rules.md") ← 未完成则停止 2. 冻结 event_definition → write event_definition.json ← 未完成则停止 3. grep presets/assets_db/{类型}.yaml 确认资产 ← 未完成则停止 4. 确定事件日期 → write event_candidates.json ← 未完成则停止 5. read event_candidates.json 读回确认 6. buildEventStudy 7. runMultiFormulaBatchStream 8. readData 9. 按 evidence-only 模板输出(禁止无证据归因) ``` > 完整事件研究链中跳过步骤 1-4 的任何一步即为执行违规。即使最终数值恰好正确,流程不合规 = 不可接受。 --- ## 执行协议:Abstract Target + Checkpoints + Backward Recovery ### Abstract Target > 交付对象:给定事件日期集合,计算目标资产在每个事件日之后的各时间窗口累计收益,以结构化表格输出。 ### Checkpoints | CP | 名称 | 已验证状态 | Acceptance Test | |----|------|-----------|-----------------| | E0 | 场景+模式冻结 | 模式(single/compare/threshold)、资产、窗口已确定 | 模式为三者之一;资产已通过 presets/assets_db 唯一命中;窗口在映射表内 | | E1 | 事件日期冻结 | 事件日期列表已确定 | 至少 1 个有效 YYYYMMDD 日期;跨时区口径已校正 | | E2 | 公式生成冻结 | buildEventStudy 已返回 formulas | formulas 数组非空;warnings 已记录 | | E3 | 数据已取 | runMultiFormulaBatchStream + readData 完成 | 每个窗口至少有 1 个有效数值;无全 NaN 列 | | E4 | 交付完成 | 格式化输出 + Acceptance Test 通过 | 见下方 Acceptance Test 节 | ### Backward Recovery(失败时) | 失败位置 | 回退到 | 允许修改的唯一 slot | 禁止 | |---------|--------|-------------------|------| | E1 搜索无果 | E0 | 搜索关键词(换措辞重搜) | 凭记忆编造日期 | | E2 buildEventStudy 失败 | E1 | 日期格式或窗口参数 | 同时改资产和日期 | | E3 runMultiFormulaBatchStream 失败 | E2 | 公式(换 prefix 或简化窗口) | 同时改日期和公式 | | E3 readData 失败/全 NaN | E2 | 读取模式(换 mode) | 切换到其他 workflow | | E4 数值不合理 | E3 | 重新 readData 验证 | 引入窗口外数据补救 | ### Retry Budget - 同一 slot 最多改 1 次,总回退上限 2 次 - 超出后进入安全失败:输出已取到的部分数据 + 说明失败原因 > ⚠️ 429 错误不受本节 Retry Budget 约束——按 global-rules.md 第 12 条「429 前置拦截」规则处理。 ### ⛔ 恢复禁区(公式失败时绝对禁止的路径) - **禁止**直接跳 `run_skill_script` 手工计算核心数值 - **禁止**用 `webSearch` 搜索来补充核心结论(如事件次数、收益率) - **禁止**在脚本 stdout 为空时手写 event_candidates - 正确路径:检查语法 → `searchFunctions` 确认函数签名 → 仅重试失败公式 ### Acceptance Test(E4 必检) - [ ] 每个事件日都有对应的窗口收益行(缺失的标注"数据不足") - [ ] 收益值保留 2 位小数,百分比格式,不补伪精度 - [ ] 样本数 < 5 时,均值行标注"有限样本参考" - [ ] 若有 warnings(窗口重叠),在表格前展示 - [ ] 阈值触发模式:明确列出阈值条件和识别到的事件日来源 --- ## 触发条件 触发词命中以下任一:复盘、历次、涨价、降息、加息、事件窗口、随后表现、超预期、不及预期、政策后表现、事件研究。 ### 排除条件(不进入本流程) 即使包含上述触发词,若用户意图属于以下情形,**不应**进入事件研究流程: - **固定区间涨跌幅查询**:用户给出明确的起止日期,只问该区间内某资产的累计涨跌幅或价格变化(如"2019年7月至2020年3月降息周期内沪深300涨了多少")→ 应走 `period-return-compare.md`,直接用 `fast_query(window)` 算区间收益 - **单纯行情走势描述**:用户只想知道某段时间的价格走势、最高最低点 → 应走 `quick-window.md` 或 `quant-standard.md` **判断口诀**: - 有明确起止日 + 只问区间数值 → `period-return-compare`(fast_query 区间查询) - 有事件 + 问"随后N天/月表现" → 本流程(因果窗口) - 有阈值条件 + 问"每次发生后表现" → 本流程(阈值触发模式) ## 场景识别 ### 单组事件(single) 用户表达类似: - 历次茅台提价后股价表现 - 美联储加息后沪深300怎么走 - 某政策出台后某股票 1周/1月/3月 表现 → 走 `single` 模式。 ### 分组对比(compare) 用户表达类似: - 超预期 vs 不及预期 - 宽松政策 vs 收紧政策 - 利好公告 vs 利空公告 → 走 `compare` 模式。 ### 阈值触发(threshold) 用户表达类似: - 国际油价单月跌幅超过20%共发生几次?每次后中国石油表现如何 - A股历次熊市(沪深300从高点下跌超30%)区间内消费行业超额收益 - 某指标突破某阈值后的市场表现 → 走 `threshold` 模式。 **threshold 模式的关键区别**:事件日期不由用户直接提供,也不通过搜索获取,而是需要**先识别满足阈值条件的历史区间/日期**,再对这些日期做标准事件窗口分析。 threshold 模式执行流程: ``` Step 0 阈值事件日识别(本模式独有) → Step 1 确认事件日期(= Step 0 的输出) → Step 2~4 同 single 模式 ``` **Step 0:阈值事件日识别** 首先判断阈值条件是否**可量化**(即能用平台数据计算): | 条件类型 | 示例 | 日期来源 | |---------|------|---------| | **可量化阈值** | 沪深300回撤>30%、油价月跌幅>20%、PE<10 | **必须**数据驱动(runMultiFormulaBatchStream + readData) | | **不可量化阈值** | 历次大规模财政刺激、贸易战升级 | LLM 知识 + webSearch 交叉验证 | #### A. 可量化阈值(数据驱动,不得跳过) 当阈值条件涉及可计算的数值指标(价格、指数点位、收益率、估值倍数等)时: 1. **用 grep presets/assets_db/{类型}.yaml 确认资产** 2. **用 confirmDataMulti 确认度量指标**(如 `收盘价()`;用户要 `PE(TTM)` 时按全局口径规则优先查询 `市盈率 TTM`,不得把 `PE(TTM)` 原样当查询词) 3. **用 runMultiFormulaBatchStream 在公式层完成阈值筛选**(begin_date 需覆盖足够历史区间)——必须在公式中直接生成布尔掩码(如 `HIT=("月收益率"<-0.20)`),而不是取回完整序列后在回答层人工扫描 4. **用 readData (`last_column_full`) 读取筛选结果(布尔掩码 / 命中月份)**——因为只有少数命中点为 1,输出不会被截断 5. **从 readData 返回的非零点直接提取事件日期** **⛔ 禁止取回完整连续序列后在回答层人工逐行扫描**: - `last_column_full` 返回的月度/日度序列可能超过数千点,会被上下文截断,导致遗漏事件 - 必须让 runMultiFormulaBatchStream 在公式层完成 `<` / `>` 阈值判断,只把 0/1 结果传给 readData - 实测反例(T-021, iter-013):模型对 134 个月的月度收益率用 `last_column_full` 全量读取,返回被截断只剩 25 个月,导致 2018-11(-20.81%)被遗漏,最终只报 1 次事件而非 2 次 **⛔ 月度/季度/年度阈值采样标准配方(硬规则,不得自由换口径)**: 当阈值条件以"月/季/年"为单位时(如"单月跌幅超过20%"),**必须**使用以下标准配方: ``` Step A: 取日频收盘价序列 — BRENT_CLOSE=收盘价(IPE-布伦特原油) Step B: 用 周期采样("收盘价序列", "月末基准日期") 提取每月最后一个交易日的收盘价 — BRENT_M=周期采样("BRENT_CLOSE","月末基准日期") Step C: 用 ("月末价"/延迟("月末价",1))-1 计算相邻月末的环比收益 — BRENT_RET=("BRENT_M"/延迟("BRENT_M",1))-1 Step D: 在公式层生成布尔掩码 — HIT=("BRENT_RET"<-0.20) Step E: readData(HIT, mode=last_column_full) 只读命中点,从非零日期提取事件月 ``` > **Step D 是关键**:必须在 runMultiFormulaBatchStream 中完成阈值判断,生成 0/1 掩码。禁止跳过 Step D 直接对 Step C 的连续序列做 readData 全量扫描——序列过长会被截断导致遗漏。 **禁止**: - 使用 `月度变频(价格序列, 中间值)` — 该函数可能导致采样点不在月末,口径不稳定 - 使用月内任意点(如月初第一个交易日)作为月度采样基准 - 使用 `MONTHLY_CLOSE()` / `MONTHLY_RETURN()` 等未经 `searchFunctions` 确认存在的函数 - 在未确认函数签名的情况下臆造函数名 → 必须先 `searchFunctions` 确认 **季度/年度同理**:`周期采样(序列, "季末基准日期")` / `周期采样(序列, "年末基准日期")` **禁止**: - 对可量化阈值使用 LLM 记忆或 webSearch 共识日期替代数据计算 - 仅凭"公开共识"跳过数据验证(如"A股历史上共有N次熊市"不是数据证据) - 手工估算阈值触发点而非从序列中精确识别 #### B. 不可量化阈值(知识驱动) 当阈值条件无法用单一数值指标表达时(如政策事件、地缘变化): 1. **LLM 领域知识**:对公开的、有确定性共识的事件,可直接给出日期,但必须逐一标注来源依据 2. **搜索验证**:用 webSearch 搜索"XX历次发生时间"进行交叉验证 3. **用户补充**:若无法从公开知识确定,告知用户"需要您提供满足条件的日期列表" --- ## 执行流程 ``` Step 0.5 事件定义冻结(⛔ 硬门禁,不得跳过) Step 1 确定事件日期 → Step 2 buildEventStudy 生成公式 → Step 3 runMultiFormulaBatchStream 执行公式 → Step 4 格式化输出 ``` ### ⛔ Step 0.5:事件定义冻结(进入 Step 1 前必须完成) 在开始搜索或回忆事件日期**之前**,必须先完成以下 checklist: - [ ] **用户原始事件定义**:逐字抄录用户对事件的描述(如"年报/半年报发布后"、"国务院或住建部出台重大房地产宽松政策") - [ ] **in-scope 事件类型**:仅限用户措辞覆盖的事件(如:年报、半年报) - [ ] **out-of-scope 事件类型**:明确排除哪些相关但不同的事件(如:业绩预告、业绩快报、三季报、季报) - [ ] **发布主体边界**(若适用):用户指定"国务院或住建部"时,排除央行、银保监会、地方政府、交易商协会等其他主体 **冻结后不得修改**:后续 Step 1~4 的所有筛选、搜索、归类必须严格以此定义为唯一入样标准。若模型认为用户定义可能遗漏重要事件,在最终回答末尾**建议**扩大范围,不得擅自扩大。 ## 事件锚点优先级(硬规则) 对于提价/公告/政策/财报/回购/分红等离散事件: 1. 默认 anchor_basis = announcement_date(首次公开披露日/公告日) 2. effective_date 仅记录,不自动替代公告日 3. 只有当用户明确要求按生效日统计时,才允许 anchor_basis = effective_date 4. 若最终锚点不是公告日,必须写明: - anchor_basis - anchor_override_reason 否则不得进入 buildEventStudy ## accepted candidate 最低证据要求(硬规则) 若 `final_decision = accepted`,以下字段不得为空、不得为 "-": - `label_evidence_quote`(支持该事件被接纳的具体证据引用) - `evidence_event_type`(事件被归类的具体类型) - `evidence_event_date`(事件发生的具体日期) - `label_confidence`(对该事件分类的置信度) 缺少任一字段的 accepted candidate 视为无效,不得进入 buildEventStudy。 ## 锚点一致性校验(硬规则) - 若 `anchor_basis = announcement_date` 且 `announcement_date` 是交易日,则 `trading_anchor_date` 必须等于 `announcement_date` - 若 `announcement_date` 非交易日,`trading_anchor_date` 才允许顺延至下一交易日 - 若 `anchor_basis` 与 `trading_anchor_date` 不一致且无合法顺延理由,不得进入 buildEventStudy ## 写后必读(强制步骤) 在写入以下文件后,必须立即 `read_skill_file` 回读并校验: - `event_definition.json` - `event_candidates.json` - `event_selection.json` 校验项: - 写入的内容与预期一致 - accepted candidates 满足上述最低证据要求 - 锚点一致性校验通过 校验不通过 = 不得进入 buildEventStudy / final answer #### 发布主体 vs 发布场合(硬规则) 当用户限定了发布主体(如"国务院""住建部")时: - **发布主体** = 实际出台/签署政策的机构 - **发布场合** = 政策公开/宣布的会议/发布会地点 - **"国务院新闻办公室发布会"只是场合,不等于国务院出台了政策** - **"国务院政策例行吹风会"只是场合,不等于国务院出台了政策** - 必须识别**真正的政策制定方**:若政策由央行/金融监管总局/银保监会制定,只是在国务院系统的发布会上宣布 → 该事件的发布主体是央行等,不是国务院 - 筛选时以发布主体为准,不以发布场合为准 #### 政策事件主体边界细化(single / compare 模式常用) 适用范围:用户询问"某行业历次重大政策出台后,相关标的随后 N 日表现如何",且明确限定了政策发布主体(如国务院、住建部、证监会、发改委)。 **仅当以下条件同时满足时启用本小节**: - 事件类型是政策/监管/产业扶持类离散事件 - 用户对发布主体有明确限定 - 当前问题走事件研究,而不是固定区间复盘 **主体边界判断表**: | 情形 | 正确认定 | 处理 | |------|----------|------| | 住建部印发正式通知/意见 | 主体 = 住建部 | 若 `subject_boundary` 包含住建部,可入候选 | | 国务院或国务院办公厅正式印发政策文件 | 主体 = 国务院 | 若 `subject_boundary` 包含国务院,可入候选 | | 国务院常务会议审议通过并形成正式政策文件 | 主体 = 国务院 | 以正式文件/会议决定为准,不以新闻标题缩写代替 | | 国务院新闻办发布会、国务院政策例行吹风会 | 仅是发布场合 | 必须继续识别真正签发机构,不得直接记为国务院 | | 央行/金融监管总局/银保监会发文,只是引用"落实国务院部署" | 主体 = 央行/金融监管总局/银保监会 | 若用户限定国务院/住建部,则 `in_scope=false` | | 地方政府取消限购、下调首付 | 主体 = 地方政府 | 若用户限定中央部委,则 `in_scope=false` | **政策性质判断表(以房地产宽松政策为例)**: | 类型 | 是否可视为宽松政策 | 典型抓手 | |------|--------------------|----------| | 需求端放松 | 是 | 降首付、降利率、认房不认贷、限购松绑、购房补贴 | | 供给端融资支持 | 是 | 白名单、支持房企融资、专项贷款、三支箭 | | 去库存政策 | 是 | 收购存量商品房、棚改货币化、PSL 扩容 | | 一般性表态 | 否 | 稳楼市、促进平稳健康发展、止跌回稳 | | 宏观会议部署/听取汇报 | 否 | 研究部署、听取汇报、政策吹风、原则性定调 | **`direct_policy_terms_hit` 预筛规则(硬规则)**: - 若用户在问题中用括号或并列举例给出抓手(如"降首付、限购松绑"),应在 `event_definition.json` 中同步记录这些 `policy_terms` - 候选事件只有在 `actual_subject` 合规且 `direct_policy_terms_hit = true` 时,才可进入 accepted 样本 - 若候选事件只体现泛宽松表态、会议部署或原则性定调,而未命中用户给出的直接抓手,必须 `in_scope=false`,`reason` 标注"未命中直接政策抓手" **日期精度补充(硬规则)**: - 政策事件若当前只能确认到年/月粒度,`date_precision` 只能写 `month` 或 `year`,不得补成该月 1 日 - 进入 `buildEventStudy` 前必须进一步细化到 `day`;若最终仍无法精确到日,该候选不得 accepted 将冻结结果写入 Working State 文件: ```json write_skill_file({ "path": "output/_working/{task_id}/event_definition.json", "content": "{\"event_definition\": \"用户原始措辞,逐字\", \"in_scope\": [\"事件类型1\", \"事件类型2\"], \"out_of_scope\": [\"排除类型1\", \"排除类型2\"], \"subject_boundary\": \"发布主体边界,若适用\", \"time_scope\": {\"raw\": \"用户原始时间表达,如过去10年\", \"type\": \"relative|absolute\", \"begin_date\": \"YYYYMMDD\", \"end_date\": \"YYYYMMDD\"}, \"sample_count_constraint\": \"用户要求的样本数,如各取2-3次;若用户未限定则为 null\"}" }) ``` **`time_scope` 字段(必填)**: - `raw`:用户原始时间表达,逐字抄录(如"过去10年"、"2015年至今"、"最近5次") - `type`:`relative`(相对,如"过去10年")或 `absolute`(绝对,如"2015-2023") - `begin_date` / `end_date`:转化为 YYYYMMDD;相对时间以当前日期为锚点计算 - 后续 Step 1 搜索到的候选事件,**必须过滤掉 time_scope 范围外的样本**;范围外的事件在 `event_candidates.json` 中标记 `in_scope: false, reason: "out of time_scope"` **`in_scope` 字段(收敛原则,硬规则)**: - `in_scope` 必须是用户措辞中**最严格的核心收敛解释**,不得为扩大样本量而纳入边缘类型 - 用户问"历次涨价"→ only include 正式出厂价/官方售价上调公告;不得扩展到"零售价指导价建议""市场自发调价" - 若用户措辞有歧义,选择**最窄的合理解释**写入 in_scope,其余分类写入 out_of_scope 并说明 - 禁止事后修改已冻结的 in_scope 以纳入更多样本 **`sample_count_constraint` 字段**: - 若用户明确限定了样本数(如"各取2-3次"),必须逐字记录 - 后续 accepted 样本数**不得超过**用户限定的上界 - 若实际可用样本少于用户下界,如实说明"仅找到N个满足条件的样本",不得用低质量样本硬补 ### Step 1:确定事件日期 **事件日期获取优先级(硬规则)** 当用户已显式给出有限个事件月份/日期、明确事件类型、明确样本数范围时,必须优先: 1. 冻结 `event_definition.json` 2. 写 `event_candidates.json` 3. 在 skill 内完成候选枚举与筛选 不得先用 `webSearch` 去"确认日期"。只有当用户时间锚点不足以形成唯一候选、或 skill 内无法完成确定性枚举时,才允许外搜。 **日期来源优先级(严格按序,首个命中即停)**: > 对"过去N年/历次X事件"类问题,若平台数据可直接识别阈值事件(如通过 runMultiFormulaBatchStream 计算阈值触发日),不得优先使用 webSearch 生成事件池;webSearch 仅作兜底或证据补强。 1. **用户已提供**:用户消息中明确给出了日期列表 → 直接使用 2. **平台案例库**:`searchSimilarCases` / `getCardFormulas` 可获取结构化事件数据 → 使用案例中的日期 3. **Agent 自带搜索工具**:如果当前 Agent 有 web search 能力 → 优先用 Agent 搜索 4. **博查兜底**:以上全不可用时,调用本地 `webSearch` 工具搜索事件日期 通用的搜索工具顺序、权威来源优先级、冲突处理和 `external_fact_status` 规则统一遵守 `workflows/external-fact-verification.md`;本 workflow 只额外约束事件研究的日期枚举、Working State 和收益计算,外部搜索不得替代平台核心数值。 #### 使用 webSearch 搜索日期 直接调用 `webSearch` 工具(无需通过 `run_skill_script`): ```json webSearch({"query": "茅台历次提价时间 日期", "count": 8}) ``` | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | query | string | 是 | 搜索关键词,应包含"日期""时间"等词以提高命中率 | | count | int | 否 | 返回条数,默认 8 | | freshness_months | int | 否 | 搜索时间范围(月),默认 36 | 从搜索结果的 snippet 中提取 YYYYMMDD 格式日期。 **日期规则**: - 用户已明确给出精确日期 → 直接使用 - 用户给出月份/年份等近似范围(如"2015年12月加息")→ 可用 LLM 知识将公开确定性事件(如 FOMC 会议日)精确到 YYYYMMDD - 用户完全未提供、需要自行查找的日期 → **必须**通过搜索或案例库获取,禁止凭记忆编造 - 搜索无果时,告知用户"需要提供事件日期清单才能继续" **⛔ 日期精度禁止伪装(硬规则)**: - 当信息源(搜索结果 / LLM 知识)只提供到"年-月"粒度时(如"2006年2月"),**禁止**默认补充为该月1日(20060201) - 必须通过进一步搜索或案例库精确到日,或标注 `date_precision: "month"` 并在候选表中说明 - 仅当有多个独立信源交叉确认具体日期时,才可将月级信息精确到日级 - **宁可少纳入一个事件,不可用伪精度日期污染事件研究** **⛔ webSearch 不免除 Working State 义务**: - 无论日期来自哪个渠道(用户提供 / LLM 知识 / webSearch / 案例库),都 **必须** 先完成 Step 0.5 的 `event_definition.json` 写入,再完成 Step 1.7 的 `event_candidates.json` 写入 - webSearch 是日期获取手段,不是结构化决策冻结的替代品 - 直接从 webSearch 结果跳到 buildEventStudy = 执行违规 **事件定义冻结规则(E1 硬规则 — 详见 Step 0.5)**: - 所有搜索结果必须对照 Step 0.5 冻结的 `in_scope` / `out_of_scope` 逐条过滤(通过 `read_skill_file("output/_working/{task_id}/event_definition.json")` 读回) - 搜索结果中出现 out-of-scope 事件时,必须**丢弃**,不得纳入日期列表 - 最终 `event_candidates.json` 中的日期仅包含通过 in_scope 校验的日期 **跨时区事件日期口径(硬规则)**: - `某天后累加` 从事件日的**下一个交易日**开始计数,因此事件日应定义为"消息公布时 A 股尚未开盘的最后一个交易日" - 对于北美/欧洲盘后公布的事件(如 FOMC 声明美东 14:00 = 北京时间次日凌晨),事件日取**美东公布当天的日历日**,而非 A 股首个反应日 - 示例:FOMC 于美东 2015-12-16 宣布加息 → 事件日 = **20151216**(非 20151217) - 这样 `某天后累加` 自然从 12-17(A 股首个反应日)开始累加,完整捕获首日反应 - **禁止**将"A 股首个反应日"作为事件日——这会导致 `某天后累加` 从 T+2 开始,系统性丢失事件首日信息 ### Step 1.5:事件日口径决策(强制执行) 当事件存在多个候选日期(公告日/执行日/生效日/首个交易日)时,必须先列候选日期,再选主口径。 **默认规则**: - 用户问"股价/市场表现"(如"涨价后股价表现""分红后市场反应""扩产后走势"等)→ **必须用公告日**(announcement_date),不得改为生效日——股价在公告日即充分定价,生效日是执行层概念,非市场信息层 - 用户明确问"公告后市场反应" → 默认用公告日 - 用户明确问"实施/生效后市场表现" → 才允许用生效日,且须写明 anchor_override_reason - 若公告时点不明确,且需区分盘中/盘后 → 不得自行脑补,必须说明不确定性 ⚠️ **T-019 教训**:问"茅台历次涨价后一周/一个月/一年股价表现"时,trace 选用了生效日(顺延交易日)而非公告日,导致锚点偏移;正确做法是锁定公告日作为事件日。 **最终回答必须说明**: - 采用哪一口径 - 未采用哪一口径 - 采用理由 ### Step 1.6:事件样本准入闸门(财报/政策/阈值类必须执行) 若问题涉及人工归类或人工筛样(如超预期/不及预期、政策宽松/收紧、年报/半年报): **在调用 buildEventStudy 前**,必须先形成候选样本表: | 事件类型 | 实际主体 | 原始披露日期 | 采用事件日 | 归类 | 证据原文 | 证据所属事件 | 证据置信度 | in_scope | |---------|---------|------------|----------|------|---------|------------|-----------|----------| | 年报 | 贵州茅台 | 2021-03-30 | 20210330 | 超预期 | "净利润同比+25%,一致预期+18%" | 2020年报 | explicit | true | **准入规则(硬规则)**: - 只有 `in_scope = true` 的样本可进入 buildEventStudy - 用户限定"年报/半年报"时,不得纳入业绩预告、业绩快报、季报等题外事件 - 用户限定某一级别政策(如"国务院或住建部")时,不得纳入地方执行、央行操作、政治局表态等题外事件 - 证据不足的样本宁缺毋滥,不得为凑样本数强行纳入 - 高度重叠的事件(间隔 < 5 个交易日)应合并为一个事件簇,取首日 - **time_scope 过滤**:不在 `event_definition.json` 中 `time_scope` 范围内的样本,`in_scope` 必须为 `false` - **sample_count_constraint 遵守**:若用户限定"各取2-3次",accepted 样本数不得超过 3 **compare 模式证据标签准入(硬规则)**: 当需要对事件进行分组标签(如超预期/不及预期、宽松/收紧)时: - 每个标签必须有**具体、可复核的证据**支撑,不得使用笼统描述作为分类依据 - **可接受的证据**:研报标题明确含"超预期/不及预期"、财务数据 vs 一致预期的数值比较、政策文件原文中的明确表述 - **不可接受的证据**:"业绩稳健增长""市场反应平淡""疫情下业绩韧性强"等模糊描述 → 不得作为超预期/不及预期的分类依据 - 若无法为某个事件找到满足上述标准的证据,该事件 `in_scope` 设为 `false`,`reason` 标注"证据不足以支撑标签判定" - **宁可因证据不足减少样本,不可用弱证据硬贴标签** #### 财报业绩超预期分类细则(compare 模式常用) 适用范围:用户询问"历次年报/半年报发布后,超预期 vs 不及预期随后表现如何"之类问题。 **仅当以下条件同时满足时启用本小节**: - 事件限定为正式年报/半年报披露,不含业绩预告、业绩快报、季报 - 需要按"超预期/不及预期/符合预期"分组 - 当前问题走 compare 模式 **推荐执行顺序**: 1. 先确定正式披露日。优先用 `报告期转发布日(...)` 获取候选披露日;必要时再用交易所公告页、东财年报季报页做交叉验证 2. 对每个披露日逐期搜索:`{股票简称} {报告类型} {yyyymmdd} 超预期 OR 不及预期` 3. 将搜索结果写回 candidate,再按下表决定 `label` 与 `label_confidence` | 分类结果 | 最低证据门槛 | `label_confidence` | 处理 | |----------|--------------|--------------------|------| | 超预期 | 至少 2 篇主流财经媒体/券商研报标题**明确**含"超预期""大超预期""显著好于预期"等同义表述 | `explicit` | 可入样 | | 不及预期 | 至少 2 篇主流财经媒体/券商研报标题**明确**含"不及预期""低于预期""弱于预期"等同义表述 | `explicit` | 可入样 | | 符合预期 | 标题原文**明确**含"符合预期""中规中矩""大致在预期内"等表述 | `explicit` | 仅当用户明确要求比较"符合预期"时入样;否则可记录为 rejected 候选 | | 证据不足 | 无结果、只有 1 篇明确证据、或标题过于模糊无法落到具体标签 | `insufficient` | `in_scope=false` | | 间接推断 | 仅凭股价反应、增速放缓、低于历史高位、市场情绪等间接线索推断 | `inferred` | `in_scope=false` | **T22 类题的额外约束**: - `label_evidence_quote` 必须直接引用标题原文,不得把搜索结论改写成自己的判断 - 可接受字眼包括:"超预期"、"大超预期"、"显著好于预期"、"低于预期"、"弱于预期" - 不可接受字眼包括:"业绩稳健增长"、"增速放缓"、"韧性仍强"、"市场反应平淡" - 若搜索结果没有明确超/不及预期字眼,**不得默认视为符合预期**;除非标题原文明确写出"符合预期"等表述,否则按 `insufficient` 处理 - 若某一组因证据不足被大量剔除,必须保留"样本不足",不得通过放宽定义来凑满 2-3 次 **`label_confidence` 自动准入规则(硬规则)**: - 每个候选样本必须填写 `label_confidence` 字段,取值仅限 `"explicit"` / `"inferred"` / `"insufficient"` - `explicit`:有数值比较、研报明确定性、政策原文等可复核证据 - `inferred`:基于间接信息(如市场反应、新闻标题)推断,证据不够硬 - `insufficient`:无法找到任何支撑标签的证据 - **`label_confidence = "insufficient"` → 强制 `in_scope = false`**,`reason` 标注"证据不足以支撑标签判定" - **`label_confidence = "inferred"` → 强制 `in_scope = false`**,无任何例外。`reason` 标注"inferred 证据不满足准入标准"。若某组全部样本均为 inferred,在最终答案中说明"未找到满足证据标准的{组名}样本,该组为空" **证据-事件交叉校验规则(硬规则)**: - 每个候选样本必须填写 `evidence_event_type`(证据实际描述的报告类型,如"2022年报""2023H1半年报")和 `evidence_event_date`(证据实际描述的报告期,如"2022年度""2023年上半年") - **`evidence_event_type` 必须与当前候选的 `event_type` 一致**:年报证据只能用于年报候选,半年报证据只能用于半年报候选 - **`evidence_event_date` 必须能唯一映射到当前候选事件的报告期**:不得用2023H1证据给2022年报贴标签 - 任一不匹配 → **强制 `in_scope = false`**,`reason` 标注"证据错配: 证据属于{evidence_event_type}({evidence_event_date}),当前候选为{event_type}" **`actual_subject` 主体校验规则(硬规则)**: - 每个候选样本必须填写 `actual_subject` 字段(真正的政策制定方/事件发布主体) - 若 `event_definition.json` 中有 `subject_boundary`,则 `actual_subject` 必须落在 `subject_boundary` 范围内 - **`actual_subject` 不在 `subject_boundary` 内 → 强制 `in_scope = false`**,`reason` 标注"主体越界: {actual_subject} ∉ {subject_boundary}" **未形成候选样本表,不得直接调用 buildEventStudy**。 ### Step 1.7:写入 EventCandidateTable(强制执行) 完成候选样本表后,**必须**通过 `write_skill_file` 将其持久化为 `event_candidates.json`。 **每个 candidate 必须包含以下字段**: - `candidate_id` - `event_type` - `actual_subject` - `raw_date` - `date_precision`: `day` | `month` | `year` - `announcement_date`(首次公开披露日) - `effective_event_date` - `trading_anchor_date` - `anchor_basis`: `announcement_date` | `effective_date`(默认 announcement_date) - `anchor_rule` - `subject_match`: `exact` | `approximate` | `out_of_boundary` - `direct_policy_terms_hit`(用户括号中给出的示例关键词是否命中,布尔值或 null) - `label`(compare 模式必填) - `label_evidence_quote` - `evidence_event_type` - `evidence_event_date` - `label_confidence`: `explicit` | `inferred` | `insufficient` - `in_scope` - `reason` - `subject_evidence_quote`(政策/主体类事件时,记录主体判定的原文依据;非政策题可填 null) - `policy_evidence_quote`(政策类事件时,记录政策性质判定的原文依据;非政策题可填 null) - `evidence_consistency_check`(简述证据与当前 candidate 事件类型/日期/主体的一致性检查结论) - `final_decision`: `accepted` | `rejected`(最终决定,与 `in_scope` 同步;校验规则:若 `subject_evidence_quote` 或 `policy_evidence_quote` 为空且该事件类型需要该证据,则 `final_decision` 不得为 `accepted`) 示例: ```json write_skill_file({ "path": "output/_working/{task_id}/event_candidates.json", "content": "{\"input_count\": 9, \"candidates\": [{\"candidate_id\": 1, \"event_type\": \"年报\", \"actual_subject\": \"贵州茅台\", \"raw_date\": \"2021-03-30\", \"effective_event_date\": \"20210330\", \"trading_anchor_date\": \"20210330\", \"anchor_rule\": \"生效日即交易日,直接使用\", \"label\": \"超预期\", \"label_evidence_quote\": \"净利润同比+25%,一致预期+18%\", \"evidence_event_type\": \"年报\", \"evidence_event_date\": \"2020年度\", \"label_confidence\": \"explicit\", \"in_scope\": true, \"reason\": \"数值证据明确支撑超预期判定\"}, {\"candidate_id\": 2, \"event_type\": \"业绩预告\", \"actual_subject\": \"贵州茅台\", \"raw_date\": \"2021-01-15\", \"effective_event_date\": \"20210115\", \"trading_anchor_date\": null, \"anchor_rule\": null, \"label\": \"-\", \"label_evidence_quote\": \"-\", \"evidence_event_type\": \"-\", \"evidence_event_date\": \"-\", \"label_confidence\": \"-\", \"in_scope\": false, \"reason\": \"out_of_scope: 用户限定年报/半年报\"}, {\"candidate_id\": 3, \"event_type\": \"半年报\", \"actual_subject\": \"贵州茅台\", \"raw_date\": \"2022-08-03\", \"effective_event_date\": \"20220803\", \"trading_anchor_date\": \"20220803\", \"anchor_rule\": \"公告日即交易日\", \"label\": \"不及预期\", \"label_evidence_quote\": \"股价下跌,市场反应负面,部分研报提及增速放缓\", \"evidence_event_type\": \"半年报\", \"evidence_event_date\": \"2022H1\", \"label_confidence\": \"inferred\", \"in_scope\": false, \"reason\": \"inferred 证据不满足准入标准\"}]}" }) ``` **⛔ 双日期字段(硬规则)**: - `effective_event_date`:事件实际发生/生效/公告的日历日(可能非交易日) - `trading_anchor_date`:buildEventStudy 传入的计算锚点(必须为交易日) - `anchor_rule`:如何从 effective_event_date 推导 trading_anchor_date(如"非交易日取下一交易日") - **最终表格默认展示 `effective_event_date`,不得将 `trading_anchor_date` 冒充事件日期** - 若两者不同,必须在口径说明中注明 **Cardinality Contract(硬规则)**: - `input_count` == `candidates` 数组长度 - `accepted_count`(in_scope=true 的数量)+ `rejected_count`(in_scope=false 的数量)== `input_count` - 后续 buildEventStudy 传入的 dates 数量必须 == `accepted_count` **样本数约束校验(硬规则)**: - 若 `event_definition.json` 中 `sample_count_constraint` 非 null(如"各取2-3次"),则: - compare 模式下,每组 `in_scope=true` 的数量**不得超过**约束上限(如 3) - 若某组超过上限 → 按 `label_confidence` 优先级(explicit > inferred)+ 证据质量裁剪至上限,被裁剪样本 `in_scope` 改为 `false`,`reason` 标注"裁剪: 超出样本数约束上限" - 若某组不足约束下限(如 < 2)→ **不得用弱证据补齐**;在最终答案中明确说明"目标样本数 X-Y 次,实际仅找到 Z 个满足证据标准的样本" **写入后**,必须 `read_skill_file("output/_working/{task_id}/event_candidates.json")` 读回,确认 in_scope=true 的日期列表,再传入 buildEventStudy。 ### Step 1.8:生成 EventSelection(强制执行) 在调用 `buildEventStudy` 前,必须基于 `event_candidates.json` 生成: `output/_working/{task_id}/event_selection.json` 结构至少包含: ```json { "accepted_dates": [20210330, 20220803], "accepted_count": 2, "rejected_dates": [20210115], "accepted_events": [{"candidate_id": 1, "trading_anchor_date": "20210330"}, ...], "rejected_events": [{"candidate_id": 2, "reason": "out_of_scope"}] } ``` 规则: - `buildEventStudy.dates` **只能**来自 `event_selection.accepted_dates` - 不允许模型绕过 `event_selection.json` 手工填写 dates - 若 accepted 样本不足,必须如实回答"样本不足",不得放宽准入补齐 - 若题目要求输出均值/分布,且最长统计窗口 **大于或等于** 相邻 accepted 事件的最小间距,必须先在 `event_selection.json` 中裁剪重叠样本,或缩短到不重叠窗口后再统计;**不得仅在答案中提示重叠后继续输出原分布** ### 事件锚点一致性合同(硬规则) 执行 `buildEventStudy` 时: - `dates` 参数必须逐项等于对应候选的 `trading_anchor_date` - 不允许模型为了"交易日方便"把公告日改成首个交易日,除非 `anchor_basis` 本身就是 `trading_anchor_date` 且 `anchor_rule` 已说明顺延 - 最终口径说明必须与 `event_candidates.json` 中的 `anchor_basis` / `anchor_rule` 一致 - 最终表格展示日期默认为 `effective_event_date`,计算锚点为 `trading_anchor_date`,两者不同时必须注明 ### ⛔ buildEventStudy 前置检查(Step 2 前必须全部通过) 1. `read_skill_file("output/_working/{task_id}/event_candidates.json")` **已执行**(不是写完就算,必须读回) 2. `in_scope=true` 的样本数 ≥ 1 3. 每个 `in_scope=true` 的样本:`event_type` ∈ `event_definition.in_scope` 4. 每个 `in_scope=true` 的样本:`effective_event_date` 在 `time_scope` 范围内 5. 每个 `in_scope=true` 的样本:`actual_subject` 落在 `event_definition.subject_boundary` 范围内(若有 subject_boundary) 6. 每个 `in_scope=true` 的样本:`label_confidence` ≠ `"insufficient"` 且 ≠ `"inferred"`(无例外) 7. 每个 `in_scope=true` 的样本:`evidence_event_type` 与 `event_type` 一致,`evidence_event_date` 能映射到当前候选的报告期 8. compare 模式下,每组 `in_scope=true` 数量 ≤ `sample_count_constraint` 上限(若有约束) 9. `date_precision` 必须为 `day`;若为 month/year,则不得进入 buildEventStudy 10. `buildEventStudy.dates` 必须逐项等于 `trading_anchor_date` 11. 对 A股事件,若 `effective_event_date` 非交易日,则 `trading_anchor_date` 必须顺延到下一交易日 12. 若 `actual_subject` 不在 `subject_boundary` 内,则强制 `in_scope=false` 13. 若用户括号中给出示例抓手(如"降首付""限购松绑"),必须命中 `direct_policy_terms_hit` 才可入池 14. `buildEventStudy.dates` 必须严格等于 `event_selection.json.accepted_dates`(逐项对应) 15. 任一命中 `out_of_scope` 的候选若仍进入 accepted_dates,则视为 workflow 失败,必须重建候选表 16. 若 `effective_event_date` 非交易日且本题统计口径要求交易锚点,则 `buildEventStudy.dates` 实际传入日期必须来自 `trading_anchor_date`,不得直接使用自然日 17. **锚点最终确认(强制)**:在通过上述 1-16 项检查后、实际调用 `buildEventStudy` 前,必须执行 `read_skill_file("output/_working/{task_id}/event_selection.json")` 读回 accepted 列表,逐条核对:(a) 每个 accepted 候选的 `anchor_basis` 与 `trading_anchor_date` 是否自洽(即 `anchor_basis=announcement_date` 时 `trading_anchor_date` 确实等于公告日或其合法顺延);(b) `buildEventStudy.dates` 数组与 `accepted_dates` 逐位相等。任一不自洽 → 修正 `event_selection.json` 后重读,禁止直接调用 `buildEventStudy` → **任一项不通过 = 修正 event_candidates.json 后重新读回,禁止调用 buildEventStudy** ### Step 2:buildEventStudy 生成公式 直接调用 `buildEventStudy` 工具(无需通过 `run_skill_script`): #### single 模式 ```json buildEventStudy({ "dates": [20170911, 20180111], "asset": "贵州茅台", "windows": ["1周", "1月", "1年"], "prefix": "MT" }) ``` #### compare 模式 ```json buildEventStudy({ "mode": "compare", "asset": "贵州茅台", "group_a_name": "超预期", "group_a_dates": [20210831, 20220330, 20230829], "group_b_name": "不及预期", "group_b_dates": [20220831, 20240829], "windows": ["1周", "1月"], "prefix": "MT" }) ``` #### 参数说明 | 参数 | 类型 | 必填 | 说明 | |------|------|------|------| | mode | string | 否 | `single`(默认)或 `compare` | | dates | int[] | single 必填 | 事件日期列表,YYYYMMDD | | asset | string | 是 | 资产名称(如"贵州茅台""沪深300") | | windows | string[] 或 int[] | 否 | 时间窗口,默认 `[5, 21]`。支持:1周/2周/1月/3月/半年/1年 | | prefix | string | 否 | 公式前缀,默认 "ES" | | group_a_name | string | compare 必填 | A 组名称 | | group_a_dates | int[] | compare 必填 | A 组日期列表 | | group_b_name | string | compare 必填 | B 组名称 | | group_b_dates | int[] | compare 必填 | B 组日期列表 | #### 窗口映射 | 说法 | 交易日 N | |------|----------| | 1周 | 5 | | 2周 | 10 | | 1月 | 21 | | 3月 | 63 | | 半年 | 126 | | 1年 | 252 | 返回值包含 `formulas` 数组和可能的 `warnings`(窗口重叠警告)。 ### Step 3:runMultiFormulaBatchStream 执行公式 将 Step 2 返回的 `formulas` 数组直接传入 `runMultiFormulaBatchStream`: ```json runMultiFormulaBatchStream({"formulas": [...Step 2 返回的 formulas...]}) ``` ### Step 3.5:readData 验证(可选) 如需读取事件收益的具体数值,使用 `readData` 工具: - 事件研究产出的收益数据为**一维**序列,使用 `mode: "last_column_full"` 读取 - **不要使用** `table_data` 模式(不支持一维采样数据) ### Step 3.6:执行后映射一致性校验(强制执行) 回答前必须检查: 1. 本次 buildEventStudy 传入的事件日期列表(记录总数) 2. 本次 readData 读取的结果条数 3. 两者是否一一对应 若 `事件数 ≠ 有效结果数`: - **不得直接输出逐事件收益表** - 必须执行以下之一: 1. 合并高时间重叠的事件簇,重新调用 buildEventStudy 2. 改为按事件簇输出(标注合并原因) 3. 仅输出可确认一一映射的事件,明确标注"以下 N 个事件因窗口重叠被排除" - 必须写明原始候选事件数、最终有效样本数、差异原因 - **禁止**:保留全部事件日但强行对齐不等长的结果列表 ### Step 4:格式化输出 #### 前置校验(进入 E4 前必检) - 逐事件日检查:是否每个日期都有至少一个窗口的有效数值?缺失的标注"数据不足(可能停牌/未上市)" - 样本数检查:总有效事件日数记录在手,< 5 时后续均值行必须标注 #### 解释原则 1. **先逐次事件,再给均值** — 列出每个事件日期对应的各窗口收益,最后汇总均值 2. **样本数 < 5 时,不把均值说成统计规律** — 改用"参考""有限样本下"等措辞 3. **compare 模式两组分别列出** — 先各组逐次,再各组均值对比 4. **threshold 模式额外输出** — 在表格前说明阈值条件和识别到的事件日列表及来源 5. **不补精度** — 工具返回几位小数就展示几位,不追加推理修饰 6. **compare 样本不足不静默降级** — 若某一组有效样本数为 0 或低于用户目标样本数,仍优先保留 compare 框架,明确写出"目标样本数 / 有效样本数",不得静默降级为 single #### 输出格式 | 事件日期 | 后 5 日收益 | 后 21 日收益 | ... | |----------|------------|-------------|-----| | 2017-09-11 | +x.xx% | +x.xx% | ... | | 2018-01-11 | +x.xx% | +x.xx% | ... | | **均值** | **+x.xx%** | **+x.xx%** | ... | #### warnings 处理 如果 Step 2 返回了 warnings(窗口重叠),在输出表格前展示警告。 #### 输出规则(合并:方法限制 + 输出边界 + 输出纪律) **必须保留的方法限制**: - 样本数 ≤ 3:注明"样本极少,均值仅供参考,不构成统计规律" - 搜索获取的日期不完整:注明"仅包含已搜索到的 N 次" - 窗口重叠(buildEventStudy 返回 warnings):在表格前展示警告,注明受影响窗口对 - 样本数 < 5 时,不得把描述性结果包装为统计规律;若输出均值,标注"仅描述性汇总" **禁止出现**: - 过程性话术(「让我」「按照流程」「Step 1 完成」「根据 workflow」等) - 总结性段落(「综上所述」「整体来看」)——数据表格本身就是结论 - 未经本轮证据支持的宏观/政策/行业因果归因 - "偏正面""通常""往往"类方向性判断 - "可能存在 ±1 个交易日偏差"类未被本轮验证的不确定性声明 - checkpoint 名、workflow 名、内部校验表、`_working/` 路径 - 未被请求的投资建议、风险提示 **归因规则(强制)**: - 默认不输出任何归因。事件研究的交付物是**数值表格 + 口径说明**,不是因果解释 - 禁止输出"主要受…影响""可能与…有关""这与当时…背景相呼应"等归因表述 - 禁止输出未经工具计算的概念(如"熔断机制""政策宽松预期""市场恐慌情绪"),除非本轮有对应工具证据 - 仅当用户**明确要求解释原因**时,才可输出归因段落,且必须标注"以下为可能解释,非本轮数据的因果证明" --- ## 公式模板参考 详见 `recipes/event-study-formulas.md`。 ### T22 / T23 专题入口 - 财报超预期 / 不及预期类 compare 问题:回看 Step 1.6 的**财报业绩超预期分类细则** - 主体受限的政策事件类 single / compare 问题:回看 Step 0.5 的**政策事件主体边界细化** - 需要先搜索打标再组装 compare 公式时:加载 `recipes/event-study-formulas.md` 的**模板 D** ### 偏移区间识别(模板 C 路由) 当用户表述包含以下模式时,应使用 **模板 C**(偏移区间回报)而非默认的模板 A: **触发表述**: - "排除事件当天" / "不含事件日" - "第 N 天到第 M 天" / "T+N 到 T+M" - "事件消化期之后" / "去掉前几天" - "从第 2 个交易日开始" / "跳过事件首日" **路由规则**: 1. 识别到偏移区间意图后,从 `recipes/event-study-formulas.md` 加载模板 C 2. 按下方「偏移口径判定」确定起点偏移 N 与终点偏移 M(模板 C 计算 `收盘(T+M) / 收盘(T+N) - 1`) 3. 模板 C 使用 **收盘价比值法**,不需要事件信号、日收益率、某天后累加或分段最终值 4. 输出表格标题应体现偏移区间(如"T+2 到 T+20 涨幅"),而非标准窗口名 #### 偏移口径判定(强规则,修复 T-037「第N个交易日」歧义) 约定:事件日(公告日)= **T+0**。区间涨幅 = `收盘(终点交易日) / 收盘(起点交易日) - 1`。**先看是否有「排除事件当天 / 宣布后 / 事件后 / 跳过事件首日」这类把计数起点移到事件日之后的措辞**: | 用户措辞类型 | 计数基准 | 第 a 个 → 第 b 个 映射 | N | M | |---|---|---|---|---| | 「宣布后/事件后/排除事件当天 第 a 个交易日 到 第 b 个交易日」 | 事件日**不**计入,第 1 个交易日 = T+1 | 第 a 个 = T+a,第 b 个 = T+b | a | b | | 「T+a 到 T+b」 | 直接偏移 | — | a | b | | 「第 a 天到第 b 天」(无「排除/之后」修饰,事件当天算第 1 天) | 事件日 = 第 1 天 = T+0 | 第 a 天 = T+(a-1),第 b 天 = T+(b-1) | a-1 | b-1 | **实测口径(T-037)**:「美联储宣布降息,统计沪深300在**宣布后第2个交易日到第20个交易日**的区间涨幅(**排除事件当天**)」→ 命中第一行 → N=2, M=20 → `收盘(T+20)/收盘(T+2) - 1`。**禁止**算成 T+1→T+20。 > **注意**:模板 C 不走 buildEventStudy 工具,也不需要选取日期生成事件信号。直接用 `取某天` 嵌套 `交易日位移` 提取两个价格点,再做价格比值即可。多个历史事件需为每次事件单独建立前缀变量(`取某天` 每次只能绑定一个日期)。 > **成功即停**:模板 C 两个端点价格取到、比值算出后,立即组织答案并停止;禁止再开第二套手工价格点重算、禁止 `fast_query → csv → Bash/Python`、禁止追加 `searchFunctions`/同义公式重算(见 global-rules.md 第 7.5 条旁路禁令)。 -
external-fact-verification.md 4 KB
# 外部时效性事实核验 > 本 workflow 只核验易变化的外部事实,例如上市、退市、更名、换代码、换交易所和并购状态。它不能替代 Quant Buddy 的行情、财务、公式、Grant 或验证收据。 ## 触发门禁(命中任一项必须执行) 1. 用户询问“最新”“目前”“现在”“是否已经”等易变化事实。 2. 结论涉及近期上市、退市、暂停交易、更名、换代码、换交易所或并购。 3. 用户陈述、模型记忆、本地资产库或 Quant Buddy 接口响应互相冲突。 4. 本地资产库已命中目标资产,但任一平台资产接口返回 `ASSET_NOT_FOUND`;该情况标记为 `asset_catalog_conflict`。 5. 结论依赖模型知识截止时间之后发生的事实。 触发后不得仅凭模型记忆下结论。`ASSET_NOT_FOUND` ≠ `公司未上市`;它只说明当前接口没有识别目标资产,不得推导为公司未上市、不存在或用户陈述错误。 ## 执行顺序 1. 保留本地资产库命中的标准名称、ticker 和市场,不得替换为相似资产。 2. 先完成当前任务要求的 Quant Buddy 独立数据接口调用;外部搜索不得替代可用的平台数据通道。 3. 若当前 Agent 具有 **Agent 宿主 Web Search**,优先使用宿主搜索。 4. 宿主搜索不可用时,调用本地 `webSearch` / Bocha: ```json { "query": "SpaceX SPCX NASDAQ listing date", "count": 8, "freshness_months": 12 } ``` 5. Agent 宿主搜索与本地 `webSearch` 均不可用或无可靠结果时,标记为 `unverified`,不得退回模型旧知识作确定性断言。 ## 来源优先级 按以下顺序寻找并交叉检查,首个可靠层级足以形成结论时停止: 1. 交易所、SEC 或其他监管机构。 2. 公司官网、公司 IR、正式公告。 3. 权威财经数据源。 4. 主流财经媒体。 只有低质量来源、搜索摘要无法支持结论或可靠来源互相冲突时,使用 `conflicted`,不得确定性宣布上市状态。 ## 状态合同 内部判断必须分别保留以下状态,不得合并: ```yaml external_fact_status: verified | unverified | conflicted quant_buddy_data_status: available | unavailable | blocked ``` - `external_fact_status=verified`:权威来源足以确认上市状态、ticker、交易所、上市日期或其他目标事实。 - `external_fact_status=unverified`:搜索能力不可用,或没有找到足以支持结论的来源。 - `external_fact_status=conflicted`:可靠来源之间存在尚未消除的冲突。 - `quant_buddy_data_status=available`:任务要求的平台行情、财务或计算通道返回有效目标资产数据。 - `quant_buddy_data_status=unavailable`:平台完成调用但未识别资产、返回空数据或缺少任务要求的数据。 - `quant_buddy_data_status=blocked`:鉴权、配额、协议、网络、超时或服务异常阻止了平台调用。 外部事实已核验但平台无数据时,使用以下语义: > 公开权威来源确认该公司已经上市,但 Quant Buddy 当前接口尚未识别、覆盖或同步该资产。 ## 证据边界 外部搜索结果只允许确认: - 公司是否上市、退市或暂停交易。 - ticker、交易所、上市日期。 - 更名、换代码、换交易所、并购等公司事件。 外部搜索结果不得: - 冒充 Quant Buddy 行情、财务或公式结果。 - 生成或替代 Grant、公式包、验证收据。 - 将网页中的行情数字描述成 Quant Buddy 实时数据。 - 因外部事实核验成功而跳过仍可独立尝试的平台数据通道。 ## 失败处理 - 外部事实无法核验:明确说明“当前无法可靠核验该外部事实”,不作确定性断言。 - 平台数据不可用:明确说明“Quant Buddy 当前未返回可靠数据”,不推导公司不存在或未上市。 - 平台系统级阻塞:报告 `blocked`,不得用网页搜索结果掩盖系统错误。 - 无论外部核验是否成功,都继续遵守对应 leaf workflow 的数据完整性、重试止损和 evidence-only 规则。 -
fast-report-period.md 7.6 KB
# fast-report-period · 财务报告期快照与固定区间序列 适用:≤1000 只 A股/港股/美股;查询最近报告期财务,或固定日期范围内的财务最后有效值/完整序列;`fast_query` 单次调用。港股/美股财务不要静态拒答,应先尝试 `fast_query(query_type="report")`,按工具实际返回决定是否支持。 ## 执行(5 步) ``` ① 提取 assets + fields + 可选日期范围 → ② 调用 newSession(若本轮尚未显式调用;调用 fast_query 前不可省略) → ③ 对每个 asset 执行 grep presets/assets_db/{类型}.yaml 查本地库: 命中唯一 → 用 ticker(如 SH600303)替换原始中文名传参 命中多条(歧义)→ 向用户澄清选哪个,禁止继续查数 未命中 → 保留原始名称,由服务端兜底解析 → ④ fast_query(query_type="report", user_query=<用户原始问题>;区间序列时传 result_mode="series") → ⑤ 取值输出 ``` > **`user_query` 必填**:调用 `fast_query` 时仍需在参数中携带用户原始问题,供服务端 trace 分析(不依赖 call.py 自动注入)。 停止:`success: true` 且 `results.{资产名}` 中全部字段均有值 → 立刻输出。 ## 字段速查 | 用户说法 | 传 fields | unit | |---|---|---| | 营业收入/收入 | `营业收入` | 元 | | 净利润 | `净利润` | 元 | | 归母净利润 | `归母净利润` | 元 | | 营业成本 | `营业成本` | 元 | | 总资产 | `总资产` | 元 | | 净资产 | `净资产` | 元 | | 经营现金流 | `经营现金流` | 元 | | 投资活动现金流 | `投资活动现金流` | 元 | | 筹资活动现金流 | `筹资活动现金流` | 元 | | ROE/净资产收益率 | `ROE` | % | | 净利率 | `净利率` | % | | 资产负债率 | `资产负债率` | %(派生) | | 毛利率 | `毛利率` | %(派生) | 不在上表 → 原样传入,服务端解析(+2s)。 ## 调用示例 最近报告期(默认 `result_mode="value"`): ```json { "assets": ["贵州茅台"], "query_type": "report", "fields": ["营业收入", "ROE"], "user_query": "<用户的原始问题>" } ``` 固定区间最后报告期值: ```json { "assets": ["贵州茅台"], "query_type": "report", "fields": ["营业收入", "ROE"], "start_date": 20200101, "end_date": 20251231, "user_query": "<用户的原始问题>" } ``` 固定区间财务序列: ```json { "assets": ["贵州茅台"], "query_type": "report", "fields": ["营业收入"], "start_date": 20200101, "end_date": 20251231, "result_mode": "series", "user_query": "<用户的原始问题>" } ``` ## 输出规则 ### value 模式(默认)— compact 字典格式 - `results.{资产名}.{字段名}` 直接是数值;日期从顶层 `dates.report_period` 获取 - 若某字段值是对象 `{v, d, fallback}` 而非数字,取 `v` 为值、`d` 为实际报告期日期 - 单位从 `fields_meta.{字段名}.unit` 获取 - 元值换算亿元(÷1e8,保留 2 位小数) - 首句:`{资产} 最新报告期({dates.report_period}):{字段} {val}{unit},…` - 固定区间最后有效值首句:`{资产} 在 {start_date} 至 {end_date} 的最后可得报告期({date}):{字段} {val}{unit},…` - 不同字段日期不一致(fallback 对象各有不同 `d`)→ 分字段各自报告期,不合并计算派生指标 #### report_period 复述规则(强规则,修复 T-039) > Fast Path 不加载 global-rules.md,故此处显式声明证据边界。 **根源(务必理解,否则改不对)**:`report_period` 是一个**原始日期**(如 `2026-03-31`)。模型看到日期会本能地"帮用户翻译"成"第几季度 / 第几财季"——这是越界补全:① 用户没要这个映射;② 工具没返回这个映射;③ **港美股财年≠自然年**(如阿里巴巴财年 3 月底结束),一翻译就错(把 `2026-03-31` 说成"2025 年第四季度"是错的,它是自然年 Q1)。工具其实已经把**口径**标全了(`hint` 与 `index_title` 都写了「单季、非累计」),缺的从来不是口径,而是**约束模型别去翻译日期**。 **因此**: - 报告期**只能原样复述 `dates.report_period`(YYYY-MM-DD)** + 字段值 + `hint`/`fields_meta` 已明确的口径(如「单季、非累计值」)。 - **禁止**把 `report_period` 翻译/推断成任何工具未返回的标签:「某年Qx」「某财年第x财季」「年报/中报/一季报/三季报」等一律不得输出(A 股也不例外——即使你"算得出",也属于工具外补全)。 - 用户主动问「这是第几季度/财年」时,只能答「工具返回的报告期为 {report_period},单季口径;未提供财季/财年映射」,不得推断。 > 一句话:**report_period 是日期不是季度名。照抄日期 + 工具已标的「单季」,绝不把日期翻译成季度/财年。** ### series 模式 — compact 列式格式 - `results.{资产名}.dates` 为升序日期数组,`results.{资产名}.{字段名}` 为等长值数组 - 若某字段日期轴不同,该字段值为 `{dates: [...], values: [...]}` - 元值字段对值数组逐项换算亿元(÷1e8,保留 2 位小数) - 百分比字段(ROE、净利率、资产负债率、毛利率)直接加 `%`,不再乘 100 - dates 已升序,直接按序展示;若用户只问序列,不额外推断趋势原因 ### CSV 模式(数据点 > 500 时自动触发) 当查询的资产 × 字段 × 日期数 > 500 时,服务端通常自动返回 CSV 模式(`mode: "csv"`);港股财报因服务端多来源合并仍返回 JSON。 - 检查响应 `mode` 字段:若为 `"csv"`,按 CSV 模式处理 - **禁止**在对话中逐行展开 CSV 内容 - 用户要具体数值/序列时:调 `python scripts/fetch_fastquery_csv.py "<csv_url>" --labels <字段>` 下载解析后据其 JSON 作答(许可路径,见 `SKILL.md` 硬规则 2 csv 例外) - 仅当用户明确要导出 CSV 文件时:直接给 `csv_fields[].csv_url` + `summary`(资产数/字段数/总点数),无需解析 ## 错误处理 | fast_query 返回 | 处理 | |---|---| | Layer 1 ASSETS_EXCEED_LIMIT / DATA_POINTS_EXCEED_LIMIT / DAILY_*_EXCEEDED | 告知用户超限,按错误 message 引导 | | Layer 1 MARKET_NOT_SUPPORTED | 按工具返回说明当前市场暂不支持该 report 查询,退出 | | Layer 1 INVALID_RESULT_MODE | 修正为 `value/series` 或退出 → `global-rules.md` → `quick-report-period.md` | | Layer 1 MISSING_START_DATE / INVALID_DATE_RANGE | 退出 → `global-rules.md` → `quick-report-period.md` 或完整链路 | | Layer 1 DATE_RANGE_WINDOW_CONFLICT | report 不应触发;若触发则退出 fast path | | Layer 1 其他 | 退出 → `global-rules.md` → `quick-report-period.md` | | Layer 2 ASSET_NOT_FOUND | 告知,其余资产继续 | | Layer 3 FIELD_MARKET_MISMATCH | 按工具返回说明该字段在当前市场不可用 | | Layer 3 FIELD_UNRESOLVABLE | 见下方恢复流程 | | Layer 4 其他 | 告知该字段暂无数据 | ### FIELD_UNRESOLVABLE 恢复(partial_ok: true) ``` ① 保留 fast_query 已成功字段的值(不重查) ② 仅对失败字段:confirmDataMulti 确认字段全名 ③ 若 confirmDataMulti 精确命中同一市场字段,再复用当前 session 调用 runMultiFormulaBatchStream(公式:"字段全名"*取出(资产名));若只命中其他市场字段,直接告知字段不可用 ④ readData → 合并①结果 → 输出 ``` ⚠️ 公式**只能用** `"字段全名"*取出(资产名)`,**禁止 LAST() 语法** ⚠️ **不得**因 FIELD_UNRESOLVABLE 重读 `quick-report-period.md` 或 `global-rules.md` 若 field_error 带 `fallback_hint`,优先按其操作。 -
fast-snapshot.md 14.9 KB
# 快速执行 · 行情/估值快照与固定区间序列 > **适用范围**:≤1000 个资产,查询最新交易日的行情/估值字段(标量),或固定日期范围内的行情/估值最后有效值/完整序列。含"今天/今日/当日/当前/现在/实时/盘中"的单资产行情查询也属于本 workflow 适用范围。 > 本 workflow 使用 `fast_query` 单次调用完成(无需 runMultiFormulaBatchStream / readData)。 > **日内刷新**:`query_type="snapshot"` 且不传 `start_date` 时,服务端自动启用盘中刷新——盘中返回最后一分钟行情,收盘后返回收盘价。无需在参数中额外声明任何 `use_minute_data` 字段。 --- ## 执行步骤(4 步,严格顺序) ``` ① 从用户意图提取 assets、fields 和可选日期范围(参照下方字段映射表) → ② 调用 newSession(若本轮尚未显式调用;调用 fast_query 前不可省略) → ③ 资产解析(见下方「资产解析硬规则」:批量 grep + 跨市场唯一性) → ④ 调用 fast_query(query_type="snapshot";区间序列时传 result_mode="series") → ⑤ 从 value/date 或 series[] 提取结果,输出最终答案 ``` **停止条件**:fast_query 返回 `success: true`,且 `results.{资产名}` 中目标字段均有值 → 立刻停止,不得再调用任何工具。 **最终呈现硬规则**:停止后必须把工具结果转成人类可读答案,禁止把完整 JSON 原样贴给用户。普通用户只需要资产名、字段值、单位和必要日期;`task_id`、`_quota`、`skill_latest_version`、`skill_update_available`、`skill_self_update`、`version_check` 等运行态/升级字段一律不展示,除非用户明确要求调试原始响应。 ### 资产解析硬规则(步骤 ③,修复 T-042 逐个 grep / T-003·T-044 跨市场同名) 1. **批量 grep(多资产必须)**:用户给出多个资产时,**一次** grep 用 `名1|名2|名3|…` 合并匹配,禁止对每个名字单独 grep。仅对未命中项再补查。 2. **跨市场唯一性(用户只给名称、未带市场/后缀时)**:grep 必须**跨市场**搜索(`presets/assets_db/stock_a.yaml`、`stock_hk.yaml`、`stock_us.yaml`、`index.yaml` 一并检索,而非默认只搜 `stock_a.yaml`)。 - 同一名称在 ≥2 个市场命中(如「宁德时代」= A股 SZ300750 + 港股 HK3750)→ **必须先向用户确认选哪个市场,禁止默认 A 股后继续查数**。 - 用户已给完整代码/后缀(如 `000063`、`HK0700`)→ 做完整代码精确匹配,禁止按数字子串模糊命中选资产。**其中,输入全为数字时**,先在所有资产库中找数字部分完全一致的记录;只要存在 `stock` 类型记录,就优先返回该股票(例如 `000001` 同时命中 `SZ000001` 股票和 `SH000001` 指数时选股票),不因同号 `index`、基金或其他资产记录而改选非股票。若同号命中多只股票,先确认市场;仅在没有同号股票时,才沿用原规则:全部精确命中里唯一的资产可直接返回,多个非股票命中则先确认。 3. **未命中表述**:某资产跨市场均未命中 → 只能说「当前资产库未识别到『{名称}』,本次未返回该资产数据」;**禁止**写「请核对名称/代码是否有误」之类把原因归到用户的表述。其余已命中资产正常输出(多资产时 `options.partial_ok = true`,先返回已识别结果,未识别项单列说明,不因个别未命中中断整批)。 --- ## ① 字段映射表(fields 参数写法) > ⚠️ 估值字段市场范围:**PE/PE_TTM/PB/PS_TTM/股息率/PCF** 支持 A/US/HK(日频,港美股自动映射;PB 不带 TTM);**总市值** 支持 A/US/HK;**流通市值、换手率** 仅 A 股。 | 用户描述的字段 | 传入 fields 的写法 | 返回 unit | 备注 | |---|---|---|---| | 收盘价 / 最新价 / close | `收盘价` | 元 | | | 开盘价 / open | `开盘价` | 元 | | | 最高价 / high | `最高价` | 元 | | | 最低价 / low | `最低价` | 元 | | | 涨跌幅 / 日涨幅 / 回报率 / pct_change | `涨跌幅` | % | value 已 ×100,直接加 % | | 成交额 / amount | `成交额` | 元 | | | 成交量 / volume | `成交量` | 股 | | | PE / 市盈率TTM / PE_TTM | `PE_TTM` | 倍 | A/US/HK | | PB / 市净率 | `PB` | 倍 | A/US/HK | | 市销率 / PS_TTM | `PS_TTM` | 倍 | A/US/HK | | 总市值 / 市值 | `总市值` | 亿元 | A/US/HK | | 流通市值 | `流通市值` | 亿元 | 仅 A 股 | | 换手率 / turnover | `换手率` | % | 仅 A 股 | | 股息率 / dividend | `股息率` | % | A/US/HK,日频 | | PCF / 市现率 | `PCF` | 倍 | A/US/HK | | PCF(现金净流量) | `PCF_现金净流量` | 倍 | A/US/HK | | PE(单季)/ PE_单季 | `PE_单季` | 倍 | 历史兼容名;港美股实际为日频,新调用使用标准字段 | | PB(单季)/ PB_单季 | `PB_单季` | 倍 | 历史兼容名;港美股实际为日频,新调用使用标准字段 | | 市销率(单季)/ PS_单季 | `PS_单季` | 倍 | 历史兼容名;港美股实际为日频,新调用使用标准字段 | | 股息率(单季)/ 股息率_单季 | `股息率_单季` | % | 历史兼容名;港美股实际为日频,新调用使用标准字段 | | 所属行业 / 行业 | `所属行业` | 无 | 仅 A 股股票;只支持 snapshot/value,返回 `swl1`、`swl2`、`jqc` 分类对象 | > 除 `所属行业` 这一专用 A 股资产元数据字段外,字段不在上表时:原样传入 `fields`,服务端自动解析(约 +2s)。 --- ## ② 调用示例 > ⛔ **leaf 级硬闸门(重复声明 SKILL.md 硬规则 1,每次进入本 workflow 都必须遵守)**: > 调用 `fast_query` 之前必须先调用 `newSession`。若本轮上一步只有 `Read` / `Grep` / `read_skill_file` 痕迹而没有 `newSession` 调用记录,**第一件事就是 `newSession`,不是 `fast_query`**。 > 这条规则优先级高于"参数已经提取完毕"等任何后续步骤说明;跳过 `newSession` 直接调 `fast_query` = MISSING_NEW_SESSION 契约失败(HIGH 级)。 > ⛔ **P0 红线**:调用 `fast_query` 前必须以本节示例为参数模板构造请求体,禁止自行推断参数结构;必传字段 `query_type`、`assets`、`fields` 缺一不可,不得用 `query` 等非标字段替代。 > **`user_query` 必填**:调用 `fast_query` 时仍需在参数中携带用户原始问题,供服务端 trace 分析(不依赖 call.py 自动注入)。 最新值 / 区间最后有效值使用默认 `result_mode="value"`,可省略: ```json { "assets": ["贵州茅台", "比亚迪"], "query_type": "snapshot", "fields": ["收盘价", "涨跌幅", "PE_TTM"], "user_query": "<用户的原始问题>" } ``` 固定区间最后有效值: ```json { "assets": ["贵州茅台"], "query_type": "snapshot", "fields": ["收盘价", "PE_TTM"], "start_date": 20210101, "end_date": 20211231, "user_query": "<用户的原始问题>" } ``` 固定区间完整序列: ```json { "assets": ["贵州茅台"], "query_type": "snapshot", "fields": ["收盘价"], "start_date": 20210101, "end_date": 20211231, "result_mode": "series", "user_query": "<用户的原始问题>" } ``` --- ## ③ 取值与输出规则 ### value 模式(默认)— compact 字典格式 - 响应为字典:`results.{资产名}.{字段名}` 直接是数值(日期已提升到顶层 `dates.{date_type}`) - 若某字段值是对象 `{v, d, fallback}` 而非数字,说明该字段日期与公共日期不同(fallback 回退),取 `v` 为值、`d` 为日期 - **指定日期的日频字段错位是正常成功结果**:`result_mode="value"` 同时传入 `start_date`、`end_date` 时,服务端对日频行情/估值从起始日向前回看 10 个自然日,先取指定区间;例如行情已更新到 2026-09-17、PE/PB 尚停在 2026-09-16 时,估值以 `{v, d:"2026-09-16", fallback:true}` 返回,并伴随 `DATE_RANGE_FALLBACK`。按字段 `d` 展示,**不得**将其当作无数据、重试或切换完整链路。 - 单位从 `fields_meta.{字段名}.unit` 获取 - **所属行业**:`results.{资产名}.所属行业` 是 `{swl1, swl2, jqc}` 分类对象,不读 `fields_meta` 单位或日期;直接展示 `swl1.name`(申万一级)、`swl2.name`(申万二级)和 `jqc[].name`(概念列表)。仅适用于 A 股股票的 snapshot/value 请求。 - **涨跌幅**:值已是百分比数(如 `-2.74`),直接加 `%`,**不再乘 100** - 未传日期范围时,`dates.trade_date` 为最新交易日;若早于当前自然日,声明「以下为最后可得交易日 YYYY-MM-DD 的数据」 - 传入日期范围时,通常取该区间内最后一个有效值;仅日频字段在整个指定区间均未更新时,才会按上述 10 个自然日回看规则以 `fallback:true` 返回区间前的最近有效值 **输出首句格式**:`{资产名} 最新数据({dates.trade_date}):{字段1} {value1}{unit},{字段2} {value2}{unit}…` #### 指数类资产口径覆盖(强规则,修复 T-005 指数说成「元」) 当资产是**指数**(命中 `presets/assets_db/index.yaml`,或用户明确问「指数/点位」,或 ticker 为指数代码如 `000300.SH`/`000905.SH`)时,价格类字段的对外单位必须改写,**不得照抄 `fields_meta.unit` 的「元」**: | 字段 | 指数对外表述 | 单位 | |---|---|---| | 收盘价 | 收盘点位 | 点 | | 开盘价 | 开盘点位 | 点 | | 最高价 | 最高点位 | 点 | | 最低价 | 最低点位 | 点 | - 涨跌幅、换手率等百分比字段不受影响,仍用 `%`。 - 个股的价格字段仍用「元」,本规则只对指数生效。 #### 单资产快照简答规范(修复 T-001 过度格式化) 工具已返回最终标量时,默认按**工具原值、原单位、原精度**直答(一句话或短列表): - 除非用户明确要求,不擅自把「元」换算成「亿元/万元」、不对百分比额外四舍五入、不改字段名口径。 - 用户已给「中文名 + 代码」时,回答优先沿用用户写法(如 `中兴通讯(000063)`),平台 ticker 可补充但不替代用户写法。 - **禁止原样贴 JSON**:即使工具响应很短,也必须提取 `results.{资产名}.{字段}` 中的 `v`/日期和 `fields_meta` 单位,组织成一句话或短表格;不要展示外层 `code/data/task_id/_quota/skill_*`。 #### 日期解释硬规则(修复 T-001 类回答漂移) - **兼容字段例外先判断**:若返回 `LEGACY_FIELD_UPGRADED`,仅对 `upgraded` 显式列出的字段按映射后标准口径解释;如 `PE_单季 → PE_TTM` 是日频 TTM 估值,旧字段冲突的 `date_type="report_period"` 不代表真实报告期。优先使用字段自身 `d` / `dates`,否则序列用与值数组对应的共享日期轴,单值用映射后标准字段的日期类型对应的公共日期;日期缺失就说明未提供,禁止猜测。已有可用结果不因元信息冲突重查;未明确升级的字段沿用下方规则。用户真正要求单季口径时,说明当前返回的日频标准估值不满足单季要求。 - 除上述兼容字段例外及字段自带 `d` 外,字段对应的交易日期 = `dates.<fields_meta.<字段>.date_type>`(通常是 `dates.trade_date`);只使用工具实际返回的日期。 - **禁止**仅凭工具未明确返回的元信息(如 `meta.latest_trade_date`、`query_time_ms`、对话外部的"今天/最新交易日")自行写出"真正最新交易日是 X,但快照返回是 Y"之类的二元日期说明。 - 工具未显式声明数据延迟时,禁止追加任何"数据延迟"/"非实时"解释;只输出 `字段 + 数值 + dates.trade_date`,由用户自行判断。 - 多字段日期不一致(部分字段返回 fallback 对象 `{v, d, fallback}`)时,按各字段自带的 `d` 标注真实日期,禁止用 `dates.trade_date` 强行覆盖。 固定区间最后有效值首句:`{资产名} 在 {start_date} 至 {end_date} 的最后可得数据({date}):{字段1} {value1}{unit}…` ### series 模式 — compact 列式格式 - `results.{资产名}.dates` 为共享日期轴(升序),`results.{资产名}.{字段名}` 为等长值数组 - 若某字段日期轴不同(如混合 trade_date + report_period),该字段值为 `{dates: [...], values: [...]}` - 单位从 `fields_meta.{字段名}.unit` 获取 - **涨跌幅序列**:值已是百分比数,直接加 `%`,**不再乘 100** - 若用户只要走势/序列,按 dates 对应输出日期和值;若用户要区间统计,可基于值数组计算最高、最低、首末变化等简单统计 --- ### CSV 模式(数据点 > 500 时自动触发) 当查询的资产 × 字段 × 日期数 > 500 时,服务端通常自动返回 CSV 模式(`mode: "csv"`);港股财报因服务端多来源合并仍返回 JSON。 - 检查响应 `mode` 字段:若为 `"csv"`,按 CSV 模式处理 - 向用户汇报 `summary` 中的资产数、字段数、总数据点数 - **禁止**在对话中逐行展开 CSV 内容 - 用户要走势/序列/具体数值时:调 `python scripts/fetch_fastquery_csv.py "<csv_url>" --labels <字段>` 下载解析后据其 JSON 作答(每字段一个 url,url 整体加引号;许可路径,见 `SKILL.md` 硬规则 2 csv 例外) - 仅当用户明确要导出 CSV 文件时:直接给 `csv_fields[].csv_url` + `summary`,无需解析 --- ## 错误处理(退出规则) | fast_query 返回 | 处理方式 | |---|---| | Layer 1(ASSETS_EXCEED_LIMIT / DATA_POINTS_EXCEED_LIMIT / DAILY_*_EXCEEDED) | 告知用户超限,按错误 message 引导缩小范围或明天重试 | | Layer 1(MISSING_START_DATE / INVALID_DATE_RANGE / INVALID_RESULT_MODE / DATE_RANGE_WINDOW_CONFLICT) | 退出 fast path → `global-rules.md` → `quick-snapshot.md` 或完整链路 | | Layer 1(任何其他 code) | 退出 fast path → `global-rules.md` → `quick-snapshot.md` | | Layer 2(ASSET_NOT_FOUND) | 告知用户该资产未识别;其余资产结果正常输出 | | Layer 3(FIELD_MARKET_MISMATCH) | 告知用户该字段仅支持 A 股(如流通市值/换手率/ROE) | | Layer 3(FIELD_UNRESOLVABLE) | 告知用户字段不在支持范围,其余字段正常输出 | | Warning(DATE_RANGE_FALLBACK) | 日频字段刷新时间与其他字段不同;字段 `v`/`d` 已是可用结果,按 `d` 标注并直接作答,不得视为错误或重试 | | Layer 4(DATA_UNAVAILABLE) | 仅在字段经过日期对齐回看后仍无数据时,才退出 fast path → 完整链路;禁止重试 fast_query,禁止 confirmDataMulti 换字段名后再重试 | | HTTP 500 / 任何网络错误 | **立即退出 fast path → 完整链路**;禁止重试同一接口 | --- ## 保护规则(4 条) 1. **evidence-only**:只输出 `results.{资产名}.{字段名}` 中的实际值;禁止推断归因 2. **去过程化**:首句必须是资产名 + 数据结论;禁止「已成功获取」「让我来」等话术 3. **涨跌幅**:值已是百分比数,直接加 `%`,**不再乘 100** 4. **条件冻结**:用户条件原样传入,不改写 -
fast-window.md 11.3 KB
# 快速执行 · 最近 N 日短窗序列 / 固定区间序列 > **适用范围**:≤1000 个资产,最近 N 日(1~2500)序列或固定日期范围序列(行情/成交);窗口统计量。 > 本 workflow 使用 `fast_query` 单次调用完成(无需 runMultiFormulaBatchStream / readData)。 --- ## 执行步骤(4 步,严格顺序) ``` ① 从用户意图提取 assets、fields,以及窗口参数:window_days(1~2500)或 start_date/end_date(二选一) → ② 调用 newSession(若本轮尚未显式调用;调用 fast_query 前不可省略) → ③ 资产解析(见下方「资产解析硬规则」:批量 grep + 跨市场唯一性) → ④ 调用 fast_query(query_type="window";传 window_days=N 或 start_date/end_date;不传 result_mode) → ⑤ 从 results.{资产名} 的列式数据提取结果,输出最终答案 ``` ### 资产解析硬规则(步骤 ③,修复 T-044 跨市场同名 / 多资产逐个 grep) 1. **批量 grep(多资产必须)**:多个资产时一次 grep 用 `名1|名2|…` 合并匹配,仅对未命中项补查;禁止逐个 grep。 2. **跨市场唯一性(用户只给名称、未带市场/后缀时)**:grep 必须跨 `stock_a.yaml`/`stock_hk.yaml`/`stock_us.yaml`/`index.yaml`/`future.yaml` 检索,不得默认只搜 `stock_a.yaml`。同名在 ≥2 个市场命中 → **先向用户确认市场,禁止默认 A 股继续查数**。用户已给完整代码 → 完整代码精确匹配,禁止数字子串模糊命中。**其中,输入全为数字时**,先在所有资产库中找数字部分完全一致的记录;只要存在 `stock` 类型记录,就优先返回该股票(例如 `000001` 同时命中 `SZ000001` 股票和 `SH000001` 指数时选股票),不因同号 `index`、基金或其他资产记录而改选非股票。若同号命中多只股票,先确认市场;仅在没有同号股票时,才沿用原规则:全部精确命中里唯一的资产可直接返回,多个非股票命中则先确认。 3. **期货主连优先**:若仅 `future.yaml` 命中,且同一简称同时命中“主连/次主连”,用户未指定时默认选择“主连”(如“螺纹钢”→“沪螺纹钢主连 / RB.SHF”),最终答案标注“按主连口径”。 4. **未命中表述**:跨市场均未命中 → 只能说「资产库未识别到『{名称}』」,禁止写「请核对名称/代码」。 5. **指数口径**:若资产是指数(命中 `index.yaml` 或指数代码),价格序列对外表述为「点位」、单位「点」,不写「元」(参照 `fast-snapshot.md` 指数口径覆盖表)。 **N > 2500 时**:安全失败,告知用户「最多支持 2500 日窗口(约 10 年交易日),请缩小范围」。 **停止条件**:fast_query 返回 `success: true`,目标序列已到手 → 立刻停止。 **兼容字段优先判断**:若返回 `LEGACY_FIELD_UPGRADED`,仅将 `upgraded` 显式列出的兼容字段按映射后的标准口径解释(如 `PE_单季 → PE_TTM` 为日频 TTM 估值),优先于旧字段冲突的 `date_type="report_period"`。日期取字段自身 `dates`,否则取与值数组对应的共享日期轴;已返回可用结果不因元信息冲突重查。不得将此例外推广到未明确升级的字段;用户真正要求单季口径时,说明返回的日频标准估值不满足单季要求。 **report_period 稀疏序列收敛规则**:排除上述显式升级的兼容字段后,当 `fast_query(window)` 成功返回 `date_type="report_period"`,或目标字段明确为财报/报告期口径字段时,`series` 稀疏不是失败。若用户只要求区间走势、最高/最低值、有效日期或序列展示,应直接基于已返回的 `series` 回答,并说明该字段按报告期更新、不是逐交易日连续数据;禁止仅因序列稀疏升级到 `confirmDataMulti` / `runMultiFormulaBatchStream` / `readData` 链路。 --- ## ① 参数提取规则 | 参数 | 提取方式 | |---|---| | `assets` | 用户提到的资产(≤1000) | | `fields` | 参照 fast-snapshot.md 字段映射表(行情字段;估值字段 `PE_TTM`/`PB`/`PS_TTM`/`股息率`/`PCF`/`总市值` 及历史兼容名 `PE_单季`/`PB_单季`/`PS_单季`/`股息率_单季`(港美股实际为日频,不是单季估值) 在 window 模式同样支持 A/US/HK;`流通市值`/`换手率` 仅 A 股;期货仅尝试行情字段:收盘价、开盘价、最高价、最低价、涨跌幅、成交额、成交量) | | `window_days` | 用户说”最近 N 日”时使用(整数,1~2500);与 `start_date`/`end_date` 二选一 | | `start_date`/`end_date` | 用户给出明确起止日期时使用,格式 YYYYMMDD | 注意: - 用户只说“最近走势 / 看走势”但未给 N 或起止日期时,默认 `window_days=20`,并在答案中说明“最近20个交易日口径” - 不传 `result_mode`:`window` 固定返回 `series[]`,无需显式传 - `window_days` 与 `start_date`/`end_date` 同时传时优先使用日期范围(忽略 window_days) **begin_date(服务端自动管理)**: | window_days | 服务端 begin_date | |---|---| | ≤ 20 | 今天 − 3 个月 | | 21 ~ 60 | 今天 − 6 个月 | | > 60 | 服务端自动扩展 | > 本 workflow 无需手动设 begin_date,服务端已处理。 --- ## ② 调用示例 > **`user_query` 必填**:调用 `fast_query` 时仍需在参数中携带用户原始问题,供服务端 trace 分析(不依赖 call.py 自动注入)。 ```json { "assets": ["贵州茅台"], "query_type": "window", "fields": ["收盘价", "涨跌幅"], "window_days": 10, "user_query": "<用户的原始问题>" } ``` 多资产示例: ```json { "assets": ["贵州茅台", "比亚迪"], "query_type": "window", "fields": ["收盘价"], "window_days": 5, "user_query": "<用户的原始问题>" } ``` 固定日期区间示例: ```json { "assets": ["贵州茅台"], "query_type": "window", "fields": ["收盘价", "涨跌幅"], "start_date": 20250101, "end_date": 20250331, "user_query": "<用户的原始问题>" } ``` --- ## ③ 序列取值与统计规则(compact 列式格式) - `results.{资产名}.dates` 为升序日期数组,`results.{资产名}.{字段名}` 为等长值数组;日期轴不同的字段为 `{dates: [...], values: [...]}` - 值数组长度 = window_days(或日期范围内的数据点数) ### 序列题强制后处理(硬规则,修复 T-038 类最值日期错答) 当响应结构含 `dates[]` + 同字段 `values[]` 时,**必须先做 date-value 配对再得结论**: 1. 在内部构造数组 `pairs = [{date: dates[i], value: values[i]} for i in range(len(dates))]`。 2. `min_value / min_date / max_value / max_date / first / last` 一律基于 `pairs` 计算,禁止凭肉眼扫长数组直接口述。 3. 校验:报出的 `min_date` 对应的 value 必须真等于 `min(values)`;`max_date` 同理。任一校验未过,**禁止输出**,先内部修正再回答。 4. 用户要求"每日走势 / 日度序列 / 逐日 / 每个交易日"时,区间长度 ≤ 60 个交易日的情况下**默认附完整 `(date, value)` 表格**;只给摘要不给逐日表 = 违反 Publish Discipline。 ### 统计与展示 - **只需统计量**(最高/最低/区间收益/振幅)→ 直接从值数组计算,无需额外工具调用: - 窗口最高 = `max(值数组)`(同时取出对应日期) - 窗口最低 = `min(值数组)`(同时取出对应日期) - 区间收益 = `(末值 - 首值) / 首值 × 100%` - 振幅 = `(窗口最高 - 窗口最低) / 窗口最低 × 100%` - **涨跌幅** 值已 ×100,为百分比数,直接展示加 `%` - dates 已升序,直接按序展示,禁止中间暴露「排序」过程 --- ### CSV 模式(数据点 > 500 时自动触发)→ 下载解析后作答(强规则) 当 资产 × 字段 × 日期数 > 500 时,服务端通常**按设计**返回 CSV 模式(`mode:"csv"`,每个字段一个 `csv_url`,OSS 链接、有有效期);港股财报因服务端多来源合并仍返回 JSON。**CSV 链接是大数据量的正常交付路径,不是失败**;但**把链接直接丢给用户不算完成**——除非用户明确只要下载文件。 处理(默认:用户要走势/序列/对比/统计): 1. 对响应里每个 `csv_fields[].csv_url`,调用本 skill 脚本下载并解析(url 含 `&`,**必须整体加引号**;多字段一次传多个 url,`--labels` 按顺序对应 `csv_fields[].intent`): ``` python scripts/fetch_fastquery_csv.py "<csv_url1>" ["<csv_url2>" ...] --labels <字段1>,<字段2> ``` 2. 脚本输出 JSON:每个资产的 `first / last / min / max / count / period_return_pct`(要逐日序列再加 `--full`,大序列按 `--max-points` 截断并标注 `series_truncated`)。 3. 据脚本输出作答:实际日期区间 + 各资产 首值/末值/最高/最低 + 区间涨跌幅 + 1~3 条总体观察;明细多时不必逐日展开,但**不得只给链接无摘要**。 > 这是「消费工具返回的 csv_url」,是**许可路径**,不受「禁止 Bash 包装原生工具」约束(见 `SKILL.md` 硬规则 2 的 csv 解析例外)。脚本对某个 url 报 `error` 时,按其错误说明该字段未取到、其余字段正常作答;**不要**改用裸 `curl`/自写解析绕过脚本。 仅当用户明确要「导出/下载原始明细 CSV 文件」时:直接给 `csv_url` + 汇报 `summary`(资产数/字段数/总点数),无需下载解析;禁止在对话中逐行展开 CSV 内容。 --- ## 错误处理 | fast_query 返回 | 处理方式 | |---|---| | Layer 1(ASSETS_EXCEED_LIMIT / WINDOW_DAYS_EXCEED_LIMIT / DATA_POINTS_EXCEED_LIMIT / DAILY_*_EXCEEDED) | 告知用户超限,按错误 message 引导 | | Layer 1(MISSING_WINDOW_PARAMS / INVALID_WINDOW_DAYS) | 退出 fast path → `global-rules-lite.md` → `quick-window.md` | | Layer 2(ASSET_NOT_FOUND) | 若资产已唯一命中 `future.yaml`,立即退出 fast path → 完整链路尝试 `收盘价(资产名)` / `涨跌幅("收盘", 1)`;其他资产告知用户,其余资产正常输出 | | Layer 3(FIELD_MARKET_MISMATCH / FIELD_UNRESOLVABLE / MARKET_NOT_SUPPORTED) | 若资产已唯一命中 `future.yaml` 且字段是行情字段,立即退出 fast path → 完整链路;其他情况告知用户,其余字段正常输出 | | Layer 4(DATA_UNAVAILABLE) | **立即退出 fast path → 完整链路(newSession → grep presets/assets_db/{类型}.yaml → runMultiFormulaBatchStream → readData)**;禁止重试 fast_query,禁止 confirmDataMulti 换字段名后再重试 | | HTTP 500 / 任何网络错误 | **立即退出 fast path → 完整链路**;禁止重试同一接口 | **期货完整链路收敛**:若 `future.yaml` 已唯一命中,但 `fast_query(window)` 不可用,完整链路优先使用 `收盘价(资产名)` 生成收盘序列,必要时用 `涨跌幅("收盘", 1)` 生成日涨跌幅;若完整链路仍失败,最终只能说「当前工具链未返回该期货行情数据」,不得说「平台不支持期货」。 --- ## 保护规则(4 条) 1. **仅窗口内计算**:所有统计只能基于 `series` 返回的 N 行数据 2. **去过程化**:首句必须是资产名 + 数据结论;禁止过程性话术 3. **涨跌幅**:`value` 已是百分比数,直接加 `%`,**不再乘 100** 4. **序列展示**:按日期升序输出,禁止中间暴露排序过程 -
global-rules-lite.md 3.1 KB
# 全局执行规则(精简版) > **适用范围**:`quick-window.md`、`period-return-compare.md`。 > 复杂工作流(`quant-standard.md`、`event-study.md`、`regime-segmentation.md` 等)继续使用完整 `global-rules.md`。 --- ## R1 evidence-only 最终答案只输出**本轮工具结果**支持的数值/日期/口径说明。无工具证据,不得归因(禁止写"受政策影响""市场恐慌""风格轮动"等推断性解释)。 --- ## R2 去过程化(文本级硬禁令) **最终答案的第一句必须是资产名或数据结论**,不得以任何过程性陈述开头。 命中以下任一模式 → 必须整段重写后再输出: - `已成功获取` / `数据已获取` / `我已查询到` / `我看到` - `让我来` / `我来帮您` / `现在开始` / `接下来` - `根据 workflow` / `根据返回结果` / `根据 description` / `从 readData 返回可见` - `Step 1` / `Step 2` / `Step 3` / `检查通过` / `校验完成` - `字段提取` / `数据提取` / `区间收益计算` - `按照流程` / `现在执行` / `首先` / `接下来执行` - `太好了!` / `完美!` / `好极了!`(感叹型开场) - `等等,我需要` / `等等,让我` / `等等,先`(草稿修正前缀) - `抱歉,让我重新` / `我来纠正一下` 禁止泄露 `_working/` 路径 / checkpoint 名 / workflow 文件名。 --- ## R3 数值精度(百分比换算) 工具返回**小数形式回报率**(如 `-0.0274`)→ 输出时 ×100 再加 `%`(`-2.74%`)。 百分比输出默认保留 2 位小数,不四舍五入到整数(除非用户明确要求)。 --- ## R4 时间核实 输出最终答案前,确认回答中**每一个日期**与工具返回的原始日期逐字一致,不得凭记忆填写。 若结果日期早于当前交易日,必须声明: > 以下为最后可得交易日 YYYY-MM-DD 的结果,非实时数据。 --- ## ⛔ 最终答案输出前自检(必须执行,输出前逐项确认) 1. 逐句扫描,删除所有命中 R2 禁语列表的内容 2. 若含涨跌幅/回报率,确认已 ×100 并加 `%` 号 3. 对照工具返回逐字确认所有日期 --- ## 配额状态格式 在最终答案正文后**空一行**,输出(不加分隔线): - **正常**:`⏱ {总cost} RU · 4h余{window.remaining}/{window.limit} · 今日余{daily.remaining}/{daily.limit}` - **预警**(任一池 remaining < 20%):追加 `⚠️ ~{nextResetIn/60取整}min 后最早一批 RU 恢复` **429 拦截(优先于 leaf Retry Budget)**: | error.code | 处理方式 | |---|---| | `RATE_LIMIT_EXCEEDED` / `CONCURRENT_LIMIT` | 读 `retryAfter` 秒后静默重试,不向用户暴露 | | `WINDOW_QUOTA_EXCEEDED` | 立即停止 → `⚠️ 4h额度已满,约 Xmin 后最早一批恢复。今日余 Y/Z。` | | `DAILY_QUOTA_EXCEEDED` | 立即停止 → `⚠️ 今日 RU 已用完,次日 00:00 重置。` | | `DAILY_SCAN_EXCEEDED` | 立即停止 → `⚠️ IC 扫描今日次数已满,次日 00:00 重置。` | | `SERVICE_OVERLOADED`(503) | `retryAfter` 秒后静默重试 1 次,仍失败则告知用户"系统繁忙,请稍后重试" | -
global-rules.md 47.8 KB
# 全局执行规则 > 本文件从 SKILL.md 拆出,包含所有 leaf workflow 必须遵守的执行合同、证据分级、简答模式、参数规范等。 > 进入 leaf workflow 前,由 leaf workflow 按需引用本文件中的规则。 ## 关键规则速查(6 条最高优先级红线) 0. **工具名与错误恢复红线**:公式执行唯一可调用工具名是 `runMultiFormulaBatchStream`;`runMultiFormulaBatch` / `runMultiFormula` / `run_multi_formula` 均为旧名/错名。收到 `未知工具` / `Unknown tool` / `tool not found` 后,同名工具 0 次重试,不尝试名称变体;已有足够结果时必须直接收敛回答。 1. **事件定义冻结**:事件类型/范围必须逐字匹配用户原始措辞,不得扩大(年报/半年报 ≠ 业绩预告;国务院/住建部 ≠ 央行/银保监会)。**筛选条件同样冻结**:对条件选股/排名题,最终筛选条件必须逐字对应用户原始约束,禁止默认新增过滤条件(非ST、仅主板、剔除北交所等),除非用户明确要求。"股票/个股"一词允许收敛到股票资产宇宙(排除指数/基金/期货),但这属于资产类型对齐,不等于新增投资筛选条件 2. **evidence-only**:最终答案只输出工具结果支持的数值/日期/排名/口径说明;无工具证据不得归因。最终答案默认禁止输出:未被工具直接支持的报告类别推断(如仅凭 2025-09-30 写成"三季报")、未被工具支持的数据点数量/时间跨度细化/行业分布/市场热度/涨停制度解释、将布尔掩码/0值/description 摘要直接翻译为更强的业务事实 3. **去过程化(文本级硬禁令)**:最终答案禁止出现以下文本模式(命中任一项,必须整段重写后再输出): - 已成功获取 / 数据已获取 / 我已查询到 / 我看到 - 让我来 / 我来帮您 / 现在开始 / 接下来 - 根据 workflow / 根据返回结果 / 根据 description / 从 readData 返回可见 - Step 1 / Step 2 / Step 3 / 检查通过 / 校验完成 - 字段提取 / 字段校验 / 完备性检查 / 数据提取 / 区间收益计算 - 按照流程 / 现在执行 / 首先 / 接下来执行 - `<think>` / `</think>` 等思维链标签(这些标签及其包裹的内容必须从最终答案中**完整删除**,绝对不能出现在用户可见文本中) - 太好了! / 完美! / 好极了! 等感叹型开场语 - 等等,我需要 / 等等,让我 / 等等,先 等自我修正前缀(若需要修正,静默修正后直接输出结果) - 抱歉,让我重新 / 我来纠正一下 等内部修正话术 禁止泄露 `_working/` 路径/checkpoint/workflow 名 4. **Working State 冻结**:关键中间决策通过 `write_skill_file` 写入 `output/_working/{task_id}/`,后续步骤必须从文件读回取参,参数不一致 = checkpoint 失败 5. **失败门禁**:脚本无输出 ≠ 已完成;公式/读取失败必须回退到最近已验证 checkpoint;禁止带着脏结果继续 6. **资产口径冻结**:本地资产库或 `confirmDataMulti` 返回的资产名称/体系必须与用户原口径一致;若仅为近似匹配或跨体系匹配(如用户说"申万消费",库中只有"可选消费(万得)"),**禁止静默替代继续计算**——必须向用户确认是否接受代理口径,或在同体系内寻找替代 7. **公式失败受控恢复**:`runMultiFormulaBatchStream` 失败时: - **提交前防拆分检查**:提交前必须校验 `formulas` 数组,若 `formulas.length > 15` 且平均每元素字符数 < 3,说明公式被逐字符拆分(如 `["个","股","近",...]`),禁止提交,重新构造为完整公式字符串后再调用。 - **失败后诊断顺序**:① 检查语法/引号错误 → ② 出现 400/前置校验失败时**必须先调 `searchFunctions` 确认函数名**(禁止凭记忆直接重写函数)→ ③ 仅重试失败公式。 - **重试止损**:同一 task_id 下,同类错误(相同错误信息或相同 formulas 内容)**最多重试 2 次**;第 3 次失败或连续两次返回相同错误,必须 safe-fail,不得继续消耗 RU。 - **禁止**直接跳到 `run_skill_script` 或 `webSearch` 补核心数值。脚本 stdout 为空 ≠ 已完成 7.5. **失败止损与旁路禁令(硬规则,修复 T-036 反复重试 / T-035 Bash 旁路)**: - 当 `runMultiFormulaBatchStream` 返回 `status: ok` 但 `summary.total == 0` / `results == []`,或同一类公式连续 2 次返回空结果时:禁止再写新公式重跑同一题面;按 leaf 的 safe-fail 路径收束。 - 已有 `downloadData` / `readData` / `write_skill_file` / `renderKLine` / `fast_query` / `runMultiFormulaBatchStream` 等原生工具时,禁止用 Bash / python scripts / 临时文件 / ls / dir 去包装它们(已在 SKILL.md 硬规则 2 声明,本节再次强调)。 - 公式失败或读取空结果后,禁止退化为"再读一遍 global-rules / quant-standard / functions.yaml"代替执行;同类 `searchFunctions` / `searchSimilarCases` / `getCardFormulas` 同一 task_id 内**最多各 1 次**。 - 当前 leaf 已有足以回答用户的数据时,禁止继续开新探索分支(与 Publish Discipline 第 5 条一致)。 8. **禁止伪恢复**:当关键参数(事件日、报告期、相对时间锚点、筛选阈值、begin_date、资产口径)未被工具证据确认时,禁止用经验值、近似日期、拍脑袋 begin_date、代理口径、手写区间替代后继续输出确定性结果。可重试;不可脑补 9. **筛选条件运算符级冻结**:对条件选股/排名题,公式中的布尔条件必须与用户原始约束逐项对应,且精确到运算符级别。"低于20" → 只能生成 `< 20`;"高于3%" → 只能生成 `> 3%`。不得默认补充 `>0`、非ST、主板、剔除北交所、流动性门槛、市值门槛等任何额外条件。若认为某额外过滤"常见但用户未说",只能在最终答案末尾提出"如需加该过滤,我可以再筛一版",不得先加后说 10. **排名题一致性校验**:对"前N / 排名 / TopK / 榜单"类问题,最终输出前必须执行——① 名次必须与展示数值严格一致;② 不得直接信任 readData 原始顺序,必须按最终排序字段显式重排;③ 若第k名数值小于第k+1名(降序场景)或大于第k+1名(升序场景),禁止输出,必须先纠正;④ 排名名单、展示数值、口径说明必须来自同一套公式口径,不得混用。**⚠️ 实测陷阱(T-028)**:`取前(..., 返回数值)` 公式的 `readData` 结果按**资产代码字母序**返回(SH... 排在 SZ... 前,同前缀再按数字序),而非按数值大小排列。例如:SH601899(紫金矿业 104.22亿)会排在 SZ300394(天孚通信 100.92亿)前面,若直接依此顺序分配排名,6-8位就会出现"值越来越大"的大错。**正确做法**:① 从 `readData.last_column_full.values` 拿到 `[{asset, value, name}, ...]`;② 将该数组按 `value` 字段**降序**排列;③ 按排列后的顺序分配排名 1, 2, 3, ...。**此排序校验必须在内部默默完成,最终答案里只输出排好序的正确表格;禁止在 ## 最终答案 里先写草稿表再"等等,需要重排"后再写正式表——这类暴露修正过程的行为等同于去过程化违规(Rule 3)**。 11. **表格占位符硬禁令**:最终答案的表格中,**禁止**使用 `-`、`N/A`、`<数值>`、`待查`、`...` 等占位符代替"未被工具读取到"的列值(如在涨幅列填 `-`、在PE列填 `<20`)。若某资产/某列的具体数值未被工具返回,只能:(a) **完全省略该列**;(b) 仅列出**已有真实数值的行**,不列未读取的行;(c) 不得用"以及其他N只"等方式补全未读取的部分。**⚠️ 截断时的正确路径(T-029 教训)**:当 readData 因过长被截断、而你手头只有 description 的名称列表时,正确处理步骤是:① 若用户只问"有哪些",直接输出 description 中的 `全部名称` 列表(仅含名称和总数)即可收工,**禁止**构建含 `-`/`<20` 占位符的"虚假数值表格";② 若用户还要具体字段值(如涨幅、PE),必须对每个候选股票 grep 本地资产库确认 ticker + 单资产 `runMultiFormulaBatchStream` 逐只取值,取完后再输出带真实数值的表格,**不得**在取不到数据时用占位符填充。**实测反例(T-029、T-030)**:readData 截断后模型仍在表格中填写 `-` 和 `<20`,构建出"看起来完整实际有残缺"的误导性答案。 12. **429 前置拦截(优先于 leaf Retry Budget)**:任何工具调用返回 HTTP 429 或返回体含 `error.code` 为以下值时,**优先按本条处理,不受 leaf workflow Retry Budget 约束**: - `RATE_LIMIT_EXCEEDED` → 读 `error.retryAfter` 秒后**静默重试**,不计入 leaf Retry Budget,不向用户暴露 - `CONCURRENT_LIMIT` → 读 `error.retryAfter` 秒后**静默重试**,不计入 leaf Retry Budget,不向用户暴露 - `WINDOW_QUOTA_EXCEEDED` → **立即停止**,读 `error.nextResetIn` 秒,按第 9 条窗口耗尽格式输出 - `DAILY_QUOTA_EXCEEDED` → **立即停止**,读 `error.resetIn` 秒,按第 9 条日配额耗尽格式输出 - `DAILY_SCAN_EXCEEDED` → **立即停止**,读 `error.resetIn` 秒,按第 9 条 IC 扫描耗尽格式输出 - `SERVICE_OVERLOADED`(503)→ 读 `error.retryAfter` 秒后静默重试 1 次,仍失败则告知用户"系统繁忙,请稍后重试" **恢复时间字段速查**: | error.code | 时间字段 | 含义 | |---|---|---| | `WINDOW_QUOTA_EXCEEDED` | `nextResetIn` / `nextResetAt` | 最早一批 RU 恢复秒数/时间点 | | `DAILY_QUOTA_EXCEEDED` | `resetIn` / `resetAt` | 距次日 00:00 秒数/时间点 | | `DAILY_SCAN_EXCEEDED` | `resetIn` / `resetAt` | 距次日 00:00 秒数/时间点 | | `RATE_LIMIT_EXCEEDED` | `retryAfter` | 建议等待秒数 | | `CONCURRENT_LIMIT` | `retryAfter` | 建议等待秒数 | | `SERVICE_OVERLOADED` | `retryAfter` | 建议等待秒数 | 完整分类表见 `references/troubleshooting.md`「配额限流」段。 13. **`runMultiFormulaBatchStream` 多公式必须评估并按需传 `force_reusable_array`(硬规则)**:每次组装 `runMultiFormulaBatchStream` 的 `formulas` 数组时,若 `formulas.length ≥ 2`,**必须同步评估并按需传 `force_reusable_array`(字符串数组,元素为公式左侧变量名)**;评估方法:对每条公式问三个问题——(a) 这个左侧变量会被 `readData` 或最终业务步骤读取吗?(b) 它会被**后续 batch / 后续 `runMultiFormulaBatchStream` 调用 / 用户后续追问**引用吗?(c) 我此刻是否能确定它"以后再不会被读"?三问任一答"会/不能确定" → **写入 `force_reusable_array`**;只有完全确定"仅本批后续公式用、再无人引用" → **不要写入数组**。命名含 `_中间`/`_条件`/`_掩码`/`_排序值`/`_因子`/`MA5`/`Signal` 等的变量基本是中间量。**末批内仅最终输出变量(readData 读取的那一条,通常命名为 `*_Top10`、`*_Result`、`*_Final` 等)必须写入数组;末批内的中间计算量(`*_Score`、`*_Ratio` 等只在本批后续公式引用的变量)仍按三问法正常判断,不因在末批而自动写入**——"末批全列"等同于全列,是 P0 级规则退化。**禁止**对多公式调用不传 `force_reusable_array`(不传则全部复用,与全列等价);同时**禁止为"保险"把本批所有变量名全部列入**——全列等同于规则退化,与未传等价。 > **缓存兜底 ≠ 数组标对**:服务端缓存层会替你兜底重算丢失的中间变量,所以即便数组填错,最终结果可能仍然能跑出来。但这不能算 `force_reusable_array` 用对了,下次同 session 追问时一旦缓存失效就会暴露。 - **多批次扩展规则(跨批保活)**:当公式链拆分为多次 `runMultiFormulaBatchStream` 时,**被后续批次公式引用的变量,在当批中必须写入 `force_reusable_array`**(即使本批不被 `readData` 读)。组装每个 batch 的数组前,先扫描后续 batch 公式的右侧引用,凡被引用到的左侧变量本批必须列入。**禁止**用全列代替依赖分析——分批后某批变量名全部列入是规则退化的明显信号,应回退重新扫描跨批引用边后再标注。 - **链条未知时(场景 B:分轮追加公式)**:首批无法预见后续公式会引用什么。此时对本批的"汇总因子"级变量及其直接操作数一律写入数组。判断标准:变量 RHS 组合了本批多个其他变量、语义是最终评分或基础综合因子、且非原子基础数据 → 该变量及其所有 RHS 直接引用的本批变量均写入数组。(示例:`R31_BasicScore` = 汇总因子 → 写入;`R31_AmtRatio` 是 `R31_BasicScore` 的直接操作数 → 也写入。) - **被动拆批后必须重新推理**:若某批因超时、服务端报错或 `task_id` 丢失而失败,被动重试或拆分时:① 不得沿用失败批次的 `force_reusable_array` 或把全部变量名都列入兜底;② 必须重新对拆分后的每个子批逐条过三问法;③ 特别注意跨越拆分点的变量(原批内的引用在拆分后变成了跨批引用)必须在对应子批中重新判断并写入数组。 14. **用户主动要求刷新盘中数据时必须先调 `refreshSnapshotTime`(硬规则)**:追问含"刷新/更新/推进/最新行情/重新算/再算一次"等刷新语义时,**必须先调 `refreshSnapshotTime '{}'`,再重跑 `runMultiFormulaBatchStream`**。不得跳过 `refreshSnapshotTime` 直接重跑公式。 15. **任务级 `execution_profile: "research_24h"` 传播(硬规则,多批策略/回测/PnL 任务必读)**:在把一个用户请求拆成多次 `runMultiFormulaBatchStream` 之前,**必须先做一次性"任务级判定"**:命中以下任一条即视为长程研究任务(`is_research_job = true`)—— 1. `user_query` / 用户最新一轮指令含 `回测` / `策略` / `PnL` / `净值` / `超额收益` / `重算` / `批量` / `研究`(中英文同义); 2. 拆批前**完整**公式计划中任一条含 `回测(`; 3. 全部批次累计 `formulas` > 20 条; 4. 拆批后 `runMultiFormulaBatchStream` 调用次数 ≥ 3。 判定为长程研究任务后,**该 task_id 下所有相关批次**——包括只含 `平均/取出/板块` 等"看起来短"的前置数据准备批、以及 FIX/重试批——调用 `runMultiFormulaBatchStream` 时都**必须显式带** `execution_profile: "research_24h"` 和 `user_query`(保留原文)。详见 `tools/run_multi_formula.md`「任务级 research_24h 传播规则」。 16. **deferred 响应必须 `resumeJob` 续传(硬规则)**:`runMultiFormulaBatchStream` 返回 `status:"deferred"` 时,**不得视为该批已完成**——这只是入队确认。必须立即用 `resumeJob`(传同 `task_id` + `trace_id`)续传,阻塞等到 `done`/`fatal` 拿到完整结果后,才可进入下一批或向用户报告结论。若 `resumeJob` 返回 `STREAM_INTERRUPTED`(`category:"transport_recoverable"`),读取 `partial.last_event_id` 用 `since` 参数再次调用,最多额外重试 2 次。**禁止**:① 收到 `deferred` 就向用户说"已完成";② 跳过续传直接提交下一批(上一批变量未算完,下一批引用会空跑);③ `transport_recoverable` 的 `STREAM_INTERRUPTED` 后放弃(任务在服务端仍在运行)。 > **按 `category` 区分,别把后端超时当连接断(关键反误导规则)**:错误返回带统一 `category` 标签——`transport_recoverable`(连接断了、任务还活着)才该 `resumeJob` 续传;`server_timeout`(`QUEUE_WAIT_TIMEOUT` / `EXECUTION_STALLED`,任务在后端已超时判死)**续传也读不到结果,别反复 resumeJob**,应稍后重试 / 拆小批 / 改异步;`input_error`(公式或参数错)该改公式再试、**别原样重试**;`server_error` 隔一会儿试一次仍失败就告诉用户。`retryable` 字段同义:只有 `true`(即 `transport_recoverable`)才值得续传重试。完整对照见 `references/troubleshooting.md`「批量公式错误分类」。详见 `tools/run_multi_formula.md`「deferred 响应与 resumeJob 续传」+ `tools/resume_job.md`。限流错误(429)按第 12 条处理。 ## 时效性事实核验门禁(所有 leaf workflow 必须遵守) 以下任一情况出现时,必须读取 `workflows/external-fact-verification.md`,不得只凭模型记忆下结论: - 用户询问最新上市、退市、更名、换代码、换交易所、并购等易变化事实。 - 用户陈述、模型记忆、本地资产库或 Quant Buddy 接口响应互相冲突。 - 本地资产库已命中目标资产,但平台返回 `ASSET_NOT_FOUND`,此时标记 `asset_catalog_conflict`。 - 结论依赖模型知识截止时间之后发生的事实。 `ASSET_NOT_FOUND` 只表示当前 Quant Buddy 接口没有识别目标资产,**不得推导为公司未上市、不存在或用户陈述错误**。必须分别保留: ```yaml external_fact_status: verified | unverified | conflicted quant_buddy_data_status: available | unavailable | blocked ``` 外部搜索只核验公司状态、ticker、交易所和事件事实,不能替代 Quant Buddy 行情、财务、公式、Grant 或验证收据。外部事实核验成功但平台无可靠数据时,明确说明“公开来源已确认该事实,但 Quant Buddy 平台数据当前不可用”,不得把搜索数字描述成平台实时数据。 ### ⛔ 最终答案自检清单(输出前逐项执行,任一不过 = 修改后再输出) 1. **删过程话术**:逐句扫描,删除所有含"让我""已成功获取""现在执行""首先""接下来""根据 workflow""Step""数据已获取"的句子;同时删除所有含"等等""让我重新排""我需要重排""重新按""让我再" 等草稿修正话术——这类自我纠错过程必须在输出前内部完成,**不得**出现在 ## 最终答案 中(T-028 教训:草稿表 → "等等,让我重新排序" → 正式表 三段式结构属于 Rule 3 违规) 2. **删无证据归因**:删除所有不能追溯到本次工具调用结果的背景解释("受疫情冲击""政策宽松预期""市场恐慌""风格轮动""利好出尽""OPEC+减产"等) 3. **校验汇总数值**:若输出均值/合计,用表中数值手算验证;不一致则修正或删除汇总行 4. **核对 Working State**:最终表格中的事件日/区间/口径是否与 `_working/` 文件中的冻结值一致;不一致 = 必须修正 5. **时间口径一致性**:`use_minute_data` 不是全局默认。仅盘中、当前分钟或当日未收盘请求设为 `true`;历史日频、回测、净值、回撤、波动率和财务请求设为 `false` 或省略。无论选择哪种频率,仍须在输出前**验证实际日期**:检查 `description` 或 `readData` 返回的 `effective_matches_date` / `last_column_full.date`,确认确实生效到了当日。若结果日期早于当前交易日(如用户 4 月 13 日问"现在",结果显示 4 月 10 日),则:(a) 禁止将结果表述为"当前/现在/今天"的答案;(b) 必须明确声明"以下为最后可得交易日 YYYY-MM-DD 的结果,非实时数据"。 6. **资产范围校验与资产宇宙污染回退(硬规则)**:若用户指定了市场/板块范围(如"A股""A股主板""沪深300成分股"),逐条检查结果名单中的资产是否全部属于该范围。发现以下任一情况即判定为"范围未对齐",禁止输出名单:(a) 出现港股代码(HKxxxx);(b) 出现美股代码(xxx.N / xxx.O);(c) 出现指数/基金/期货(如xx主连、xx.SHF、xx.CFE、xx.DCE);(d) 用户说"主板"但结果含创业板(300xxx)或科创板(688xxx)。范围未对齐时只能说明"筛选范围未成功限定,暂不能给出可信名单"。**特别注意**:`description` 中的 `其中十个分别名称` 是快速排查资产范围的捷径——若样本中出现 HK/期货/指数代码,说明公式中缺少资产宇宙掩码,必须回退修正公式后重跑。 **资产宇宙污染回退规则**:对股票筛选/股票名单/股票TopN题,若中间或最终结果中出现指数、基金、期货、债券、港股、美股等非目标资产证据,视为"资产宇宙未对齐",不得直接回答——必须回退并补加股票资产宇宙掩码后重跑。不得在自然语言层把"全市场资产结果"口头解释成"股票结果"。常见污染信号:description 中出现指数名称/主连合约/基金名称、readData 返回中出现 .CFE/.DCE/.SHF 等代码模式、用户问"普通股票"但结果覆盖了非股票资产 7. **多数据源日期一致性**:当本次任务使用了多个数据源(如涨跌幅、PE、非ST 等),检查各数据源 `readData` / `description` 返回的 `effective_date`(或 `effective_matches_date` / `last_column_full.date`)是否一致。若不同数据源的有效日期不同(如涨幅数据 = 0410、PE 数据 = 0407、非ST 数据 = 0413),则:(a) **禁止**将结果表述为"今天/当前/最新"——必须逐项列出各数据源的实际日期;(b) 优先尝试对齐日期:历史任务使用一致的 `begin_date`/报告期;只有明确盘中行情任务才可对支持映射的数据源使用 `use_minute_data: true`;(c) 若无法对齐,最终答案必须明确声明"以下结果基于不同日期的数据(涨幅: YYYY-MM-DD, PE: YYYY-MM-DD),仅供参考"。**实测教训**:T-029 涨幅(0410) + PE(0407) + 非ST(0413) 三个日期不一致却声称"今天",属于严重误导 8. **表格完整性自检**:若最终答案包含表格,扫描每列的缺失值(`-` / `0` / `NaN` / 空值)。若任一展示列缺失率 > 20%,不得使用"完整""已获取全部""以下为完整结果"等确定性陈述——必须声明缺失比例或标注哪些字段暂不可得 9. **配额状态输出**:在最终答案正文后**空一行**,再输出配额摘要(不加分隔线)。从本轮**最后一次**工具返回的 `_quota` 字段提取数据;多次工具调用时取 `session_total_cost`(由 SDK 累加注入),无该字段则手动累加各次 `_quota.cost`。 - **正常格式**:`⏱ {总cost} RU · 4h余{window.remaining}/{window.limit} · 今日余{daily.remaining}/{daily.limit}` - **预警格式**(任一池 remaining < 20%):在正常格式后追加 `⚠️ ~{nextResetIn/60取整}min 后最早一批 RU 恢复`(注意:滚动窗口下 RU 按请求逐批恢复,不是一次性全部恢复) - **窗口耗尽格式**(429 `WINDOW_QUOTA_EXCEEDED`):替代正常答案输出 `⚠️ 4h额度已满,约 {nextResetIn/60取整}min 后最早一批恢复。今日余 {daily.remaining}/{daily.limit}。` - **日配额耗尽格式**(429 `DAILY_QUOTA_EXCEEDED`):替代正常答案输出 `⚠️ 今日 RU 已用完({daily.used}/{daily.limit}),次日 00:00 重置。` - **IC 扫描耗尽格式**(429 `DAILY_SCAN_EXCEEDED`):替代正常答案输出 `⚠️ IC 扫描今日次数已满,次日 00:00 重置。` - `RATE_LIMIT_EXCEEDED` / `CONCURRENT_LIMIT` 静默重试,**不输出**配额行 - **tier 感知 + 统一 20 条保守模板**:`_quota.tier` 标识用户层级(`free`/`plus`/`pro`/`ultra`)。**无论何种 tier,单次 `runMultiFormulaBatchStream` 一律按 20 条切批**(保守模板,已与服务端确认)。后端虽对 plus=30/pro=40/ultra=不限,但前端必须保守在 20,以降低依赖回溯失败、超时与跨批保活复杂度 - 若工具返回不含 `_quota` 字段,跳过此项 --- ## 全局执行合同(所有 leaf workflow 必须遵守) 每个 leaf workflow 在执行时必须满足以下四项合同,不可豁免: 1. **Abstract Target**:进入 leaf 后,先用一句话定义本次任务要交付的对象(如"单资产最近 N 日收盘价序列"),与实现路径无关。 2. **Acceptance Test**:只校验交付对象是否满足,不校验实现方式。例如"统计值仅覆盖最近 N 个交易日"而不是"必须使用 last_n_rows 模式"。 3. **Backward Recovery**:执行失败时回退到最近一个已验证的 checkpoint,只修改一个 slot(数据对象 / 读取模式 / 实现路径 / 池口径 / 返回 schema 五选一),禁止在失败态同时改多个维度、禁止无限探索(searchSimilarCases / searchFunctions / 新 preset)。 4. **Publish Discipline**:禁止发布中间结果、禁止输出未验证元信息、禁止把半成品伪装为用户要求的交付对象。禁止在最终回答中出现「已成功获取」「我来帮您查询」「让我先…」「接下来执行」等过程性话术——工具调用完成后直接输出结果。禁止在最终回答中出现 `_working/` 路径、checkpoint 名称(如 E0/E1/K0/W0)、workflow 文件名(如 quick-snapshot.md)等内部信息。 5. **结果收敛强制出答**:当 `readData(last_column_full)` 已成功返回有效截面且命中资产可识别时,必须立即收敛组织最终答案。禁止在此之后继续开新的探索分支、重新搜索函数/数据集、或尝试替代公式。若已有足够证据回答用户问题,拖延交付 = 违反发布纪律。 6. **公式失败诚实披露**:若关键过滤条件(如 ST 过滤、板块限定、资产宇宙约束)的公式执行失败,最终答案中**不得**将其表述为"已排除/已过滤/已限定"。未成功执行的条件必须如实标注为未生效,或在回答前修复。 7. **口径声明与结果一致性**:最终答案中的口径声明(如"已排除ST股""仅限A股""盘中实时数据")必须与实际结果一致。若答案声明"已排除ST"但结果表中出现 `*ST/ST` 开头的股票,或声明"A股"但结果含港股/指数/期货,或声明"盘中数据"但实际数据日期非当日——以上任一不一致,**禁止输出**,必须修正声明或修正结果。 > 各 leaf 的具体 checkpoint / slot / acceptance test 定义在各自文档中。本节只约束框架,不定义局部 SOP。 --- ## Working State 文件协议(所有 leaf workflow 必须遵守) 在多步决策任务中,关键中间结论通过 `write_skill_file` 工具持久化到 `output/_working/{task_id}/` 目录,作为后续步骤的唯一参数来源,防止长对话中的决策漂移。 ### 四条硬规则 | # | 规则 | 说明 | |---|------|------| | 1 | **关键决策写入文件** | 事件候选表、区间识别表、阈值冻结等中间对象,通过 `write_skill_file("output/_working/{task_id}/xxx.json", ...)` 持久化 | | 2 | **下一步开始前读回** | 进入下一阶段前,必须 `read_skill_file("output/_working/{task_id}/xxx.json")` 读回上一步产物,从中取参数 | | 3 | **最终数值可追溯** | 最终答案中的数值、日期、口径必须可追溯到 `_working/` 文件中的冻结值 | | 4 | **路径禁止泄露** | `_working/` 路径、文件名、task_id 禁止出现在最终回答中 | ### 适用场景 - **event-study**:Step 1 后写入 `event_candidates.json`(候选事件表) - **regime-segmentation**:Step 2 后写入 `segment_table.json`(区间识别表) - **简单查数 workflow**(quick-snapshot / quick-window / quick-report-period / render-kline):工具调用链本身就是参数传递,**不需要**写入 Working State 文件 - **quant-standard**:工具链较长但参数通过 confirmDataMulti → runMultiFormulaBatchStream → readData 自然传递;仅当筛选条件特别复杂时,可选写入 `filter_spec.json` --- ## 全局最终交付硬规则(必须遵守) 当某个 leaf workflow 已满足其停止条件时,必须在当轮立即输出最终答案,不得继续: - 再次读取 SKILL.md 或任一 workflow 文档 - 追加新的分析分支或补充未被请求的扩展内容 - 输出仅对内部有用的中间产物而不交付用户结论 - 因"还可以再优化措辞/结构"而延迟最终答复 若已成功生成用户请求的最终产物(如数值答案、表格、K线图、Markdown、图片包): 1. 必须在同一轮给出面向用户的最终交付 2. 最终交付至少包含:本轮完成了什么 + 关键结果摘要 + 产物路径(如有文件/图片) 3. 禁止出现"产物已生成但未回复用户"的结束状态 此规则优先级高于"继续优化表达"的倾向。 --- ## 全局证据分级与回答门槛(必须遵守) 所有查数类 workflow 统一遵守以下证据分级;若 leaf workflow 有更细规则,以 leaf workflow 为准,但不得放宽本节红线。 ### A级证据:正式取值结果(可直接用于最终答案) - `readData` 返回的结构化样本(如最新1行、最近N行、last_valid结果) - `runMultiFormulaBatchStream` 返回中可明确识别的结构化末值字段 - 仅在 `quick-snapshot.md` 明确允许的受控场景下,`description` 中满足固定模式 `最后值: 数值(日期)` 的末值文本,可视为等价的快照末值证据 只有 A 级证据可用于回答具体数值。 ### B级证据:确认类结果(不可直接回答数值) - `confirmDataMulti` → 只能用于确认字段名称、维度、频率、候选 index_title B 级证据中附带的任何数值(如 description 里的 last_value)不得作为最终答案。 ### C级证据:描述文本 / 统计摘要(默认不可单独作答) - `index_info.description` 中的均值、中位数、历史极值、去年同期、近20日均等 - `description` 中出现的"最后值: ...(...)"等文本片段 ### 例外:快照类窄场景的受控文本抽取(仅限 quick-snapshot) 仅在同时满足以下条件时,允许把 `description` 中的 `最后值: 数值(日期)` 作为等价末值证据: 1. leaf workflow 已明确是 `quick-snapshot.md` 2. `runMultiFormulaBatchStream` 已成功 3. 字段属于最新交易日标量字段,而非滚动窗口统计、报告期财务或确认类返回 4. 字段文本中不存在多个相互竞争的末值口径 5. 最终回答前已通过该 workflow 的日期一致性与格式化校验 除此以外: - 窗口统计题不得把 description 最后值日期解释为极值发生日 - 报告期财务题不得仅凭 description 或 confirm 类结果回答数值 - confirmDataMulti 附带的 description 数值永远不能作为最终答案 ### 回答门槛 若用户要求"具体数值 + 日期/报告期",最终答案必须同时具备: 1. 数值来自 A 级证据 2. 日期或报告期来自同一正式取值链路 不满足时,必须进入安全失败或部分成功模板,不得猜测、补全、默认填 0、默认补单位。 --- ## 简单查数默认简答模式(必须遵守) 对于以下问题:明确列出的资产(≤1000个)、最新交易日或最近N个交易日、少量基础字段或最近报告期基础财务字段: - 直接输出最终结果 - 不输出推理过程、workflow 步骤、停止条件、工具执行说明、公式、校验说明 - 不输出「数据已成功获取」「根据 Step 3」「我来提取」「按照流程」等内部过程话术 - 未被要求时,不主动补充历史统计、行业比较、投资建议、风险提示 - 不以「我来帮您查询」「让我为您查看」等服务型口吻开头,直接给结论 - 不对同一结果做重复表述(如先列表再表格、先总结后展开、先原始值后格式化值) - 不在回答末尾追加「如需了解更多…」「希望对您有帮助」等客套话,除非用户追问 - 不对数值结果做主观评价(如「表现强劲」「走势不佳」「值得关注」),除非用户要求分析 - 不输出「已成功获取」「根据返回结果」「让我开始执行」「现在我来计算」「Step 1/2/3」等过程化话术 - 不在简单题中回显内部「校验表」「路径A/B」「workflow 名称」「checkpoint 名称」 - 若已获得全部字段,不再读取额外文档、不再补历史分析、不再扩展未被请求内容 原则:查到即答;查不到则安全失败;不展示内部过程;不包装不点评。 --- ## 简单查数题统一门禁(必须遵守) 对明确列出的资产(≤1000个)的简单查数题: - 不得擅自增加用户未要求的筛选条件、比较口径或展示维度(如排除ST、补充市值门槛、改成自然月、改成平均日收益、自动展示证券代码) - 不得把未验证的单位、时间范围、数据点数量、图表元信息写成事实 - 不得在最终回答中重复表达同一结果(如同时给列表和表格) - 不得输出"已成功获取 / Step 1 / 接下来执行 / 根据 workflow"等过程化话术 - 若需要做派生计算,必须说明该值为回答层计算,并使用 leaf workflow 规定的固定口径 --- ## 结果口径冻结(所有查数 workflow 必须遵守) 在一次查数任务中,首次工具返回的"最新交易日"即为本轮锚点日期,后续所有查询和回答必须基于此锚点: - 第一次 `runMultiFormulaBatchStream` / `readData` 返回的最新日期,记为 **锚点日期** - 后续追加字段或补取数据时,若返回日期与锚点不一致,必须: - 优先用 `readData` 读取与锚点同日的数据 - 若无法对齐(如该字段该日无数据),明确在回答中标注日期差异 - 禁止在同一回答中混合不同交易日的数据却不说明 此规则对 quick-snapshot、quick-window、quick-report-period 均适用。 --- ## 指标口径精确匹配(所有查数 workflow 必须遵守) 当用户指定了具体指标名称时(如 "PE(TTM)"、"归母净利润"、"ROE(加权)"),必须保留用户要求的指标口径,但 `confirmDataMulti` 查询词应优先使用平台更容易命中的中文规范表达: 1. **用户口径即需求,不等于查询词必须原样照抄**:用户说 `PE(TTM)` 时,首选 `confirmDataMulti("市盈率 TTM")`;用户说 `归母净利润` / `ROE(加权)` 时,首选对应中文规范名。禁止把用户英文、缩写或带括号表达无转换地直接传入,除非中文规范词查询失败后作为补充同义词重试。 2. **确认结果回检**:`confirmDataMulti` 返回后,检查匹配到的字段名是否与用户要求一致——若返回的是 `PE(静态)` 而用户要的是 `PE(TTM)`,必须**重新查询**或**告知用户差异**,不得默认执行。 3. **冻结口径**:指标口径确认后冻结(如 `field: PE(TTM), index_title=市盈率 TTM, data_id=xxx`),后续公式只能使用返回的 `index_title` / `data_id`;复杂场景可写入 `_working/` 文件。 4. **禁止口径偷换**:执行了 A 指标却在回答中写成 B 指标(如执行 PE(静态) 回答写 PE(TTM))属于严重错误。 典型错误: | 用户要求 | 错误做法 | 正确做法 | |---------|---------|---------| | PE(TTM) | 执行 PE(静态),回答写"PE(TTM)";或直接查 `"PE(TTM)"` 后失败 | 首选 confirmDataMulti 查 `"市盈率 TTM"`,再回检返回字段是否为 TTM 口径 | | 归母净利润 | 用 净利润 替代 | 用中文规范名精确查询,确认返回字段为归母口径 | | ROE(加权) | 用 ROE(摊薄) 替代 | 用中文规范名精确查询,确认返回字段为加权口径 | --- ## 终答一致性检查(所有查数 workflow 必须遵守) 在进入最终回答前,必须对照工具返回的原始数据执行以下检查,确保"工具结果 → 自然语言回答"零偏差: | 检查项 | 检查内容 | 典型错误 | |--------|----------|----------| | 日期/年份 | 回答中的日期与工具返回的日期逐字一致 | 工具返回 2026 年,回答写成 2025 年 | | 报告期 | 回答中标注的报告期与 readData 返回的日期一致 | 2025Q3 写成 2024Q3 | | 数值 | 回答中的数值与工具返回的原始值一一对应;禁止二次估算或口头改写 | 小数点位移、四舍五入错误、把 -0.005 写成 -0.46% | | 百分比换算 | 若原始值为小数收益率/回报率,必须先 ×100 再展示为百分比,默认保留 2 位小数 | `0.01` 写成 `0.01%`、`-0.005` 写成 `-0.50%` 却算成 `-0.46%` | | 单位 | 百分比/元/亿元/万手等单位与工具口径一致;若单位不能可靠确认则保守不补 | 小数收益率未乘100就标%、成交额单位错、成交量乱补单位 | | 排序 | 排名/TopN 的顺序与工具返回一致 | 升序降序搞反、漏掉中间项 | | 资产名称 | 回答中的股票/指数名称与确认结果一致 | 简称写错、代码后缀不对 | | 枚举名词 | 资产名/行业名/指数名/板块名优先复用工具返回原字符串,不自行改写别名 | "公共事业"写成"公用事业"、"非银金融"写成"非银" | 执行方式: - 不需要输出校验过程,只需在回答前内部核对 - 若发现不一致,必须修正后再输出,不得"先发再改" - 此检查适用于所有查数类 workflow(quick-snapshot / quick-window / quick-report-period / quant-standard) --- ## 全局参数规范(所有 workflow 共用) 以下参数规范在任何 workflow 调用对应工具时均须遵守,不允许省略: ### runMultiFormulaBatchStream ```json {"formulas": ["..."], "begin_date": "<T-N>", "include_description": true, "use_minute_data": "<仅盘中为 true;历史为 false 或省略>", "force_reusable_array": ["变量名2"]} ``` - `begin_date` **必须传**——begin_date 越晚返回越快。原则:取最小够用的起点。 - 公式含滚动窗口 N(`平均(…,N)` / `涨跌幅(…,N)` / `最大/最小/标准差(…,N)` / `EMA(…,N)` / `MACD` 默认 N=35 / `同比` N=365 等)→ `T - N 自然日` - 公式属于"需要长历史"场景(`回测`/`IC评估`/`事件研究`/`区间统计量`/`累计最大`/`从头平均`/`年度累计` 以及多报告期财务查询)→ `20160101 / 20210101 / T-N年` 等档位 - 用户明确指定区间 → 直接用 - 无法判定 N / 单点快照 / 仅取最新值 → 直接 `T`(今日) - 详细分档见 `tools/run_multi_formula.md` § begin_date 分档方案 - `include_description` **必须传** `true`,确保返回 description 供后续判断 - `use_minute_data` 按时间意图选择:仅盘中、当前分钟或当日未收盘行情传 `true`;历史日频、回测、净值、回撤、波动率和财务报告期省略或设为 `false` - **`force_reusable_array` 在 `formulas.length ≥ 2` 时必须主动评估并按需传递(硬规则)**: - 评估方法:对每条公式问一句「这个左侧变量名,会被本批后续公式引用,又会被 `readData` / 最终业务步骤读取吗?」 - 仅被本批后续公式引用、**不会**被 `readData` 读 → 不要写入数组 - 会被 `readData` 读,或是最终输出 → 写入 `force_reusable_array` - 单公式(`formulas.length == 1`)可省略 - 形如 `..._中间`、`..._条件`、`..._掩码`、`..._筛选`、`..._排序值`、`..._因子矩阵`、`MA5`、`MA20`、`Signal` 这类命名的变量,几乎都是中间量,应主动不写入数组 - 数组长度可短于 `formulas`,缺失位置默认 `true`;只接受布尔 `false`,`null` / 字符串 `"false"` 不生效 - 禁止省略 `begin_date` 和 `include_description` - 禁止传 `refresh_snapshot_time`;需要强制刷新分钟数据截止时间时,先调用独立 `refreshSnapshotTime` 工具 ### readData - 各场景的 readData 模式选择**以对应 leaf workflow 为准**(quick-snapshot / quick-window / quick-report-period 各有明确规定) - **全局禁止**:`full` 模式(任何 workflow 均不得使用)、`smart_sample`(短窗场景禁用) - `last_column_full` 仅在 leaf workflow 明确允许时才可使用(目前:quick-window、quant-standard、event-study) --- ## 原生工具优先规则(硬规则) 当平台已提供原生工具时,必须直接使用原生工具,不得通过脚本包装、shell 命令、环境变量注入或 `run_skill_script` 二次调用同名能力。 适用工具(包括但不限于): - `confirmDataMulti` - `runMultiFormulaBatchStream` - `readData` - `renderKLine` - `renderChart` `run_skill_script` / `scripts/call.py` 仅允许用于: 1. `newSession` 等会话/管理动作; 2. workflow 明确要求的本地脚本步骤; 3. 平台不存在等价原生工具时的兜底动作。 明确禁止: - `GZQ_PARAMS='...' python scripts/call.py runMultiFormulaBatchStream` ← 禁止 - shell 失败后继续修补参数转义而不切回原生工具 ← 禁止 - 为了"传参方便"用脚本中转已有原生能力 ← 禁止 --- ## 证据边界与无证据扩写禁令(硬规则) 当任务目标是查数、统计、排序、对比、区间收益、事件窗口结果时: 1. 最终答案默认只输出本轮工具结果直接支持的: - 数值 - 日期/报告期 - 排名/对比关系 - 必要口径说明 2. 未经本轮证据支持,禁止默认输出: - 宏观因果归因(如"因美联储加息导致…") - 政策归因、行业归因、风格归因 - "通常/往往/偏正面/说明市场如何定价"类规律性结论 - "可能存在 ±1 个交易日偏差"类未被本轮验证的不确定性声明 3. 若用户明确要求解释原因,可使用弱化措辞: - "可能反映…" - "仅作解释性猜测,非本轮证据结论" 但不得写成确定性事实。 4. 小样本(< 5)场景下: - 不得把描述性结果表述为统计规律 - 若输出均值,必须标注"仅描述性汇总,不代表统计规律" --- ## 失败门禁与脚本证据规则(硬规则) 1. **Silent output is not evidence** - 若 `run_skill_script` / 本地脚本执行后 stdout 为空,且未产出结构化结果文件,则视为未完成。 - 不得把"脚本已运行"当成已得到证据。 2. **Failure must trigger rollback** - 公式失败、脚本失败、读取失败、映射失败后,必须回退到最近已验证 checkpoint。 - 不得在失败后继续沿未验证路径组织最终答案。 3. **No manual reconstruction of key results** - 对事件列表、TopN 名单、区间列表、窗口收益等关键中间结果,不得仅凭大段原始输出人工挑数后直接作答。 - 必须有结构化中间产物或明确可复核的 readData 结果。 4. **脚本使用边界** - 平台原生工具可完成时,不得把脚本作为默认主路径。 - 脚本只可用于受控补充整理,不得替代主流程。 --- ## fact-only 模式(查数/统计/排名/对比场景) 若用户意图是:统计、排名、列表、多资产横向对比,默认只输出: - 时间锚点 - 结果表/列表 - 必要的比较结论(如"最强/最弱") 除非用户明确要求,否则不追加: - 市场点评 - 趋势判断 - 原因解释 - 投资建议 --- ## 数值精度规则:原始值计算,展示再格式化(硬规则) 对所有派生指标(振幅、比率、百分比、区间收益率等): 1. **必须基于工具返回的原始值计算**(不得用已格式化/截断的展示值做二次计算); 2. 展示时统一做保留位数/单位格式化; 3. 若工具返回了高精度小数,优先保留足够精度完成计算,展示阶段再舍入。 明确禁止: - 先把 `40.3592` 显示成 `40.36`,再用 `40.36` 参与振幅计算 - 先把 `0.007` 显示成 `0.70%`,再反向用 `0.70` 继续计算 - 用 `description` 中已格式化的字符串替代原始数值做二次计算 --- ## 不补精度原则(所有 workflow 必须遵守) 工具返回几位小数就展示几位(经格式化规则处理后)。具体: 1. 工具未返回的字段,不得通过推理生成数值 2. 工具返回的数值精度即为可信精度上限,不得追加小数位 3. description 中的描述性文字(如"小幅上涨""表现平稳")不得转译为具体数值 4. 若用户追问更高精度,回答"工具返回精度到此为止",不得凭推理补位 --- ## 方法限制说明保留原则(所有 workflow 必须遵守) 当以下情况出现时,最终回答中**必须**保留对应的方法限制说明,不得为了"简洁"而省略: | 情况 | 必须保留的说明 | |------|---------------| | 事件研究样本数 ≤ 3 | "样本极少,均值仅供参考" | | 搜索获取的日期不完整 | "仅包含已搜索到的 N 次" | | 多字段日期不一致 | 注明各字段的实际日期 | | 工具返回 warnings | 原文展示 warning 内容 | | 回答层派生计算 | 标注"回答层计算"及口径 | | 阈值事件日来源为 LLM 知识 | 注明"基于公开共识,±1 交易日偏差" | 原则:**宁可多说一句限制,不可让用户误判数据可靠度。** --- ## 最终答案禁语清单(所有 workflow 必须遵守) 以下词句/模式**不得出现在面向用户的最终回答中**。此清单整合了各节散落的禁止项,作为统一检查表。 ### 过程性话术(禁止) - 「已成功获取」「数据已获取成功」「成功获取了」 - 「让我来」「我来帮您」「让我先」「接下来我来」「现在我来」 - 「根据 workflow」「按照流程」「根据返回结果」「根据 Step X」「根据 description」 - 「Step 1/2/3」「Checkpoint」「Acceptance Test」 - 「接下来执行」「开始执行」「让我开始」 - 「数据已成功获取」「我已经获取了」「我可以提取以下数据」 - 「现在我来整理结果」「原始数据如下」「让我重新排序」 ### 内部信息泄露(禁止) - `_working/` 路径、文件名、task_id - checkpoint 名称:E0/E1/E2/K0/K1/W0/W1 等 - workflow 文件名:quick-snapshot.md、event-study.md 等 - 内部校验表、路径A/B、slot 名称 - 工具原始 JSON(整段回显) ### 冗余客套(禁止,除非用户要求) - 「如需了解更多…」「希望对您有帮助」「如有其他问题…」 - 「综上所述」「总结一下」(数据结果本身就是结论) - 「表现强劲」「走势不佳」「值得关注」等主观评价(除非用户要求分析) ### 自检方式 在输出最终回答前,内部扫描是否包含以上禁语。如命中,删除后再输出。此检查不需要输出到回答中。 --- ## 最终回答重写触发器(硬规则) 若最终回答命中以下任一表达,必须整段重写后再输出: - 已成功获取 / 数据已获取 / 公式执行成功 - 让我来 / 我来帮您 / 现在我来 / 接下来 - 根据 workflow / 根据返回结果 / 根据 description - Step 1 / Step 2 / Step 3 - 路由到 / 使用了某 workflow / 读取了某文档 ## 最终回答默认白名单 对查数/统计/对比类问题,最终回答默认只允许包含: 1. 标的名称(必要时含代码) 2. 日期/报告期/时间范围 3. 用户请求的字段值或表格 4. 必要的单位、百分比格式 5. 用户明确要求时才额外包含的最小口径说明 除上述之外的内容,默认不输出。 ## 图表类 artifact 回答边界(硬规则) 若答案为图表类 artifact(如 K 线图、净值曲线),最终回答中不应额外宣称工具未返回的区间起止日期、数据点数量、统计摘要。 --- ## 最终答案证据门禁(硬规则) 若最终答案要输出以下任一内容: - 精确数值(涨幅百分比、PE、成交额等) - 精确日期/报告期 - 排名名次 - "涨停/跌停/前10/后10"等强结论 - 图表数据点数量、精确起止日期 则必须满足以下二选一: 1. 该信息在本轮工具返回中清晰可见;或 2. 已通过逐资产/逐事件/逐字段重算得到可审计结果。 禁止: - 从被截断的大矩阵输出中人工"读出"精确值 - 从 description / 图表 artifact / 草稿排序中推断强结论 - 在无直接证据时写"今日均涨停""共有287个数据点""2025年三季报"等确定性表述 **强制 fallback**:当 `readData(last_column_full)` 返回资产数 > 200 且输出被截断,而最终答案需要为命中的少量候选股票列出精确字段值时,必须进入"逐只核验模式"——对每个候选 grep 本地资产库确认 ticker + 单资产 `runMultiFormulaBatchStream` 取值,而非从截断大表中提值。 -
industry-ranking-fast.md 4.5 KB
# 行业涨跌幅排名活页快路径 > **唯一适用范围**:用户明确要求申万一级行业/行业板块最近 N 个交易日涨跌幅排名,并且要求“图、排名图、柱状图、可视化”。典型问题:“做一张申万一级行业最近20个交易日涨跌幅排名图,我想快速看看哪些行业最强、哪些行业最弱。” ## 顶部硬闸门 命中本流程后,本 Turn **只读 `SKILL.md` 与本文件**。不得读取 `global-rules.md`、`quant-standard.md`、`recipes/industry-aggregation.md`、案例库或函数文档;本文件已包含完整公式与恢复规则。 不得调用 `searchFunctions`、`searchSimilarCases`、`getCardFormulas`、`confirmDataMulti`。不得调用 `renderChart`、`renderKLine` 或先生成 PNG;页面主图由 QBV 使用已物化结果生成。 ## 固定执行序列 1. 在任何平台工具前,先执行活页路由: ```powershell python scripts/live_page_routing.py route --user-query "用户本轮原话" ``` 只有 `route=create` 才继续本流程;`none/suggest` 按路由结果回到 QBS。 2. 本 Session 首轮调用一次 `newSession`;追问调用一次 `beginTurn`。保存返回的 `task_id/turn_id`,不重复初始化。 3. 只执行以下一条公式。把 `N` 替换为用户要求的交易日数;“最近一个月”默认 N=20: ```text 行业近N日涨跌幅=成分平均汇总(涨跌幅("全市场每日收盘价",N),"申万资产所属指数") ``` 调用 `runMultiFormulaBatchStream` 时: - `formulas` 只含这一条; - `force_reusable_array` 传 `['行业近N日涨跌幅']`; - `begin_date` 使用当前年份 1 月 1 日;若当前日期早于 2 月 15 日,使用上一年 10 月 1 日; - `include_description=true`; - 不为“更全面”扩大计算区间或增加第二条公式。 4. 批次成功后,**只使用 `results[0].indexinfo_id` 作为 data_id**。`expression_id` 不是 `readData` 的数据 ID,禁止读取它。只调用一次: ```json { "ids": ["results[0].indexinfo_id"], "mode": "last_column_full", "task_id": "当前 task_id" } ``` 若返回 31 个左右行业,直接排序并回答。行业名称必须原样复用;值乘 100 后显示百分比。口径必须写成:**“个股近 N 日区间收益按申万一级行业做成分股算术平均”**,不得写成指数本身的累计涨幅。 5. `readData` 成功后,直接调用一次低自由度准备命令;不要创建中间 JSON: ```powershell python scripts/live_page_routing.py prepare-industry-ranking-page --data-id "<indexinfo_id>" --index-title "行业近N日涨跌幅" --window-days N --asset-count 31 --as-of-date "<readData 返回 YYYYMMDD>" ``` 命令会从当前 QBS Session 自动补齐 `task_id/turn_id/user_query/source_skill_version`,并一次性生成 computation capsule、Handoff 和幂等 QBV Job。它同时携带: - 已物化 `data_id`,供 QBV 只做一次 `readData`; - 固定行业聚合公式,供页面注册实时刷新合同; - 横向排名柱状图、正红负绿、零轴、排序、hover 和口径展示要求。 **终止性规则**:命令 `code=0` 后禁止再次执行 `handoff`、`prepare`、`prepare-validated-page` 或第二次页面准备;也禁止再调用 `renderChart`。 6. `should_spawn=true` 时,使用宿主真实提供的内部委派能力把返回的 `handoff_file` 与 `qbv_job_id` 交给独立 QBV SOP,只等待接受回执,不等待页面完成。用户可见表述只能说“正在后台生成可交互页面”,不要说“新开一个子 Agent”。 若当前测试宿主没有内部委派工具,必须执行: ```powershell python scripts/live_page_routing.py mark-delegation-unavailable --qbv-job-id "<qbv_job_id>" ``` 此失败只影响页面旁路,不影响 QBS 排名答案;不得假称页面正在生成。 ## QBS 首答 首答立即给出: - 数据日期与计算口径; - 最强 3~5 个行业; - 最弱 3~5 个行业; - 全部行业表格可按篇幅保留或压缩; - 只做强弱与分化描述,不添加未经数据验证的政策、资金或宏观归因。 ## 工具预算 | 类型 | 上限 | |---|---:| | Skill/workflow Read | 2(`SKILL.md` + 本文件) | | 路由 Bash | 1 | | Session/Turn | 1 | | `runMultiFormulaBatchStream` | 1 | | `readData` | 1 | | 页面准备 Bash | 1 | | 内部委派或失败终态 Bash | 1 | | **目标总调用** | **7~8** | 任何成功角色不得重复调用。目标是 QBS 先快速回答,QBV 只消费现有 `data_id`,绝不重跑相同公式。 -
live-page-routing.md 18.7 KB
# QBS → QBV 并行活页路由 > 本文只定义 **QBS 何时交接**、Handoff 合同和后台 Job 边界。页面范式、direct/fork/unmatched、页面归属、复制、发布、Card Runtime 与公网验收全部由 `quant-buddy-view` 的现有 SOP 决定。 ## 0. 已有文件转活页优先例外 已有 JPG/PNG、HTML、PDF 等文件 + 活页化意图(含研究改造复合需求),先进入 QBV `workflows/existing-file-static-first.md`,先静态托管、验收并交付链接,再查数增强。此路径不等待本轮计算、图表分类器、Handoff 或 computation capsule;保留同一 task_id/turn_id 和后续 page_id/URL。下述“先完成查询/计算”的通用顺序不适用于首次静态交付。文件分析但不要求发布、明确暂不发布不触发。 ## 1. 不阻塞原则 - QBS 必须先完成本轮查询、计算、校验并准备好正常答案;QBV 失败不得撤销、覆盖或延迟这些业务结果。 - `route=create|existing_page` 时,QBS 必须使用宿主提供的**内部子 Agent 委派工具**启动独立 QBV 子任务,优先使用 `spawn_agent`;仅当宿主实际提供其他等价内部委派工具时才使用该工具。 - 委派调用只等待“已接受/已创建子 Agent”的即时回执,**不得在 QBS 第一条回答前等待页面生成完成**。 - 只有拿到真实委派回执,第一条回答末尾才可写“正在生成活页,完成后补充链接”;委派能力不可用或调用失败时必须把 Job 写为失败,但仍正常返回 QBS 答案,不得假称已开始生成。 - 子 Agent 完成后,父 Agent 或宿主回调向原用户、原任务追加第二条消息;不得另开用户可见任务代替回推。 ## 2. 强制执行闸门 凡用户要求任何图表 artifact,QBS 在业务数据/静态图验证完成后、发送第一条回答前,必须实际执行分类器。**读取本文、生成 PNG 或模型自行判断均不算完成路由。** ```powershell python scripts/live_page_routing.py route --user-query "用户本轮原话" ``` Windows / PowerShell 优先使用上面的 `--user-query` 形式,避免内联 JSON 引号被宿主 Shell 改写;自动化场景也可继续使用 `@payload.json`。宿主 Bash 的 cwd 已是 Skill 根目录时,必须原样执行上述相对路径命令,**不要 `cd`、不要改写为宿主猜测的绝对路径、不要先运行 `python -c` 测试权限**。 强图表自然表达包括但不限于: - “把股价、成交量和 PE **放在一张图里**”; - “把这些指标**画成一张图**”; - “放到**同一个图里比较**”; - “把价格和估值放一起对比,**做成图**”; - “将多个指标**绘制成图表**”。 `route` 输出的 JSON 必须保留在本轮执行证据中。分类器失败是旁路错误,记录后继续 QBS,不得阻断首答;但禁止无命令调用记录就宣称已判定路由。 ## 3. 四种 QBS 路由 | route | 条件 | QBS 动作 | |---|---|---| | `none` | 一次性查数、普通分析、弱“看看走势”、用户只要 PNG/本地图片 | 直接回答,不交接 | | `suggest` | 有持续复用价值但结构不稳定;或页面依赖未经确认的高风险状态 | 直接回答并询问/建议,确认前不入队 | | `create` | 明确 K 线/分时/收益净值回撤曲线/多资产对比/指标曲线/排名图/热力图/动态看板;明确可刷新、可交互、可分享、持续跟踪;或命中“低PE高ROE选股 TopN”这类已确认高频且结构稳定的因子榜单 | 正常回答,同时准备并启动 QBV Job | | `existing_page` | 用户给出 `page_id`、活页链接或明确修改已有活页 | 正常回答,把页面引用交给 QBV;QBS 不判断归属 | ### 3.1 高频稳定榜单例外 `A股低PE高ROE选股Top20` 虽然没有出现“活页/图表”等产品词,但它是已确认的高频复用场景,页面 schema 稳定:TopN 表格、PE、ROE、综合比值、排序/筛选/刷新。因此确定性路由为 `create`,原因码为 `structured_factor_screening_ranking`。 边界: - 仅对同时出现“低PE/低市盈率/低估值”与“高ROE/高净资产收益率/高盈利”,并明确“选股/筛选/排名 + TopN/前N”的表达生效。 - 普通一次性条件选股仍为 `none`;不能因为结果是列表就自动建页。 - 用户明确“只要表格/不要网页/不需要活页”时优先 `none`。 - QBS 必须先完成 TopN 文本答案和验证;QBV 只复用结构化结果生成增值页面。 ## 4. 高风险持久状态 以下状态可用于本轮临时计算,但未经用户确认不得写入活页: - 持仓、仓位、成本价、买入价、股数; - 止损、止盈、加减仓、调仓条件; - 自动交易、自动化规则和触发条件。 若页面依赖这些状态: 1. 路由改为 `suggest`; 2. `requires_persistence_confirmation=true`; 3. 用户明确确认后再以 `persistence_confirmed=true` 重新分类并准备 Job。 ## 5. Handoff 合同 QBS 只能生成 `create` 或 `existing_page` Handoff: ```json { "schema_version": "qbs_qbv_handoff_v1", "task_id": "同一用户任务 ID", "turn_id": "触发页面的当前 Turn ID", "source_skill_id": null, "source_skill_id_status": "unavailable", "source_skill_name": "quant-buddy-skill", "source_skill_version": "当前 QBS 版本", "user_query": "当前用户原话", "route": "create | existing_page", "route_reason": ["visualization_required"], "page_reference": null, "validated_outputs": [], "validation_receipts": [], "computation_capsule": { "schema_version": "qbs_computation_capsule_v1", "page_intent": {}, "asset_resolution": {}, "validated_contracts": [], "validated_outputs": [], "validated_insights": [], "validation_receipts": [] }, "requires_persistence_confirmation": false, "persistence_confirmed": false } ``` 硬约束: - `source_skill_id` 有真实运行值时必须原样记录并设 `source_skill_id_status=available`;拿不到时写 `null` 与 `source_skill_id_status=unavailable`,同时记录 `source_skill_name/source_skill_version`。`source_skill_id` 缺失不得阻止 Handoff、Job 或子 Agent 委派,且禁止猜测任一历史 `skill_*`。 - `task_id` 与 QBS 当前 Session 相同;`turn_id` 必须是当前用户消息对应的不可变 Turn。 - `validated_outputs` / `validation_receipts` 保留旧 Handoff 兼容;新链路优先传 `computation_capsule`。胶囊不能只传 PNG/文件路径,必须同时给出结果快照、可复现合同、字段映射、fingerprint/hash、核心问题和主图意图。 - QBV 使用 `qbs_handoff_adapter.py` 判断覆盖度:`covered → skip`、`partial → delta_only`、`unusable → normal`。只跳过资产识别、同字段取数、同公式和已验证结论;Grant/Package 注册、页面运行时首次查询、页面构建、发布和公网验收不得跳过。 - Handoff 不得出现 `update_owned`、`copy_to_owned`、`direct`、`fork` 或 `unmatched`。 ### 5.1 计算胶囊(优先) 真实用户示例: > 帮我把长鑫科技最近一年的股价、成交量和 PE 变化放在一张图里,我想看看估值和价格是不是同步。 QBS 完成资产识别、行情/估值查询和本轮分析后,把**已经验证过的部分**写成胶囊: ```powershell python scripts/qbv_computation_capsule.py build '@capsule-input.json' ``` 最小输入结构: ```json { "task_id": "当前 QBS task_id", "turn_id": "当前用户 Turn", "user_query": "用户原话", "page_intent": { "question_to_answer": "估值和价格是否同步", "recommended_page_type": "price_volume_valuation", "primary_visualization": "price_volume_pe_timeseries", "required_roles": ["main_series"] }, "asset_resolution": { "query": "长鑫科技", "canonical_name": "长鑫科技", "canonical_id": "平台已验证的资产 ID", "market": "CN" }, "validated_contracts": [{ "role": "main_series", "kind": "fast_query", "contract": {"kind": "fast_query", "payload": {}} }], "validated_outputs": [{ "role": "main_series", "artifact_file": "QBS 本轮结果 JSON/CSV 的绝对路径", "row_count": 0, "field_mapping": {"date": "trade_date", "price": "close", "pe": "pe_ttm"} }], "validated_insights": [], "validation_receipts": [] } ``` 脚本自动补齐合同 fingerprint、artifact SHA256 并验证文件存在。把返回的 `computation_capsule` 原样放入 Handoff;task/turn/query 不一致、hash 被篡改时禁止宣称可复用。没有足够结构化产物时可以省略胶囊,让 QBV 无损走原流程,但不得用低质量胶囊强行跳过 QBS 验证。 ## 6. 一命令准备已验证页面 QBS 已经得到排名、对比、回测、热力图或其它结构化结果 artifact 时,不再让 Agent 手工拼装 Capsule、Handoff 和 Job。把最小业务输入写入 `skill/output` 下的 UTF-8 JSON: ```json { "task_id": "当前 task_id", "turn_id": "当前 turn_id", "user_query": "用户本轮原话", "route": "create", "source_skill_id": null, "source_skill_version": "当前 QBS 版本", "page_intent": { "question_to_answer": "页面要回答的核心问题", "recommended_page_type": "industry_return_ranking", "primary_visualization": "horizontal_ranked_bar_chart", "required_roles": ["industry_ranking"] }, "asset_resolution": {"universe": "申万一级行业"}, "validated_roles": [{ "role": "industry_ranking", "kind": "formula_package", "contract": { "kind": "formula_package", "window_days": 20, "aggregation": "constituent_mean" }, "artifact_file": "D:/.../skill/output/.../industry-ranking.json", "row_count": 31, "field_mapping": {"label": "industry", "value": "return_20d"}, "validation_receipt_file": "D:/.../skill/output/.../validation-receipt.json" }], "validated_insights": [] } ``` 然后只执行一次: ```powershell python scripts/live_page_routing.py prepare-validated-page @output/_working/<task_id>/prepare-page.json ``` 该命令负责: 1. 重新执行并核对确定性 route,禁止模型用输入字段强行覆盖分类结果; 2. 校验 task/turn/query、artifact、row_count、field_mapping 和 receipt lineage; 3. 自动生成 contract fingerprint、artifact SHA256 与 receipt SHA256; 4. 构造 `qbs_computation_capsule_v1` 和 `qbs_qbv_handoff_v1`; 5. 创建或复用幂等 `qbs_qbv_job_v1`; 6. 返回 `should_spawn/qbv_job_id/handoff_file/job_file/computation_coverage`。 `validation_receipt_file` 可包含单个 object、array,或 `{ "validation_receipts": [...] }`;若其中出现 `task_id/turn_id/user_query` 或 `lineage`,必须与当前 Turn 一致。`required_roles` 可以多于 `validated_roles`,用于明确让 QBV Adapter 返回 `partial` 并只补 `missing_roles`,不得伪造已覆盖 role。 若成功 Receipt 含 `runtime_contract.schema_version=qbs_formula_runtime_contract_v1`,`prepare-validated-page` 会把它原样规范化进 Capsule 的 `formula_runtime_contract`。调用方通常不需要重复填写;只有同一请求中存在多个不同公式合同、必须明确选择时才显式传入。显式值必须与 Receipt 完全一致,否则失败关闭。公式字面、顺序和输出名是执行合同,不得为 QBV 缩写、翻译、合并或重新推导。 **请求 JSON 防转义重试规则**:Receipt 已经保存原始公式时,`validated_roles` 不要再填写 `formula`,也不要在每个 role 重复 `validation_receipt_file`。优先顶层传一次 `"validation_receipts": ["<validation_receipt_file>"]`(也兼容内联 Receipt 对象),或省略后使用同任务 Receipt 自动发现;这样 `prepare-page.json` 只保留 `data_id/row_count/date/artifact/field_mapping/value_semantics` 等无须二次转义的最小字段。 **低 PE 高 ROE Top20 固定交接**:该场景必须把 QBS 已物化结果拆成六个 `validated_roles`:`ranking_top10`、`ranking_next10`、`pe_top10`、`pe_next10`、`roe_top10`、`roe_next10`,每个 `row_count=10`,并让 `required_roles` 使用同一集合。顶层只引用一次本轮 Validation Receipt;Receipt 中的精确公式合同必须包含 11 条 Top10/Next10 公式和六个 `last_day_stats` reads。QBV Adapter 命中后应返回 `coverage=covered`、`qbs_action=skip`、`formula_runtime_action=register_exact`,不得再次调用 QBS 重算。页面端合并两段、按 Score 降序得到 20 行;标题、分享标题和元数据统一使用“A股低PE高ROE选股 Top20”,不得继承模板中的单只股票名称。 **终止性规则**:`prepare-validated-page` 返回 `code=0` 后,Capsule、Handoff 和 Job 已全部生成。禁止再次执行 `qbv_computation_capsule.py build`、`handoff`、`prepare` 或第二次不同参数的页面准备;只消费返回的 `should_spawn` 和文件路径进行真实内部子 Agent 委派。 ## 7. Job 与幂等 > 若当前 leaf 使用 `prepare-fast-query-page` 或 `prepare-validated-page`,该命令已经原子完成 capsule + Handoff + Job;成功后禁止再调用本节的 `handoff` 或 `prepare`。本节两步命令只用于没有结构化 validated roles 的兼容工作流。 先构造 Handoff,再准备本地持久 Job: ```powershell python scripts/live_page_routing.py handoff '@handoff-input.json' python scripts/live_page_routing.py prepare '{"handoff_file":"D:/.../handoff.json"}' ``` 默认 Job registry 位于系统临时目录 `quant-buddy-qbv-jobs/`;可用 `QBS_QBV_JOB_DIR` 显式覆盖。幂等键固定为: ```text task_id + turn_id + route + normalized_page_reference ``` `prepare` 返回: - `should_spawn=true`:本次负责启动子 Agent; - `should_spawn=false`:已有 queued/running/completed 或不可重试失败 Job,禁止重复建页; - `qbv_job_id`、`handoff_file`、`job_file`:交给子 Agent 和审计。 Job 状态:`queued → running → completed | failed`。相同 Turn 的网络重试必须复用原 Job。仅 `failed + retryable=true` 且显式 `retry_failed=true` 时允许重试原 Job。 ## 8. 子 Agent 委派模板 当 `should_spawn=true` 时: 1. 检查当前宿主实际提供的**内部子 Agent 委派工具**,优先使用 `spawn_agent`。兼容宿主若只提供 `sessions_spawn` 等同类内部工具,可以使用真实存在的工具,但不得把某个工具名写死为唯一实现。 2. **不得使用 `create_thread`、`fork_thread` 或其他用户可见的新任务能力代替子 Agent**;它们会改变用户任务边界,也不能证明会向原任务回推第二条消息。 3. 必须存在真实的子 Agent 工具调用记录;只在思考、提示词或最终回答里说“新开一个子 Agent”不算启动。 4. 子 Agent 的任务文本必须包含绝对 `handoff_file`、绝对 `job_file`、`qbv_job_id`,并明确要求读取和使用 `quant-buddy-view/SKILL.md`,执行完整 QBV SOP。推荐提示: ```text 你是本任务的 QBV 子 Agent。 必须读取并使用 quant-buddy-view/SKILL.md。 请消费 Handoff 文件 {handoff_file},处理 QBV Job {qbv_job_id}(Job 文件 {job_file}): - 复用其中的 task_id 与 turn_id,不得新建用户 Turn; - 运行 quant-buddy-view/scripts/trace_context.py beginHandoff; - 自行决定 direct、fork 或 unmatched,并执行页面 ownership SOP; - 本人页面原位更新;他人页面复制到本人名下后修改;归属未知禁止写入; - 先运行 `quant-buddy-view/scripts/qbs_handoff_adapter.py evaluate`,同时传 `handoff_file + qbv_job_id + qbv_job_file`;Adapter 会自动把对应 Job 写为 `running`;`covered` 不重复相同 role,`partial` 只补 missing roles,`unusable` 回退原 QBS bridge; - 完成上传、发布、公开访问、内容、实时数据与 Card Runtime 验收; - 不要手改 Job JSON;若 Adapter 已返回 `marked_running/already_running`,无需再重复调用 QBS 脚本;真实 `target_skill_id` 由终态公开 URL 提取或由已验证参数提供,禁止猜测历史 skill ID; - 完整执行必须包在异常兜底中:确定终止且没有成功终态时,调用 `qbs_handoff_adapter.py fail-job` 写回 `failed + failure_code`,禁止遗留永久 `running`; - 只有同时取得真实 `target_page_id`、`public_url`、`published=true`、`public_verified=true`,并完成公开访问与 Card Runtime 验收后才算完成;`publish_verified` 会自动把匹配 Job 写回 `completed`,返回的 `qbv_job_lifecycle.updated=true` 或 `reason=already_completed` 才是闭环证据; - 最终仅返回活页标题、公开链接、创建/更新说明和验收结果,失败则返回 failure_code 与简短原因。 ``` 5. 委派工具返回成功回执后,把 Job 更新为 `running`,记录实际 `delegation_tool/delegation_id`;为兼容旧宿主,也可额外记录 `spawn_run_id/child_session_key`。`target_skill_id` 仅在宿主或子 Agent 确知真实 QBV skill_id 时填写,禁止猜测。进入 `running` 时 Job 会记录 `started_at/expires_at`。 6. 获得委派成功回执后立即发送 QBS 第一条完整答案,不等待子 Agent完成。父 Agent不得重复执行已经委派给子 Agent的 QBV SOP。 7. Worker 的成功终态必须包含真实 `target_skill_id + target_page_id + public_url + published=true + public_verified=true`;缺任一项时 `update completed` 会被拒绝。 8. 父 Agent或真实编排宿主必须在 `expires_at` 后执行 watchdog:`python scripts/live_page_routing.py expire-stale --qbv-job-id "<qbv_job_id>"`。它会把仍为 `queued/running` 的 Job 写成可重试失败。Skill 仓库只提供确定性 watchdog,不伪装自己拥有生产后台调度器。 9. 子 Agent终态由父 Agent或宿主回调投递到同一用户、同一任务;成功补充公开链接,失败补充简短失败状态。 Worker 完成公网验收后的终态写回示例: ```powershell python scripts/live_page_routing.py update '{"qbv_job_id":"<id>","status":"completed","target_skill_id":"<真实QBV skill_id>","target_page_id":"page_xxx","public_url":"https://pages.quantbuddy.cn/...","published":true,"public_verified":true}' ``` 委派工具不存在或调用失败时,必须执行低自由度终态命令: ```powershell python scripts/live_page_routing.py mark-delegation-unavailable --qbv-job-id "<qbv_job_id>" ``` 它会把已准备的 Job 标记为 `failed + DELEGATION_UNAVAILABLE + retryable=true`。然后正常返回 QBS 业务答案。不得留下无终态的 `queued` Job,也不得对用户声称活页正在生成。 ## 9. 第二条消息合同 - 成功:`活页标题 + 公开链接 + 已创建/已更新说明`。 - 失败:简短说明活页未完成,并明确前面的 QBS 分析结果仍有效。 - 不得把内部 Job JSON、临时路径、API Key、范式搜索过程或 ownership 查询细节发给用户。 - 页面公开链接只有经过 QBV 公网验收后才能发送;上传接口成功不等于完成。 -
period-return-compare.md 8 KB
# 固定区间累计涨跌幅对比 > **适用范围**:给定明确起止日期/区间,查询 ≤1000 个股票或指数的区间累计涨跌幅,并做简单横向比较。 > 本流程定义本场景的专用执行步骤;全局执行合同、证据分级、禁语与回答边界以 `workflows/global-rules-lite.md` 为准(进入本流程前必须已读取该文件)。 > ⛔ **硬前置条件**:进入本流程前 **必须** 已读取 `workflows/global-rules-lite.md`。 > 未读取 = 本流程所有步骤无效,不得继续。 ### 内联核心红线(来自 global-rules-lite,无需额外读取即刻生效) 1. **evidence-only**:最终答案只输出工具结果支持的数值/日期/口径说明;无工具证据不得归因(禁止风格归因等推断性解释) 2. **去过程化**:禁止「已成功获取」「让我来」「按照流程」「Step 1/2/3」「数据已获取」「从返回的数据中提取」等过程性话术;最终答案第一句必须是资产名或数据结论 3. **资产口径冻结**:`assets_db` 本地资产库 返回的资产名称/体系必须与用户原口径一致;若仅为近似匹配或跨体系匹配,**禁止静默替代继续计算**——必须向用户确认 4. **失败门禁**:公式/读取失败必须回退到最近已验证 checkpoint;禁止带着脏结果继续 5. **输出前自检**:逐句删除过程话术 → 确认涨跌幅已 ×100+% → 核对所有日期与工具返回一致 --- ## 触发条件 - 用户给出**明确起止日期**(如"2019年7月至2020年3月""从A到B") - 只问区间累计涨跌幅/区间收益/区间表现 - 资产数量 ≤1000 个 - 不涉及因子、回测、选股、可视化 ## 反例(不属于此流程,不要误路由) | 用户问题 | 应走流程 | |----------|----------| | 事件后 N 天/月表现 | `event-study.md` | | 阈值定义的连续阶段统计 | `regime-segmentation.md` | | 需要画 K 线图 | `render-kline.md` | | 区间内因子/选股 | `quant-standard.md` | --- ## 执行协议 ### Abstract Target > 交付对象:按区间内首末有效交易日收盘价计算的累计涨跌幅结果表。 ### Checkpoints | CP | 名称 | 已验证状态 | Acceptance Test | |----|------|-----------|-----------------| | P0 | 资产与区间冻结 | 资产、起止日期已确认 | presets/assets_db 唯一命中;起止区间明确 | | P1 | 收盘价序列已取 | 每个资产的一维收盘价序列已生成 | fast_query(window) 成功返回 series | | P2 | 区间首末值已取 | 区间内首末有效交易日价格已读取 | series 中存在可用首末值 | | P3 | 区间收益已算 | 每个资产区间收益可复核 | 仅基于区间首末值计算 | | P4 | 交付完成 | 结果表 + 简洁比较结论 | 无无证据归因 | ### Backward Recovery | 失败位置 | 回退到 | 允许修改的唯一 slot | 禁止 | |---------|--------|-------------------|------| | P1 fast_query 失败 | P0 | 资产代码或字段 | 改用公式链试错 | | P2 序列为空 | P1 | 日期范围 | 引入区间外数据 | | P3 收益异常 | P2 | 实现路径 | 引入区间外数据 | > ⚠️ 429 错误不受本节 Backward Recovery 约束——按 global-rules.md 第 12 条「429 前置拦截」规则处理。 --- ## 执行流程 ### Step 1:确认资产与区间 1. `assets_db` 本地资产库 确认所有资产 2. 解析用户的起止日期为 YYYYMMDD 格式 3. 资产和区间已由 `assets_db` 本地资产库 冻结,无需额外状态文件 ### Step 2:取收盘价序列 对已确认的股票/指数(≤1000),固定使用 `fast_query(window)` 的日期范围模式拉取区间收盘价序列(资产 × 日期 > 500 时返回 CSV 模式,见 `tools/fast_query.md`): ```json { "assets": ["资产1ticker", "资产2ticker"], "query_type": "window", "fields": ["收盘价"], "start_date": {用户起始日期 YYYYMMDD}, "end_date": {用户结束日期 YYYYMMDD}, "user_query": "<用户原始问题>" } ``` 本流程禁止使用 `runMultiFormulaBatchStream`、`runMultiFormulaBatch`、`readData`、`force_reusable_array`、`force_reusable_flags`、`use_minute_data` 或 `getCardFormulas`。 #### CSV 触发 → 下载解析(强规则) 多资产 × 长区间一次 `fast_query(window)` 可能因 资产×日期 > 500 返回 `mode:"csv"`(每字段一个 `csv_url`)。**这是正常交付,不是失败**;区间收益只需首末值,脚本已直接给出: 1. 调 `python scripts/fetch_fastquery_csv.py "<csv_url>" --labels 收盘价`(url 含 `&`,**必须整体加引号**),读其 JSON 里每个资产的 `first` / `last` / `period_return_pct`,据此填结果表。 2. 这是消费工具输出的**许可路径**(见 `SKILL.md` 硬规则 2 的 csv 解析例外);**不要**自写 `curl`/python 解析绕过本脚本,也不要为了避开 CSV 而把题目拆成多次单资产查询。 3. 仅当用户明确要「导出原始明细/CSV 文件」时,才直接提供 `csv_url`。 > 注:本节调用 `scripts/fetch_fastquery_csv.py` 是上面「禁止使用 runMultiFormulaBatchStream/readData 等」的**例外**——它不查数、只解析已返回的 csv_url。 ### Step 3:从 fast_query series 定位区间首末值 compact 列式格式:`results.{资产名}.dates` 为升序日期数组,`results.{资产名}.收盘价` 为等长值数组。 - 区间内**首个有效交易日**收盘价 = 值数组第一个元素(对应 `dates[0]`) - 区间内**最后一个有效交易日**收盘价 = 值数组最后一个元素(对应 `dates` 末尾) 若不同资产区间内首末有效交易日不一致,最终对比前需明确标注,不得假装完全同日可比。 ### Step 4:计算区间收益 ``` 区间收益 = 末日收盘价 / 首日收盘价 - 1 ``` - 必须基于原始精度计算,展示时再统一四舍五入 - 所有资产使用相同的日期口径(区间内首末有效交易日) ### Step 5:格式化输出 --- ## 最终答案输出合同(强制) 最终答案只能包含: 1. 一句口径说明:按区间内首末有效交易日收盘价计算({首日} 至 {末日}) 2. 一个结果表格 3. 一个"表现差异"小节(若用户未明确要求"对比/比较",可省略此小节,只保留结果表) "表现差异"只允许: - 最强 / 最弱 - 谁跑赢谁 - 强弱排序 - 两两百分点差 - 同涨 / 同跌 / 一涨一跌 **当比较对象为 3 个资产时,"表现差异"至少包含:** - 最强、最弱 - 强弱排序 - 第一名相对第二名的差值 - 第一名相对第三名的差值 - 第二名相对第三名的差值 禁止: - workflow / Step / 数据已获取 / 根据返回结果 - 我需要 / 我已获取 / 让我来 / 现在整理 / 数据提取 / 区间收益计算 - 逐项展开收益公式(除非用户明确要求) - 风格标签或属性标签,如: - 成长 / 价值 - 大盘 / 小盘 - 蓝筹 / 赛道 / 防御 - 宏观、政策、资金面归因 ### 默认回答模板 按区间内首末有效交易日收盘价计算({首日} 至 {末日}): | 资产 | 区间起点 | 区间终点 | 累计涨跌幅 | |---|---:|---:|---:| | ... | ... | ... | ... | 表现差异: - 最强:... - 最弱:... - 强弱排序:A > B > C ### 输出规则 - 必须写明计算口径:按区间内首末有效交易日收盘价计算 - 默认只输出:结果表 + 强弱排序 - **禁止默认输出**:宏观因果归因、政策归因、行业归因、风格归因 - 若用户明确要求解释原因,只能用"可能反映…",且标注为推测 ### 输出纪律(硬规则) - 禁止过程性话术 - 禁止内部状态/checkpoint 泄露 - 禁止对结果做主观评价(除非用户要求) - 禁止重复表述同一结果 ### 差异分析边界(硬规则) 对"表现差异"的说明,仅允许以下表达: - 谁涨得更多/跌得更少、排名顺序、涨跌幅差值、是否同涨/同跌 **禁止**(在未额外调用支持工具前): - 风格归因:成长、价值、大盘、小盘、赛道、防御、蓝筹 - 宏观因果归因、政策驱动解释 - 行业结构解释、资金偏好解释 --- -
quant-standard.md 75.6 KB
# 量化标准流程 日行情与季度财务联合选股先读[发布日对齐与筛选合同](daily-financial-screen.md),保留用户原文与解析口径,验收目标交易日,并分离桥接 stdout JSON 与 stderr 进度。 > **路由排除**:若请求是“单资产 + 2~4 个 `fast_query` 标准历史字段 + 明确放在同一张图”,不得使用本 workflow;返回 `visual-page-fast-path.md`。该场景禁止 `searchFunctions` 和公式引擎。 > 本文档是量化选股/回测/因子/图表场景的完整流程规范——唯一权威来源。 > 所有 workflow 文件中涉及量化公式执行的步骤,都遵循本文档的流程和规则。 --- ## 核心原则 开始执行前先明确两件事: **问题1:最终输出是什么?** - 净值曲线 → 公式终点是 `回测()` - 时序走势图 → 公式输出数据序列,最后 `renderChart` - 选股列表 / 因子矩阵 → 公式输出二维布尔或数值矩阵 **问题2:每个输入数据从哪来?** - **单个资产的价格/行情** → 用 `收盘价(资产名)` 等函数 - **全市场数据集**(财务指标、估值、行业归属等)→ 必须先 `confirmDataMulti` 获取 `index_title`,否则报"数据名不存在" ## 不适用场景(先回退到快速查数微流程) 若用户问题属于以下类型,不要优先使用本 workflow,直接按以下规则路由: | 用户问题类型 | 路由目标 | |------|------| | 最新交易日行情/估值(少量字段) | `workflows/quick-snapshot.md` | | 最近 N 个交易日序列/窗口统计 | `workflows/quick-window.md` | | 最近报告期财务指标 | `workflows/quick-report-period.md` | | 单股综合指标画像 / 个股概览 | `workflows/stock-profile.md` | | 单次查数后简单四则计算/对比 | 同上,按字段类型路由 | | 行业/板块近N日涨跌幅排名、行业平均涨跌幅 TopN | 优先用行业聚合快速模板(见 `recipes/industry-aggregation.md`),不要先走完整标准流程 | 原则: - `quant-standard` 面向选股、回测、因子、图表等复杂任务 - 对简单查数题,进入本 workflow 往往会增加自由度、复杂度和失败率 --- ## 简单横截面选股微流程(直达模式) 适用于: - 全A股 / 指定板块 / 指定指数成分股 - 1~3 个横截面指标阈值条件 - 排序字段明确 - 输出前 N 名列表 - 不要求回测、图表、行业中性或复杂因子处理 执行规则: 1. 一次性 `confirmDataMulti` 确认全部字段 2. 构造最小公式链:资产池 → 指标 → 条件 → 排序值 → 取前N 3. 如需展示附带字段,再补一轮结果字段提取 4. 直接回答 默认禁止: - 读取 `presets/functions.yaml`(公式构造不需要额外函数查找) - `searchSimilarCases`(简单横截面不需要案例参考) - `getCardFormulas` - 擅自增加用户未要求的筛选条件(见下方"口径冻结规则") 补充: - 对行业聚合排名题,若 recipe 已提供行业归属字段(如 `申万资产所属指数`),默认直接走聚合公式; - 不要先对"申万一级行业"做远程实体确认;优先使用已确认的数据字段或本地板块/主题列表。 --- ## 横截面/TopN 任务会话隔离(强制) > SKILL.md 硬规则要求首次问题 newSession、每条追问 beginTurn。本节补充 quant-standard 特有的隔离约束。 规则: 1. 只允许读取本轮 session 中生成的结果 2. 不得复用历史 session 的中间变量或结果 id 3. 同一 Session 追问时,无论是否涉及不同选股条件/资产池,都必须先 beginTurn;只有独立 Session 才重新 newSession --- ## 公式流式执行与续传约束(强制) - 公式执行只允许使用 `runMultiFormulaBatchStream`,禁止旧名 `runMultiFormulaBatch` / `run_multi_formula`。 - `runMultiFormulaBatchStream` 返回 deferred、trace_id 或流中断后,续传只允许使用会注入 `Accept: text/event-stream` 的 `resumeJob` 工具路径,或 `python scripts/executor.py resumeJob @params.json` / `python scripts/call.py resumeJob ...`。 - 禁止手动拼 `/runMultiFormulaBatch/stream` 裸 GET 请求;缺少 SSE header 会被服务端拒绝并消耗无效 RU。 - `resumeJob` 必须同时包含 `task_id` 与 `trace_id`;缺任一字段时先停止并说明缺少续传参数,不要试探调用。 --- ## 字段确认的口径保真(强制) 当用户显式写出以下限定词时,`confirmDataMulti` 确认数据不得静默降级: | 用户口径 | 确认要求 | 禁止降级示例 | |---------|---------|-------------| | PE(TTM) / 滚动 | 确认到的 index_title 必须是 TTM 口径 | 不得用静态 PE 代替 | | 归母净利润 | 确认到的必须是归属母公司口径 | 不得用净利润代替 | | 扣非净利润 | 确认到的必须含“扣非” | 不得用普通净利润代替 | | 前复权 / 后复权 / 不复权 | 确认到的必须匹配复权类型 | 不得用其他复权类型代替 | 处理流程: 1. 用户要 `PE(TTM)` → `confirmDataMulti("市盈率 TTM")` → 检查返回的 index_title 是否含 TTM 2. 若返回了非 TTM 口径 → 重试同义词(如 `"滚动市盈率"` `"PE TTM"`) 3. 重试后仍无法匹配精确口径 → 告知用户“平台仅有 XX 口径,是否接受?” 4. **禁止**:在未告知用户的情况下,用低精度口径替代高精度口径 --- ## 选股范围冻结(强制) 当用户明确给出市场范围/板块范围时,公式中必须显式编码该范围。 示例: - 全A股 → 必须加入全A股范围掩码(如 `板块(万得全A)` 或 `缺失填零("非ST股")`) - 沪深300成分股 → 必须加入对应成分范围 - 银行板块 → 必须加入对应行业/板块范围 若最终公式未显式包含用户要求的范围约束,则不得输出结果。 ### 资产宇宙先收敛(强制) 当用户说"股票""个股""A股"进行全市场排名/TopN 时,必须在公式中**首先**施加 A 股资产宇宙掩码(如 `板块(万得全A)`),再进行排序/筛选。 原因:平台二维数据集可能包含港股、美股、指数、基金等非 A 股标的。若不先收敛资产宇宙,TopN 结果中会混入非目标资产,导致榜单无效。 **⚠️ 全市场个股数据必须用 `"全市场每日收盘价"` 等二维数据集,禁止用 `收盘价("万得全A")`**: - ✅ `A股收盘价 = "全市场每日收盘价" * 板块(万得全A)` — 正确:全市场二维矩阵 - ❌ `A股收盘价 = 收盘价("万得全A")` — **错误**:`收盘价()` 返回**万得全A指数本身**的一维价格序列,无法做个股均线/筛选运算,导致所有后续公式崩溃 **规则**: - 排名/TopN 类查询,若用户未指定板块但用了"股票/个股/A股"一词 → 默认施加 `板块(万得全A)` 掩码 - 若用户指定了具体板块(如"银行股""沪深300"),则使用用户指定的板块掩码即可,无需额外加全A掩码 - 资产宇宙掩码必须在排序公式中生效(通过乘法或布尔过滤),不能仅作为 readData 后的文本过滤 - **掩码必须在排序之前施加**:正确写法是 `排序值="全市场每日回报率"*板块(万得全A)` → 再 `取前("排序值",N,...)`。禁止先 `取前("全市场每日回报率",N,...)` 再事后过滤——这会导致前 N 中混入指数/港股/期货等非目标资产 ## 高频默认口径:低 PE + 高 ROE TopN(强制) 用户只说“低PE高ROE选股TopN / 低市盈率高净资产收益率排名前N”,但没有给阈值或主排序字段时,**这不是需要反问的歧义场景**。该表达已具备稳定、可解释的默认口径,直接执行,避免线上高频问题被澄清轮次阻断: 1. 资产池:用户说 A股/股票且未指定板块时,使用 `板块(万得全A)`。 2. 指标:PE 默认使用最新 `PE(TTM)`;ROE 使用平台确认到的最新 ROE 报告期字段。 3. 有效样本:`PE(TTM) > 0` 且 `ROE > 0`,并排除缺失值;不得擅自追加非ST、市值、流动性或行业条件。 4. 默认排序:计算 `ROE / PE(TTM)`,从高到低取用户指定的 TopN。 5. 默认展示:排名、股票代码、名称、PE(TTM)、ROE、ROE/PE、数据日期。 6. 答案必须披露“ROE 为百分比展示值,ROE/PE 按平台返回 ROE 数值直接除以 PE(TTM)”以及未附加其它过滤条件。 7. 用户一旦给出阈值、权重、主排序字段或其它过滤条件,必须完全按用户口径执行,禁止套用本默认值。 8. **首次公式调用的 `begin_date` 不得设为当天**:ROE 是报告期数据,默认至少覆盖当前自然年;例如当前日期为 2026-08-19 时使用 `begin_date=20260101`。如果跨年后当前自然年尚无有效 ROE,则只把窗口扩到最近 12~18 个月并重试一次。不得先用当天窗口制造全 0/无数据,再做第二次相同计算。 9. 完成 `readData` 的 TopN 对齐与排序验证后、输出首答前,必须执行本文件“最终回答合同”的 durable 路由硬闸门;用户没有说“图/网页/活页”不是跳过理由。 10. 本轮 `runMultiFormulaBatchStream` 成功后,必须复用 Validation Receipt 自动保存的 `qbs_formula_runtime_contract_v1`。Top20 不得作为一个无界数组交给 Formula Package;固定拆为 Top10 + Next10,完整执行以下 11 条公式: ```text 低PE高ROE_条件=("A股市盈率(PE, TTM)〔估值数据〕">0) *("A股净资产收益率ROE">0) *板块(万得全A) 低PE高ROE_排序值="低PE高ROE_条件" *("A股净资产收益率ROE"/"A股市盈率(PE, TTM)〔估值数据〕") 低PE高ROE_Top10掩码=取前("低PE高ROE_排序值",10) 低PE高ROE_Top20掩码=取前("低PE高ROE_排序值",20) 低PE高ROE_Next10掩码="低PE高ROE_Top20掩码"-"低PE高ROE_Top10掩码" 低PE高ROE_Top10Score=取前("低PE高ROE_排序值",10,返回数值) 低PE高ROE_Next10Score="低PE高ROE_Next10掩码"*"低PE高ROE_排序值" 低PE高ROE_Top10PE="低PE高ROE_Top10掩码"*"A股市盈率(PE, TTM)〔估值数据〕" 低PE高ROE_Next10PE="低PE高ROE_Next10掩码"*"A股市盈率(PE, TTM)〔估值数据〕" 低PE高ROE_Top10ROE="低PE高ROE_Top10掩码"*"A股净资产收益率ROE" 低PE高ROE_Next10ROE="低PE高ROE_Next10掩码"*"A股净资产收益率ROE" ``` `force_reusable_array` 只包含六个最终输出:`Top10Score/Next10Score/Top10PE/Next10PE/Top10ROE/Next10ROE`。QBS 为本轮文本回答可对六个物化 `data_id` 使用 `readData(mode="last_column_full")`,按资产对齐后合并两段并按 Score 降序输出完整 20 行;页面运行合同则必须使用六个受支持的 `last_day_stats` reads。禁止把 QBS 的 `readData` mode 复制成 Formula Package read mode,也禁止只返回 Top10 却把标题写成 Top20。 该默认值只解决高频短语的执行口径,不代表投资建议或投资置信度。 ## TopN 结果表生成(强制) 当用户要求"按指标A排序,同时展示指标B/C"时,必须按以下顺序执行: 1. 先得到 TopN 名单/掩码 2. 基于同一 TopN 名单提取所有展示列 3. 检查: - 各列资产集合一致 - 各列行数一致 - 各列日期一致 4. 任一不一致 → 禁止直接回答,必须重读或重算 ## 微流程:当前截面条件选股 TopN(强制模板) 适用于: - 当前/最新截面 - 筛选 + 排序 + 前N名 - 输出股票列表而非回测/图表 固定步骤: 1. 确定资产池(如全A股/指定板块) 2. `confirmDataMulti` 一次性确认所需指标 3. 所有二维数据集与中间结果,在后续公式中统一使用双引号引用 4. 公式至少生成: - 条件掩码 - 排序值 - TopN 掩码 - TopN 目标展示值 - TopN 辅助展示值 5. 读取结果时,**必须只读 TopN 相关数据**: - 若读取对象是布尔掩码、全市场二维数据集、或可能返回数千行的大表,必须先用 `description` 判断名单;仍无法判断时才尝试 `readData(mode="precheck")`。 - 若公式已通过 `取前(排序值, N, 返回数值)` / TopN 展示值生成了 ≤N 行结果,**不要调用 `precheck`**,直接用 `readData(mode="last_column_full")` 读取精确展示值。 - 若 `precheck` 对当前结果类型返回“不支持”,不得围绕 `precheck` 重试;直接切换到已收敛的 TopN 数值结果并用 `last_column_full`。 - **禁止直接对全市场二维数据集使用 `last_column_full`**——这会返回数千行资产数据,导致上下文爆炸 - **description 优先 — readData 决策流程(硬规则,逐步执行)**: 1. **检查 description**:`runMultiFormulaBatchStream` 返回后,先检查每个结果的 `description` 字段 2. **名单判定**:若 description 包含 `全部名称:` 字段(如 `全部名称:中远海能(SH600026),小商品城(SH600415)`),且列出的资产数量 ≤ 20,则**这些名称即为最终名单** 3. **是否需要数值?** - 用户只需名单(如"有哪些")→ **不调 readData**,直接用 description 中的名单回答 - 用户需要具体数值(如涨跌幅百分比、成交额、PE)→ 仅对**数值型展示数据**(如 `TOP10涨幅值`、`跌幅超5点数值`)调 readData,**绝不对布尔掩码调 readData** 4. **禁止事项**: - ⛔ 对 `is_bool: true` 的掩码数据调 `readData(last_column_full)` — 会返回数千行(绝大多数 value=0/1),产生数万字符无效输出 - ⛔ 当 description 已含完整名单时,仍对同一掩码或其交叉结果调 readData 5. **实测教训(连续 3 轮未遵守)**:T-027 掩码 description 已含 2 只股票名 → 模型仍读 594 行/24K 字符全表;T-029 掩码 description 已含 2 只股票名 → 模型仍读 5374 行/605K 字符全表。这些 readData 调用 100% 是浪费,且 605K 触发了截断 - **禁止对布尔掩码调用 `last_column_full`**:布尔掩码的 `last_column_full` 会返回全部资产行(绝大多数为 0),产生数万至数十万字符的无效输出。改用 `description` 中的 `筛选出的股票数量` 和 `全部名称` 字段获取结果 - **禁止对 `effective_matches=0` 的掩码调用 `last_column_full`**:description 已明确说明筛选结果为 0 只,读全表只会浪费 token - 若无法避免大表返回,读取后只保留 TopN 非空/非零资产行,立即丢弃其余 6. 输出前必须核对: - 有效结果数是否等于 N - 排序顺序是否与用户要求一致 - 是否剔除了大量 0 / NaN 的非入选资产 7. 一旦拿到 TopN 明细,立即组织最终答案,不再继续读取大表或追加分析 禁止: - 默认使用 `last_valid_per_asset` 读取当前榜单 - 在中间公式中混用裸变量与带引号变量 ### 公式引用字段预确认(TopN 选股必须执行,在组装公式之前) 构建 `runMultiFormulaBatchStream` 之前,逐条核对将用到的所有二维数据集字段: | 检查项 | 通过条件 | 失败处理 | |--------|---------|---------| | 字段名来自 `presets/data_catalog.yaml` | `index_title` 精确匹配 | → `confirmDataMulti` 确认 | | 字段名来自本文档固定模板表 | 直接使用,无需确认 | — | | `confirmDataMulti` 返回 `dimension` | 记录并在公式中区分一维/二维 | 一维 → 直接 readData;二维 → runMultiFormulaBatchStream + 取出 | | 变量名 ≠ `index_title` | 左侧变量名与右侧数据集名不同名 | → 改名(`{股票}_{指标}`) | **硬规则**: - 公式中每引用一个 `"数据集名"`,都必须能追溯到 presets 条目 或 confirmDataMulti 返回的 `index_title` - 不得凭印象或用户用语直接写入公式右侧 - 若同一 session 内已确认过某字段,后续不需要重复确认 ### 隐含约束自检(TopN 选股必须执行) 筛选/排序类选股中,本 workflow **不再定义任何会改变用户结果集合的"默认隐含过滤"**。 **允许自动添加的只有"资产类型宇宙对齐"**: - 用户说"股票/个股/A股普通股票" → 增加股票资产宇宙掩码(如 `板块(万得全A)`),排除指数/基金/期货等非股票资产 **默认禁止自动添加**: - PE > 0 - 非ST股 - 仅主板 - 剔除北交所 / 创业板 / 科创板 - 市值门槛 / 流动性门槛 - 任意行业白名单/黑名单 若模型认为这些条件"常见但有帮助",只能在最终答案后建议"如需加该过滤,我可以再筛一版",不得先执行。 **提交前自检**:在执行 `runMultiFormulaBatchStream` 前,必须内部逐项核对: - 用户原条件列表 - 公式实际条件列表 若公式条件比用户条件更严格或多出任一过滤,则不得执行,必须先改公式。 规则: - **隐含约束仅限资产类型宇宙对齐**,不得擅自发明其他约束(如 PE>0、非ST、市值门槛、上市时间等) - 若用户明确不要求排除(如"包含负PE"),以用户指令为准 - 对于全局条件冻结规则中未提及的约束,一律不主动添加 - 确需使用非标准约束时,必须先通过 `confirmDataMulti` 确认对应字段存在 - **判断标准**:只要用户提供了至少一个量化指标条件(如阈值、排名、TopN),即视为"已给出具体筛选条件",此时禁止自行添加任何额外过滤 ### TopN 结果核验(readData 之后必须执行) 读取 TopN 结果后,在回答前核对: 1. **有效行数**:非零/非NaN 的行数是否 ≥ N;若不足,说明筛选条件可能过严 2. **排序一致性**:结果是否按用户要求的字段和方向(升序/降序)排列 3. **数值合理性**:若结果中出现明显不合理值(如负股息率排名靠前),回查条件掩码是否遗漏约束 4. **覆盖率完整性**:仅当本轮实际执行过 `readData(mode="precheck")` 且返回 `total_count` 时才检查覆盖率;未执行 `precheck` 的 TopN 数值读取不适用此项。若全A股排名但 `total_count` 远低于预期(如仅 300 只,而全A应有 5000+),说明资产宇宙收敛可能存在问题(如分钟频数据覆盖不足、板块掩码维度不对齐)——此时必须在答案中**明确披露**实际覆盖范围(如"本次排名基于分钟频数据覆盖的 XXX 只 A 股"),不得将其表述为"全A股排名" 5. **异常时处理**:若结果数 = 0 或全为 NaN,先检查百分比阈值口径是否正确(展示口径 vs 小数口径),修正后重试 1 次 6. **最终表格排序校验(硬规则 — 必须逐行执行)**:`readData` 返回的行顺序**不保证**按排序字段有序。在组装最终答案表格前,**必须**执行以下步骤: - (a) 从 readData 返回的 `values` 数组中提取所有非零/非NaN 行 - (b) 按用户指定的排序字段和方向(如"涨幅最大" → 数值从大到小)对这些行重新排序 - (c) **逐行验证**:排序列的数值必须满足单调性(降序:每行 ≥ 下一行;升序:每行 ≤ 下一行) - (d) 排序后再编排名次(#1, #2, ...)并输出 - **实测反例**:T-028 成交额前10,readData 返回中 #6 永鼎股份(8.27B) < #7 天华新能(8.36B),模型照搬原始顺序导致排名错误。正确做法:先按成交额降序排列,永鼎应排在天华之后 - **禁止**将 readData 原始顺序直接作为最终排名呈现 - **输出纪律(硬规则)**:排序必须在输出前**一次性完成**,最终答案中只呈现排好序的最终表格。**禁止**在答案正文中展示以下内容:(1)排序前的原始数据列表;(2)"让我重新排序""更正:XX应排在第N位"等纠错过程;(3)同一张表格的多个版本(草稿版+正式版)。这些中间推理属于内部工作过程,对用户无价值且违反去过程化规则。**实测反例**:T-026 涨幅前10,模型在答案中先列出"原始数据(按代码顺序)"→再列"按涨幅降序排列"→发现排错→"让我重新排序"→最终正式表格,同一张表出现 3 次 ### TopN 数值提取 readData 模式强制(硬规则) 当公式已通过 `取前(排序值, N, 返回数值)` 生成了带数值的 TopN 结果时: 1. **必须**对该数值结果执行 `readData(mode="last_column_full")` 获取精确排名数值 2. **禁止**用 `smart_sample` 作为 TopN 最终数值来源——`smart_sample` 返回的是采样摘要,不保证包含全部 TopN 条目的精确值 3. **禁止**从 `description` 中提取近似值作为最终排名数值——`description` 的 `其中十个分别名称` 仅用于名单确认和资产范围排查,不是精确数值来源 **数值提取后排序校验(强制)**: - `readData(last_column_full)` 返回后,提取所有非零/非NaN 行 - 按排序字段按用户要求的方向(升序/降序)**显式重排** - 逐行验证单调性(降序:每行值 ≥ 下一行值) - 不满足单调性 → 以重排后结果为准,不得沿用 readData 原始顺序 **实测反例(T-028,iter-012)**:模型对成交额 Top10 使用 `smart_sample` 读取,结果中部分股票的成交额值来自采样而非完整截面,导致排名顺序与真实排名不一致。正确做法:对 `取前("成交额排序值", 10, 返回数值)` 的结果执行 `readData(mode="last_column_full")`。 ### 条件筛选 / TopN 名单提取合同(强制) 适用于:TopN 排名、排名后再条件筛选、盘中阈值名单查询、布尔掩码结果。 规则: 1. **description 优先**:若 description 已包含 `effective_matches` / 筛选数量 和全部名称,且命中数 ≤ 20,则"名单问题"优先直接用 description 回答,不得默认再读布尔掩码全表 2. **精确字段值按需读取**:若用户还要求精确字段值(涨幅、PE、成交额等),只能读取"目标展示值"或"目标排序值",禁止对布尔掩码执行 `readData(last_column_full)` 作为默认步骤 3. **排序证据要求**:若最终答案要输出"排序后的名单/表格",必须已读取排序字段的结构化值;仅有布尔掩码或 description 名单时,不得声称"按X排序" 4. **小名单定向提取**:对命中名单 ≤ 20 的场景,若需要精确数值,优先用 `presets/assets_db/{类型}.yaml` 唯一命中名单 → 逐只或定向提取目标字段,而不是全市场大表扫描 5. **description 用途边界**:`runMultiFormulaBatchStream.description` 只能用于数量确认、名单确认(仅名单题)、最后有效日期确认,不得单独作为"完整表格 + 数值 + 排序"的唯一证据 6. **零命中快速退出**:若目标掩码的 description 已显示 `effective_matches = 0`,直接回答"当前无符合条件的标的",不再对该布尔掩码执行 `readData(last_column_full)` ### 最终答案证据门禁(TopN 场景,硬规则) 若用户要的是前 N 名股票名单、排名表格、或带数值的横截面榜单,最终回答前必须满足以下至少一条: 1. 已成功 `readData` 到 TopN 明细(资产名 + 排序字段值 + 展示字段值);或 2. 已成功读取排序字段最后一列并能据此重建榜单。 若上述条件不满足: - **不得**输出确定性的前 N 名名单 - **不得**用"已筛出/复盘如下"等措辞包装半成品结果 - 只能明确说明"筛选公式已执行,但最终明细读取失败,暂不能给出可信榜单" 若最终答案包含表格,则表中每一列都必须有直接工具证据支持。 以下情况视为证据不足,禁止直接回答: - 展示列来自未对齐读取 - 返回资产与最终表中的资产不一致 - 大量 0/NaN 占位 - 多数据源未确认共同日期 若最终问题含"TopK/前K/排名前K",必须验证返回结果是否已按目标方向有序;若返回更像集合而非有序列表,不得直接按读取顺序写名次。 若 `last_day_stats` / TopN 摘要读取失败,立即 fallback: - `readData(ids=[排序值], mode="last_column_full")` - `readData(ids=[排序字段原始数据], mode="last_column_full")` ### 最终回答合同(强制) **图表与 durable 场景路由硬闸门**:只要本轮用户要求任何图表 artifact(包括“放在一张图里”“画成一张图”“同图比较”“绘制成图表”),或命中已登记的高频稳定榜单“低 PE + 高 ROE + 选股/排名 + TopN”,在数据/图片验证完成后、输出 QBS 第一条回答前,必须: 1. 读取 `live-page-routing.md`; 2. 实际执行 `python scripts/live_page_routing.py route --user-query "用户本轮原话"`; 3. 保留 route JSON;不得仅凭模型判断,也不得因为已生成 PNG 而跳过; 4. `create|existing_page` 且本流程已有经过证据门禁的排名、对比、回测或其它结构化 artifact 时,把最小 `page_intent + validated_roles` 请求写入 `output/_working/<task_id>/prepare-page.json`,执行一次 `python scripts/live_page_routing.py prepare-validated-page @output/_working/<task_id>/prepare-page.json`;成功后禁止再手工 build capsule / handoff / prepare。只有没有结构化 artifact 的兼容场景才继续 handoff → prepare。随后调用宿主内部子 Agent 委派工具(优先 `spawn_agent`),只等待即时成功回执;不得使用 `create_thread/fork_thread` 或只口头声明。宿主没有内部委派工具时,立即执行 `python scripts/live_page_routing.py mark-delegation-unavailable --qbv-job-id <ID>`,不得遗留 queued Job 或声称正在生成。`source_skill_id` 缺失时标记 `unavailable`,不阻断交接。`none|suggest` 按返回结果继续;任何路由/委派失败必须写入 Job 失败终态,但不阻断本轮答案。 “低 PE 高 ROE TopN”命中 `create` 时,优先直接复用本轮三个已验证 `data_id`,不要为了建页重复下载或重算。请求至少包含: ```json { "page_intent": { "question_to_answer": "当前全A股中,哪些股票兼具较低PE和较高ROE?", "recommended_page_type": "equity_factor_screening_ranking", "primary_visualization": "sortable_topn_table", "required_roles": ["factor_screening_ranking", "pe_values", "roe_values"] }, "validated_roles": [ {"role": "factor_screening_ranking", "data_id": "<TopN Score data_id>", "row_count": 20, "date": "<共同数据日期>", "universe": "万得全A", "value_semantics": "ROE/PE descending"}, {"role": "pe_values", "data_id": "<TopN PE data_id>", "row_count": 20, "date": "<共同数据日期>", "value_semantics": "PE(TTM)"}, {"role": "roe_values", "data_id": "<TopN ROE data_id>", "row_count": 20, "date": "<共同数据日期>", "value_semantics": "ROE percent display value"} ] } ``` `task_id`、`turn_id`、`user_query`、`source_skill_version` 同时写入请求。 同一业务 role 一次返回多个对象 ID 时,直接使用 `"data_ids":["<id1>","<id2>"]`,保持服务端顺序和字符串原样;不要手工拆 role 或改写 ID。若 run 返回 `validation_receipt_file`,优先只在请求顶层写 `"validation_receipts": ["<validation_receipt_file>"]` 一次;该数组同时兼容内联 Receipt 对象。也可以完全省略,由 `prepare-validated-page` 按同一 `task_id + 全部 data_id` 自动发现。**不要把 Receipt 中含双引号的原始公式再手抄进 `validated_roles[].formula`,也不要把同一 Receipt 重复塞进三个 role**;运行公式合同由 Receipt 原样注入,role 只描述已物化数据的业务语义。请求必须由 `write_skill_file` 写成合法 JSON,首次解析失败后只允许修正该文件一次,不得改写公式合同或重跑数据。`prepare-validated-page` 成功输出的 `qbv_job_id` 和 `handoff_file` 必须保留在 Trace。 只有用户明确说“只要 PNG / 本地图片 / 表格 / 不要网页”时,分类器才应返回 `none`。弱“看看走势”且没有明确图表 artifact 也保持 `none`。 完成上述硬闸门(非图表且非已登记 durable 场景无需执行)后,若已拿到满足证据门禁的数据,**直接输出结果并停止**。最终答案的第一个字必须是数据表格或结论本身(如"符合条件的股票共N只:"或直接输出表格),不得以任何过程性陈述开头。QBS 不等待 QBV 页面完成。 ✅ **正确示范**(第一句话即数据结论): ``` 符合条件的股票共 8 只(数据截至 2026-04-14): | # | 名称 | 涨跌幅 | PE(TTM) | |---|------|--------|---------| | 1 | xxx | x.xx% | xx.x | ``` ❌ **绝对禁止的开头**(命中 = 删除后重写): ``` 已成功获取数据。根据返回结果... 数据已获取。从筛选结果中,我可以看到... 让我整理一下结果... 根据 description 中的信息... ``` ### TopN 表格完整性门禁(硬规则) 若 TopN 最终表格中存在以下任一情况,**禁止直接输出**: 1. **展示字段缺失率 > 20%**:某列中超过 20% 的行显示为 `-` / `0` / `NaN` / 空值 2. **排序字段有缺失**:排序列中出现任何 NaN/0/空值(排序字段不允许缺失) 3. **行数不足**:用户要求 TopN 但实际有效行数 < N 且未说明原因 **遇到不完整表格时的处理流程**: 1. 先检查公式是否正确(字段维度对齐?掩码正确?) 2. 尝试补查缺失字段:对缺失行的个股,用 `presets/assets_db/{类型}.yaml` 唯一命中后 → 单资产 `runMultiFormulaBatchStream` 逐只取值 3. 若补查后仍不齐,**必须**在答案中声明不完整性:"以下 N 只中有 M 只的 XX 字段暂不可得" 4. **禁止**将不完整表格包装为完整结论(如"以下为完整的Top10排名"但实际3只缺PE值) **实测反例(T-012,iter-012)**:模型对 PE<15 选股结果输出 Top10 表格,其中多只股票的 PE 值显示为 0,但答案中未做任何说明,用户无法区分"PE=0"和"PE 数据缺失"。 **条件选股/TopN 回答模板**: ``` 符合条件的{资产类型}共 {N} 只(数据截至 {YYYY-MM-DD}): | # | 名称 | {排序字段} | {展示字段1} | ... | |---|------|-----------|------------|-----| | 1 | xxx | ... | ... | ... | ``` **禁止输出**(命中任一项 = 整段重写后再输出): - 已成功获取 / 数据已获取 / 我已查询到 / 我看到 / 数据提取完成 - 让我来 / 我来帮您 / 现在开始 / 接下来 / 让我整理 - 根据 workflow / 根据返回结果 / 从 readData 返回可见 - Step 1/2/3 / 检查通过 / 校验完成 - 同一张表格的多个版本(草稿版+正式版) --- ## 微流程:TopN 中再过滤(排名后条件筛选) 适用于:用户要求"涨幅前50中PE<20的""成交额前100里换手率>5%的"等**先排名再过滤**类问题。 **核心原则**:取前N 得到集合 → 集合内施加条件掩码 → 交集即最终结果。**禁止对交集再做 `取前`**。 ### 标准模板 ```text # Step 1: 构建排序值(含资产宇宙掩码) 排序值 = "全市场每日回报率" * 板块(万得全A) # Step 2: 取前 N — 得到 0/1 掩码 前N掩码 = 取前("排序值", 50) # Step 3: 构建条件掩码(PE < 20) PE条件 = ("PE数据" < 20) # Step 4: 交集 = 前N掩码 × 条件掩码(结果仍是 0/1 掩码) 目标掩码 = "前N掩码" * "PE条件" # Step 5: 提取展示值 目标PE = "目标掩码" * "PE数据" 目标涨幅 = "目标掩码" * "全市场每日回报率" ``` ### ⛔ 禁止模式(实测 T-029 犯的错误) ```text # ❌ 错误:对交集掩码再做取前 目标掩码 = "前50掩码" * "PE条件" 最终结果 = 取前("目标掩码", 50) ← 画蛇添足!交集 ≤ 50,二次取前无意义且可能改变结果 ``` **为什么禁止**: - 交集结果的数量**必然 ≤ N**(因为来源是前N的子集),再做 `取前(交集, N)` 语义等价于 `取出全部`,但实际上 `取前` 会重新按掩码本身的 0/1 值排序,可能改写结果顺序或引入意外行为 - 正确做法:交集掩码直接乘以展示字段提取数值,然后 readData ### 结果数量预期 - 交集数量通常远小于 N(如涨幅前50中 PE<20 的可能只有 5-15 只) - description 中的 `effective_matches` 即为交集数量,若 = 0 说明无满足条件的股票,应直接告知用户"涨幅前N中没有满足条件X的",**不需要再 readData** --- ## 微流程:实时横截面 TopN 排名(股票) 适用于: - 现在/当前/盘中/今天 + 全A股/全市场/某板块 - 涨幅前N / 跌幅前N / 成交额前N / 成交量前N - 输出目标是名单或名单+数值 规则: 1. 若 `cases_index.yaml` 命中实时 TopN 卡片,优先执行卡片原公式或 skill 内显式允许的等价模板; 2. 命中卡片后,禁止自行改写核心口径(如"昨收到当前分钟"改成"开盘到当前时点"); 3. 这是明确的实时横截面场景,必须显式传 `use_minute_data: true`; 4. 若用户只问"哪些股票",优先使用 description 中的名单直接回答,不强制补取数值; 5. 最终名单、数值、口径说明必须三者一致。 **强制运行参数**: - `use_minute_data: true` - `force_reusable_array`:**多公式(≥ 2 条)调用时必须主动评估**。`getCardFormulas` 返回多条公式时,逐条判定「是否会被 `readData` 或最终步骤读取 / 是否会被后续 batch / 后续 `runMultiFormulaBatchStream` 调用 / 用户后续追问引用」——会被读取或后续引用的变量名**写进 `force_reusable_array`**;只被本批后续公式引用、且**确定后续不会再被任何调用引用**的中间量**不要写进数组**。组装 payload 时同步附上这个字符串数组,不得遗漏。 - **单批场景**:仅最终输出(被 `readData` 读取的变量)写进数组,其余全部省略。 - **多批场景(跨批保活)**:除最终输出外,还须将「被后续批次公式引用」的变量也写入数组。组装每个 batch 的 `force_reusable_array` 前,先扫描后续 batch 的右侧引用,凡被引用到的左侧变量本批必须列入。**特别注意**:在最后一批中也要扫描"是否会有 Agent 自己稍后追加的补公式"——例如 TopN 掩码后还要再算一次 `*_TopN_Score` 时,对应评分变量必须列入。 - **未来未知场景(首批保守 + 后续修正)**:当用户第一轮只给出部分链、明确说"后面还会加新需求"时,首批可以仅把"末输出 + 跨批引用"写入数组,其余省略(不要为保险把全部变量都列进去);当后续轮次新公式回引到首批某个早期、当时未列入的变量时,**必须先调用修正接口**(如 `markReusable` / `updateForceReusable` / 同义接口)把目标变量加入复用集合,再继续后续 batch;切勿假设变量还在缓存里、也勿一次性把首批全列入。若当前后端**尚未提供修正接口**,应在最终答案中明确说明"此次需要重新计算前置变量",而不是默默兜底。 - **⚠️ 全列 = 规则退化**:若某批 `force_reusable_array` 列出了本批全部左侧变量名(且公式数 > 5),说明未做跨批依赖分析,应回退重新扫描依赖边再标注。这与"未传 `force_reusable_array`"等价,**不能视为已正确使用**。 - **⚠️ 缓存兜底成功 ≠ 数组用对**:服务端缓存层会自动兜底,所以数组标错时最终结果也常常能跑出来。这是误导信号,必须独立检查每条公式的变量名是否按上述规则正确加入或省略,不能以"是否能拿到结果"作为评判标准。 - **分批切点原则**:优先在依赖图弱耦合处切分(即切点跨过的引用边数最少)。一般而言,公式链的依赖层级从下到上是:基础行情/掩码层 → 中间因子/统计层 → 合成评分层 → 最终输出层;在层与层的边界处切批,可最小化跨批写入数组的变量数,降低服务端缓存压力。**单批不超过 20 条(所有 tier 一律 20)**。 `refresh_snapshot_time` 已从 `runMultiFormulaBatchStream` 移除,禁止传入。若 freshness guard 失败且需要推进同一 session 的分钟数据截止时间,先调用独立 `refreshSnapshotTime`,再重跑公式。 **freshness guard(硬规则)**: - 若用户问题含"现在/当前/今天/今日/盘中/实时",执行后必须检查结果日期是否为当前交易日 - 若不是当前交易日:先做一次 freshness retry;若仍不是当前交易日,最终答案必须写"当前工具返回的最新有效日期为 YYYY-MM-DD,以下结果基于该日期,非当日实时快照" - 未通过 freshness guard,不得表述为"盘中实时/今天/当前" **用户主动要求刷新(硬规则)**: - 追问中含"刷新/更新/推进/最新行情/重新算/再算一次"等明确刷新语义时,**必须先调 `refreshSnapshotTime '{}'`,再重跑 `runMultiFormulaBatchStream`** - 禁止直接复用上次公式结果或跳过 `refreshSnapshotTime` 直接重跑 **输出限制**: - 默认只输出:日期/时点、排名、名称、代码、数值、必要口径 - 禁止默认附加:市场热度、涨停制度解释、资金偏好、原因分析 **直达执行(禁止预查探索)**: - 命中本微流程后,禁止先用 `searchSimilarCases` / `getCardFormulas` 做探索性参考 - 禁止再做与当前主指标无关的 `confirmDataMulti` 试探 - 直接按本微流程的固定公式模板执行 --- ## 微流程:排名后再条件筛选(强制模板) 适用于: - "今天涨幅前50里 PE<20 的有哪些" - "成交额前100中 PB<2 的股票" - "盘中涨幅前N里满足某条件的名单" 执行步骤: 1. 冻结用户原始条件,不得新增过滤; 2. 先生成排名掩码; 3. 再生成条件掩码; 4. 求交集得到目标掩码; 5. 若用户只问"有哪些",优先使用 description 中的 `全部名称` 直接回答; 6. 若用户还要精确字段值,必须对候选股票逐只核验,或读取与同一掩码绑定的展示值; 7. 禁止从超大 `last_column_full` 截断大表中人工提值。 **强制 fallback:** - 当目标名单很少(如 ≤ 20)且最终答案需要精确字段时,优先: - `presets/assets_db/{类型}.yaml` 本地检索 - 逐只单资产 `runMultiFormulaBatchStream` - 再回答 --- ## 微流程:盘中阈值筛选_名单查询 适用于: - 盘中/实时 + 阈值条件(如"跌幅超过5%的")+ 名单 - 与实时TopN不同,此类题不需排名,只需满足阈值条件的完整名单 规则: 1. 这是明确的盘中阈值场景,必须显式传 `use_minute_data: true`; 2. 条件掩码严格来自用户原始条件,不得扩展或收窄; 3. description 中的 `全部名称` 和 `effective_matches` 即为最终结果; 4. 若 `effective_matches` ≤ 20,直接用 description 名单回答即可,不必 readData; 5. 若需补取精确数值,读取展示值即可,不得读布尔掩码全表。 --- ## 微流程:事件研究(历次事件后表现 / 事件窗口分析) > **已迁移至独立 leaf workflow**:事件研究现在由 `workflows/event-study.md` 全权处理。 > 若触发词命中(复盘、历次、涨价、降息、加息、事件窗口、随后表现、超预期、不及预期、政策后表现), > SKILL.md 路由表会直接指向 `workflows/event-study.md`,无需经过本文件。 > > **禁止**在 quant-standard 流程中内联执行事件研究步骤——即使用户问题同时涉及选股/回测和事件窗口, > 事件窗口部分也应单独路由到 `event-study.md`。 --- ## 子流程:固定区间累计涨跌幅(单资产/多资产) > **已迁移至独立 leaf workflow**:固定区间累计涨跌幅现在由 `workflows/period-return-compare.md` 全权处理。 > 若触发词命中(明确起止日期 + 区间收益/累计涨跌幅/区间表现/多资产区间对比), > SKILL.md 路由表会直接指向 `workflows/period-return-compare.md`,无需经过本文件。 > > **禁止**在 quant-standard 流程中内联执行区间涨跌幅计算——即使用户问题同时涉及选股/回测和区间收益, > 区间收益部分也应单独路由到 `period-return-compare.md`。 --- ## 预设资源(presets/) 预设文件存放已验证的常用资产、函数、数据集信息,按需加载可减少 API 调用次数。 | 文件 | 内容 | 何时加载 | |------|------|----------| | `presets/assets_db/*.yaml` | 全量资产字典(A股/港股/美股/指数/期货分文件,共 1 万+ 条;**不含指数成分股映射**) | 涉及资产时**用 grep 检索对应文件**(如 `grep "中航重机" presets/assets_db/stock_a.yaml`),⚠️ **禁止 `read_file` 整文件**(5505 行会爆 context);未命中或多命中 → 报错/澄清,不再远程兜底。用户说「确认资产 / 批量确认 / 找ticker」也必须先查本地库,不得直接远程确认。若用户要「标普500全部成分股 / 沪深300成分股」这类指数成分名单,`assets_db` 只能确认指数本身,不能推出成分股列表。 | | `presets/functions.yaml` | 常用函数的 `format`(公式写法) | 涉及常见函数时先查此文件;没有再调 `searchFunctions` | | `presets/data_catalog.yaml` | 全市场数据集的 `index_title` + 维度 | 需要全市场数据时先查此文件;没有再调 `confirmDataMulti` | | `presets/cases_index.yaml` | 111 张知识卡片目录(id \| name \| tags) | **任务开始时必先读**(一次读完),按 tags 匹配卡片 | | `presets/sectors.yaml` | 板块/行业名(用于 `板块()` 函数) | 构建板块过滤公式时查阅 | | `presets/themes.yaml` | 概念/题材名(用于 `板块()` 函数) | 构建题材过滤公式时查阅 | > presets 条目来自平台实际返回值,可直接信任。若报错则以 API 返回值为准并更新 presets。 --- ## Step 0:初始化 session ```powershell python scripts/call.py newSession ``` `call.py` 在本地生成 UUID 并写入 `output/.session.json` 或 `output/.session.<key>.json`(设置 `QBS_SESSION_KEY` 时),此后所有工具调用自动注入 task_id,无需手动传。 > **用户要上传自有 CSV?** → 先读 `recipes/upload-custom-data.md`,按两阶段流程(preview → confirm)操作,完成后再继续后续步骤。 --- ## Step 0.5:识别数据频率需求 频率决定从 `cases_index.yaml` 里匹配哪类卡片,匹配错误会导致公式执行失败。 | 用户说了… | 数据频率 | 匹配过滤条件 | |-----------|---------|-----------------------------| | 盘中、实时、当前、分钟、现在、今天日内、今天 | 分钟频 `[freq:minute]` | 只选 tags 含 `盘中`/`分钟`/`实时`/`[freq:minute]` 的卡片 | | 昨日、日频、收盘后、复盘、历史 | 日频 `[freq:daily]` | 排除含 `[freq:minute]` 的卡片 | | 未明确说明 | 默认日频 | 按日频处理 | > ⚠️ **频率识别同时决定卡片匹配和 `use_minute_data` 参数。**只有盘中、当前分钟或当日未收盘请求设为 `true`;历史、复盘、回测、净值、回撤、波动率、历史日线及财务报告期设为 `false` 或省略。 > > 卡片频率和执行参数必须一致:分钟频场景匹配分钟卡片并开启盘中映射;日频场景匹配日频卡片且不得开启分钟模式。 ### 频率→参数注入(强制) 参数按频率条件注入:分钟频场景传 `true`,历史日频计算传 false 或省略: ```json // 盘中/当前分钟 {"formulas": ["..."], "use_minute_data": true} // 历史日频/回测/净值/回撤/波动率/财务 {"formulas": ["..."], "use_minute_data": false} ``` > `use_minute_data: true` 会把 7 个日频行情数据名的最新列替换为“最后一分钟更新版”,所以只能用于需要当前盘中截面的请求。主动写盘中公式时仍优先使用日频数据名,由服务端自动映射;历史计算和财务计算不要开启。 ### 分钟频场景强制卡片路由(硬规则) 当 Step 0.5 识别出数据频率为**分钟频**时,必须在继续 Step 1-6 之前执行以下强制步骤: 1. **必须读取 `presets/cases_index.yaml`**,按 `[freq:minute]` 标签筛选分钟频相关卡片 2. **关键词匹配**:将用户问题中的核心语义(涨幅前N / 跌幅筛选 / 涨停 / 日内回报等)与卡片 name 和 tags 做交叉匹配 3. **若命中 1-3 张相关卡片** → 调用 `getCardFormulas` 获取公式骨架 → 优先使用日频映射写法,加上 `use_minute_data: true` 参数即可 4. **若未命中任何分钟频卡片** → 进入通用 Step 1-6 流程,但必须严格使用 `use_minute_data: true` **禁止**:在分钟频场景下跳过 cases_index.yaml 直接走通用 confirmDataMulti → 自行拼公式的路径。 分钟频卡片已包含经过验证的公式模板和口径说明,绕过它们会显著增加口径错误和维度不匹配的风险。 ### cases_index 强制命中规则(硬规则) > **卡片是公式 know-how 的来源,不是答案外壳**。命中卡片只为拿到正确的函数写法(如 `昨天()` 包裹的非跳空突破、`支撑线()` 口径),下面两条对**所有**走卡片的题强制生效(修复 T-026/T-030/T-035 条件污染、T-034 拿到结果不收口): > > 1. **卡片条件不继承**:`getCardFormulas` 返回的公式/insight 里若带用户**未明确提出**的附加条件(`非ST`/`普通股`/`封板`/`流动性门槛`/`市值门槛`/`主板` 等),**必须先删除再执行**,禁止默默带进最终公式或答案(见 SKILL.md 硬规则 8)。卡片说「非ST」但用户没说 → 不加;反之卡片漏了用户要的条件 → 必须补。 > 2. **命中即用、收到即停**:拿到卡片公式 → 调整参数 → `runMultiFormulaBatchStream` → `readData` 拿到目标截面后**立即组织答案**;禁止再读第二张卡片、再 `searchSimilarCases`、再开替代公式分支(与全局执行合同第 5 条一致)。 > > **与「简单横截面直达模式」的边界**:仅当题目用到下表的**专用函数 know-how**(非跳空突破需 `昨天()`、支撑/阻力需 `支撑线/阻力线`、涨跌停判断)时才走卡片;若是普通的「单指标 TopN / 1~3 个阈值筛选」(如简单涨幅前N、PE<x 且 股息率>y),按「简单横截面选股微流程(直达模式)」直接构造,**不强制**读 cases_index / 调 getCardFormulas。 以下关键词场景**必须**在执行公式前查阅 `presets/cases_index.yaml`,若命中卡片则**必须优先使用卡片公式**: | 用户问题关键词 | 必须匹配的 cases_index tags | 禁止行为 | |---------------|---------------------------|---------| | 突破N日新高/创N日新高/盘中新高 | `昨天函数`, `新高`, `突破` | 禁止自造 `最大()` 公式替代卡片 | | 跌破N日新低/创N日新低 | `新低`, `跌破` | 禁止自造 `最小()` 公式替代卡片 | | 涨停/跌停/封板 | `涨停`, `跌停` | 禁止自造涨跌停判断公式 | | 成交额/成交量排名 TopN | `成交额`, `排名`, `TopN` | 禁止跳过卡片自拼公式 | | 靠近支撑位/接近支撑线/支撑位附近 | `支撑位`, `支撑线`, `靠近支撑位` | 禁止自造支撑线/阻力线拟合公式替代卡片。注意:"突破阻力线"或"通道宽度筛选"虽涉及相同函数(`支撑线`/`阻力线`),但筛选条件与卡片不同——应从 `functions.yaml` 查找函数用法后自组公式,不要误套支撑位卡片 | **执行流程**: 1. 读取 `presets/cases_index.yaml`(若本 session 尚未读取) 2. 按上表关键词匹配 → 找到 1-3 张相关卡片 3. `getCardFormulas` 获取卡片公式 4. **直接使用卡片公式作为骨架**,仅按用户需求调整参数(如 N 值、板块范围) 5. 禁止在命中卡片后仍自行从 `confirmDataMulti` 起步构造替代公式 **实测反例(T-033,iter-012)**:`cases_index.yaml` 中已有卡片 `69de0b5dacdb527849c73f1a | A股盘中非跳空突破60日新高筛选`(tags: 昨天函数, 三掩码交集),该卡片公式已正确使用 `昨天()` 包裹。但模型跳过 cases_index 自造公式,未用 `昨天()`,导致逻辑失效。 ### 分钟频卡片公式使用说明 分钟频场景优先使用日频数据名(如 `"全市场每日收盘价"`)并通过 `use_minute_data: true` 由服务端自动映射。若历史卡片包含原生分钟数据名(如 `"全市场1分钟收盘价"`)或 `变频到分钟(...)`,不要照搬为新公式;优先改写为日频映射口径。 **使用示例**(盘中涨幅前10): ```json { "formulas": [ "个股涨幅=涨跌幅(\"全市场每日收盘价\")", "TOP10=取前(\"个股涨幅\",10,从大到小)" ], "use_minute_data": true, "include_description": true } ``` > 日频数据名写法(如 `"全市场每日收盘价"` + `use_minute_data: true`)是默认推荐口径。 ### 分钟频×日频维度不对齐(常见陷阱) 原生分钟频行情数据的资产维度通常远小于日频数据(如分钟频仅覆盖 ~660 只活跃标的,而日频板块掩码覆盖 ~2900+ 只)。当公式中**直接**出现原生分钟频行情字段和日频掩码/板块字段时,二维矩阵乘法/布尔索引会因维度不一致而报错: > `boolean index did not match indexed array along dimension 0; dimension is 662 but corresponding boolean dimension is 2892` **处理规则(按优先级)**: 1. **优先改写为日频映射口径**:不要直接使用原生分钟频数据名;改用 `"全市场每日收盘价"` 等映射表内日频数据名,并传 `use_minute_data: true` 2. **结果层后过滤**:若历史卡片确实只能使用原生分钟频字段且维度不对齐,采用两步走——先仅用分钟频数据计算排序/排名结果,再通过 `readData` 读取非零资产名单,最后在结果层按板块/行业归属进行文本过滤 3. **禁止忽略报错继续**:维度不对齐报错后不得跳过该过滤条件声称"已完成筛选"——这违反「公式失败诚实披露」规则 --- ## 参数传递与防漂移 > quant-standard 流程步骤多、自由度高,是决策漂移的高发区。 > 参数通过 confirmDataMulti → runMultiFormulaBatchStream → readData 工具链自然传递,不需要额外的状态文件。 > 若筛选条件特别复杂(如多条件交叉筛选 + 自定义排名),可选择通过 `write_skill_file("output/_working/{task_id}/filter_spec.json", ...)` 冻结筛选规格。 **关键防漂移规则**: - `confirmDataMulti` 返回的 `index_title` / `data_id` 是后续公式中唯一合法的数据名 - 用户原始条件一旦在 Step 0 中确认,后续步骤不得增删任何条件 - 百分比阈值口径(展示口径 3 vs 小数口径 0.03)一旦选定不得更改 - `runMultiFormulaBatchStream` 返回的 result id 是 readData 的唯一合法输入 - `delivery_ready: false` 时禁止输出最终答案,必须回退排查 - 百分数阈值归一(强制):平台的涨跌幅/回报率数据统一为**小数口径**(0.05 = 5%,-0.03 = -3%)。用户说"超过5%"时,公式阈值必须写 `0.05`;用户说"跌幅超过3%"时,公式阈值必须写 `-0.03`。**禁止**直接使用整数百分数(如 5、-3)作为阈值——这会导致筛选条件永远为假或命中所有资产 --- ## 公式语法强制规范(硬规则) `runMultiFormulaBatchStream` 的 `formulas` 数组中每条公式必须遵守以下语法规则,违反即报错: ### 1. 双引号引用规则 - **平台数据集**必须用双引号包裹:`"全市场每日回报率"`、`"A股市盈率(PE, TTM)〔估值数据〕"` - **本 session 前序步骤产出的中间变量**在后续公式中也必须用双引号引用:`"涨幅排名"`、`"筛选结果"` - **禁止裸变量引用**:`涨跌幅数据*掩码` ❌ → `"涨跌幅数据"*"掩码"` ✅ - 仅 `=` 左侧的新变量名不需要引号:`结果="掩码"*"涨跌幅数据"` ✅ ### 2. 函数名必须经过验证 - `presets/functions.yaml` 中已列出的函数可直接使用 - 不在 presets 中的函数,必须先通过 `searchFunctions` 确认存在且获取正确签名 - **禁止凭印象使用未验证函数**(常见错误:`横截面排名` ❌、`按天排序分(...,降序)` ❌) - `取前` 函数的合法排序选项:`从大到小`(默认)/ `从小到大` / `desc` / `asc`。**不接受** `降序` / `升序` / `倒序` - **`LOAD()` 函数不存在,禁止使用**。`confirmDataMulti` 返回的数据集名直接用双引号引用即可: - ❌ `原油收盘价=LOAD("布伦特原油收盘价")` — LOAD 不是合法函数,服务端会报格式校验失败 - ❌ `布伦特原油收盘价=LOAD("布伦特原油收盘价")` — 变量名与数据集同名触发循环依赖 - ❌ `布伦特原油收盘价=周期采样("布伦特原油收盘价",...)` — 同样循环依赖 - ✅ `BRENT_CLOSE=收盘价(IPE-布伦特原油)` — 用 `presets/assets_db/{类型}.yaml` 唯一命中的资产名 + 函数 - ✅ `BRENT_M=周期采样("布伦特原油收盘价","月末基准日期")` — 用 `confirmDataMulti` 确认的数据集名,变量名用缩写 ### 3. 公式结构规范 - 每条公式格式:`变量名=表达式` - **变量名(`=` 左侧)禁止与右侧引用的任何数据集名相同**,否则服务端报"循环依赖"。正确做法:加前缀/缩写区分: - ❌ `布伦特原油收盘价=周期采样("布伦特原油收盘价","月末基准日期")` → 循环依赖 - ✅ `BRENT_M=周期采样("布伦特原油收盘价","月末基准日期")` - ❌ `中国石油收盘价="中国石油收盘价"` → 循环依赖 - ✅ `SH601857=收盘价(中国石油)` - 布尔筛选用乘法:`"掩码"*("数据集">阈值)` - 多条件交叉:`("条件A")*("条件B")*("原始数据")` - 禁止在一条公式中嵌套超过 3 层函数调用 ### 4. 禁止为数据集名创建无谓中间变量(硬规则) **平台数据集名称可以直接内联写入公式右侧,不需要先赋值给一个中间变量再引用。** ❌ **禁止(浪费一次 runMultiFormulaBatchStream 计算配额)**: ``` PE数据="A股市盈率(PE, TTM)〔估值数据〕" 股息率数据="股息率" 条件=("PE数据"<15)*("股息率数据">0.03) ``` ✅ **正确(直接内联,节省算力)**: ``` 条件=("A股市盈率(PE, TTM)〔估值数据〕"<15)*("股息率">0.03) ``` **允许创建中间变量的场景(以下情况才需要)**: 1. 该中间结果需要被**多个后续公式**引用(复用超过 1 次) 2. 该中间结果本身需要被 `readData` 单独读取(如 precheck) 3. 通过函数变换生成了真正的**新数据**(如 `HS300_DD=("HS300"/"累计最大(HS300)"-1)*100`) **实测反例(T-012,iter-012)**: ``` # × 错误:先把数据集名赋给别名,再在后续公式中引用别名 PE数据="A股市盈率(PE, TTM)〔估值数据〕" 股息率数据="股息率" 条件=("PE数据"<15)*("股息率数据">0.03) # ✓ 正确:直接内联 条件=("A股市盈率(PE, TTM)〔估值数据〕"<15)*("股息率">0.03) ``` 这一错误在 Kimi 和 gpt-5.4 两类模型上均有出现,导致公式条数无谓增加 2 条,浪费计算配额。 --- ## 公式模板三级查找链 构建公式时按此优先级查找参考,找到即停: 1. **recipes/** → 本地已验证模板,无需 API。按场景标签匹配: - 均线/趋势策略 → `recipes/ma-crossover-backtest.md` - PE/估值选股 → `recipes/value-pe-strategy.md` - 行业聚合/排名 → `recipes/industry-aggregation.md` 2. **presets/cases_index.yaml** → 找到相关卡片后 `getCardFormulas` 拉取远程公式,**用返回的 `all_formulas`(字符串数组)**作为公式骨架,理解做法后针对用户需求重写。**禁止将 `action_steps` 对象直接传给 `runMultiFormulaBatchStream`** - **拿到 `all_formulas` 后必做:标注中间量**——逐条扫一遍,哪些左侧变量只在本批后续公式中被引用、不会被 `readData` 读?这些变量名**不要**写进 `force_reusable_array`。卡片骨架里 `*_条件` / `*_掩码` / `*_中间` / `MA*` / `Signal` / 各类阶段性打分变量基本都是中间量。最终输出(如 TopN 名单、回测净值)才需要写进 `force_reusable_array`。这一步与公式重写同步完成,不得跳过。 - **拿到 `all_formulas` 后还必须做:卡片条件对齐检查(硬规则)**——对比卡片公式中包含的筛选条件 vs 用户原始约束,若卡片带有用户未明确要求的附加过滤(如 `非ST股`、`仅主板`、`剔除北交所`、特定指数成分、市值门槛),**禁止直接作为最终执行公式**。只能:①去掉附加条件后再执行;②向用户说明"卡片带有附加限制口径:XXX,如需使用请确认",等待用户确认。不得默认沿用附加条件并在最终答案末尾才披露。 3. **searchSimilarCases** → 向量搜索 fallback(仅前两级都没找到时)。query 同时含**资产名+操作/机制词** --- ## 窗口比较标准口径(硬规则) 当公式需要"截至昨日的近 N 日统计值"作为基准,与**当日价格**比较判断是否突破/创新高/创新低时,**必须**对统计函数包裹 `昨天()` 以排除当日数据。 **核心原则**:若不排除当日,则当日数据参与基准计算,"今日是否突破昨日前的N日最高"这一判断会因自引用而失效(今天的最高价同时出现在基准和比较值中)。 **标准口径模板**: ```text # 场景:筛选"今日盘中突破近60日新高"的股票 # ✅ 正确:用昨天()排除当日 基准 = 昨天(最大("全市场每日最高价", 60)) 条件1 = ("全市场每日开盘价" <= "基准") # 开盘未突破(非跳空) 条件2 = ("全市场每日最高价" > "基准") # 盘中突破 目标掩码 = "条件1" * "条件2" * 板块(万得全A) # ❌ 错误:未用昨天(),当日最高价参与了基准计算 基准_错 = 最大("全市场每日最高价", 60) # 包含今天! ``` **适用场景**: | 用户表述 | 基准公式 | 必须包裹昨天()? | |---------|---------|----------------| | 今日突破/创近N日新高 | `最大("全市场每日最高价", N)` | ✅ 必须 | | 今日跌破/创近N日新低 | `最小("全市场每日最低价", N)` | ✅ 必须 | | 今日价格相对N日均线偏离 | `平均("全市场每日收盘价", N)` | ✅ 必须(若均线基准要排除当日) | | 近N日累计涨跌幅排名(不与当日比较) | `涨跌幅("全市场每日收盘价", N)` | ❌ 不需要(不存在自引用问题) | **实测反例(T-033,iter-012)**:用户问"盘中非跳空突破60日新高",模型使用 `最大("全市场每日最高价",60)` 未包裹 `昨天()`,导致当日最高价参与基准计算,突破判断逻辑失效,筛选结果为 0。正确做法:`昨天(最大("全市场每日最高价",60))`。 --- ## 工具参数速查表 > 参数名写错直接返回 400。`query` / `data_desc` / `intentions` 各工具不同,按此表调用。 > **参数报 400?** → 查对应 `tools/*.md` 的 ⚠️ 常见参数错误 小节。 | Step | 工具 | 必填参数与调用格式 | 说明 | 何时加载 tools/ 完整文档 | |------|------|-------------------|------|--------------------------| | 1a | *(读文件)* | 读 `presets/cases_index.yaml`,按 tags 找 1-3 张卡片 | 理解平台做法� -
quick-lookup.md 10.3 KB
# 快速查数流程(路由器 + 规则参考总表) > **本文档做两件事**: > 1. 将请求路由到对应的 leaf workflow > 2. 保留一份共享基础规则的**参考总表**(各 leaf 已自包含同等规则) > > **执行时不需要读本文档**:各 leaf workflow(quick-snapshot / quick-window / quick-report-period)均已自包含所有执行规则。 > 本文档仅用于:① 路由判断;② 规则变更时的同步参考;③ K 线图路由到 render-kline.md。 --- ## ① 子流程路由 进入本 workflow 后,**必须先判断属于哪个子流程,再用 `read_skill_file` 加载对应的 leaf workflow 文档,然后在 leaf workflow 中执行。禁止在本文档内直接执行查数操作(K 线图除外)。** ### 路由硬闸门(必须遵守) **在调用任何平台工具(confirmDataMulti / runMultiFormulaBatchStream / readData)之前,必须先用 `read_skill_file` 读取对应的 leaf workflow 文档。** 未读取 leaf workflow 就调用平台工具 = 违规。 | 特征 | 跳转目标 | |------|----------| | 最新交易日行情/估值(≤1000 个资产,查标量,字段齐即停) | `workflows/quick-snapshot.md` | | 最近 N 个交易日序列 / 窗口极值 / 振幅统计 | `workflows/quick-window.md` | | 最近报告期财务指标(营收/净利润/总资产/ROE 等) | `workflows/quick-report-period.md` | | 用户明确要求看 K 线图(走势可视化) | `workflows/render-kline.md` | | 单股指标画像 / 个股综合指标概览(如"分析一下这只股票""个股画像""估值财务资金走势综合看一下") | `workflows/stock-profile.md` | | 涉及选股/回测/因子/图表/行业排名 | `workflows/quant-standard.md` | | **下跌归因 / 后期展望**(如"为什么今天跌""后期怎么看""能买吗") | 可先按 `workflows/stock-profile.md` 给出预计算指标画像或按需组合快查数据;归因仅输出可量化数据事实(如"跌幅超过沪深300同期X个百分点""成交量较前5日均值放大X倍"),**禁止**无工具证据的宏观/政策/行业定性归因;对"后期怎么看""投资建议"类前瞻问题,**必须 safe-fail**:回复"本工具提供定量历史数据查询,不提供投资建议或价格预测",不得为了回答而无限扩展公式调用。 | **路由判断规则(优先级从高到低):** 1. 开放式单股综合指标概览,且不是单字段/明确窗口/IC 预测力 → `stock-profile` 2. 时间锚点是"最近 N 日窗口" → `quick-window` 3. 时间锚点是"最近报告期"且字段属于财务类 → `quick-report-period` 4. 其余(最新交易日标量、估值标量、多资产对比) → `quick-snapshot` --- ## ② 共享基础规则(参考总表,各 leaf 已自包含) > **以下规则仅作参考存档。执行时以各 leaf workflow 内嵌的规则为准,无需回到此处查阅。** > **全局合同(证据分级、不补精度、方法限制说明等)见 `workflows/global-rules.md`。** --- ### Step 1:确认资产(所有子流程共用) **优先级规则(按序执行,满足即停):** 1. 先 grep `presets/assets_db/{类型}.yaml`,唯一命中则直接使用标准 `name` / `ticker` 2. 若预设已能唯一映射标准资产名,可直接使用;不要为了形式完整重复 `assets_db` 本地资产库 3. 若用户同时提供了中文名称和6位代码:**以中文名称作为主确认意图**,代码仅作辅助校验 4. 裸代码(如 `000063`)若未带交易所后缀(`.SZ`/`.SH`),**不能单独作为可靠主键**,不要把它作为独立 intention 传入 5. 名称不存在或存在歧义时,停止并向用户说明/澄清,不再调用远程资产确认 ```bash grep "资产名或代码" presets/assets_db/{类型}.yaml ``` #### 资产确认熔断规则 `assets_db` 本地资产库 匹配失败(返回空或报错)时: 1. **重试 1 次**,用不同别名 2. 仍失败 → **立即停止**,告知用户「该资产不在平台覆盖范围内,目前支持 A 股个股、港股个股、美股个股及主要宽基指数」 3. **禁止**用相似资产替代(如用户问"红利ETF"不能替换为"中证红利指数") `assets_db` 本地资产库唯一命中、但后续平台接口返回 `ASSET_NOT_FOUND` 时,不适用上述“本地未命中”熔断: 1. 标记 `asset_catalog_conflict`,保留本地标准名称、ticker 和市场,不得换成相似资产。 2. 读取 `workflows/external-fact-verification.md`,核验近期上市、退市、更名或换代码事实。 3. `ASSET_NOT_FOUND` 不得推导为公司未上市或不存在。 4. 外部事实确认但 Quant Buddy 各独立数据接口仍未返回任务所需数据时,明确说明“Quant Buddy 平台数据当前不可用”;搜索结果不得冒充平台行情、财务或公式证据。 --- ### 禁用写法(硬规则,所有子流程共用) 禁止使用以下未声明或高风险写法: - `GET(...)` - `GETDATA(...)` - `FETCH(...)` - `PRICE(...)` - 将用户意图词直接当正式数据名(如 `"毛利率"`、`"净利率"`),除非 `confirmDataMulti` 已明确返回其可直接引用 - 在财务题中写成 `"数据名"(代码)`、`"数据名"(资产名)` 这类函数式资产传参 - 在已存在原生回报率/涨跌幅模板时,优先手写 `REF/DELAY/ROC` + 四则运算 - 在 quick-lookup 场景中把 `scanDimensions`、`renderKLine` 当作数值查询兆底 --- ### 失败恢复规则(硬规则,所有子流程共用) #### A. `runMultiFormulaBatchStream` 返回 `PARTIAL_SUCCESS` 时 - 记录并保留已成功项,不重跑成功项 - 仅修失败项 - 优先复用 `available_outputs` - 若返回中提供 `suggested_next_steps` / `tunable_knobs`,优先按其提示处理 - 若失败项属于数据名问题,先 `confirmDataMulti` - 若失败项属于函数写法问题:**若该字段在本子流程模板表中已列出(预设字段),跳过 `searchFunctions`,直接按 Rule B 路径重写为标准模板**;仅当字段完全不在任何模板表中时,才调用 `searchFunctions` - 同一结构失败后,不得用同结构再次重试 #### B. `runMultiFormulaBatchStream` 返回 500 / Internal Server Error 时 按以下顺序处理: 1. 先检查是否使用了非标准模板或禁用写法 2. 若是,重写一次为标准模板 3. 若标准模板仍失败: - 对预设字段:不要自由搜索一堆同义公式 - 优先改为"取原始序列 → readData → 回答层聚合/计算" 4. 仍失败则停止重试,明确说明未成功获取的字段 #### C. 重试上限 - 同一意图下、同类公式结构最多重试 2 次 - 超过上限必须 fallback,不得继续撞接口 #### D. 禁止的错误恢复行为 - "成交量"失败后改查"成交额"并冒充成交量 - 关键字段缺失时转去 `renderKLine` / `scanDimensions` - 用图表或主观分析顶替结构化数值 #### E. readData 调用预算 - 单题 readData 总调用上限:**3 次** - 每次优先使用能限制返回规模的读取模式;读取连续区间原始数据时使用 `range_data` 并明确传 `start_date` / `end_date`,不得使用旧的 `full` 模式 - 超过上限后若仍有未获取字段,按"关键字段缺失"路径回答,不得继续读取 --- ### 查数题格式化规则(硬规则,所有子流程共用) **日期:** - 统一展示为 `YYYY-MM-DD` **价格/点位:** - 默认保留 2 位小数 - 股票价格单位写"元";指数可写"点" **涨跌幅 / 回报率:** - 若原始值为小数收益率,展示时必须 ×100 后再加 `%` - 默认保留 2 位小数 - 禁止把原始小数与百分号混用 **成交额:** - 默认优先换算为"亿元",保留 2 位小数 - 若不足 1 亿,可视情况改用"万元" **成交量:** - 根据原始口径补单位;若无法可靠判断单位,则只报原值并注明来自原始数据口径 **财务金额:** - 根据数量级选择"亿元/万元/元",同一答案内口径保持一致 若 description 中的展示值与统一换算规则存在冲突: - 优先按统一规则基于原始值重算 - 不凭语言模型主观改写 --- ### 关键字段缺失时的回答门禁(所有子流程共用) 若用户请求的是精确数值查询,而本轮仍有关键字段缺失: - 必须明确写出: - 已成功获取的字段 - 未成功获取的字段 - 是否已尝试标准补救路径 - 不得输出"查询结果如下""最新数据如下"等暗示已完成的标题 - 不得输出主观走势判断、强弱结论、投资风格建议 - 不得建议用户去外部平台查看,除非用户主动要求替代渠道 **部分成功答复模板:** 本轮已成功获取: - ... 暂未成功获取: - ... 我已按标准路径尝试补救;由于该字段本轮未返回可靠结构化结果,这里不做猜测。 --- ## K线图 > **K 线图请求已迁移至独立 leaf workflow:`workflows/render-kline.md`** > > 遇到 K 线图需求时,用 `read_skill_file` 加载 `workflows/render-kline.md` 并按其流程执行。 > 本节保留基础参数速查,完整流程及硬规则见独立文档。 **renderKLine 参数速查:** ```bash GZQ_PARAMS='{"ticker": "SH600519", "begin_date": 20240101}' python scripts/call.py renderKLine ``` > `ticker` 格式:**SH/SZ + 6位代码**(如 SH600519、SZ000001)。 > 可选参数:`indicators`(技术指标)、`show_volume`、`title`。 > 完整参数 → `tools/render_kline.md` 禁止: - 在精确数值未成功取得时,把 `renderKLine` 当作最终数值答案来源 - 根据图像目测生成精确价格或涨跌幅 --- ## 注意事项 1. **资产覆盖范围**:A股个股、港股、美股、主要宽基指数按各 leaf workflow 的字段范围执行;ETF/LOF/场外基金、期货行情/窗口序列为有条件支持,先 grep 对应本地资产库,唯一命中则按工具返回尝试;期货估值/财务/K线图和期权不支持。确认资产失败时禁止替换为相似资产——直接告知用户未识别或当前字段不可得 2. **变量名不能重复**:同一 session 内相同变量名会冲突,加后缀区分(`收盘2`) 3. **财务数据按季度更新**:日期列是财报期(20240331=2024Q1),不是交易日 4. **数据名必须精确**:模板中的引号内字符串已确认,不要修改 5. **用户要求下载** → 先问时间范围,再 `downloadData`(或 `readData(mode="range_data", start_date=..., end_date=...)`) -
quick-report-period.md 23.3 KB
# 快速查数 · 最近报告期财务 > **适用范围**:单只资产,查询**最近一个已披露报告期**的财务指标(利润表/资产负债表/ROE 等)。 > 港股/美股财务不要静态拒答;先走 `fast-report-period.md` 的 `fast_query(query_type="report")`,仅当工具明确返回不支持/字段缺失时再告知限制。本文档中的公式链固定模板主要用于 A 股完整链路。 > 本流程定义本场景的专用执行步骤;全局执行合同、证据分级、禁语与回答边界仍以 `workflows/global-rules.md` 为准。 --- ## 触发条件(满足全部才进此流程) - 时间锚点:最近一个已披露报告期(季报/半年报/年报,频率为季度) - 资产类型:单只资产;港股/美股优先走 fast_query(report),A股可进入本文完整公式链 - 字段类型:营收、净利润、归母净利润、总资产、总负债、净资产、ROE、资产负债率等财务字段 - 目标:得到同一报告期下的若干财务数值,允许在回答层做简单派生计算 ## 反例(不属于此流程,不要误路由) | 用户问题 | 应走流程 | |----------|----------| | 最新 PE / PB / 总市值(日频估值) | `quick-snapshot.md` | | 最近 5 日收盘价 / 成交额 | `quick-window.md` | | 多期财务对比 / 同比增速 / 财务趋势图 | `quant-standard.md` | | 行业财务排名 / 横向对比筛选 | `quant-standard.md` | --- ## 执行流程 ``` Step 1 确认资产(见本文档末尾「自包含执行规则」) → Step 2 合并所有财务字段为一次 runMultiFormulaBatchStream → Step 3 报告期对齐检查(本流程核心) → Step 4 回答层派生计算(如需) → Step 5 字段完备性检查 → Step 6 按固定格式回答 ``` ### 正式取值结果门槛(本流程必须遵守) 财务字段的回答必须基于完整的正式取值链路: ``` runMultiFormulaBatchStream 成功 → readData 读取最后有效值与日期 → 回答 ``` 正式取值链路统一为: 1. `runMultiFormulaBatchStream` 生成正式数据对象 2. `readData` 读取最后有效值与最后有效日期 - 默认优先:`mode="last_valid_per_asset"` - 若该模式不可用,再用 `mode="range_data"` 读取受限报告期区间并取最后一个有效值 3. 只有完成上述读取后,才能进入最终回答 规则: - `runMultiFormulaBatchStream` 本身不视为最终取值完成 - `description` 与 `confirmDataMulti` 只可用于字段确认与口径辅助,不可直接用于回答财务数值 - 若未完成 `readData`,本流程视为未完成 **各证据来源的允许用途:** | 来源 | 允许用途 | 禁止用途 | |------|----------|----------| | runMultiFormulaBatchStream 成功 + readData 读取 | 正式回答数值 + 报告期 | — | | runMultiFormulaBatchStream 的 description 文本 | 辅助确认字段名称、口径 | 直接作为最终答案数值 | | confirmDataMulti 的 description 文本 | 确认字段存在、日期范围、口径 | 直接作为最终答案数值 | **禁止捕捉 description 中的文本值作为最终答案。**财务字段的报告期语义比行情更复杂,description 中的 last_value 可能有口径偏差,必须通过 readData 确认报告期日期和实际值。 --- ### Step 2:财务字段固定模板 对以下高频稳定字段,**直接使用固定模板,不需要先 `confirmDataMulti`**: | 字段 | 公式 | |------|------| | 营业收入 | `营收 = "A股营业收入〔报告期利润表〕" * 取出(资产名)` | | 净利润 | `净利润 = "A股净利润〔报告期利润表〕" * 取出(资产名)` | | 归母净利润 | `归母净利润 = "A股归属于母公司股东的净利润〔报告期利润表〕" * 取出(资产名)` | | 总资产 | `总资产 = "A股资产总计〔报告期资产负债表〕" * 取出(资产名)` | | 总负债 | `总负债 = "A股负债合计〔报告期资产负债表〕" * 取出(资产名)` | | 净资产(股东权益)| `净资产 = "A股股东权益合计〔报告期资产负债表〕" * 取出(资产名)` | | ROE | `ROE单季 = "A股净资产收益率ROE" * 取出(资产名)` | | 毛利率(单季) | 需要两步中间变量,见下方毛利率公式链 | | 净利率(单季) | `{股票名}_净利率 = "A股销售净利率:单季〔财务指标〕" * 取出(资产名)` | | 营业利润率(单季) | `{股票名}_营业利润率 = "A股营业利润/营业总收入:单季〔财务指标〕" * 取出(资产名)` | **仅当用户问的字段不在上表时**,才先 `confirmDataMulti` 确认数据集名称。 > **重要命名规则**:`runMultiFormulaBatchStream` 中,最终单资产输出变量名必须加股票名前缀(`{股票名}_{指标名}`),与右侧引用的数据集名不得相同,否则触发循环依赖 500 错误。 **毛利率单季公式链(固定模板,不需 confirmDataMulti):** > 毛利率无直接平台单字段,必须用两个中间变量(二维全市场)计算,最后用 `取出()` 提取单股。 ```json {"formulas": [ "单季_收入 = \"A股营业总收入:单季〔利润表〕\"", "单季_成本 = \"A股营业成本:单季〔利润表〕\"", "毛利率_单季 = ((\"单季_收入\" - \"单季_成本\") / \"单季_收入\") * 100", "{股票名}_毛利率 = \"毛利率_单季\" * 取出({股票名})" ], "begin_date": 20230101} ``` 注:`单季_收入`、`单季_成本`、`毛利率_单季` 是全市场二维中间变量;`{股票名}_毛利率` 是最终一维结果。 **组合取数示例(毛利率+净利率+ROE 三件套,T-010 典型场景):** ```json {"formulas": [ "单季_收入 = \"A股营业总收入:单季〔利润表〕\"", "单季_成本 = \"A股营业成本:单季〔利润表〕\"", "毛利率_单季 = ((\"单季_收入\" - \"单季_成本\") / \"单季_收入\") * 100", "格力电器_毛利率 = \"毛利率_单季\" * 取出(格力电器)", "格力电器_净利率 = \"A股销售净利率:单季〔财务指标〕\" * 取出(格力电器)", "格力电器_ROE = \"A股净资产收益率ROE\" * 取出(格力电器)" ], "begin_date": 20230101} ``` **组合取数示例(财务三件套):** ```json {"formulas": [ "营收 = \"A股营业收入〔报告期利润表〕\" * 取出(宁德时代)", "归母净利润 = \"A股归属于母公司股东的净利润〔报告期利润表〕\" * 取出(宁德时代)", "总负债 = \"A股负债合计〔报告期资产负债表〕\" * 取出(宁德时代)", "总资产 = \"A股资产总计〔报告期资产负债表〕\" * 取出(宁德时代)" ], "begin_date": 20240101} ``` > ⚠️ **财务查询严禁传 `use_minute_data: true`**:财务报告期数据(dimension=one-row)不支持分钟级刷新,强行传入会导致服务端 HTTP 500。本流程所有 `runMultiFormulaBatchStream` 调用均不得携带 `use_minute_data` 参数(或必须设为 `false`)。 **begin_date 默认参数说明**: - 对"最近一个已披露报告期"场景:默认 `20240101`(近两年),不鼓励无界历史。 - 对"最近 N 年趋势"场景(如"最近5年ROE走势""过去10年净利润"):必须将 begin_date 推早至 `T − (N+1)年`,确保覆盖用户要求的全部报告期。示例:"最近5年" → `20200101`;"最近10年" → `20150101`。 --- ### 默认读取路径(本流程固定) 对"最近一个已披露报告期"的财务查询,默认按以下顺序执行: 1. `runMultiFormulaBatchStream` 成功后 2. 优先使用 `readData(mode="last_valid_per_asset")` 读取每个字段的最后有效值与日期 3. 若该模式不可用或返回不满足本题需要,再退回逐字段 `readData(mode="range_data", start_date=..., end_date=...)`,从返回区间中取最后一个有效值 **readData 模式决策表(本流程固定):** | 场景 | 使用模式 | 说明 | |------|---------|------| | 单字段最后有效值 | `last_valid_per_asset` | 默认首选,返回最后有效值+日期 | | 上述模式不可用 | `range_data` + `start_date` / `end_date` | 受控降级,读取受限报告期区间后取最后一个有效值 | | 多字段需逐一读取 | 每字段单独 `last_valid_per_asset` | 保证各字段独立获取报告期 | | 全量历史/采样 | **禁止** | 不使用 `table_data` / 旧 `full` / `smart_sample`;`range_data` 必须限制日期区间 | 说明: - 本流程默认不使用 `readData(mode="table_data")` 作为最近报告期财务读取方式 - 报告期对齐检查应基于"每个字段最后有效值 + 最后日期"完成 - **只有通过 readData 确认的数值才能进入最终回答** **对比 quick-snapshot**:quick-snapshot 中 description 的 last_value 值得信赖(因为行情字段口径简单),但本流程处理的是财务字段,报告期口径复杂(累计/单季/TTM),必须通过 readData 确认。 - **绝对禁止**:读取多期历史数据后"顺便"做同比/环比分析 > ⚠️ 429 错误不受本流程 Retry Budget 约束——按 global-rules.md 第 12 条「429 前置拦截」规则处理。 --- ### Step 3:同报告期一致性校验(硬门槛,本流程核心) > **报告期对齐是内部校验,不默认向用户展示**。仅当校验发现不一致且无法安全对齐时,才在回答中说明。 只有当所有回答字段(以及所有用于派生计算的基础字段)的最后有效日期完全一致时,才允许合并回答为"最近一个已披露报告期"。 若不一致: - 禁止直接输出合并后的答案 - 禁止跨报告期混算资产负债率/毛利率/净利率 - 必须明确说明"字段最后有效报告期不一致,无法按同一报告期安全汇总" 当用户一次查询多个财务字段,且这些字段需要合并展示或用于派生计算时,必须先拿到每个字段的: - 最后有效值 - 最后有效日期 然后按以下规则处理: 1. 若所有关键字段最后日期一致: - 该日期才能作为"最近一个已披露报告期"写入答案。 2. 若日期不一致: - 不得直接混合展示或计算; - 优先退回到"所有字段共同可用的最近报告期"重新组织答案; - 若本轮无法可靠确定共同最近报告期,则明确说明"字段披露期不一致,无法安全合并计算"。 **明确禁止:** - 不得仅凭某一个字段的最后日期代表全部字段报告期 - 不得将不同报告期的数据混算资产负债率、毛利率、净利率等派生指标 - 不得把"最后值日期"想当然写成"最近已披露报告期",除非关键字段已通过一致性校验 - 用不同报告期的数据合并计算派生指标(如用 Q3 营收和 H1 净利润算利润率) - 把报告期日期与交易日日期混淆 - 当日期无法对齐时仍展示派生指标(如资产负债率、毛利率)——必须先通过一致性校验才能做派生计算 **执行要求:** - 在进入最终回答前,必须形成一个字段-报告期对齐表: | 字段 | 最后有效值 | 报告期日期 | |---|---:|---| | 营收 | ... | ... | | 归母净利润 | ... | ... | | 总负债 | ... | ... | | 总资产 | ... | ... | **处理分支:** - 全部日期一致 → 可回答,可做派生计算 - 部分日期不一致且可识别共同最近报告期 → 退回共同报告期重组答案 - 部分日期不一致且无法安全对齐 → 必须明确说"字段披露期不一致,无法安全合并计算",不得混答 **终答检查(回答前必须完成):** - 回答中标注的报告期(如"2025年三季报"/"2025-09-30")必须与 readData 返回的日期逐字一致 - 数值必须与 readData 返回的原始值一致(注意单位换算) - 年份不得写错(如 readData 返回 2025 年数据,回答中不得写成 2024 年) - 若有派生计算,分子/分母均须来自同一报告期 --- ### Step 4:回答层派生计算与兜底口径 > **比率类指标优先级规则(硬规则)**:毛利率、净利率等比率类指标必须优先通过工具直接取数;仅当正式取数链路全部失败时,才允许在回答层派生;派生值必须标注"回答层计算",不得表述为"查询值"或"披露值"。 允许在回答层做以下派生,**不需要再调用 `runMultiFormulaBatchStream`**: | 派生指标 | 固定口径 | 触发条件 | |----------|----------|----------| | 资产负债率 | `总负债 / 总资产 × 100%` | 用户请求或作为标准附带 | | 债务权益比 | `总负债 / 净资产` | 用户请求 | | 毛利率 | 优先用 Step 2 公式链取数;失败时回答层计算,见下方规则 | 仅当用户明确询问时 | | 净利率 | 优先用 Step 2 固定字段取数;失败时回答层计算,见下方规则 | 仅当用户明确询问时 | **毛利率取值规则(固定公式链,不走 confirmDataMulti):** 1. **直接使用公式链**(见 Step 2 固定模板):`单季_收入`/`单季_成本` 二维中间变量 → `毛利率_单季` → `{股票名}_毛利率 = "毛利率_单季" * 取出({股票名})`,流程结束 2. **回答层派生**:仅当 Step 2 公式链 runMultiFormulaBatchStream 执行失败时,使用 `(营业收入 - 营业成本) / 营业收入` 在回答层计算;必须注明"该指标为回答层计算" > **禁止**:用 `confirmDataMulti` 查"毛利率"/"销售毛利率"后,把匹配到的数据集名称直接作为公式左侧变量名(即 `毛利率 = "毛利率" * 取出(...)` 是循环依赖,**100% 报 500 错误**)。 **净利率取值规则(固定字段,不走 confirmDataMulti):** 1. **直接使用固定字段**(见 Step 2 固定模板):`{股票名}_净利率 = "A股销售净利率:单季〔财务指标〕" * 取出({股票名})`,流程结束 2. **回答层派生**:仅当上述字段 runMultiFormulaBatchStream 执行失败时,使用 `净利润 / 营业收入` 在回答层计算;必须注明"该指标为回答层计算" > **禁止**:用 `confirmDataMulti` 查"净利率"后,把匹配到的数据集名称直接作为公式左侧变量名。 **禁止**(通用):跳过工具取数直接走回答层派生;直接字段可得时改用基础字段派生替代 **派生计算的边界:** - 直接指标优先于回答层派生(毛利率、净利率均适用此原则) - ROE 不做回答层自算,优先直接查询;若仍失败则明确说明未成功获取 - 所有派生计算必须使用同一报告期下的字段 - 最终回答中必须注明"该指标为回答层计算",必要时写出分子/分母 - 禁止在回答层自行推算同比增速(需要多期数据,走 `quant-standard.md`) --- ### Step 5:字段完备性检查 - 用户请求的每个字段是否都成功返回? - 各字段是否处于同一报告期? - 若要做派生计算,依赖的基础字段是否都通过? - 若有缺失 → 按本文档末尾「失败恢复规则」处理 --- ### 停止条件(本流程核心) **满足以下全部条件时,立刻进入回答,不得补充其他内容:** 1. 所有请求字段均已返回且通过报告期对齐检查 2. 若需派生计算,基础字段均已获取 3. 未混合不同报告期数据 **绝对禁止**:字段取完后继续补充同比增速、行业排名、经营评价、业绩预测等未被请求的扩展内容 --- ### Step 6:固定格式回答 ## 派生指标展示边界(硬规则) 对资产负债率、毛利率、净利率等可由底层字段派生的指标: - 模型可以内部查询或计算所需底层字段 - 但若用户未明确要求查看过程,最终回答中不得默认展开总资产、总负债、营业成本、完整算式、原始元金额长数字 仅在以下情况允许展开中间字段: 1. 用户明确要求"给计算过程" 2. 派生口径存在歧义,必须解释 3. 工具结果冲突,需要说明为何采用某口径 ## 报告期表达边界(硬规则) - 若工具直接返回的是报告期日期,则最终答案默认只写:`报告期:YYYY-MM-DD` - 不得仅凭模型常识把 `YYYY-MM-DD` 扩写为"年报/中报/三季报/一季报" - 只有当工具返回或本 workflow 明确给出可机械映射的标签字段时,才可补充该标签 ## 最终回答合同(强制) {公司名}最近一个已披露报告期为 {YYYY-MM-DD}: - {字段1}:{值} - {字段2}:{值} - {字段3}:{值} 若某指标为回答层派生,仅在该字段后简短标注: - 资产负债率:63.02%(按总负债/总资产计算) 禁止在最终答案中出现: - 数据已成功获取 - 现在我可以 / 现在整理结果 - 根据 workflow / 根据工具结果 / 数据汇总 - 元金额换算过程 - 非用户要求的中间字段(总资产、总负债等) - 股票代码(除非消歧必需) **最终出答 checklist(回答前必检):** - [ ] 所有展示字段是否同一报告期 - [ ] 所有派生指标的分子分母是否同一报告期 - [ ] 回答中的报告期日期是否与 readData 返回日期逐字一致 - [ ] 是否未把日期擅自扩写为报告类型标签 **终答合同(Final Answer Contract)** 默认回答仅包含:标的名 + 报告期 + 各指标值。不默认附加口径解释、同比增速、行业对比。 | 允许默认输出 | 需用户明确请求 | 绝对禁止 | |------------|--------------|---------| | 标的名、报告期日期 | 口径说明(累计/单季) | 同比增速(需多期数据) | | 各字段名 + 数值 + 单位 | 派生指标(资产负债率等) | 行业排名/对比 | | "回答层计算"标注 | 报告期详细时间表 | 投资建议/经营评价 | **推荐读取模式**:若一次查询 ≥ 3 个财务字段,优先使用 `readData(mode="last_valid_per_asset")` 逐字段批量读取;仅在该模式不可用时才用受限日期区间的 `range_data` 降级。 **标准回答模板:** ``` [资产名] 最近一个已披露报告期([YYYY-MM-DD]): - 营业收入:[X] 亿元 - 归母净利润:[X] 亿元 - ... ``` **部分成功模板:** ``` [资产名] [YYYY年X季报](报告期 [YYYY-MM-DD]): - 营业收入:[X] 亿元 以下字段未能查到:[字段名] ``` 规则:- 先报告期(如:`报告期 2025-09-30`) - 再逐字段列出数值和单位 - 派生计算结果注明"回答层计算" - **口径说明约束**: - 利润表数据(营收/净利润/归母净利润)通常为**报告期累计值** - 但只有在返回结果中明确写出累计/单季/TTM时,才允许在回答中写出该口径 - 否则只报告报告期和数值,**不扩写口径解释** - 禁止补充同比增速、行业对比、主观经营评价等未请求内容 **公式失败受控恢复(本流程固定)**: runMultiFormulaBatchStream 500 错误时: 1. 先检查 payload 结构(变量名 ≠ 数据集名、引号正确) 2. 同口径重试一次 3. 若仍失败,再切到本 workflow 明示的备选模板 4. 不得临时自由改口径、不得跳过公式直接用 description 数值 ### 输出纪律(硬规则) **回答长度**:不超过用户请求字段数 × 2 行(表格行不计入)。 **禁止出现以下内容**: - 总结性段落:「说明」「小结」「走势分析」「整体来看」「综上所述」「总结一下」 - 过程性语言:「让我」「根据 workflow」「我将为你」「按照流程」「接下来我」 - 主动延伸:未被请求的同比增速、行业对比、投资建议、风险提示、口径解释 **原则**:数据到手 → 格式化 → 回答 → 停止。不解释你做了什么,不总结你查到了什么。 --- ## 自包含执行规则(本流程所有步骤的执行依据) > 以下规则在本文档内完整定义,执行本流程时无需读取 quick-lookup.md。 ### 确认资产(Step 1 依据) **优先级规则(按序执行,满足即停):** 1. 先 grep `presets/assets_db/{类型}.yaml`,唯一命中则直接使用标准 `name` / `ticker` 2. 用户同时提供中文名称和6位代码:以中文名称为主,代码仅供辅助校验 3. 裸代码(如 `000063`)未带交易所后缀,不能单独作为可靠主键 4. 名称不存在或有歧义时,停止并向用户说明/澄清,不再调用远程资产确认 ```bash grep "资产名或代码" presets/assets_db/{类型}.yaml ``` **资产确认熔断:** 匹配失败 → 重试1次(换别名)→ 仍失败 → 立即停止,告知用户「该资产不在平台覆盖范围内」。禁止用相似资产替代。 --- ### 禁用写法(硬规则) 禁止使用以下写法: - `GET(...)` / `GETDATA(...)` / `FETCH(...)` / `PRICE(...)` - 将意图词直接当数据名(如 `"毛利率"`),除非 `confirmDataMulti` 已明确返回 - 财务题中写 `"数据名"(资产名)` 这类函数式资产传参 - 把 `scanDimensions`、`renderKLine` 当作数值查询兜底 --- ### 确认 ≠ 结果(硬规则) `confirmDataMulti` 返回的是资产名称映射或数据集ID,**不包含任何财务数值**。 拿到确认结果后,必须再调用 `runMultiFormulaBatchStream` 才能获取实际数据。 --- ### 失败恢复规则(硬规则) **多字段批量查询失败时:** - 不要连续更换多种写法反复试验 - 先拆成逐字段单查 - 同一指标的不同写法重试上限为 1 轮 **毛利率 / 净利率直取失败时:** - **第 1 级**:先用 `confirmDataMulti` 尝试确认"毛利率"/"净利率"的正式数据集名,若找到则用正式名重建公式重试 1 次 - **第 2 级**:若第 1 级仍失败或 confirmDataMulti 找不到对应数据集,则转兜底字段:营业收入、营业成本、净利润 - **第 3 级**:用兜底字段在回答层按 Step 4 固定口径计算,且**必须标注"该指标为回答层计算,口径可能与直取指标有差异"** - 禁止跳过第 1 级直接进入第 3 级 **ROE 直取失败时:** - 第 1 级:用 `confirmDataMulti` 确认正确数据集名后重试 1 次 - 仍失败则明确说明该项未成功获取,不得编造 - 禁止用"净利润/净资产"在回答层自算 ROE(缺乏口径保证) **不作为本流程标准步骤:** - `searchFunctions` - `table_data` - 与本题无关的额外指标探索 **`runMultiFormulaBatchStream` 返回 `PARTIAL_SUCCESS`:** - 保留已成功项,仅修失败项 - 失败项属于数据名问题 → 先 `confirmDataMulti` - 失败项属于函数写法问题且字段**在本文档模板表中** → 直接重写为标准模板,跳过 `searchFunctions` - 同一结构失败后不得用同结构再次重试 **重试上限:** 同一意图、同类公式结构最多重试 **2 次**,超过上限必须 fallback。 **readData 调用预算:** 单题最多 **3 次**,超出后按「关键字段缺失」路径回答。 --- ### 格式化规则(硬规则) | 数据类型 | 格式 | |----------|------| | 日期 | `YYYY-MM-DD` | | 财务金额 | 按数量级选"亿元/万元/元",同一答案内口径保持一致 | | 百分比指标(ROE等) | 若原始值为小数,展示时 ×100 后加 `%` | 若 description 中展示值与规则冲突,优先按规则基于原始值重算。 --- ### 关键字段缺失时的回答门禁 若本轮仍有关键字段缺失,必须明确写出已成功获取的字段、未成功获取的字段、是否已尝试标准补救路径。禁止输出暗示已完成的标题,禁止输出主观判断或投资建议。 **部分成功答复模板:** ``` 本轮已成功获取: - [字段名]:[数值](报告期 [日期]) 暂未成功获取: - [字段名] 我已按标准路径尝试补救;由于该字段本轮未返回可靠结构化结果,这里不做猜测。 ``` -
quick-snapshot.md 19.2 KB
# 快速查数 · 最新时点快照 > **适用范围**:单资产或少量资产,查询**最新交易日**的行情/估值少量字段,最终拼出若干标量。 > 本流程定义本场景的专用执行步骤;全局执行合同、证据分级、禁语、精度与回答边界仍以 `workflows/global-rules.md` 为准。 --- ## 触发条件(满足全部才进此流程) - 时间锚点:最新交易日 / 最近一个交易日(一个时点,不是窗口) - 资产数量:≤1000 个明确资产 - 字段类型:基础行情(开盘/收盘/最高/最低/涨跌幅/成交量/成交额/换手率)或估值(PE/PB/PS/市值/股息率) - 目标:得到若干标量 → 立刻回答,无需进一步序列操作 ## 反例(不属于此流程,不要误路由) | 用户问题 | 应走流程 | |----------|----------| | 最近 20 日最高价 / 最低价 / 振幅 | `quick-window.md` | | 最近 5 日收盘价序列 | `quick-window.md` | | 最近报告期营收 / 净利润 | `quick-report-period.md` | | 用户明确要看 K 线走势图 | `quick-window.md` 或 `renderKLine` | | 全市场筛选 / 行业排名 | `quant-standard.md` | --- ## 执行流程 ``` Step 1 确认资产(硬门槛) → Step 2 合并所有字段为一次 runMultiFormulaBatchStream → Step 3 从 description 判断是否可直接停止 → Step 4 字段完备性检查 → Step 5 按固定格式回答 ``` ### Step 1:确认资产(强制,不得跳过) 只要用户问题中出现了资产名称、简称、代码、名称+代码混合表达中的任一种,在首次 `runMultiFormulaBatchStream` 前都必须完成资产确认。跳过本步骤视为 workflow 违约。 - 当用户以"名称(代码)""名称+代码""代码+名称"指向同一只股票时,必须先在脑内归一为**一个资产候选**。 - 不得把同一资产的名称和代码拆成两个独立 intention 传给 `assets_db` 本地资产库。 - 名称已足够唯一时,优先只用名称确认;代码仅作交叉校验。 - 禁止出现"同一资产确认 1/2 成功"这类可避免的部分失败。 - 无论资产数多少(**含单资产**),都必须先 `grep presets/assets_db/{类型}.yaml` 查本地资产库,不得因为只有 1 只资产就跳过。 - 即使用户已提供 6 位代码,也不得跳过确认——最终公式中的资产对象必须使用确认后的标准资产。 - 未完成资产确认前,不得直接对自然语言资产名执行 `runMultiFormulaBatchStream`。 - 若存在歧义、近似匹配或跨体系匹配,必须先向用户澄清;不得继续查数。 ### 命名锁定规则(Step 2 前置) 在进入 Step 2 之前,必须确定本次查询中每个字段的「规范名称」,并在后续所有步骤中一致使用: - 收盘价 → 「收盘价」(不是“最新价”“股价”“现价”) - 涨跌幅 → 「涨跌幅」(不是“回报率”“涨幅”) - 成交额 → 「成交额」(不是“交易金额”“成交量”) - PE / PB / PS → 保持英文缩写,括号内标注口径(如 PE(TTM)、PE(静态)) - 市值 → 明确「总市值」或「流通市值」,不得笼统称“市值” 最终回答中的字段名必须与此处一致,不得在回答层自发重命名。 ### Step 2 命名规范(硬规则) `runMultiFormulaBatchStream` 中每个 leftName 必须使用"完整资产名 + 字段名": - 比亚迪收盘 - 长城汽车涨幅 - 中兴通讯成交额 禁止使用会造成歧义的缩写: - 长城收盘 - 茅台涨幅 ### Step 2:模板选取与合并执行 从下表选取全部所需字段,**合并进一次 `runMultiFormulaBatchStream` 并行取数**,不要分多次调用。 > 对下表中已列出的字段,**直接使用固定模板,无需先调用 `confirmDataMulti`**。仅当用户问到下表以外的字段时,才先 `confirmDataMulti` 确认数据集名称。 #### 行情类 | 字段 | 公式 | |------|------| | 收盘价 | `收盘 = "全市场每日收盘价" * 取出(资产名)` | | 开盘价 | `开盘 = "全市场每日开盘价" * 取出(资产名)` | | 最高价 | `最高 = "全市场每日最高价" * 取出(资产名)` | | 最低价 | `最低 = "全市场每日最低价" * 取出(资产名)` | | 涨跌幅 | `涨幅 = "全市场每日回报率" * 取出(资产名)` | | 成交量 | `成交量 = "全市场每日成交量" * 取出(资产名)` | | 成交额 | `成交额 = "全市场每日成交额" * 取出(资产名)` | | 换手率 | `换手率 = "A股换手率〔估值数据〕" * 取出(资产名)` | #### 估值类 > ⚠️ 以下公式模板使用 A 股 index_title,仅适用于 A 股的 `runMultiFormulaBatchStream` 路径。港股/美股查估值请优先走 `fast_query`(PE_TTM/PB/PS_TTM/股息率/PCF 已支持 A/US/HK);流通市值、换手率仅 A 股。 | 字段 | 公式 | |------|------| | PE(静态) | `PE = "A股市盈率(PE)〔估值数据〕" * 取出(资产名)` | | PE(TTM) | `PE = "A股市盈率(PE, TTM)〔估值数据〕" * 取出(资产名)` | | PB | `PB = "A股市净率(PB)〔估值数据〕" * 取出(资产名)` | | PS(TTM) | `PS = "A股市销率(PS,TTM)〔估值数据〕" * 取出(资产名)` | | 流通市值 | `市值 = "A股流通市值〔估值数据〕" * 取出(资产名)` | | 总市值 | `总市值 = "A股总市值〔估值数据〕" * 取出(资产名)` | | 股息率 | `股息 = "A股股息率〔估值数据〕" * 取出(资产名)` | #### 多资产对比:并行取数示例 ```json {"formulas": [ "茅台收盘 = \"全市场每日收盘价\" * 取出(贵州茅台)", "五粮液收盘 = \"全市场每日收盘价\" * 取出(五粮液)", "茅台涨幅 = \"全市场每日回报率\" * 取出(贵州茅台)", "五粮液涨幅 = \"全市场每日回报率\" * 取出(五粮液)" ], "begin_date": 20240101} ``` **runMultiFormulaBatchStream 参数(按用户时间意图选择)**: ```json { "begin_date": "<T>", "include_description": true, "use_minute_data": "<盘中/当前分钟为 true;历史或收盘快照为 false>" } ``` - `begin_date` 直接取**今日**(`T`,YYYYMMDD 整数)。快照场景公式无滚动窗口、只取最新值,begin_date 越晚返回越快。 - **禁止**固定写 `20240101` / `T-90日` 等远期锚点;除非用户明确指定起点,否则一律 `T` - 仅当用户明确要求盘中、当前分钟或当日未收盘数据时使用 `use_minute_data: true`;历史或收盘快照使用 `false` 或省略。不得因为“快照”二字自动开启分钟模式 - 若用户问题含名单/排名/选股/筛选/TopN/信号等横截面需求,应改走 `quant-standard` 的对应微流程,quick-snapshot 只处理单资产或少量资产的静态快照 > ⚠️ 429 错误不受本流程 Retry Budget 约束——按 global-rules.md 第 12 条「429 前置拦截」规则处理。 --- ### Step 3:结果提取规则与停止条件(本流程核心) 对每个用户请求字段,按以下优先级提取"最新交易日值 + 该值日期": **① 优先使用结构化末值/末日期** - 若 `runMultiFormulaBatchStream` 返回对象中存在可明确识别的末值、末日期字段,优先使用。 **② 受控解析 `description`** - 仅当 `runMultiFormulaBatchStream` 成功,且 `description` 中明确出现模式: - `最后值: 数值(日期)` 或等效结构化文本 - 才允许将该字段视为可答。 **③ 若上面两者任一缺失** - 对该字段调用 `readData(mode="last_valid_per_asset")` 补取最后有效值与日期。 **④ 若仍无法明确得到最新值** - 标记为不可答,进入部分成功模板;不得补猜、不得默认填 0。 **明确禁止误取:** - 不得把均值 / 中位数 / 去年同期 / 近20日均 / 历史极值当作本题答案 - 不得把时间范围终点直接当作该字段末值日期 - 不得在看不到明确末值时自行猜测涨跌幅为 0.00% **停止规则:** - 所有字段均已获得"最新值 + 对应日期" → 立刻停止,进入 Step 5 回答 - 部分字段可答、部分不可答 → 进入「部分成功回答模板」 - 所有字段均不可答 → 安全失败,告知用户 - quick-snapshot 成功后,默认不再调用 `readData(table_data)` 或任何全量模式 仅在上述"③ 补取"情况时才调用 `readData`,且仅用 `last_valid_per_asset`,**禁止旧的 `full` / `last_n_rows` 或 `last_column_full` 模式**。 **绝对禁止:** - 拿到最新值后继续读取历史均值/极值 - 主动补充近 20 日统计、去年同期、历史分位等未被请求的扩展信息 - 缺少某字段时默认填 0 或缺省值 **百分比/收益率换算规则(强制):** - 若工具返回的原始末值为小数形式的收益率/涨跌幅(如 `-0.005`、`0.0123`) - 最终展示必须先 `×100` 再标记 `%`,默认保留 2 位小数 - 示例: - `0.0123 → 1.23%` - `-0.005 → -0.50%` - `-0.0274 → -2.74%` - 禁止直接把小数当百分比展示(如 `-0.005` 写成 `-0.005%`) - 禁止在换算过程中引入额外四舍五入偏差(如 `-0.005` 算成 `-0.46%`) --- ### Step 3.5:内部校验(必须执行,但不默认外显) 以下校验表仅用于内部决策,不得在最终答案中复述、粘贴或概括。 在进入最终回答前,内部核对每个请求字段(**不向用户展示此表,除非用户明确要求明细**): | 字段 | 原始末值 | 原始日期 | 展示值 | |---|---:|---|---| | 收盘价 | ... | ... | ...元 | | 涨跌幅 | ... | ... | ...% | | 成交额 | ... | ... | ...亿元 | 强制检查: 1. 每个字段都已拿到"末值 + 日期" 2. 多字段日期必须一致;若不一致,不得装作同一交易日 3. 百分比字段若原始值是小数收益率,必须执行 `原始值 × 100` 再展示 4. 百分比展示统一保留 2 位小数 5. 最终答案中的数值必须与校验表完全一致,不得二次口头改写 6. **日期/年份交叉核对**:回答中写出的年份、月份、日期必须与校验表中的原始日期逐字一致,禁止凭记忆填写年份(如工具返回 2026-03-31,不得写成 2025-03-31) 7. **口径锚定**:若本轮首次查询锚定了某个最新交易日,后续所有字段均以该日期为准;若追加字段返回了不同日期,须在回答中明确标注 强制示例: - `-0.003 -> -0.30%` - `0.007 -> 0.70%` - 不得写成 `-0.28%`、`0.71%` 等与原始值不自洽的结果 **校验不通过时的处理规则:** - 若任一项校验不通过,必须基于当前已有的工具返回值重新整理答案 - 不得新增工具调用来弥补 - 不得凭记忆修辞性改写数值(如把 -0.50% "优化"为 -0.46%) - 不得"先发一版再更正"——必须一次性给出正确答案 **多资产跨标的同日校验(资产数 ≥ 2 时必检):** - 逐资产提取各自的最新日期,确认所有资产的最新交易日相同 - 若某资产最新日期与其他资产不同(如停牌、未开盘),必须在回答中注明日期差异,不得将不同日期的数据放入同一对比表格而不说明 - 对比结论(如"谁涨幅更大")只能基于同一交易日的数据 **不补精度(硬规则):** - 工具返回几位小数就展示几位(经格式化规则处理后),不得追加未经工具验证的额外精度 - 工具未返回的字段不得通过推理补充数值 - 禁止将 description 中的近似描述性文字(如"小幅上涨")转译为具体数值 --- ### Step 4:字段完备性检查 - 用户请求的每个字段 → 是否都成功返回? - 每个字段是否附带对应日期? - 多字段的日期是否是同一个最新交易日? - 若有缺失字段 → 按本文档末尾「失败恢复规则」处理,不得把"部分成功"包装成"完全完成" --- ### Step 5:固定格式回答 ## 最终回答合同(强制) 若已拿到所需字段的最后值与对应日期,直接输出结果并停止。 **单资产模板** ``` {资产名}最新交易日({YYYY-MM-DD}): - {字段1}:{值1} - {字段2}:{值2} ``` **多资产模板** ``` 截至 {YYYY-MM-DD}: | 标的 | {字段1} | {字段2} | |---|---:|---:| | {名称A} | ... | ... | | {名称B} | ... | ... | ``` 若用户明确要求"对比/比较/横向对比",仅追加: ``` 横向对比: - {字段A}更高:... - {字段B}更大:... ``` **禁止输出** - 数据已成功获取 / 我已获取 / 我看到 - 根据返回结果 / 根据 description / 从 readData 返回可见 - 我来帮您 / 现在执行 / 让我来 / 现在开始 - 中间算式(如 `-0.002 × 100`)、原始值换算过程(如"原始值为 xxx 元") - 非用户要求的代码后缀(如 `.SZ/.SH`) - Step 1/2/3 / workflow / 路由 - 字段提取表 / 原始末值表(除非用户明确要求) - 用户未要求的额外字段 **规则**:只回答用户点名字段,不扩写。遵守本文档「格式化规则」(日期格式、单位换算、保留位数)。 ### 字段展示与单位策略(硬规则) - 价格类字段(开盘价/收盘价/最高价/最低价): - 默认按价格展示,保留 2 位小数; - 股票默认单位"元",指数默认单位"点"。 - 涨跌幅/回报率: - 若原始值为小数(如 `0.0227`),**必须** `× 100` 后加 `%`,保留 2 位小数(如 `2.27%`)。 - 换算必须严格:`-0.003 × 100 = -0.30%`,不得出现 `-0.28%` 等不自洽结果。 - 若原始值已是百分数形式(如 `2.27`),直接加 `%` 即可,不再 ×100。 - 成交量: - 若工具返回显式单位,按原单位输出; - 若未显式返回单位,仅输出数值,**不得自行补充"股""手"等单位**。 - 成交额 / 市值: - 若原始口径可明确识别为金额,按统一规则换算为"亿元/万元/元"; - 若无法可靠识别金额单位,则保留字段名和值,不臆造单位。 原则:优先保证可用且保守;不因单位未显式返回而输出"单位未明"。 ### 部分成功时的回答规则 当部分字段取到 A级证据、部分字段失败时,使用以下模板: ``` 截至 [YYYY-MM-DD]: - [字段A]:[数值][单位] - [字段B]:[数值][单位] 以下字段未能查到:[字段C]、[字段D] ``` 禁止: - 将失败字段静默省略,把「部分成功」包装成「完全成功」 - 对失败字段梳测填 0 或默认值 - 用“暂时无法获取”等模糊说法而不列出具体字段名 ### 输出纪律(硬规则) **回答长度**:不超过用户请求字段数 × 2 行(表格行不计入)。 **禁止出现以下内容**: - 总结性段落:「说明」「小结」「走势分析」「整体来看」「综上所述」「总结一下」 - 过程性语言:「让我」「根据 workflow」「我将为你」「按照流程」「接下来我」 - 主动延伸:未被请求的对比分析、投资建议、风险提示、口径解释 **原则**:数据到手 → 格式化 → 回答 → 停止。不解释你做了什么,不总结你查到了什么。 对简单快照题,优先使用结果型回答,不要以"已成功获取数据""根据返回结果"为开头。 --- ## 自包含执行规则(本流程所有步骤的执行依据) > 以下规则在本文档内完整定义,执行本流程时无需读取 quick-lookup.md。 ### 确认资产(Step 1 依据) **优先级规则(按序执行,满足即停):** 1. 先 grep `presets/assets_db/{类型}.yaml`,唯一命中则直接使用标准 `name` / `ticker` 2. 若用户同时提供中文名称和6位代码:以中文名称作为主确认意图,代码仅供辅助校验 3. 裸代码(如 `000063`)未带交易所后缀(`.SZ`/`.SH`),不能单独作为可靠主键 4. 若用户输入为"名称(代码)"或"名称 代码"混合串: - 先拆分为名称与代码 - 优先使用名称确认 - 再用代码做交叉校验 - 不直接把混合字符串作为唯一查询词 5. 名称不存在或有歧义时,停止并向用户说明/澄清,不再调用远程资产确认 ```bash grep "资产名或代码" presets/assets_db/{类型}.yaml ``` > ⚠️ 参数名只有 `intentions` 有效——不是 queries / query / names。`types` 必须传 `["asset"]`。 **资产确认熔断:** 匹配失败 → 重试1次(换别名)→ 仍失败 → 立即停止,告知用户「该资产不在平台覆盖范围内」。禁止用相似资产替代。 --- ### 禁用写法(硬规则) 禁止使用以下写法: - `GET(...)` / `GETDATA(...)` / `FETCH(...)` / `PRICE(...)` - 将意图词直接当数据名(如 `"毛利率"`、`"净利率"`),除非 `confirmDataMulti` 已明确返回 - 财务题中写 `"数据名"(资产名)` 这类函数式资产传参 - 已有原生回报率模板时优先手写 `REF/DELAY/ROC` 四则运算 - 把 `scanDimensions`、`renderKLine` 当作数值查询兜底 --- ### 确认 ≠ 结果(硬规则) `confirmDataMulti` 是**确认类工具**,返回的是资产名称映射或数据集ID,**不包含任何行情数值**。 - 拿到确认结果后,**必须再调用 `runMultiFormulaBatchStream`** 才能获取实际数据 - 禁止把确认类工具的返回结果当作最终数值直接回答用户 --- ### 失败恢复规则(硬规则) **`runMultiFormulaBatchStream` 返回 `PARTIAL_SUCCESS`:** - 保留已成功项,仅修失败项 - 失败项属于数据名问题 → 先 `confirmDataMulti` - 失败项属于函数写法问题且字段**在本文档模板表中** → 直接重写为标准模板,跳过 `searchFunctions` - 同一结构失败后不得用同结构再次重试 **`runMultiFormulaBatchStream` 返回 500 / Internal Server Error:** 1. 检查是否使用了非标准模板或禁用写法 2. 若是 → 重写为标准模板重试一次 3. 仍失败 → fallback:取原始序列 → readData → 回答层计算 **重试上限:** 同一意图、同类公式结构最多重试 **2 次**,超过上限必须 fallback,不得继续撞接口。 **readData 调用预算:** 单题最多 **3 次**,超出后按「关键字段缺失」路径回答。 **Token 预算意识(硬规则):** - fallback 到 readData 后,必须使用 `last_valid_per_asset` 模式,禁止 `last_column_full` 和旧的 `last_n_rows` - 单题总 token 软上限 50K;接近上限时优先精简读取范围,不继续扩展操作 --- ### 格式化规则(硬规则) | 数据类型 | 格式 | |----------|------| | 日期 | `YYYY-MM-DD` | | 价格/点位 | 保留2位小数;股票单位"元",指数单位"点" | | 涨跌幅/回报率 | 原始小数值 ×100 后加 `%`,保留2位小数 | | 成交额 | 优先换算为"亿元",保留2位小数;不足1亿可用"万元" | | 成交量 | 保留原始口径,注明单位来源 | | 财务金额 | 按数量级选"亿元/万元/元",同一答案内口径一致 | 若 description 中展示值与规则冲突,优先按规则基于原始值重算,不凭模型主观改写。 --- ### 关键字段缺失时的回答门禁 若本轮仍有关键字段缺失,**必须**明确写出已成功获取的字段、未成功获取的字段、是否已尝试标准补救路径。 禁止输出暗示已完成的标题,禁止输出主观走势判断或投资建议。 **部分成功答复模板:** ``` 本轮已成功获取: - [字段名]:[数值]([日期]) 暂未成功获取: - [字段名] 我已按标准路径尝试补救;由于该字段本轮未返回可靠结构化结果,这里不做猜测。 ``` -
quick-window.md 25.8 KB
# 快速查数 · 最近 N 日短窗序列 > **适用范围**:单资产,查询**最近 N 个交易日**的价格/成交序列,或在该窗口内求统计量(极值、振幅、均值等)。 > 本流程定义本场景的专用执行步骤;全局执行合同、证据分级、禁语、精度与回答边界以 `workflows/global-rules-lite.md` 为准(进入本流程前必须已读取该文件)。 --- ## 执行协议:Abstract Target + Checkpoints + Backward Recovery ### Abstract Target > 交付对象:单资产最近 N 个交易日的目标字段序列(或该窗口内的统计量),与实现路径无关。 ### Checkpoints | CP | 名称 | 已验证状态 | Acceptance Test | |----|------|-----------|-----------------| | W0 | 目标冻结 | 资产、N、目标统计已确定 | 只有一个资产、N 为正整数、目标字段在本文档模板表中 | | W1 | 公式模板冻结 | 单资产一维时间序列已生成 | runMultiFormulaBatchStream 成功,返回一维 data_id | | W2 | 尾部数据已读取 | N 条尾部记录已在手 | 返回行数 ≤ N,尾部日期与最新交易日一致(或注明停牌) | | W3 | 统计/交付完成 | 窗口统计已算完,准备输出 | 所有统计值仅基于这 N 行,无窗口外数据引用 | ### 首选路径(默认必须先尝试) 1. `runMultiFormulaBatchStream` 生成单资产一维时间序列 2. `readData(mode="range_data", start_date=..., end_date=...)` 读取受限日期区间的连续数据,再在回答层取尾部 N 条有效记录 ### Backward Recovery(失败时) | 失败位置 | 回退到 | 允许修改的唯一 slot | 禁止 | |---------|--------|-------------------|------| | W1 公式执行失败 | W0 | 数据对象(换备选公式模板) | 同时改资产和公式 | | W2 readData 失败或模式不支持 | W1 | 读取模式(受控降级,见下文) | 同时改公式和读取模式 | | W2 返回行数异常 | W1 | 读取参数 | 切换到 searchSimilarCases | | W3 统计结果不合理 | W2 | 实现路径(统计方式) | 引入窗口外数据 | ### 受控降级(仅 W2 失败时) - 仅当 `range_data` 因工具能力限制明确不可用时,才允许进入受控降级 - 降级后必须只保留末尾 N 行与必要统计结果,不得继续保留全量历史文本 - 降级最多 1 次,仍失败则进入安全失败模板 ### Retry Budget - 同一 slot 最多改 1 次,总回退上限 2 次 - 超出后进入「部分成功」或「安全失败」模板 > ⚠️ 429 错误不受本节 Retry Budget 约束——按 global-rules.md 第 12 条「429 前置拦截」规则处理。 ### 禁止在失败态扩展 - 禁止去 searchSimilarCases(当前失败不是"缺案例") - 禁止去 searchFunctions(当前模板表已覆盖所有标准字段) - 禁止在失败态同时改公式架构和读取模式 --- ## 触发条件(满足全部才进此流程) - 时间锚点:最近 N 个交易日的**尾部窗口**(N 通常为 5 / 10 / 20,不是单一时点) - 资产数量:通常 1 个资产(多资产短窗对比也可,但不常见) - 目标:得到有界序列,或对窗口内数据做统计(最大值/最小值/振幅/区间收益等) ## 反例(不属于此流程,不要误路由) | 用户问题 | 应走流程 | |----------|----------| | 贵州茅台最新收盘价 / 今日涨跌幅 | `quick-snapshot.md` | | 最新 PE / PB / 总市值 | `quick-snapshot.md` | | 最近报告期营收 / 净利润 | `quick-report-period.md` | | 要求画 K 线图(不是取数值) | `renderKLine`(在 quick-lookup 路由中直接触发) | | 全市场因子排名 / 行业筛选 | `quant-standard.md` | --- ## 执行流程 ``` Step 1 确认资产(见本文档末尾「自包含执行规则」) → Step 2 执行公式,获取基础序列 → Step 3 最小必要读取(只取尾部窗口) → Step 4 窗口内统计(如用户需要) → Step 5 字段完备性检查 → Step 6 按固定格式回答 ``` --- ### Step 2:公式模板 优先使用以下固定模板,**禁止为了"简单"而手写 REF/DELAY/ROC 差分公式**(平台有原生每日回报率)。 > 对下表中已列出的字段,**直接使用固定模板,无需先调用 `confirmDataMulti`**。仅当用户问到下表以外的字段时,才先 `confirmDataMulti` 确认数据集名称。 | 字段 | 首选公式 | 备选公式(首选失败时) | |------|----------|----------------------| | 收盘价序列 | `收盘 = 收盘价(资产名)` | `收盘 = "全市场每日收盘价" * 取出(资产名)` | | 每日涨跌幅序列 | `日涨幅 = 涨跌幅("收盘", 1)` | `日涨幅 = "全市场每日回报率" * 取出(资产名)` | | 成交量序列 | `成交量 = "全市场每日成交量" * 取出(资产名)` | — | | 成交额序列 | `成交额 = "全市场每日成交额" * 取出(资产名)` | — | | 最高价序列 | `最高 = "全市场每日最高价" * 取出(资产名)` | — | | 最低价序列 | `最低 = "全市场每日最低价" * 取出(资产名)` | — | > 「首选」更简洁,优先使用;仅当首选公式执行返回错误时,改用「备选」。 **日涨跌幅序列强制优先级(必须遵守):** 当用户查询"单资产最近 N 个交易日的日涨跌幅序列"时,默认强制优先: 1. `收盘 = 收盘价(资产名)` → `日涨幅 = 涨跌幅("收盘", 1)` 2. 只有首选路径失败时,才允许使用:`日涨幅 = "全市场每日回报率" * 取出(资产名)` **`begin_date` 上下文预算保护(硬规则,违反会导致 token 超限崩溃):** > ⚠️ **本节规则按"调用何种工具"分两档**: > - 走 **`runMultiFormulaBatchStream` + 公式内聚合**(路径 A,下文 Step 3)→ `begin_date = T - N 自然日`(越晚越快,N 取公式窗口) > - 走 **`readData(mode='range_data')` 拉完整序列**(路径 B)→ 必须按下方分段表覆盖足够自然日,否则取不到 N 个交易日 ## 参数合同:最近 N 个交易日(硬规则) - 用户原话中的 N 必须冻结,不得改写。 - **路径 A**(仅 `runMultiFormulaBatchStream`,公式形如 `最大(…,N) / 最小(…,N) / 平均(…,N) / 涨跌幅(…,N)`,从 `description` 末值或 `last_column_full` 取结果): - `begin_date` 取 **`T - N 自然日`**(N 即公式中的窗口参数);如 N=20 且今天 20260331 → `begin_date = 20260311` - **禁止**写 `20240101` 等远期锚点 - **路径 B**(`readData(mode='range_data')` 读完整 N 日序列):`begin_date` 必须覆盖足够自然日才能保证 N 个交易日全部落入区间,按下方分段表执行;不得自由发挥成"今年1月1日""最近几个月"等临时策略。 **路径 B 的分段表(仅适用于 readData range_data)**: | 用户窗口 N | `begin_date` 必须设为 | 示例(假设今天 20260331) | |-----------|------------------------|----------------------------| | N ≤ 5 | 当前日期 − 4 个月 | `20251201` | | 6 ≤ N ≤ 30 | 当前日期 − 4 个月 | `20251201` | | 31 ≤ N ≤ 60 | 当前日期 − 8 个月 | `20250801` | | N > 60 | **安全失败** | 告知用户"窗口过大,建议改用下载流程" | 计算方法:当前日期减去月数,取 YYYYMMDD 整数。接近年初/长假密集时进一步前移。 若用户明确指定窗口起点,则按用户指定值,不受此表约束。 > 绝对禁止在路径 B 使用 `20240101` 等超过 1 年前的 begin_date——`range_data` 会返回数百个数据点并导致 token 超限。 --- ### Step 3:根据用户需求选择路径 根据用户最终要什么,选择不同的执行路径: #### 路径 A:用户只需要窗口统计量(极值/振幅/区间收益)→ 公式内计算,跳过 readData **这是首选路径**——直接在 `runMultiFormulaBatchStream` 中用聚合函数计算统计结果,避免读取原始序列。 平台支持的窗口聚合函数(来自 `presets/functions.yaml`): - `最大(数据, N)` — 滚动 N 日最大值 - `最小(数据, N)` — 滚动 N 日最小值 - `平均(数据, N)` — 滚动 N 日均值 **示例(N=20 的最高/最低/振幅):** ```json { "formulas": [ "窗口最高 = 最大(\"全市场每日最高价\" * 取出(招商银行), 20)", "窗口最低 = 最小(\"全市场每日最低价\" * 取出(招商银行), 20)" ], "begin_date": "<T-20 自然日>", "include_description": true } ``` > `begin_date` 取 **`T - 20 自然日`**(窗口 N=20 → 回退 20 天,如今天 20260331 → `20260311`),越晚返回越快。 然后从 `description` 中的"最后值"提取窗口最高/最低: - 窗口最高 description 的最后值 = 最近 20 日内最高价的最大值 - 窗口最低 description 的最后值 = 最近 20 日内最低价的最小值 - 振幅在回答层计算:`(最高 - 最低) / 最低 × 100%` **标准交易日窗口口径(硬规则)**:将自然语言时间描述转为交易日数量时,必须使用以下固定口径,禁止自行换算: | 用户描述 | 交易日数 N | |---------|-----------| | 1 周 / 一周 | 5 | | 1 个月 / 一个月 | 20 | | 3 个月 / 三个月 | 60 | | 6 个月 / 半年 | 120 | | 1 年 / 一年 | 240 | 若用户明确指定了交易日数(如"最近20个交易日"),以用户指定为准。若用户用自然月表述,必须告知所用口径(如"1个月=20个交易日"),不得悄悄换成21、22等近似值。 **路径 A 不需要调用 readData,直接进入 Step 5 → Step 6 回答。** **路径 A 的安全表达限制(必须遵守):** - 这里得到的是"截至某日的最近 N 日窗口统计结果" - `description` 中的最后值日期,只能解释为统计结果对应的截至日 - 不得把该日期写成"窗口最高价发生日"或"窗口最低价发生日" - 未额外读取明细序列时,不得自行补窗口起始日期 **窗口统计提取口径(硬规则,路径 A/B 通用):** 对 `最大(..., N)` / `最小(..., N)` / `平均(..., N)` 这类最近 N 日窗口统计: - 若返回为滚动序列,最终答案一律取最后一个有效时点的值 - description 中的历史极值/范围/均值仅用于诊断,不得替代最终答案 - 最终答案中的日期应写为最后值对应的截至日期,而不是历史极值发生日 - **反例提醒**:不要把"窗口最高序列的历史最大值"当成"最近 N 日最高价";不要把截至日写成最高价发生日;不要因为 description 很长就引用"历史极值/范围"替代最后值 #### 路径 B:用户需要完整 N 日序列(日涨跌幅序列、收盘价序列等)→ 必须 readData 仅当用户明确要看**逐日数据列表**时才走此路径。 调用 `readData` 时只读取覆盖尾部 N 条所需的受限日期区间,禁止展开全量历史分析。 **readData 调用规则(必须逐条遵守):** 1. **每次只读一个 data_id**:即使有多个序列,也必须逐个调用。禁止一次传多个 id。 2. 使用 `range_data` 模式。 3. **必须同时传 `start_date` 和 `end_date`**:`start_date` 设为 `当前日期 − 3×N 天`(自然日),`end_date` 设为当前日期或用户指定结束日。 - 示例:N=5,今天 20260331 → `start_date: 20260316, end_date: 20260331` - 示例:N=20,今天 20260331 → `start_date: 20260201, end_date: 20260331` ```json {"ids": ["<单个_data_id>"], "mode": "range_data", "start_date": 20260201, "end_date": 20260331, "nan_handling": "keep"} ``` > `range_data` 返回完整连续区间数据,不采样;读取后从 `range_data.dates` / `range_data.values` 末尾取 N 条有效记录。 > **若日期区间内有效记录不足 N 条**,必须前移 `start_date` 后最多重试 1 次;仍不足则说明可用交易日不足。 - ⛔ 禁止 `smart_sample`(返回均匀采样,无法获取连续尾部数据) - ⛔ 禁止 `last_day_stats`(只有末日统计,无序列) - ⛔ 禁止 `table_data`(一维数据不支持,会返回错误) - ⛔ 禁止旧的 `full` / `last_n_rows` 模式 - 最终只围绕用户指定的 N 日窗口展开,**不引用窗口外的数据点** **返回结果校验:** - 从返回的 `range_data.dates` / `range_data.values` 末尾取 N 条有效记录,忽略其余 - 确认尾部日期与最新交易日一致(若不一致可能停牌,需注明) - 每个数据点必须同时具备日期和数值,缺一不可 **绝对禁止:** - 从全历史数据里找任意时间段的极值并混入回答 - 因为读了更多历史数据就"顺便"分析更长周期趋势 - 把"近1年走势""历史最高"等窗口外统计写进最终回答 --- ### 派生指标计算纪律(Step 4 前置,路径 A/B 通用) 对窗口最高/最低类 one-row 结果,优先冻结: - high - low - as_of_date 最终答案中的每个数值都必须能追溯到这三个冻结值或它们的回答层派生。 当目标统计量可由已取标量通过四则运算直接得出时,**在回答层计算,不得为此再发 `runMultiFormulaBatchStream`**: | 可直接派生 | 来源 | 公式 | |-----------|------|------| | 振幅 | 窗口最高 + 窗口最低 | `(最高 - 最低) / 最低 × 100%` | | 区间收益 | 窗口首值 + 窗口末值 | `(末值 - 首值) / 首值 × 100%` | | 最大单日涨幅 | N 日涨跌幅序列 | 序列 max | | 最大单日跌幅 | N 日涨跌幅序列 | 序列 min | **精度规则(硬规则)**: - 所有派生值必须基于工具返回的原始精度值计算 - 禁止用已四舍五入/截断的展示值做二次运算 - 计算完成后统一格式化到展示精度(默认 2 位小数) - 若原始值精度不足以支撑派生计算(如原始值本身只有 1 位小数),在回答中标注"精度受限于原始数据" ### Step 4:窗口内统计(仅路径 B 中、且用户明确要求时执行) > 若走了路径 A,统计已在公式内完成,跳过此步。 用户要求窗口内最高/最低/振幅/区间收益时: - **最高价**:窗口内 `最高序列` 的最大值及对应日期 - **最低价**:窗口内 `最低序列` 的最小值及对应日期 - **振幅**:`(窗口最高 - 窗口最低) / 窗口最低 × 100%` - **区间收益**:`(窗口末值 - 窗口首值) / 窗口首值 × 100%` - 整体趋势判定:窗口末值 > 窗口首值 → 上涨;< → 下跌;相等 → 持平 **精度规则(硬规则)**:振幅、区间收益等派生值必须基于工具返回的原始数值计算,禁止用已四舍五入/截断的展示值做二次计算;计算完成后再统一格式化到展示精度(默认保留 2 位小数)。 计算仅在窗口内完成,不引用窗口外数据。 --- ### 最近 N 个交易日抽取协议(读到明细序列时必须执行) 对 `readData` 返回的时间序列: 1. 只保留有效数据点 2. 按日期升序排列 3. 取最后 N 个点,定义为"最近 N 个交易日" 4. 所有窗口统计(最大值、最小值、区间收益、最大单日涨跌幅等)只能在这 N 个点上计算 5. 最终展示时按日期升序输出 6. 禁止把更早数据混入答案 7. 禁止向用户展示"我需要重新整理/重新排序/等等"等中间草稿话术 8. 若 `readData` 返回行数超过 N,必须只取最后 N 行,禁止手动从大窗口中挑选 9. 禁止从超长序列中人工挑选"有代表性"的数据点替代完整 N 行 10. `begin_date` 最多回推 N×2 个自然日(防止拉取过量数据);若仍不足 N 行则如实说明 --- ### Compaction:工具返回后立即压缩工作状态(每次工具调用后必须执行) 每次 `runMultiFormulaBatchStream` 或 `readData` 返回后,立即将原始输出压缩为结构化工作状态,然后丢弃原始文本。后续步骤只引用压缩后的状态,不回溯原始工具输出。 **路径 A(公式内聚合)压缩模板:** ``` [资产名] 窗口统计 状态: - 窗口最高: {值} | 截至日: {YYYY-MM-DD} - 窗口最低: {值} | 截至日: {YYYY-MM-DD} - CP: W3 ✓ ``` **路径 B(读取明细序列)压缩模板:** ``` [资产名] 序列 状态: - 有效点数: {M} | 保留末尾: {N} - 窗口范围: {首日} ~ {末日} - [若需统计] 最大: {值}({日期}) | 最小: {值}({日期}) - CP: W2 ✓ → W3 进行中 ``` **硬规则:** - 压缩后的工作状态是后续所有步骤的唯一数据来源 - 禁止在回答时重新翻阅工具原始返回去"补充细节" - 若压缩时发现数据不足以完成目标统计,立即标记对应 Checkpoint 失败并回退 --- ### Step 5:字段完备性检查 - 用户要求的 N 日序列 → 是否完整返回 N 条? - 若要求极值/窗口统计 → 是否每项都在窗口内计算? - 若有缺失数据点 → 说明原因(按本文档末尾「关键字段缺失门禁」模板),不得用窗口外数据补充 --- ### 停止条件(本流程核心) **满足以下全部条件时,立刻进入回答,不得继续操作:** 1. 用户要求的 N 日序列数据已完整返回 2. 若有窗口统计需求,已在窗口内计算完毕 3. 未引用任何窗口外数据 **绝对禁止**:序列取完后继续补充均线、技术指标、更长周期趋势分析等未被请求的扩展内容 --- ### 回答前自检(Step 6 之前必须逐条确认) | # | 检查项 | 通过标准 | |---|--------|---------| | 1 | 资产已确认 | 来自 `presets/assets_db/{类型}.yaml` 返回 | | 2 | 时间序列为一维 | `runMultiFormulaBatchStream` 返回的 data_id 对应单资产序列 | | 3 | 行数 = N | `readData` 返回行数 ≤ 用户要求的 N(不足需注明) | | 4 | 仅窗口内统计 | 所有计算仅基于读取到的 N 行,无窗口外数据 | | 5 | 日期/年份一致 | 回答中写出的每个日期(年、月、日)与工具返回的原始日期逐字一致,不得凭记忆填写 | | 6 | 数值转写一致 | 回答中的每个数值均可直接追溯到工具返回或窗口内计算结果,无凭空出现的数字 | 任何一项未通过 → 回退到对应 Checkpoint,不得跳过直接输出。 > **T-003 防护**:年份转写是高频错误。若工具返回的日期含 `2026`,回答中绝不能写成 `2025`。输出前逐条比对日期列。 --- ## 回答前检查清单(Step 6 前必须执行) - [ ] 是否只使用了最近 N 个交易日窗口内的数据 - [ ] 是否未把截至日写成极值发生日 - [ ] 是否未展开不必要的中间算式 - [ ] 若展示振幅,是否由原始值计算后再格式化 - [ ] 是否未追加用户未要求的技术指标解释 --- ### Step 6:固定格式回答 ## 最终回答合同(强制) 若已拿到所需字段的窗口值与截至日期,**直接输出结果并停止**。最终答案的第一个字必须是资产名或数据结论,不得以任何过程性陈述开头。 ✅ **正确示范**(第一句话即资产名+数据): ``` 招商银行最近20个交易日(截至 2026-04-14): - 最高价:40.36 元 - 最低价:38.55 元 - 振幅:4.70% ``` ❌ **绝对禁止的开头**(命中任意一项 = 删除后重写): ``` 已成功获取(任何后缀均不合法,包括"已成功获取最近10个交易日……") 数据已获取 从返回的数据可以看到 根据 description 中的信息 让我来计算 ``` 默认回答模板: ``` {资产}最近{N}个交易日(截至 {date}): - 最高价:{high} - 最低价:{low} - 振幅:{amp}% ``` 若需补口径,只允许一句: > 振幅按(最高价 - 最低价)/ 最低价计算。 禁止: - 数据已成功获取 - 现在开始计算 / 路径A / 根据 workflow - 展开逐步算式(除非用户明确要求) - 重复给出同一结论 **序列类回答模板:** ``` [资产名] 最近 [N] 个交易日[字段名]: | 日期 | [字段名][单位] | |------|------| | YYYY-MM-DD | 数值 | | ... | ... | ``` **窗口统计类默认安全模板:** ``` [资产名] 最近 [N] 个交易日(截至 [YYYY-MM-DD]): - 最高价:[X] 元 - 最低价:[X] 元 - 振幅:[X]% - 区间收益:[X]% ``` 其中,价格振幅按 `({high} - {low}) / {low}` 计算。 **只有在本轮明确拿到了窗口明细序列并在窗口内计算出对应日期时,才允许扩展为:** ``` [资产名] 最近 [N] 个交易日([起始日] 至 [截至日]): - 最高价:[X] 元([发生日期]) - 最低价:[X] 元([发生日期]) - 振幅:[X]% - 区间收益:[X]% ``` **部分成功时回答模板:** ``` [资产名] 最近 [N] 个交易日(截至 [YYYY-MM-DD]): - [字段A]:[值] 以下字段未能获取:[字段B](原因简述) ``` 用户要求序列时: - 以表格形式列出 `日期 | 字段值` - 仅列出用户要求的 N 条,禁止额外延伸 用户要求窗口统计时: - 逐条列出:`最高:X 元(YYYY-MM-DD)` - 可附一句整体走势判断 - **禁止**主动补充均线、MACD、布林带等技术指标解读 **禁止出现**: - 已成功获取 / 从返回结果中提取 / 现在计算 - Step 1/2/3 / workflow / 路径A/B ### 输出纪律(硬规则) **回答长度**:序列表格 + 不超过用户请求字段数 × 2 行总结(表格行不计入)。 **禁止出现以下内容**: - 总结性段落:「说明」「小结」「走势分析」「整体来看」「综上所述」「总结一下」 - 过程性语言:「让我」「根据 workflow」「我将为你」「按照流程」「接下来我」 - 主动延伸:未被请求的均线/技术指标分析、投资建议、风险提示 - 工具/流程内部名词:workflow 名称、checkpoint 编号、路径 A/B 标签、readData/runMultiFormulaBatchStream 等工具名 - 重复表述:同一数值在表格和正文中各出现一次以上 **原则**:数据到手 → 格式化 → 回答 → 停止。不解释你做了什么,不总结你查到了什么。 --- ## 自包含执行规则(本流程所有步骤的执行依据) > 以下规则在本文档内完整定义,执行本流程时无需读取 quick-lookup.md。 ### 确认资产(Step 1 依据) **优先级规则(按序执行,满足即停):** 1. 先 grep `presets/assets_db/{类型}.yaml`,唯一命中则直接使用标准 `name` / `ticker` 2. 用户同时提供中文名称和6位代码:以中文名称为主,代码仅供辅助校验 3. 裸代码(如 `000063`)未带交易所后缀,不能单独作为可靠主键 4. 名称不存在或有歧义时,停止并向用户说明/澄清,不再调用远程资产确认 ```bash grep "资产名或代码" presets/assets_db/{类型}.yaml ``` **资产确认硬闸门(必须执行):** - 只有明确获得 `asset_match`(或 presets 中已唯一映射到标准资产)才算确认成功 - 若 `asset_match = null` 且仅有 `sector_match` / `theme_match`,视为确认失败,不得进入 Step 2 - 若用户输入为"名称 + 代码"或"名称(代码)",必须先拆分: 1. 先用名称确认 2. 再用代码做交叉校验 3. 不得把混合串直接当成唯一 intention - 首轮模板公式必须使用**已确认资产名**,不得优先使用裸代码 - 连续重试后仍无 `asset_match` → 立即停止并请求用户澄清 - 禁止用相似资产替代 - 禁止在未确认资产时进入 Step 2 --- ### 禁用写法(硬规则) 禁止使用以下写法: - `GET(...)` / `GETDATA(...)` / `FETCH(...)` / `PRICE(...)` - 将意图词直接当数据名,除非 `confirmDataMulti` 已明确返回 - 已有**原生每日回报率模板**时优先手写 `REF/DELAY/ROC` 四则运算——首选模板表中的原生涨幅公式 - 把 `scanDimensions`、`renderKLine` 当作数值查询兜底 --- ### 确认 ≠ 结果(硬规则) `confirmDataMulti` 返回的是资产名称映射或数据集ID,**不包含任何行情数值**。 拿到确认结果后,必须再调用 `runMultiFormulaBatchStream` 才能获取实际数据。 --- ### 失败恢复规则(硬规则) **`runMultiFormulaBatchStream` 返回 `PARTIAL_SUCCESS`:** - 保留已成功项,仅修失败项 - 失败项属于数据名问题 → 先 `confirmDataMulti` - 失败项属于函数写法问题且字段**在本文档模板表中** → 直接重写为标准模板,跳过 `searchFunctions` - 同一结构失败后不得用同结构再次重试 **`runMultiFormulaBatchStream` 返回 500 / Internal Server Error:** 1. 检查是否使用了非标准模板或禁用写法 2. 若是 → 重写为标准模板重试一次 3. 仍失败 → fallback:取原始序列 → readData → 回答层计算 **重试上限:** 同一意图、同类公式结构最多重试 **2 次**,超过上限必须 fallback。 **readData 调用预算:** 单题最多 **3 次**,超出后按「关键字段缺失」路径回答。 **Token 预算意识(硬规则):** - fallback 到 readData 后,必须使用 `range_data` 并限制 `start_date` / `end_date`,禁止 `last_column_full` 和旧的 `last_n_rows` - 单题总 token 软上限 50K;接近上限时优先精简读取范围,不继续扩展操作 **上下文预算保护(硬规则):** - `readData` 返回结果超过 100 行时,仅保留末尾 N 行,立即丢弃多余内容 - 禁止在对话中保留全量历史文本——即使工具返回了更多数据,回答层只引用窗口内数据 - 若窗口 N ≤ 20 但返回结果 > 100 行,说明读取模式错误,立即停止并回退到 W2 --- ### 格式化规则(硬规则) | 数据类型 | 格式 | |----------|------| | 日期 | `YYYY-MM-DD` | | 价格/点位 | 保留2位小数;股票单位"元",指数单位"点" | | 涨跌幅/回报率 | 原始小数值 ×100 后加 `%`,保留2位小数 | | 成交额 | 优先换算为"亿元",保留2位小数 | | 成交量 | 保留原始口径,注明单位来源 | --- ### 关键字段缺失时的回答门禁 若本轮仍有关键字段缺失,必须明确写出已成功获取的字段、未成功获取的字段、是否已尝试标准补救路径。禁止输出暗示已完成的标题,禁止输出主观走势判断或投资建议。 **部分成功答复模板:** ``` 本轮已成功获取: - [字段名]:[数值]([日期]) 暂未成功获取: - [字段名] 我已按标准路径尝试补救;由于该字段本轮未返回可靠结构化结果,这里不做猜测。 ``` -
regime-segmentation.md 21.2 KB
# 阈值区间 / 连续阶段统计 > **适用范围**:由价格/回撤/涨跌幅等阈值条件定义的**连续区间**(如"历次熊市""从高点回撤超过 30% 的阶段"),研究区间内资产表现、超额收益、区间统计。 > 本流程定义本场景的专用执行步骤;全局执行合同、证据分级、禁语与回答边界仍以 `workflows/global-rules.md` 为准。 > ⛔ **硬前置条件**:进入本流程前 **必须** 已读取 `workflows/global-rules.md`。 > 未读取 = 本流程所有步骤无效,不得继续。 ### 内联核心红线(来自 global-rules,无需额外读取即刻生效) 1. **evidence-only**:最终答案只输出工具结果支持的数值/日期/排名/口径说明;无工具证据不得归因 2. **去过程化**:禁止「已成功获取」「让我来」「按照流程」「Step 1/2/3」「数据已获取」等过程性话术;禁止泄露 `_working/` 路径/checkpoint/workflow 名 3. **资产口径冻结**:`assets_db` 本地资产库 返回的资产名称/体系必须与用户原口径一致;若仅为近似匹配或跨体系匹配,**禁止静默替代继续计算**——必须向用户确认。**特别注意**:"食品饮料(申万)"≠"申万消费行业指数","可选消费(万得)"≠"申万消费",同体系不同层级/对象 = 近似匹配,必须确认 4. **Working State 冻结**:关键中间决策写入 `_working/`,后续步骤从文件读回取参 5. **失败门禁**:公式/读取失败必须回退到最近已验证 checkpoint;禁止带着脏结果继续 6. **输出前自检**:输出最终答案前,必须执行 global-rules 的 ⛔ 最终答案自检清单(删过程话术 → 删无证据归因 → 校验汇总数值 → 核对 Working State) ### 最小执行链(强制,不得跳过任何一步) ``` 1. read_skill_file("workflows/global-rules.md") ← 未完成则停止 2. grep presets/assets_db/{类型}.yaml 确认基准+目标资产 ← 未完成则停止 → 口径不一致?→ 必须向用户确认,禁止静默替代 3. 冻结阈值条件 → write regime_spec.json ← 未完成则停止 4. 取回基准序列 (runMultiFormulaBatchStream + readData) ← 未完成则停止 5. 在回答层识别连续区间 → write segment_table.json ← 未完成则停止 6. read segment_table.json 读回确认 7. 计算每段区间统计(收益/超额) 8. 按 evidence-only 模板输出(禁止无证据归因) ``` > 跳过步骤 1-5 中的任何一步即为执行违规。即使最终数值恰好正确,流程不合规 = 不可接受。 ### 失败止损机制 - 同一 slot 最多改 1 次,总回退上限 2 次 - 超出后**立即进入安全失败**:输出已取到的部分数据 + 说明失败原因 - **禁止**在已失败 > 2 次后继续尝试新的工具调用/公式变体/readData 模式 - Token 消耗超过 100 万时,若尚未完成 R2(区间识别),必须进入安全失败 - 安全失败必须包含:已完成了什么、卡在哪一步、具体错误信息 > ⚠️ 429 错误不受本节失败止损约束——按 global-rules.md 第 12 条「429 前置拦截」规则处理。 --- ## 触发条件 触发词命中以下任一: - 过去N年 / 历次 / 每次 / 平均 - 回撤超过 / 从高点下跌超过 / 连续阶段 / 熊市区间 / 牛市区间 - regime / 阶段划分 / 区间内表现 / 区间超额收益 ### 与 event-study 的区分 | 本流程(regime-segmentation) | event-study | |------------------------------|-------------| | 研究**连续区间**内的表现 | 研究离散**事件日**之后的窗口表现 | | 输出:每段区间的起止日、区间收益、区间超额 | 输出:每个事件日后 N 天/月的窗口收益 | | 关键词:区间内、阶段内、期间表现、区间超额 | 关键词:随后N天/月、事件后表现 | **判断口诀**: - 用户问"历次熊市**期间内**某行业表现" → 本流程 - 用户问"历次大跌**之后**的反弹表现" → event-study(阈值触发模式) ### 排除条件(不进入本流程) - 用户给出固定起止日期、只问区间累计涨跌幅 → `quant-standard.md`(固定区间子流程) - 用户问离散事件发生后的窗口表现 → `event-study.md` --- ## 执行协议:Abstract Target + Checkpoints + Backward Recovery ### Abstract Target > 交付对象:识别满足阈值条件的所有历史连续区间,计算每段区间内目标资产的收益/超额收益,以结构化表格输出。 ### Checkpoints | CP | 名称 | 已验证状态 | Acceptance Test | |----|------|-----------|-----------------| | R0 | 场景冻结 | 基准资产、阈值条件、目标资产已确定 | 资产已通过 presets/assets_db 唯一命中;阈值条件可量化 | | R1 | 阈值序列已取 | 基准序列已取回 | readData 验证 NaN 率 < 20%;序列覆盖用户要求的历史区间 | | R2 | 区间已识别 | 所有满足阈值的连续区间已列出 | 至少识别到 1 段区间;每段有明确起止日 | | R3 | 区间统计已算 | 每段区间收益/超额收益已计算 | 每段区间至少有起止日收盘价;收益计算公式正确 | | R4 | 交付完成 | 格式化输出 + Acceptance Test 通过 | 见下方 Acceptance Test | ### Backward Recovery | 失败位置 | 回退到 | 允许修改的唯一 slot | 禁止 | |---------|--------|-------------------|------| | R1 序列取不到 | R0 | 换数据源或收窄时间范围 | 凭记忆猜测区间 | | R2 未识别到区间 | R1 | 放宽阈值后向用户确认 | 自行降低阈值 | | R3 区间收益异常 | R2 | 复查区间边界日期对齐 | 同时改阈值和资产 | | R4 输出不合理 | R3 | 重新 readData 验证 | 引入区间外数据补救 | ### Retry Budget - 同一 slot 最多改 1 次,总回退上限 2 次 - 超出后进入安全失败:输出已识别到的部分区间 + 说明失败原因 ### Acceptance Test(R4 必检) - [ ] 每段区间都有明确起止日期 - [ ] 区间收益保留 2 位小数,百分比格式 - [ ] 区间数 < 3 时,均值标注"有限样本参考" - [ ] 区间边界来自平台数据计算,非 LLM 记忆或 webSearch --- ## 执行流程 ``` Step 0 确认资产与阈值条件 → Step 1 取回基准序列 → Step 2 识别连续区间 → Step 3 计算区间统计 → Step 4 格式化输出 ``` ### Step 0:确认资产与阈值条件 1. **确认基准资产**:用 `assets_db` 本地资产库 确认阈值判定所用的基准资产(如"沪深300") 2. **确认目标资产**:用 `assets_db` 本地资产库 确认需要统计区间表现的目标资产(如"消费行业指数");若目标 = 基准,则只需确认一次 3. **⛔ 口径确认门禁**:每个资产的确认结果必须满足以下之一,否则**禁止进入 Step 1**: - **严格命中**:返回名称与用户指定完全一致 → 可继续 - **近似命中**(同体系不同对象、跨体系近似、上下级行业)→ **必须向用户确认**,未确认前禁止 write regime_spec / runMultiFormulaBatchStream / readData - **错配/歧义** → 直接告知用户平台无精确匹配,请用户指定替代 ## 关键口径确认闸门(硬规则) 对区间统计 / 超额收益 / 连续阶段识别题: - 若任一核心研究对象(指数、行业指数、板块、基准)未获得精确确认: - 不得擅自改用同义项 - 不得擅自改用子行业 - 不得跨体系替代(如申万 → 万得) - 不得先算一个"近似答案" - 必须先请求用户确认候选口径,确认前不得进入 Step 1 ## 阈值区间最小证据链(硬规则) 最终回答前必须同时具备以下全部证据环节: 1. 基准资产价格序列(来自 readData) 2. 阈值序列/回撤序列(来自公式计算) 3. 由该序列推导出的连续区间表(来自回答层识别,写入 segment_table.json) 4. 每个区间的目标资产与基准资产收益(来自对应区间首末值计算) 缺少任何一环: - 不得手写历史区间补齐 - 不得手写价格反推收益 - 不得输出精确结论 - 必须进入安全失败模板,说明卡在哪一环 ## ⛔ 禁止人工编造区间/价格/日期(硬规则) 当公式执行、readData 或回答层区间识别**未能产出可用于阈值判定的完整序列或区间结果**时,必须**立即停止后续分析**,进入安全失败。 **绝对禁止**: 1. 用历史常识、经验值、公开共识、维基百科知识**手写**区间起止日(如"2018年1月-2019年1月为熊市") 2. 手写价格高点/低点/回撤幅度(如"高点约5178点,低点约2638点") 3. 用 webSearch 搜索到的文字描述直接替代数据计算结果作为区间边界 4. 把 LLM 对历史走势的"常识"包装成工具计算结论 5. **以"数据被截断"或"脚本无输出"为由,改用历史记忆填充区间**(见下方 T-025 iter-013 反例) **判断标准**:若最终输出的区间起止日期**无法追溯到本次 readData 返回的时间序列中的精确数据点**,则该区间属于"人工编造",违反本规则。`smart_sample`(50点采样)的日期只能用作覆盖率验证,**不能作为区间参考起止点**。 **安全失败模板**: ``` 工具未能完整计算出满足条件「{用户阈值条件}」的历史区间。 已完成:{已成功取到的数据,如"沪深300收盘价序列,覆盖2005-至今"} 卡在:{具体环节,如"回撤序列计算后,区间识别步骤未能可靠标记连续区间的起止点"} 如需,我可以: - 调整阈值条件后重试 - 仅提供原始序列数据供您自行分析 ``` **实测反例(T-025,iter-012)**:模型声称识别出"2007-10至2008-11""2015-06至2016-01"等区间并给出精确收益,但这些区间和数值均来自 LLM 对 A 股历史的记忆,而非本次工具计算结果——这属于严重违规。正确做法是:取到回撤序列后在回答层精确识别,若识别失败则进入安全失败。 **⚠️ 新增反例(T-025,iter-013)**: ``` // 触发情境:readData 返回的回撤序列在上下文中被截断,run_skill_script 输出为空 // 错误行为:在 Python 脚本注释中写下意图,然后最终用记忆日期输出答案 run_skill_script(""" # 由于数据被截断,我将基于已知的A股历史大熊市来识别区间 ← 违规! # 2008年金融危机期间... # 2015年股灾期间... """) // → 脚本输出空 → 模型直接在最终答案中手写 "2008-01-21 → 2008-11-04" ``` **为何错误**:数据截断/脚本无输出 = 计算失败,应安全失败,**不是改用记忆的许可证**。 **正确做法**:遇到截断时,先用 `write_skill_file` 把 readData 结果明确保存为 JSON 文件,再让 run_skill_script 读取该文件处理。若仍失败 → 安全失败。 **当行业/板块/指数名称歧义时**,优先读取 `presets/sectors.yaml` 组织同体系候选;禁止使用 `searchFunctions` 解决资产命名问题。 ## 口径确认失败时的固定模板(强制) 当关键资产/行业/指数未获得精确匹配时,最终回复只能包含: - 已确认资产 - 未精确确认的原始名称 - 候选项列表(仅来自 presets/assets_db 或 presets/sectors.yaml) - 请求用户回复序号 模板: ``` 平台可确认到:`[已确认资产]` 但 `"[用户原始名称]"` 当前未拿到精确匹配。可选口径为: 1. `[候选1]` 2. `[候选2]` 3. `[候选3]` 请回复序号;确认后我按该口径继续统计。 ``` 禁止出现: - 建议选哪个 - 覆盖更广 / 更核心 / 更代表 - 综合指数 / 一级行业 / 广义消费 等解释 - workflow / Step / 让我来 4. **冻结阈值条件**:将用户自然语言转化为可计算的数值条件 - 示例:"熊市(从高点回撤超过 30%)" → 回撤 ≥ 30% - 示例:"估值低于历史中位数" → PE(TTM) < 历史中位数 ``` write_skill_file({ "path": "output/_working/{task_id}/regime_spec.json", "content": "{\"base_asset\": \"{基准资产中文名} ({ticker})\", \"target_asset\": \"{目标资产中文名} ({ticker})\", \"threshold\": \"{可量化阈值条件}\", \"history_range\": \"{回溯区间,如 20050101-至今}\"}" }) ``` ### Step 1:取回基准序列 1. **确认数据项**:用 `confirmDataMulti` 确认所需的度量指标(如收盘价;用户要 PE(TTM) 时按全局口径规则优先查询 `市盈率 TTM`,不得把 `PE(TTM)` 原样当查询词) 2. **取回序列**:用 `runMultiFormulaBatchStream` 计算基准序列 - begin_date 需覆盖足够历史区间(通常 ≥ 15 年) 3. **验证**:用 `readData(mode="smart_sample")` 检查 NaN 率和覆盖率(仅验证用,不得用 50 个采样点作为区间识别依据) 4. **读取完整序列**:用 `readData(mode="range_data", start_date={history_start}, end_date={history_end})` 读取完整连续时间序列 5. **保存为文件(必须)**:用 `write_skill_file` 将第 4 步 readData 返回的数据保存为 JSON 文件到 `output/_working/{task_id}/full_series.json`,然后通过 `run_skill_script` 读取该文件来执行区间识别,**禁止在脚本中用 print 说明"已知历史区间"替代实际计算** #### 公式链模板(回撤型区间) ```python # 1) 取指数收盘价(1维序列)— 资产名不加引号 HS300=收盘价(沪深300) CONSUMER=收盘价(食品饮料(申万)) # 2) 计算历史累计高点 + 回撤百分比 HS300_MAX=累计最大("HS300") HS300_DD=("HS300"/"HS300_MAX"-1)*100 # 3) 计算日收益率 HS300_RET=涨跌幅("HS300") CONSUMER_RET=涨跌幅("CONSUMER") EXCESS="CONSUMER_RET"-"HS300_RET" ``` > **注意**:`收盘价(资产名)` 的资产名不加引号;后续引用已定义变量时加双引号,如 `"HS300"`。 ### Step 2:识别连续区间 **区间表先行(硬规则)** - 在计算任何区间收益/超额收益前,必须先生成结构化 `segment_table` - `segment_table` 至少包含: - `segment_id` - `start_date` - `end_date` - `trigger_rule` - `source_series` - 禁止: - 凭经验手写历史阶段 - 用"从数据观察到"代替区间识别 - 在脚本中硬编码日期并包装为计算结果 - 若区间表无法可靠生成,则停止后续收益统计,转为说明卡点或请求澄清 在取得完整序列后,**在回答层**(非公式层)根据阈值条件识别所有满足条件的连续区间。 #### 定义锁定卡(Definition Lock,硬规则) 将用户的阈值定义冻结为可计算规则,**后续步骤不得修改**: | 用户表述 | 默认量化释义 | 禁止替换为 | |---------|-------------|-----------| | "从高点累计下跌超过X%" / "历史高点回撤超X%" | 相对**历史累计高点**(cumulative max)的回撤 | 滚动N日高点(rolling max N) | | "估值低于历史中位数" | 低于全序列PE(TTM)中位数 | 近N年中位数(除非用户指定) | | "连续N个月下跌" | 连续N个月末收盘价环比为负 | 月内任意日跌幅 | - 若用户明确说"滚动250日高点",则用滚动250日;否则**默认 cumulative max** - 阈值定义一旦冻结,写入 `_working/` 文件(regime_spec.json),后续公式/读取/判定必须严格使用冻结定义 - 定义变更需回退到 R0 并向用户确认 #### 资产口径守恒(Asset Taxonomy Preservation,硬规则) 当用户明确指定了指数体系(申万/中证/万得/国证)时: - **同体系优先**:必须在同体系内寻找最佳匹配 - **禁止静默跨体系替代**:不得将"申万消费行业指数"偷换为"中证全指消费" - **若使用代理**:必须在输出中显式声明: - `proxy_mode: true` - 原口径:用户指定的指数名 - 代理口径:实际使用的指数名 - 代理原因:如"平台未收录申万消费行业指数,使用XX作为近似代理" - 若 presets/assets_db 无法命中同体系指数,向用户确认是否接受替代,不得自行决定 #### 常见阈值类型的识别方法 **A. 回撤型(如"从高点下跌超过 X%")**: 1. 从序列计算**历史累计高点**(cumulative max,即截至每日的历史最高收盘价) 2. 计算每日回撤 = (当前值 - 历史累计高点) / 历史累计高点 3. 标记回撤首次触达阈值的日期为区间起点 4. 标记回撤恢复到阈值以内(或创新高)的日期为区间终点 5. 合并重叠或间隔极短(< 5 个交易日)的区间 > 注意:此处使用 cumulative max 而非 rolling max(N)。只有当用户明确说"滚动N日高点"时才使用 rolling max。 **B. 估值型(如"PE 低于 X")**: 1. 从序列找出所有 PE < X 的日期 2. 将连续满足条件的日期合并为区间 3. 区间起点 = 首次满足条件的日期,终点 = 最后连续满足条件的日期 **C. 自定义型**: - 按用户阈值条件类比上述方法,先标记→再合并 **硬规则**: - 区间边界**必须**来自平台时序数据的精确计算 - **禁止**用 webSearch 或 LLM 记忆直接给出区间(如"A 股历史上共有 N 次熊市") - webSearch 仅允许用于补充区间的背景描述(如"这段时间对应全球金融危机") - 若用户提到的概念(如"熊市")没有公认量化标准,必须向用户确认阈值定义后再计算 #### 写入 SegmentTable(强制执行) 区间识别完成后,**必须**通过 `write_skill_file` 持久化为 `segment_table.json`: ```json write_skill_file({ "path": "output/_working/{task_id}/segment_table.json", "content": "{\"regime_spec\": {\"anchor\": \"沪深300\", \"threshold\": \"历史累计高点回撤≥30%\", \"definition\": \"cumulative max drawdown\"}, \"regimes_found\": 4, \"segments\": [{\"id\": 1, \"start_date\": \"20071017\", \"end_date\": \"20081104\", \"trading_days\": 256}, {\"id\": 2, \"start_date\": \"20150615\", \"end_date\": \"20160128\", \"trading_days\": 142}]}" }) ``` **写入后**,必须 `read_skill_file("output/_working/{task_id}/segment_table.json")` 读回,从中获取区间列表进入 Step 3。 ### Step 3:计算区间统计 对每段已识别区间,计算目标资产的区间表现: 1. **取目标资产序列**:若目标 ≠ 基准,用 `runMultiFormulaBatchStream` + `readData` 取目标资产的收盘价序列 2. **计算区间收益**:对每段区间 `[start, end]`: - 区间收益 = 末日收盘价 / 首日收盘价 - 1 - 若需超额收益:超额 = 目标区间收益 - 基准区间收益 3. **补充统计**: - 区间天数(交易日) - 区间最大回撤(可选) - 区间年化收益(可选,仅区间 > 126 个交易日时计算) **硬规则**: - 首日/末日不是交易日 → 使用区间内首个/末个有效交易日,并在输出中注明 - 不得对结果做二次平滑或修饰 - 若某段区间目标资产数据缺失 → 标注"数据不足",不填充 计算结果更新到 `segment_table.json`(追加 `base_return`、`target_return`、`excess_return` 字段),再 read_skill_file 读回用于格式化输出。 ### ⛔ 完成闭环门禁(硬规则) **已取到完整序列但未完成区间识别与统计 = R3 未通过,不得终止任务。** 即使中途遇到 API 错误、Token 接近上限或部分步骤失败,也必须执行以下之一: 1. **正常完成**:识别区间 → 计算每段收益 → 输出汇总表 → 进入 R4 2. **安全失败**(仅当正常完成不可行时): - 输出已取到的部分数据(如"已取到 XX 的收盘价序列,覆盖 YYYY-YYYY") - 明确说明"后续步骤因 {具体原因} 未完成" - 列出已识别到的区间(如果有) - **禁止**无回答地终止 **禁止出现**:拿到序列数据后不做计算就直接结束、无任何输出地中断。 ### Step 4:格式化输出 #### 输出格式 | 阶段 | 起始日 | 结束日 | 交易日数 | 基准收益 | 目标收益 | 超额收益 | |------|--------|--------|---------|---------|---------|---------| | 第 1 段 | 2007-10-17 | 2008-11-04 | 256 | -65.2% | -52.3% | +12.9% | | 第 2 段 | 2015-06-15 | 2016-01-28 | 142 | -43.1% | -38.7% | +4.4% | | **均值** | — | — | **199** | **-54.2%** | **-45.5%** | **+8.7%** | #### 前置校验 - 区间数与 R2 识别数一致 - 每段区间的首末日期在序列范围内 - 收益值数量级合理(无符号错误、无 100 倍偏移) #### 输出规则 - 区间 < 3 段时,均值行标注"有限样本参考" - 若某段区间为当前进行中(未结束),标注"进行中,收益截至最新交易日" - 若用户使用了代理指标(如用沪深300代理"A股大盘"),在表前说明代理关系 #### 方法限制说明(必须保留) 在输出末尾,若存在以下情况必须注明: - 阈值定义由模型建议而非用户指定:注明"阈值定义为 {具体条件},可根据您的需求调整" - 区间数 ≤ 2:注明"样本极少,均值仅供参考" - 基准与目标频率不同:注明频率对齐方式 ### 输出纪律(硬规则) **禁止出现以下内容**: - 过程性语言:「让我」「根据 workflow」「按照流程」「Step 1 完成」 - 总结性段落:「综上所述」「总结一下」——数据表格本身就是结论 - 主动延伸:未被请求的投资建议、风险提示、归因分析 - 格式回显:不要在回答中暴露 checkpoint 名、workflow 名、内部校验表 - **用 webSearch 结果定义区间边界**:webSearch 只能补充背景描述 -
render-kline.md 16.5 KB
# K 线图渲染流程 > **适用范围**:用户明确要求 K 线、蜡烛图、OHLC、开高低收或带成交量的 K 线 artifact。普通“股价、成交量、PE 放在一张图”不是 K 线,必须改走 `visual-page-fast-path.md`。 > **本流程不提供精确数值**——K 线图是 artifact 交付,不是查数工具;用户只说“看走势/最近走势”且未明确要图片时,应改走 `fast-window.md` 数值走势。 > > **并行活页硬闸门**:明确 K 线/带成交量图属于 `create` 强触发。PNG 验证完成后、展示第一条回答前,必须实际执行 `live_page_routing.py route`;不得只读规则、不得自行口头判定、不得因 PNG 已生成而跳过。`create` 时必须把已经生成的 PNG 作为结构化 artifact,按下方固定 JSON 执行一次 `prepare-validated-page`;该命令会原子生成 Capsule、Handoff 和 QBV Job,成功后禁止再手工执行 `qbv_computation_capsule.py build`、`handoff` 或 `prepare`。随后调用宿主内部子 Agent 委派工具(优先 `spawn_agent`),只等待即时成功回执,不在首答前等待页面完成;不得用 `create_thread/fork_thread` 代替。若宿主没有内部委派工具,必须对返回的 `qbv_job_id` 执行 `mark-delegation-unavailable`,不得遗留 queued Job。用户明确说“只要 PNG / 不要网页 / 不要活页”时仍要执行 route 并得到 `none`;弱“看看走势”走数值窗口,不进入本流程。 --- ## 执行协议:Abstract Target + Checkpoints + Backward Recovery ### Abstract Target > 交付对象:一张 K 线图(PNG artifact),包含用户指定资产和时间范围的 OHLCV 可视化,与取数路径无关。 ### Checkpoints | CP | 名称 | 已验证状态 | Acceptance Test | |----|------|-----------|-----------------| | **K-1** | **newSession 已建立** | 本轮已显式调用 `newSession` | 准备调用 `renderKLine` / `fast_query` / 任何平台工具前,本轮必须已有 `newSession` 调用记录。**未通过 K-1 = 禁止调用任何平台工具**(MISSING_NEW_SESSION,HIGH 级) | | K0 | 资产已确认 | ticker 代码已确定 | `presets/assets_db/{类型}.yaml` 返回明确资产 | | K1 | 时间窗口已解析 | begin_date 已计算 | "最近 N 月"转为 YYYYMMDD;未指定时默认近 6 个月 | | **K1.5** | **日期锚点已获取** | **当前日期来自可靠来源** | 当前日期锚点来自**系统上下文 或 工具返回**(非模型猜测);begin_date 基于该锚点计算得出。**未通过 K1.5 = 禁止调用 renderKLine** | | K2 | K 线图已渲染 | renderKLine 成功 | 返回 base64 图片,data_points > 0 | | **K2.5** | **活页路由已执行** | 已运行 `live_page_routing.py route` 并保留 JSON | `create` 已完成非阻塞 handoff/prepare/spawn accepted,或 `none/suggest` 有明确返回;命令失败已记录为软降级 | | K3 | 交付完成 | 图片已展示 + 简短说明 | 无多余文字,无目测数值;不等待 QBV 完成 | ### 首选路径 0. `newSession`(若本轮尚未调用;不可省略,K-1 未通过则禁止往下做任何平台工具调用) 1. `presets/assets_db/{类型}.yaml`→ 获得 ticker 2. 解析用户时间描述 → 计算 `begin_date` 3. `renderKLine(ticker, begin_date, ...)` → 获得图片并验证;用户明确要求 PNG 时必须传 `output_format="png"`,直接使用返回的 `artifact_file`,禁止再调用 Bash/Python 转换格式 4. 读取 `live-page-routing.md`,实际执行 `python scripts/live_page_routing.py route --user-query "用户本轮原话"` 5. `create|existing_page`:把 `renderKLine` 已生成的 PNG 写入下方 `prepare-page.json`,执行一次 `prepare-validated-page`,然后调用宿主内部子 Agent 委派工具(优先 `spawn_agent`),只等待即时成功回执;`none|suggest`:直接继续;委派不可用或失败:把 Job 更新为 `DELEGATION_UNAVAILABLE` 后软降级 6. 展示图片 + 一句话说明;不等待 QBV 完成 ### K 线活页固定准备合同 `renderKLine` 成功且 route 返回 `create|existing_page` 后,用 `write_skill_file` 写入合法 JSON。`task_id`、`turn_id`、`user_query` 和 `source_skill_version` 可由当前 QBS Session 自动补齐,不要手工构造 `qbs_computation_capsule_v1` 或 `qbs_qbv_handoff_v1`: ```json { "page_intent": { "question_to_answer": "打开查看贵州茅台最近半年的K线图和成交量", "recommended_page_type": "kline_chart", "primary_visualization": "candlestick_with_volume", "required_roles": ["kline_chart"] }, "asset_resolution": { "query": "贵州茅台", "canonical_name": "贵州茅台", "canonical_id": "SH600519", "market": "CN-A" }, "validated_roles": [ { "role": "kline_chart", "kind": "renderKLine", "contract": { "kind": "renderKLine", "payload": { "ticker": "SH600519", "begin_date": 20260305, "show_volume": true, "output_format": "png" } }, "artifact_file": "D:/absolute/path/to/kline.png", "row_count": 185, "field_mapping": { "asset": "贵州茅台", "ticker": "SH600519", "chart": "candlestick", "volume": "shown" } } ] } ``` 其中 `artifact_file`、`row_count`、ticker、日期和 `show_volume` 必须复制 `renderKLine` 的真实返回/调用参数,不得照抄示例值。然后只执行: ```powershell python scripts/live_page_routing.py prepare-validated-page @output/_working/<task_id>/prepare-page.json ``` 成功返回 `qbv_job_id + handoff_file + job_file + should_spawn` 后,不得再次执行页面准备命令。没有内部委派工具时立即执行: ```powershell python scripts/live_page_routing.py mark-delegation-unavailable --qbv-job-id "<qbv_job_id>" ``` ### Backward Recovery | 失败位置 | 回退到 | 允许修改的唯一 slot | 禁止 | |---------|--------|-------------------|------| | K0 资产确认失败 | — | 重试 1 次(换别名) | 用相似资产替代 | | K1.5 日期锚点获取失败 | K1 | 重试 1 次(换轻量工具获取当前日期);重试仍失败 → **必须 safe-fail**,回复"当前日期暂时无法从工具获取,无法确认起始日期,请稍后重试" | 模型自行猜测/估算当前日期(**即使看起来合理也绝对禁止,HTTP 错误不是猜测的许可证**) | | K2 renderKLine 失败 | K1 | 调整 begin_date 或去掉 indicators | 改用 runMultiFormulaBatchStream 自行画图 | | K2 返回空图 | K1 | 缩短时间范围 | 切换到取数路径 | ### Retry Budget - renderKLine 失败最多重试 1 次 - 仍失败 → 告知用户"K 线图渲染暂时不可用",不得 fallback 到取数路径 > ⚠️ 429 错误不受本节 Retry Budget 约束——按 global-rules.md 第 12 条「429 前置拦截」规则处理。 ### ⛔ K1.5 硬规则:HTTP 错误不是猜测的许可证 > 以下反例来自实际失败 trace: **❌ 错误行为(T-011 反例)**: ``` runMultiFormulaBatchStream → HTTP 500 runMultiFormulaBatchStream → HTTP 500 // 错误推理:"服务器有问题,让我用合理的begin_date来覆盖最近半年。根据当前是 2026 年 4 月..." renderKLine(begin_date=20251001) ← 违规:日期来自模型记忆,不是工具返回 ``` **✅ 正确行为**: ``` runMultiFormulaBatchStream → HTTP 500 runMultiFormulaBatchStream(换公式重试)→ HTTP 500 // 重试全部失败,K1.5 未通过 → 禁止调用 renderKLine 回复用户:"当前日期暂时无法从工具确认,无法生成 K 线图,请稍后重试" ``` **核心原则**:`renderKLine` 的 `begin_date` 必须来自**系统上下文提供的日期 或 工具返回的日期**,而不能来自模型自身的时间感知("我知道现在大约是×年×月")。 --- ## 执行流程 ### Step 1:确认资产 1. grep `presets/assets_db/{类型}.yaml`,唯一命中则获取 ticker 代码 2. 未命中或多命中 → 停止并向用户说明/澄清 3. 若唯一命中 `future.yaml`: - 用户只是“看走势/最近走势”,且未明确要求图片/K线 → 停止本流程,改走 `fast-window.md` 数值走势 - 用户明确要求期货 K 线图/画图 → 受控说明“当前 K 线渲染工具只支持 A 股”,并建议改查最近 N 日收盘价/涨跌幅走势;禁止调用 `renderKLine` 4. ticker 格式:`SH` / `SZ` 前缀 + 6 位代码(如 `SH600519`、`SZ000858`) > ⚠️ `renderKLine` 的 `ticker` 参数**只接受 A 股 SH/SZ 代码**,不接受中文名、港美股代码或期货代码。 **ticker 格式转换(必须执行):** - `assets_db` 本地资产库 返回的 code 通常是后缀格式(如 `600519.SH`) - `renderKLine` 要求前缀格式(如 `SH600519`) - 转换规则:`XXXXXX.SH` → `SHXXXXXX`,`XXXXXX.SZ` → `SZXXXXXX` - **禁止直接把 `assets_db` 本地资产库 返回的 code 传给 renderKLine** ### Step 2:解析时间窗口(相对时间必须相对化) **当前日期获取(硬规则)**:禁止模型凭自身知识估算当前日期。必须按以下优先级顺序获取,**首个可用即停止,无需继续往下执行**: 1. **系统上下文已有当前日期**(如 system prompt 中的"当前日期是 YYYY-MM-DD")→ 直接用于换算 begin_date,**无需任何额外工具调用** 2. 本轮已有工具返回的日期(如之前 `runMultiFormulaBatchStream` / `readData` 返回的最新交易日) 3. 以上均无 → 仅用最简赋值公式调用一次 `runMultiFormulaBatchStream`(如 `close="全市场每日收盘价"`),从返回中读取最新日期;**禁止在此步骤使用任何 `presets/functions.yaml` 中不存在的函数名**(如 `LASTDAY`、`MA` 均不在 functions.yaml,调用会返回 HTTP 500) 将用户时间描述转为 `begin_date`(YYYYMMDD 整数)时,必须以**工具确认的当前日期**为锚点: | 用户说法 | begin_date 计算 | |---------|----------------| | 最近半年 | 当前日期回推约 6 个月 | | 最近 3 个月 | 当前日期回推约 3 个月 | | 最近 1 年 | 当前日期回推约 12 个月 | | 今年以来 | 当年 1 月 1 日 | | 最近 N 天 | 当前日期回推 N 个自然日 | | 最近 N 个交易日 | 若工具仅接受 begin_date,则取足够覆盖 N 个交易日的近端起点 | | 未指定 | 默认当前日期回推 6 个月 | 硬规则: - 不得把"最近半年/最近一年"等相对表达悄悄改成任意固定历史日期 - 常见相对时间默认直接执行,不要额外澄清 - 禁止把用户的相对时间表达(如"最近半年")转换为具体起止日期写入回答(如"2025年10月至2026年3月");回答中应保留用户原始表达 **相对时间解析红线(硬规则)** - 当用户使用"最近半年 / 最近3个月 / 最近1年 / 近N日"等相对时间表达时: - 必须先获得可验证的当前日期锚点 - 若锚点获取失败,禁止模型自行猜测、手填或近似替代 `begin_date` - 禁止使用"合理起点""大致覆盖""先画一个差不多的半年图"等做法 - 若仍无法精确解析,只能透明说明无法锁定区间边界,不得继续渲染 **相对时间硬门槛(Step 2 checkpoint)**: 若用户给的是相对时间(最近半年/近3个月/最近1年/年初至今): 1. 必须先获得当前日期锚点(**优先检查系统上下文:若 system prompt 已提供当前日期,此即有效锚点,直接跳到步骤 2**) 2. 再换算 begin_date 3. 未获得锚点时,禁止调用 renderKLine 4. 禁止使用"大约""按当前日期应该是"之类估算替代 若无法获得当前日期锚点: 1. 允许重试一次推荐路径(如轻量 runMultiFormulaBatchStream 获取最新交易日) 2. 若仍失败,只能说明"当前无法精确锁定相对时间窗口,暂不返回可能误导的图" 3. 禁止自行猜测 begin_date,禁止使用"大约/约等于/通常半年从某月开始"这类口径 ### Step 3:调用 renderKLine(必须使用原生工具) 直接调用原生 `renderKLine` 工具,参数如下: ```json { "ticker": "SH600519", "begin_date": 20240101, "show_volume": true, "title": "贵州茅台 近6个月K线" } ``` **参数选择:** - `show_volume`:用户提到"成交量"或"带量"时,**必须显式设置 `show_volume: true`**,不得依赖默认值——工具返回的参数记录中必须可见该字段 - `indicators`:默认不强制附加;仅当用户明确要求均线/技术指标时再添加(如 `["MA5", "MA20"]`、`["MACD"]`);用户明确说"不要均线"时才省略 - `title`:设为资产中文名 + 时间范围(如 `"贵州茅台 近6个月K线"`) - 完整参数说明 → `tools/render_kline.md` **禁止通过脚本包装调用 renderKLine**(如 `GZQ_PARAMS='...' python scripts/call.py renderKLine`);直接调用原生工具。 ### Step 4:交付(成功后立即收束) **渲染结果一致性校验(K2 通过后、交付前必须执行)**: - 若 `renderKLine` 返回了 `data_points` 字段,检查其是否 > 0 - 若 `data_points = 0` 或返回空图,回退到 K1 调整 begin_date 后重试 1 次 - 禁止在最终回答中输出内部状态文本(如 checkpoint 信息、`_working/` 路径等) **K 线图必须以可见图像 artifact 形式交付,这是本流程唯一的完成条件。** - 若运行环境支持图片展示:在最终回答中嵌入图片(`` 或等价方式) - 若运行环境不支持图片展示:提供图片文件路径/链接,使用户可访问 - **禁止**:仅口头宣称"图已生成"而不提供任何可见图像或可访问路径——这不算完成交付 最终回答只允许: - 展示图片 artifact(必须) - 一句话说明(含资产名、时间范围、是否含成交量/指标) - 提供最多 3 个后续可选项 ## 最终回答白名单(K线图场景) 最终答案仅允许包含: - 图表 artifact - 资产名称 - 用户请求的时间范围原话或等价简述 - 是否包含成交量/技术指标(仅限渲染参数直接支持) K线图渲染默认走最小闭环:确认资产 → 解析时间 → renderKLine → 交付。 除非本 workflow 明确要求,否则不要调用 `searchSimilarCases`。 禁止包含: - 未经工具明确返回支持的具体起止日期 - "共 N 个数据点" - 区间涨跌幅 / 高低点 / 成交额统计 - 任何归因描述 - 原始 base64 文本 - 若用户只要求"画图",默认不补充任何数据摘要;若需要说明,只允许一句"已生成包含成交量/指标的K线图" 推荐模板: >  > > 已为您生成【资产名】【时间范围】K线图,包含成交量【及MA5/MA20】。 > 如需,我还可以: > - 调整时间范围 > - 增加技术指标 > - 继续查询具体行情数据 --- ## 参数传递规则 - K 线图流程简短,参数通过 presets/assets_db → renderKLine 工具链自然传递,不需要写入 Working State 文件 - **K0 特别注意**:`asset` 必须是已转换为前缀格式的 ticker(如 `SH600519`),后续 renderKLine 调用必须使用此值,禁止从 presets/assets_db 命中结果重新取值 --- ## 硬规则 1. **禁止目测报数**:不得根据 K 线图目测生成精确价格、涨跌幅、成交量等数值 2. **禁止原始工具结果回显(硬规则)**: - `renderKLine` 成功后,最终交付物是图片 artifact(可通过 base64 内联嵌入或 sandbox 路径展示),而不是工具返回的原始文本 - 严禁在最终回答中**裸输出**: - 未包裹在图片标签中的大段 base64 字符串 - 工具原始 JSON - 大段参数回显 - **禁止用"图已生成"代替实际图片交付**:若无法展示图片也无法提供可访问路径,必须明确告知用户"K 线图渲染暂时无法在当前环境交付",不得假装已完成 3. **禁止捏造元数据**:图片说明中不得捏造未从 API 返回的信息(如精确最高价) 4. **禁止替代取数**:用户问精确数值时,不得用 K 线图代替结构化查数流程 5. **时间窗口必须相对**:基于当前日期计算 begin_date,不硬编码固定日期 6. **禁止未经工具确认的具体起止日期**:回答中不得出现未从工具返回的精确日期范围(如"2025-10-08至2026-03-31"),除非工具明确返回了这些日期 7. **交付简洁但完整**:图片 + 一句话说明(含图表内容概述)+ 后续可选操作(调时间/叠指标/查行情),之后立即停止。禁止添加: - "从图中可以看到…"等走势描述 - 时间范围、数据点数量等技术细节 - 技术分析、趋势判断、投资建议 - 任何超过一句话的额外文字 -
run-formula-chain.md 6.7 KB
# Leaf Workflow — 直接运行用户给定的公式链文件 > **场景**:用户提供一个本地文件(`.md` / `.txt` / `.json`),里面已写好编号公式链,要求"直接运行 / 跑一遍 / 执行"文件里的全部公式,并按最末公式的语义给出最终结果。 > > **核心约束**:本场景最容易触发"自写 driver 脚本"反模式(heredoc + subprocess 循环驱动多批)。SKILL.md 硬规则 #2 已明文禁止。本 workflow 给出**唯一合规路径**。 --- ## ⛔ 反模式(违规即失败) 下列写法**全部禁止**,无论公式数量多少、依赖多复杂: ```bash # ❌ heredoc 包 subprocess 多批循环 python - <<'PY' for start in range(0, len(formulas), 20): subprocess.run(['python','scripts/call.py','runMultiFormulaBatchStream', ...]) PY # ❌ python -c 包装 python -c "import subprocess; [subprocess.run([...]) for b in batches]" # ❌ node -e + execSync node -e "require('child_process').execSync('python scripts/call.py runMultiFormulaBatchStream ...')" ``` **根本原因**:每多套一层解释器/subprocess,就多一处 session 漂移、stdout 阻塞、TMP 路径错乱、配额误算的可能。call.py 的兜底地位**只允许一层**:`shell → call.py`。 --- ## ✅ 合规执行路径 ### Step 1:读公式链文件 用 `read_file` 读取用户给定路径的全部内容(一次读完,禁止 limit)。 ### Step 2:LLM 在推理中解析公式数组 不写解析脚本。提取每行编号公式(典型形式 `^\s*\d+\.\s*(.+)$`),形成 `formulas: string[]`。 > 文件可能不止"编号 + 公式"一种格式(也可能是 yaml/json 列表、或带注释的 markdown 块)。识别格式由 LLM 在推理中完成,不要为此写脚本。 ### Step 3:LLM 在推理中生成 `force_reusable_array` 按 SKILL.md 硬规则 #2 + `global-rules.md` §13 三问法判定每条: 1. 左侧变量是否会被 `readData` 或最终业务步骤读取? 2. 是否会被**后续 batch / 后续 `runMultiFormulaBatchStream` 调用 / 用户后续追问**引用? 3. 此刻是否能确定它"以后再不会被读"? 任一答"会/不能确定" → **写入 `force_reusable_array`**;只有完全确定"仅本批后续公式用、再无人引用" → **不要写入数组**。 **末批内仅 readData 读取的最终输出变量必须写入数组**(通常是末条公式,如 `*_Top10`、`*_Result`、`*_Final`);末批内的 `*_Score`、`*_Ratio` 等中间量仍按三问法判断,不因在末批而自动写入。**禁止全列**(全列 = 规则退化,与未传等价)。 ### Step 4:切批 + 落盘 batch JSON 按服务端单批硬上限(当前 20 条)切分。用 `create_file` 把每批落盘到 `output/tmp_batches/batch_K.json`: ```json { "formulas": ["...", "..."], "force_reusable_array": ["需保活的变量名", "..."], "use_minute_data": "<仅盘中/当前分钟为 true;历史日频为 false>", "execution_profile": "research_24h", "user_query": "用户原始问题" } ``` - `use_minute_data`:整条公式链先按用户时间意图判定;仅盘中/当前分钟链为 `true`,历史区间收益、回测、净值、回撤、波动率或历史日线为 `false` 或省略;拆批后每批保持一致 - `execution_profile` + `user_query`:按 global-rules 规则 15 判定 `is_research_job`,若为 true 则**每批**都带这两个字段 - 跨批联动:组装第 K 批的 `force_reusable_array` 前,先扫描第 `K+1..N` 批的右侧引用,凡被后续引用的本批左侧变量必须写入数组 ### Step 5:逐批发起独立 shell 调用 **每批一条独立的 `run_in_terminal` 调用**,前一批返回后再发起下一批: ```bash cd <SKILL_ROOT> GZQ_PARAMS="$(cat output/tmp_batches/batch_K.json)" python scripts/call.py runMultiFormulaBatchStream ``` **禁止**: - 用 `&&` 把多批拼到一条命令里(含 `cd X && python ...` 形式) - 写 python/node 脚本一次跑完所有批 - 用 `for` 循环(shell 或 inline 脚本)驱动多批 - 上一批还没返回就发起下一批 > ⚠️ `cd <SKILL_ROOT>` 与 `GZQ_PARAMS=... python ...` 必须分开为**两条独立命令**,或只在已在 SKILL_ROOT 目录时省略 cd。绝不允许用 `&&` 链接。 **deferred 处理**:若返回 `status:"deferred"`,必须立即用 `resumeJob` 续传到 `done` 后才可发起下一批(global-rules 规则 16)。 ### Step 6:取最终输出 末批返回后,从返回 JSON 的 `data.data[]` 里找到**用户最终关心变量**(通常是末条公式的左侧)的 `index_info._id`,再发起一次 `readData`: ```bash GZQ_PARAMS='{"ids":["<_id>"],"mode":"last_column_full"}' python scripts/call.py readData ``` 按用户问法解析返回(取前 N、阈值筛选、等等),输出数据结论。 --- ## 失败处理 | 现象 | 处理 | |---|---| | 某批返回 `code=-1` | 读返回的 `errors` 数组,定位失败公式;不重试整批,先报告错误位置 | | `SKILL_VERSION_MISMATCH` | 按 SKILL.md 硬规则 #8 自愈(newSession + 重读文档 + 重跑) | | 配额超限 | 按 SKILL.md 全局 429 处理表 | | stdout 截断 | 回读 `/tmp/gzq_out.txt`(仅 Linux/macOS)或用返回的 task_id 查询 | | `STREAM_INTERRUPTED` | 用 `partial.last_event_id` 作 `since` 参数再调 `resumeJob`,最多额外重试 2 次(详见 `tools/resume_job.md`) | | 某批**超时 / 报错被动拆分** | 不得沿用失败批次的 `force_reusable_array` 或把全部变量名都列入兜底;必须重新对拆分后的每个子批逐条过三问法;特别注意:原批内的引用在拆分后变成跨批引用的变量,必须在对应子批重新写入数组 | --- ## 多轮追加公式(场景 B)前置检查 SOP 收到第 2 轮(或后续轮)追加公式时,在发起 `runMultiFormulaBatchStream` 之前执行以下检查: 1. 扫描新公式的右侧,列出所有引用了前批左侧变量的依赖项; 2. 对照历史 batch 的 `force_reusable_array`,查看这些依赖变量是否已写入数组; 3. 若发现有依赖变量在历史 batch 中未写入数组: - **有修正接口时**:先调 `markReusable` / `updateForceReusable`,再继续执行新公式; - **无修正接口时**:在终态答案中**显式说明**「变量 X 在首批未保活,本次需重新计算前置链(#N1 ~ #N2)」,并先补跑这些前置公式(`force_reusable_array` 列入全部前置变量名),再跑新公式;**禁止默默依赖缓存兜底**。 --- ## 最终回答合同 第一句必须是数据结论,按用户问法直接给出(资产名 + 数值 / 表格 / 名单 / 等等)。 禁止:「已成功执行 N 条公式」「按照 workflow Step 1/2/3」「让我来分批执行」等过程话术。 -
stock-profile.md 6.1 KB
# stock-profile · 单股指标画像 > **适用范围**:用户要求“分析一下 XX 个股”“看一下 XX 这只股票”“个股画像”“指标概览”“估值财务资金走势综合看一下”等开放式单股综合指标概览。 > 本 workflow 使用 `stockProfile` 单次调用完成,不使用 `fast_query` / `runMultiFormulaBatchStream` / `readData` 拼接画像,不使用 `scanDimensions` 做 IC 扫描。 --- ## 路由边界 | 用户意图 | 路由 | |---|---| | 最新价、今日涨跌幅、PE、PB、市值、ROE、营收等单字段或少量字段 | `fast_query` 对应 workflow | | 最近 N 日走势、窗口最高/最低、区间涨跌幅序列 | `fast_query` 对应 workflow | | 单股指标画像、全维度概览、综合看估值/财务/资金/走势 | 本 workflow | | IC、预测力、哪个维度有效、维度打分 | `scanDimensions` 相关路径 | | 多股票横向比较、选股、回测、自定义公式 | `quant-standard.md` 或对应 workflow | | 能不能买、后期怎么看、目标价 | 本 workflow 可先给指标画像,但必须拒绝投资建议和价格预测 | --- ## 执行步骤(严格顺序) ``` ① 提取单个 asset 和用户关注维度 → ② 调用 newSession(若本轮尚未显式调用;调用 stockProfile 前不可省略) → ③ 对 asset 执行 grep presets/assets_db/{stock_a,stock_hk,stock_us}.yaml 查本地库 - 命中唯一 → 优先使用标准名称或代码传参 - 命中多条 → 向用户澄清,禁止继续查数 - 未命中 → 保留原始 asset,由 stockProfile 服务端兜底解析 → ④ 调用 stockProfile → ⑤ 按返回 dimensions 输出事实摘要 ``` 调用参数模板: ```json { "asset": "贵州茅台", "user_query": "<用户的原始问题>" } ``` `task_id` 由调用层自动注入,不要手写伪造。 **停止条件**:`stockProfile` 返回 `code: 0` 且 `data.indicators_count > 0` → 直接基于 `dimensions` 输出,不再调用 `fast_query`、`scanDimensions` 或公式链。 --- ## 输出结构 默认按以下顺序组织,缺失维度直接跳过: 1. **资产走势**:`close_price`、20/60/120/250 日收益等。 2. **估值**:PE/PB/PS/股息率及百分位。 3. **财务分析**:毛利率、净利率、收入增速、ROE、ROIC、现金流等。 4. **资金流向**:换手率、卖空比例、基金持仓、换手趋势等。 5. **波动率**:年化波动率、标准差、百分位。 6. **宏观胜率背景**:行业估值、情绪、换手背景等。 7. **千维动态维度**:接口返回的相对强度、趋势结构、量能与流动性等市场动态维度。 8. **其他**:未映射到固定维度但接口返回的指标。 每个指标优先输出: ```text 指标名:最新值 单位(latest_date),上一有效值 previous_value 单位(previous_date) ``` 读取规则(响应已压缩): - `latest_date` 和 `unit` 可能在维度层级而非指标层级——先查维度,再查指标自身,取最具体的。 - 数值已由服务端按精度 round,直接展示即可。 - `unit` 为空时不强加单位。 - `latest_value` 或 `previous_value` 为 null / 缺失时,只输出可用字段;缺少 `previous_*` 表示本轮没有可用历史,不得写成 0,也不要再用 `fast_query` 或公式链补算。 - 预计算指标的 `previous_*` 是上一期;千维动态数值指标的 `previous_*` 是严格早于 `latest_date` 的上一有效值;千维布尔指标则表示上一次命中。 - `latest_date` 是 `YYYYMMDD` 数字,可展示为原值;不要自行改成报告期描述,除非字段语义明确。 - `variants` 中的变体是同一指标的不同口径(如 `quarter_yoy`、`ttm_level`、`pctrank:1Y`),按用户关注点选择性展开,不必全部罗列。变体的 `date` / `pre_date` 仅在与父指标不同时出现,缺失则继承父指标的日期。 --- ## 空数据和错误处理 | 情况 | 处理 | |---|---| | `ASSET_NOT_FOUND` 且本地资产库未命中 | 说明“无法识别资产:{asset}”,不得替换为相似股票 | | `ASSET_NOT_FOUND` 且本地资产库已命中 | 标记 `asset_catalog_conflict`,读取 `external-fact-verification.md` 核验上市/更名/换代码等事实;不得推导为公司未上市。外部事实确认但平台仍无数据时,说明“Quant Buddy 平台数据当前不可用” | | `indicators_count = 0` | 说明该资产暂无预计算指标数据 | | `warnings` 存在 | 原样吸收为口径提示,简明告知用户 | | 某维度为空 | 跳过该维度,不说“异常” | | 港股/美股部分维度缺失 | 说明“本轮接口未返回该维度指标”,不推断为公司没有该指标 | | HTTP 429/503 | 按全局 429/503 规则处理 | | HTTP 500 / 网络错误 | 标记 `quant_buddy_data_status=blocked` 并受控失败;外部搜索不得掩盖系统错误或补写平台数据 | --- ## 最终回答合同 首句必须是数据结论,例如: ```text 贵州茅台(600519.SH)的个股画像显示:本轮接口返回了估值、财务分析和资产走势等维度,最新计算时间为 2026-05-13T23:12:23.141Z。 ``` 随后按维度列出关键指标。用户没有指定关注点时,每个有数据维度选 2~4 个代表性指标,避免把全部字段机械倾倒。 如果用户问“能买吗 / 后期怎么看 / 目标价多少”: ```text 本工具返回的是预计算指标画像,不能据此给出买卖建议、目标价或价格预测;可基于已返回指标说明当前估值、财务、资金和走势的事实状态。 ``` --- ## 保护规则 1. **evidence-only**:只输出 `stockProfile` 返回支持的指标、日期和口径。 2. **不补字段**:接口未返回的指标不得用模型常识、外部网页或其他工具补写。 3. **不抢简单查数**:用户只问单字段或明确窗口时,回到 `fast_query` 路由。 4. **不做 IC 扫描**:用户要求预测力、IC_IR、维度打分时,转向 `scanDimensions`,不要用 `stockProfile` 代替。 5. **不做投资建议**:禁止直接回答“买/卖/持有”、目标价、确定性涨跌判断。 6. **不泄露过程**:最终答案不出现 workflow、工具调用过程、内部路径。 -
visual-page-fast-path.md 5.6 KB
# 单资产多指标活页快路径 > **唯一适用范围**:一个已明确资产 + 2~4 个 `fast_query(window)` 标准历史字段 + 用户明确要求“放在一张图里 / 画成一张图 / 同图比较”。典型问题:“把贵州茅台最近一年的股价、成交量和市盈率变化放在一张图里”。 > > 这不是 K 线请求。只有用户明确说“K 线 / 蜡烛图 / OHLC”才进入 `render-kline.md`。 ## 顶部硬闸门 命中本流程后,本 Turn **只读本文件,不得读取 `quant-standard.md`、`render-kline.md` 或 `render-chart.md`**。本流程已有完整参数和恢复规则,不得为了“更全面”扩大文档范围。 不得调用 `searchFunctions`、`searchSimilarCases`、`confirmDataMulti`、`runMultiFormulaBatchStream` 或 `readData`。不得调用 `renderChart`;不得调用 `renderKLine`。字段已由 `fast_query` 返回时立即停止取数,禁止重复计算。 ## 固定执行序列 1. 用一次 `Grep` 在资产库确认唯一资产代码;未命中或跨市场同名时按 `SKILL.md` 资产规则处理,不猜测。 2. 本 Session 首轮调用 `newSession`;追问调用 `beginTurn`。之后才能调用平台工具。 3. **先执行路由**,不要等静态图或公式计算: ```powershell python scripts/live_page_routing.py route --user-query "用户本轮原话" ``` 4. 只有返回 `create` 才继续本快路径。若为 `none/suggest`,按路由结果正常回答,不准备页面 Job。 5. 调用一次 `fast_query`: ```json { "assets": ["资产库确认后的代码或唯一名称"], "query_type": "window", "fields": ["用户要求的 2~4 个标准字段"], "window_days": 250, "user_query": "用户本轮原话" } ``` 时间口径:一年默认 250 个交易日;半年 120;三个月 60;用户给 N 日则用 N(1~2500)。不要传 `result_mode`。 6. 若返回 `mode:"csv"`,把 `csv_fields[].csv_url` 按原顺序交给受控脚本;`--labels` 必须对应 `csv_fields[].intent`。**无需预建目录**,脚本会安全创建 `--output` 的父目录;禁止额外调用 `mkdir`: ```powershell python scripts/fetch_fastquery_csv.py "<csv_url1>" "<csv_url2>" "<csv_url3>" --labels "收盘价,成交量,市盈率" --full --output "output/_working/<task_id>/main-series.json" ``` stdout 是紧凑 receipt;完整序列已写入 artifact,不要再读取整份 artifact 回模型上下文。直接使用 receipt 的 `series_summaries`(首值、末值、区间涨跌、极值)、字段统计和 `pairwise_analysis` 支持 QBS 首答;禁止再用 `curl`、`Invoke-WebRequest`、`read_skill_file` 或其它方式读取 CSV/artifact。 7. 一次命令生成 computation capsule、Handoff 和幂等 Job: ```powershell python scripts/live_page_routing.py prepare-fast-query-page --task-id "<task_id>" --turn-id "<turn_id>" --user-query "用户本轮原话" --source-skill-version "<当前 SKILL.md version>" --asset-id "<资产代码>" --asset-name "<资产名称>" --fields "收盘价,成交量,市盈率" --window-days 250 --artifact-file "output/_working/<task_id>/main-series.json" ``` 有真实 `source_skill_id` 时增加 `--source-skill-id`;不知道就省略,禁止猜测。命令返回的 `handoff_file`、`qbv_job_id`、`should_spawn` 是唯一交接依据。 **终止性规则**:`prepare-fast-query-page` 返回 `code=0` 后,capsule、Handoff 和 Job 已全部生成。此后只允许二选一:调用一次内部委派,或调用一次 `mark-delegation-unavailable`;然后立即基于既有 receipt 给出 QBS 首答。**禁止再次执行 `handoff`、`prepare` 或第二次 `prepare-fast-query-page`,也不得再调用任何取数、下载或文件读取工具**;不得把 `qbv_job_id` 当作 Handoff JSON 输入。 8. `should_spawn=true` 时,使用宿主真实提供的内部委派能力把该 Handoff 交给独立 QBV SOP;只等待接受回执,不等待页面完成。用户可见表述只能说“正在后台生成可交互页面”,不要说“新开一个子 Agent”。若宿主无委派能力或调用失败,执行: ```powershell python scripts/live_page_routing.py mark-delegation-unavailable --qbv-job-id "<qbv_job_id>" ``` 该命令把 Job 写为 `failed + DELEGATION_UNAVAILABLE + retryable=true`;QBS 首答照常发送,且不得对用户声称页面正在生成。 9. QBS 首答必须回答用户的分析问题:给出区间、价格/估值变化、相关系数与方向一致率的谨慎解释。**禁止**写“价格主要由估值驱动”“PE 导致股价变化”等因果结论;PE 指标通常包含价格项,高相关可能带有定义上的机械关系,必须明确写出“同步不等于因果,不能据此判断驱动因素”。页面完成后由终态消息补公开链接。 ## Inline 返回的窄恢复 若 `fast_query` 未进入 CSV 模式而直接返回列式序列:只允许用 `write_skill_file` 将本次 `results` 原样写到 `output/_working/<task_id>/main-series.json`,然后执行同一个 `prepare-fast-query-page` 命令。不得为了制造 CSV 或图表改走公式引擎。 ## 工具预算 | 类型 | 上限 | |---|---:| | Skill/workflow Read | 2(`SKILL.md` + 本文件) | | 资产 Grep | 1(只允许未命中时补 1 次) | | Session/Turn 工具 | 1 | | 路由 Bash | 1 | | `fast_query` | 1 | | CSV artifact Bash 或 `write_skill_file` | 1 | | `prepare-fast-query-page` Bash | 1 | | 内部委派 | 1 | | **目标总调用** | **8~10** | 任何一步成功后不得重复调用同角色工具。`fast_query` 只有明确返回字段失败时才允许对失败字段做一次降级;否则超预算即停止扩张并基于已有结果回答。
-
-
CHANGELOG.md 115 KB
# CHANGELOG — quant-buddy-skill 按版本从新到旧排列;日期为源码变更日期,不代表发布日期。未发布版本单独标注。 历史记录用于追溯;当前规则以 `SKILL.md`、`workflows/`、`tools/` 和 `references/troubleshooting.md` 为准。 --- ## [4.25.44] — 2026-09-23 - 刷新资产目录:A 股 5316、港股 2858、美股及境外 ETF 1069、指数 513、期货 239 条;沿用现有分类、排序和过滤规则。 - 新增“中国交易日”“期货同合约收盘涨跌幅”,指标目录由 2564 增至 2566 条。排除本次导出的 23 条示例数据,48 条已有期权数据不重复导入。 - 同步目录计数和校验基准,保留脱敏分类与精确 ID/数据名。 - 修正港美股估值兼容字段的解释规则:服务端明确升级为日频标准字段时,优先采用升级口径和实际返回日期,避免旧 `report_period` 元信息误导回答。 --- ## [4.25.43] — 2026-09-22(并入 4.25.44) - 补充 7 项美股及境外 ETF 资产;修正美股股息率在精选目录中的维度为 `two`。 - 纠正估值字段说明:PB 不带 TTM;港美股“单季”兼容字段实际为日频,报告期稀疏序列规则不再套用于这些字段。 - 移除入口及工作流中的资产特例和指标 ID 硬编码,继续使用数据目录检索;补充数据与字段契约测试。 --- ## [4.25.42] — 2026-09-17~2026-09-22(并入 4.25.44) - 对齐日频行情/估值的显式日期查询:单值优先取指定区间;区间无值时,服务端可从起始日向前回看 10 个自然日。 - `DATE_RANGE_FALLBACK` 按字段返回的 `v` 和 `d` 展示,不误报无数据、重试或切换完整链路。 - 新增 48 项美股期权数据目录,覆盖 E1/E2 的 ATM、25 Delta 合约价格、量仓、隐含波动率、到期日及行权价。 - 更新 Skill 规则、文档及离线测试;依赖服务端支持上述日期回看行为。 --- ## [4.25.41] — 2026-09-15 - 全数字资产代码优先精确匹配股票;同号命中多个股票时先确认市场。无股票匹配时保留原有唯一匹配规则。 - 带市场前缀或后缀的代码仍须完整匹配;补充 snapshot/window 双入口回归。 --- ## [4.25.40] — 2026-09-15 - 对齐服务端 `MINUTE_HISTORY_OUT_OF_RANGE`、部分覆盖警告及覆盖范围信息,适用于查询、授权注册和刷新。 - 不将越界错误当作空 CSV,不将市场覆盖起点当作单个资产首条数据;保留换月及覆盖警告,不写入冻结请求参数。 --- ## [4.25.39] — 2026-09-15 - 增加市场与资产类型的分钟历史覆盖表:全窗口越界时不调用,部分覆盖时说明实际范围,不静默改日期。 - 区分单日查询与历史分钟工具的适用范围;覆盖起点不保证单个资产数据完整。 --- ## [4.25.38] — 2026-09-15 - 接入 `fast_query_minute_range` 的工具声明、执行路由、参数校验和 CSV 读取;区分历史分钟、当日分钟及日频查询。 - 对齐期货合约、换月事件和附加警告的返回规则,保留旧工具的 `fields` 参数。 - 补充日行情与季度财务按发布日对齐的筛选示例,区分观察日、报告期和发布日;不修改计算引擎。 - 明确 QBS 批次证据与 QBV 包级发布校验的边界,分离 JSON 输出与进度日志,修正测试中的硬编码版本号。 --- ## [4.25.37] — 2026-09-11 ### 文件活页化先原样发布再研究 - 对齐QBV可恢复文件入口:file_prepare → 原始静态版托管与链接交付 → 自动继续已授权研究及同页QBS增强。 - 强调复合纠错/重做请求同样先发布原版;查数失败不影响已交付页面,纯分析和未授权发布不触发。不改变QBS查询能力或数据目录。 --- ## [4.25.36] — 2026-09-11 ### 用户侧数据目录与内部来源分离 - 数据目录按业务分类命名;内部来源标识、源文件名和校验信息不再随 Skill 发布。 - 保留数据记录的 ID、精确数据名和已有查询字段契约,不改变数据口径。 - 导入时先校验来源与完整性,再生成公开目录;本地打包和正式标签包发布均检查内部来源标识。 - 本次为公开目录与发布边界清理补丁,不包含回测功能修复或测试框架改动。 --- ## [4.25.35] — 2026-09-10 ### 已有文件转活页先静态交付 - 已有 JPG/PNG、HTML、PDF 等文件的活页化请求(含研究改造复合需求)优先静态转换、托管、验收和链接交付,再执行 QBS 数据接入,不等待查数、范式匹配或计算胶囊。 - 调整 QBS 入口和交接顺序:已有文件活页化不先进入查询 leaf、图表分类器或计算胶囊;复用任务身份交给 QBV 0.6.71 静态托管,交付后再查数增强。纯文件分析及明确暂不发布不触发,不改变一般查询场景。 --- ## [4.25.34] — 2026-09-07 ### K 线活页交接、公式链约束与运行时稳定性 - K 线 PNG 完成后统一通过 `prepare-validated-page` 原子生成计算胶囊、QBV handoff 与 Job,避免重复 handoff/prepare;宿主无法委派时将 Job 收敛为 `DELEGATION_UNAVAILABLE`,不再遗留 queued 状态。 - 已构建的完整 handoff 可由 `handoff` / `prepare` 命令直接重新校验,避免把 handoff JSON 误当构造参数后暴露 Python 参数错误。 - 均线回测示例明确只允许平台公式链计算,并要求多公式调用显式声明 `force_reusable_array`,禁止退化为 CSV 加临时 Python/Bash。 - Windows UTF-8 输出改为原地 `reconfigure()`,不再替换或关闭 pytest/宿主共享的 stdout、stderr;同时隔离 QBS/QBV 同名测试模块并限制测试发现目录。 - 更新回归运行器的受控 QBV 脚本、终态回复草稿/校验器和临时文件白名单,补充路由、委派降级、stdio 与 trace audit 回归。 ## [4.25.33] — 2026-09-04 ### 公式包指定日期读取与资产预设刷新 - `last_day_stats` / `last_column_full` 支持通过互斥的 `mode_params.date` 或 `offset` 读取指定截止日数据,并补充完整截面列 `last_column_full` 的参数与返回结构说明。 - 刷新 A 股、港股和指数资产预设:A 股标的由 5300 条增至 5303 条,港股维持 2858 条;同步指数名称及别名变更。 ## [4.25.32] — 2026-09-01 ### QBS→QBV 复数对象 ID 直传 - `validated_roles[].data_ids` 按服务端返回顺序确定性展开为唯一 role,原始 ID 不截断、不改写、不排序,并同步 `page_intent.required_roles`。 - Validation Receipt 自动发现同时识别单数 `data_id` 与复数 `data_ids`,避免多资产公式结果在 Handoff 阶段因缺少单数证据字段而失败。 - 回归数据中的回测图表问题改为自包含策略描述,不再依赖批处理不存在的上一轮上下文。 ## [4.25.31] — 2026-08-31 ### Turn 追踪非阻塞与公式对象 ID 直传 - Turn 登记失败统一返回 `code:0 + tracking_recorded:false + blocking:false`;attempted Turn 只写诊断日志,不保存为可信上下文,也不进入业务请求或 `x-turn-id`。 - 无可信 Turn 时继续携带真实 `task_id`、`user_query` 与 `agent_intent` 执行业务;内部工具调用和重试不因追踪降级生成新 User Turn。 - 公式结果支持从顶层/嵌套 `results` 提取 `data_id`、`indexinfo_id`、`expression_id` 与通用 ID,并提供按服务端原始字节顺序排列的 `ids` 供 `read_data` 直接消费。 - 增加 2026-08-31 事故脱敏 Trace Replay,锁定幽灵 Turn 不传播、正确对象 ID 不改写,以及全部 item 失败仍可由 Agent 继续修正。 ### 对齐细分指标组合选股契约 - `selectByComposition` 的 QBS 路由不再把 `indicator_type:"综合"` 当作准入条件:已物化、当前可选的细分与综合指标均可进入选择器。 - 选股位置改为按 `output_type` 分配:score 用于 `composition` / `score_filters` / screen 模式排序,screen 用于 `screens`;在线目录的 `selection_ready`、`selection_mode`、`as_of` 用于预检,服务端选择器仍是最终校验。 - `dimensions.yaml` 明确为本地候选快照,不再被表述为选择器唯一来源或实时可选性证明;细分指标和本地未命中项可通过 `listDimensionIndicators` 在线发现。 - 同步更新组合选股工作流、工具说明和指标库说明,补充 `score_filters`、日期对齐、可选性错误及 compact/verbose 贡献拆解的当前接口契约。 ## [4.25.30] — 2026-08-28 ### 个股画像返回千维上一有效值 - `stockProfile` 契约补充千维动态维度:最新值来自 `qw_indicator`,`previous_value` / `previous_date` 来自 `qw_indicator_history` 中严格早于最新日期的上一有效记录。 - `fast_query` 路由边界补充“画像上一值 / 前后两期对比”场景,统一转 `stockProfile`,不再临时拼接历史或公式补算。 - 明确数值型千维指标保留 0、跳过空值,布尔型指标的 `previous_*` 表示上一次命中;历史不足时仅省略前值字段。 ## [4.25.29] — 2026-08-26 ### 自更新备份改为受管理目录 - 默认备份根目录改为 `<workbuddy-root>/backups/skills/quant-buddy-skill/`,不再向 `.workbuddy` 根目录新增 `quant-buddy-skill-backup-*`。 - 新备份以时间和被替换版本命名,写入受管理 metadata;完成原子替换和健康检查后才迁移经过 slug 校验的历史 root backup,并自动保留最近 3 个且不超过 30 天。 - 清理只接触 metadata/schema/slug 全部匹配的 QBS 备份;未知目录、活动 skill、lock、staging 和 trash 受保护。安装过程中任一 swap 或健康检查失败都会回滚活动目录,且不会触发迁移或清理。 ## [4.25.28] — 2026-08-26 ### 每个用户 Turn 增加可选 Agent Intent - `newSession` / `beginTurn` 接受并返回规范化后的 `agent_intent`,本地 Session 分别保存首轮与当前轮 Intent;追问切换只更新当前轮,不覆盖首轮。 - QBS→QBV 的 `qbs_qbv_handoff_v1` 增加可选 Intent,旧 Handoff 继续兼容;Intent 不进入页面 Job 幂等键,也不要求普通行情、公式或业务工具重复上报。 - Skill 指令要求正常 Agent 展开追问指代,写清对象、动作、约束和产物;禁止复制用户原话、记录内部推理或提前编造结论。追踪或持久化失败继续 fail-soft。 - Turn 追踪失败的原始服务端/传输错误不再进入 Agent 可见 JSON;响应只保留 `tracking_recorded:false` 与可继续执行的中性提示,详细错误写入 Session 邻接 JSONL 诊断文件。 - 已显式提供完整 `task_id + turn_id + user_query` 的活页路由调用不再因缺少本地 Session 或可选 Intent 失败;Intent 上下文补充保持 fail-soft,兼容旧调用方。 ### 同步最新资产和数据目录 - 在港股半年财务字段目录基础上补充 4 条港股/美股换手率字段,`guanzhao` 目录由 161 条增至 165 条,全量 `index_info_catalog` 由 2535 条增至 2539 条。 - 刷新 A 股和港股资产预设,纳入最新标的、简称及曾用名变化;保持既有五列格式和 ticker 唯一性约束。 ## [4.25.27] — 2026-08-25 ### 补齐港股半年财务字段目录 - 使用 `indexInfo_fmp_fa_1324.xlsx` 重新生成 `fmp_fa.yaml`,记录数由 1144 增至 1324。 - 新增 180 条港股“半年”财务字段,原有港股/美股单季、年度与 TTM 字段无删除。 - 全量 `index_info_catalog` 记录数由 2355 更新为 2535,并同步 manifest、SKILL 目录说明及导入脚本期望总数。 ## [4.25.26] — 2026-08-24 ### 同步 QBV 0.6.52 Card Runtime 通用化与故障隔离 - 默认 companion 同步到 `quant-buddy-view 0.6.52`,完整 Card Runtime 重建必须显式提供视觉合同,不再按真实 `page_id` 选择页面专属视觉。 - QBV retrofit 模块改为命令内懒加载;Renderer、合同、取数或验证失败只影响 `retrofit_card_runtime`,不阻断 QBS 数据查询及普通 QBV 页面流程。 - 页面专属迁移合同不进入正式 Skill 包,现有线上页面不会因升级被自动改写;QBS 数据工具、公式执行与 Handoff 合同保持不变。 ## [4.25.25] — 2026-08-21 ### 刷新正式资产预设并过滤半年以上未更新资产 - 从正式 `quanLiang.tkrsInfo` 刷新 `stock_a / stock_hk / stock_us / index / future` 五类资产快照,排除 `deleted=true` 的 13 条记录。 - 以 2026-08-21 为同步日,过滤 `last_date` 早于 2026-02-21 或有值但无法解析的 525 条资产;缺失 `last_date` 继续保留兼容。 - 五类最终收录 10062 条:A 股 5299、港股 2858、美股及境外 ETF 1061、指数 604、期货 240。 - 同步后再次 dry-run 为零新增、零删除;五列格式、必填字段、ticker 唯一性与排序校验通过。 - 同步修正 `SKILL.md` 中全量数据名目录数量为 manifest 已验证的 2355 条;`index_info_catalog` 内容未改写。 ## [4.25.24] — 2026-08-20 ### 修复 QBS 物化结果到 QBV 发布证据的确定性绑定 - `runMultiFormulaBatchStream / resumeJob` 的 compact 结果若只返回不透明 `expression_id`,仅在结果数量与已提交公式数量完全一致时,按后端保序合同恢复 `index_title`,并将可信原始公式写入 Validation Receipt;数量不一致时不猜测绑定。 - 当同一已验证角色同时具有 `data_id` 与本地 `artifact_file` 时,Computation Capsule 优先交付可被 QBV 发布链路消费的 `quant_buddy_data_id`,并用 Receipt、runtime formulas 与 reads 三方校验 `data_id / index_title / formula`。 - 缺少或冲突的绑定在 QBS `prepare-validated-page` 阶段以 `DATA_REFERENCE_BINDING_REQUIRED` 等受控错误快速失败,不再让 QBV 运行到发布证据阶段才报 `HANDOFF_DATA_REFERENCE_MISSING`。 - 新增真实 `A股低PE高ROE选股Top20` 响应形态的跨 Skill 合同回归,验证 QBS Handoff 能直接通过 QBV `build_publish_evidence`;该 QBS 修复不要求 QBV 重算已验证结果。 ## [4.25.23] — 2026-08-20 ### 委派合同显式传递 Job 文件并使用 QBV 自动终态回写 - 子 Agent 提示合同新增绝对 `job_file`,QBV Adapter 消费 Handoff 时自动进入 `running`,无需模型手改 Job JSON。 - 页面通过 `publish_verified` 的发布与公网验收后由 QBV 自动写回 `completed`;确定终止时统一调用 Adapter `fail-job`。 - QBS 独立回答、QBV 独立建页和页面 ownership SOP 均不改变;本次只收紧增值链路的审计闭环。 ## [4.25.22] — 2026-08-20 ### 修复低 PE 高 ROE Top20 的 Next10 资产对齐 - 将固定 11 条公式中的 `Next10Score` 改为 `Next10掩码 * 排序值`,与 `Next10PE`、`Next10ROE` 复用同一资产掩码,避免 `Top20 返回数值 - Top10 返回数值` 因资产轴不稳定而触发第二次修正计算。 - 保持六个物化输出、`last_day_stats` reads、Handoff 角色和 QBV Adapter 合同不变;QBV 继续使用 `covered / skip / register_exact`,不重复调用 QBS。 - 新增权威 workflow 回归断言,明确禁止恢复旧的差分数值公式;QBS/QBV 独立路径不变。 ## [4.25.21] — 2026-08-20 ### 保留 deferred 公式任务的精确页面运行合同 - `runMultiFormulaBatchStream` 返回 `deferred` 时,按 `task_id + trace_id` 原子保存已脱敏的 `qbs_formula_runtime_contract_v1` 草稿;只包含公式、执行参数、受支持 reads 与 fingerprint,不落盘 API Key 或 Authorization。 - `resumeJob` 成功后只从完全相同的 `task_id + trace_id` 恢复该合同并写入完成 Receipt,使 `prepare-validated-page` 能继续把 QBS 已验证公式交给 QBV `register_exact`,避免 QBV 重复计算。 - 不同 trace、缺失上下文、篡改 fingerprint 或无界输出继续失败关闭;QBS/QBV standalone 路径和 QBV `0.6.47` SOP 不变。 ## [4.25.20] — 2026-08-20 ### 修复低 PE 高 ROE Top20 的实时页面复用合同 - 将 `A股低PE高ROE选股Top20` 的可复用输出从单个无界 Top20 数组改为 `Top10 + Next10` 两段:11 条原始公式物化 Score、PE、ROE 共六个最多 10 行的输出,QBS 合并并按 Score 降序返回完整 20 行。 - Validation Receipt 只在公式结构可证明每个输出不超过 10 个资产时附加 `qbs_formula_runtime_contract_v1`;支持直接 `取前(...,10)`、同源 `Top20-Top10` 以及有界掩码乘指标。普通单输出 Top20 失败关闭,不伪造页面运行合同。 - QBV Formula Package 复用六个受支持的 `last_day_stats` reads,不再尝试服务端不支持的 `last_column_full`;QBV 主 SOP 与独立使用路径不变,版本保持 `0.6.47`。 - 页面交接固定使用 `ranking_top10/ranking_next10/pe_top10/pe_next10/roe_top10/roe_next10` 六个角色,避免把只有 10 行的结果误标为 Top20;标题与元数据必须为当前低 PE 高 ROE榜单,不得继承来源模板资产名。 ## [4.25.19] — 2026-08-20 ### 接受顶层 Receipt 文件路径,消除真实 Agent 的一次无效重试 - `prepare-validated-page` 的顶层 `validation_receipts` 现在同时接受内联 Receipt 对象与 Receipt 文件路径字符串;真实 Agent 可直接复用 `runMultiFormulaBatchStream` 返回的 `validation_receipt_file`,无需了解 Receipt 内部对象结构。 - 保持 Receipt 文件的 task/turn/query lineage、SHA256 与公式运行合同校验不变;Receipt 文件内部禁止继续嵌套引用其它文件,避免递归加载。 - 针对 `A股低PE高ROE选股Top20` 的真实 GPT-5.5 回放补齐相同输入形状的回归测试,避免首次 `INVALID_VALIDATION_RECEIPTS[0]` 后再删字段重试。 ## [4.25.18] — 2026-08-20 ### 补齐真实 Agent 路径的公式 Receipt 与自动交接 - 修复真实 Agent 通过 `QuantAPI._call()` 执行 `runMultiFormulaBatchStream` 时绕过 `call.py`、导致成功公式没有 Validation Receipt 的问题;compact SSE 结果会规范为统一信封、映射 `indexinfo_id → data_id` 并落盘脱敏后的精确运行合同。 - `prepare-validated-page` 在未显式传 Receipt 时,可按同一 `task_id` 与全部已验证 `data_id` 自动发现唯一匹配 Receipt;跨任务、输出不完整或多个不同运行合同同时命中时均拒绝复用。 - 真实问题 `A股低PE高ROE选股Top20` 已验证生成包含五条原始公式和三个读取合同的 Handoff;QBV Adapter 对三个角色返回 `coverage=covered`、`qbs_action=skip`、`formula_runtime_action=register_exact`。同时收紧交接请求为最小 JSON:Receipt 已含公式时不再手抄 `validated_roles[].formula` 或按 role 重复 Receipt,避免双引号转义失败造成额外重试。 - 保持 QBS 与 QBV standalone 路径不变;当前无子 Agent 委派能力的测试宿主仍以 `DELEGATION_UNAVAILABLE` 终止后台 Job,不伪造已生成页面。QBV 本轮不升级版本。 ## [4.25.17] — 2026-08-20 ### QBS → QBV 原样复用已验证公式执行合同 - `runMultiFormulaBatchStream` 成功 Receipt 新增 `qbs_formula_runtime_contract_v1`,只保存脱敏后的精确公式、执行参数、物化输出读取方式和 SHA256 fingerprint,不记录 API Key 或 Authorization。 - computation capsule 与 `prepare-validated-page` 可从 Receipt 自动提取该合同;显式合同与 Receipt 不一致、输出左值缺失或 fingerprint 被篡改时失败关闭。 - 修复 `A股低PE高ROE选股Top20` 交接时把五条已跑通平台公式缩写成不可解析单式的问题;QBV thin adapter 返回 `register_exact`,要求 Formula Package 原样注册,不再重复计算 covered 角色。 - 保持 QBS standalone 行为不变;没有公式运行时合同的旧 Handoff 继续兼容。 ## [4.25.16] — 2026-08-19 ### 同步 iter-017 的 A 股所属行业快照字段 - 合入 `iter-017` 提交 `76a63afa26751d687b7e05b5f95921e29591462b`:`fast_query(snapshot/value)` 新增 A 股股票 `所属行业`,仅支持 `type=stock` 且 `market_id=1/2`。 - 返回结构按申万一级 `swl1`、申万二级 `swl2` 和概念列表 `jqc` 展示;同步根路由、Fast Path 字段映射、工具说明及发布热更新文件清单。 - 保留 4.25.15 的低 PE 高 ROE TopN 活页路由、QBS→QBV Handoff 与 Job 终态合同;QBV 版本保持 0.6.45。 ## [4.25.15] — 2026-08-19 ### 支持低 PE 高 ROE TopN 的默认口径与活页增值路由 - 将线上高频自然语言 `A股低PE高ROE选股Top20` 识别为结构稳定的因子榜单,确定性路由为 `create`,同时保留“只要表格/不要网页”的显式退出。 - 为未指定阈值和主排序字段的“低PE高ROE TopN”定义默认执行口径:全A股、PE(TTM)>0、ROE>0、按 ROE/PE 降序,并展示代码、名称、PE、ROE、比值和数据日期,避免不必要的澄清轮次。 - 将该高频稳定榜单加入真实 Agent 的强制路由硬闸门:QBS 验证 TopN 后必须执行 route,并用 Score/PE/ROE 三个已物化 `data_id` 一次准备 Capsule、Handoff 与幂等 Job;无内部委派工具时写入 `DELEGATION_UNAVAILABLE`,不遗留假 queued 状态。 - 低 PE 高 ROE 首次公式窗口默认覆盖当前自然年,避免把报告期 ROE 按当天窗口查询而产生一次全 0 的无效计算。 - 普通一次性条件选股仍保持 `none`;本次只扩展已确认高频且页面 schema 稳定的榜单,不把所有 TopN 查询强制活页化。 - QBV 保持独立,版本仍为 `0.6.45`;QBS 只负责文本答案、结构化产物和 Handoff,页面构建/发布/验收继续由 QBV SOP 完成。 ## [4.25.14] — 2026-08-19 ### 收紧 QBS、图表产物与 QBS → QBV Job 终态合同 - 修复长任务恢复请求:`QuantAPI._call("resumeJob")` 改走专用 SSE 调用并发送正确的 `Accept` Header,避免普通 GET 路径触发 HTTP 406,同时保留 `task_id` / `turn_id`。 - 修复图表产物合同:`renderChart` / `renderKLine` 返回的 base64 会解码并写入 `quant-buddy-skill/output` 的真实文件;用户明确要求 PNG 时可传 `output_format=png` 并在本地完成真实格式转换,写入或转换失败返回 `artifact_error`,不再生成不存在的 `sandbox:/mnt/data/...` 路径。 - 修正公式频率路由:盘中、当前分钟和当日未收盘行情才启用分钟数据;历史收益、回测、净值、回撤、波动率、历史日线与财务计算默认保持日频或报告期频率,图表意图本身不再误开分钟模式。 - 将 QBS → QBV Job 升级为 `qbs_qbv_job_v2`,补充 `published`、`public_verified`、Worker 超时和终态时间字段;只有真实 `target_skill_id`、`target_page_id`、公开链接、发布成功及公网验收同时成立时才允许 `completed`。 - 新增 `expire-stale` watchdog,将超时的 `queued` / `running` Job 写为可重试失败,避免非终态永久悬挂;生产宿主仍须负责定时调用,本 Skill 不冒充后台调度器。 - QBV 未改动,版本保持 `0.6.45`;本版本只收紧 QBS standalone 与 QBS → QBV 增值交接边界。 ## [4.25.13] — 2026-08-19 ### 在 iter-017 基线上集成 QBS → QBV 快速并行交接 - 以 `iter-017` 的 QBS 4.25.10 与 QBV 0.6.44 为基线,合入活页路由、`qbs_computation_capsule_v1`、`qbs_qbv_handoff_v1`、幂等 Job 和已物化结果复用能力,不再依赖旧功能分支运行。 - 明确图表、排名、对比、回测等强活页场景可在 QBS 正常首答后交接 QBV;一次性查数、普通分析和弱图表表达继续停留在 QBS,高风险持久状态未确认时禁止入队。 - 行业排名和单资产多指标快路径继续复用 QBS 已验证的 `data_id`、公式合同、artifact 与 receipt;QBV 只补缺失角色,证据不可用时才回退独立 SOP。 - 保留当前 Agent 注册、单批最多 20 条公式和 Windows UTF-8/幂等保护,并增加合并后的合同与回归测试。 - 根据真实 Skill Trace 收紧单资产多指标快路径:CSV 脚本自行创建输出目录,紧凑 receipt 增加 `series_summaries`(首末值、区间收益与极值);生成 Handoff 后禁止额外 `mkdir`、重复下载或重读 artifact,减少无效工具轮次并保持完整序列只在 QBS → QBV artifact 中复用。 ## [4.25.12] — 2026-08-14 ### 行业排名图直接复用已物化结果生成 QBV Handoff - 新增 `industry-ranking-fast.md`,明确“申万一级行业最近 N 日涨跌幅排名图”只读窄流程,不再加载 `global-rules.md`、`quant-standard.md` 与 255 行行业 recipe。 - 固定为一次行业聚合公式、一次 `readData(last_column_full)`;强制使用 `indexinfo_id`,禁止误读 `expression_id`,也不再由 QBS 调用 `renderChart`。 - 新增 `prepare-industry-ranking-page`,从当前 Session 自动补齐 lineage,一次绑定已物化 `data_id`、实时刷新公式、横向排名图视觉合同、Handoff 与幂等 QBV Job。 - QBV 仍执行独立完整 SOP;本快路径只让 QBV 读取现有 `data_id`,不重复运行相同公式。 - 增加窄路由、公式/引用合同、CLI、幂等、宿主 Bash 白名单与文档预算回归测试;真实 Skill Trace 与页面质量验收留待打包后的端到端实测。 ## [4.25.11] — 2026-08-14 ### 通用已验证结果一键交接给 QBV - 新增 `live_page_routing.py prepare-validated-page @request.json`,将排名、对比、回测、热力图等 QBS 结构化结果一次性转换为 hash-bound computation capsule、Handoff 和幂等 QBV Job。 - 输入改为面向业务 role 的紧凑合同;脚本自动生成 contract fingerprint、artifact SHA256、receipt SHA256,并校验 receipt 中的 task/turn/query lineage。 - 分类器结果不能被请求中的 `route` 强行覆盖;高风险持久状态未确认时禁止准备 Job。 - 更新测试宿主 Bash 白名单与说明,使真实 Skill Agent 可从 `skill/output` 安全调用新命令。 - 新增一键准备、UTF-8 无 BOM、幂等复用、route 冲突、高风险确认和 receipt lineage 回归测试;QBV 核心 SOP 与版本保持不变。 ## [4.25.10] — 2026-08-14 ### 修复用户主目录 Git 仓库误判开发 checkout - 将 `.agents/skills`、`.claude/skills`、`.cursor/skills`、`.codex/skills` 明确认定为正常 Agent Skill 安装根,不再受更高层用户目录 `.git` 影响。 - 普通 `repo/skills/quant-buddy-skill` 仍保留开发 checkout 保护,避免自动安装污染源码仓库。 - 增加用户目录含 `.git`、canonical `.agents` 与 logical `.claude` 链接场景的 QBV 注册回归测试。 ## [4.25.9] — 2026-08-14 ### 收紧活页快路径的终态交接 - 消除测试宿主固定 cwd 与“必须先 cd”的冲突:宿主已定位 Skill 根目录时直接运行相对命令,避免一次无效 Bash 重试。 - `prepare-fast-query-page` 成功后明确禁止再次调用 `handoff` / `prepare`;新增 `mark-delegation-unavailable` 低自由度命令,委派能力缺失时写入可重试失败终态,不遗留 queued Job。 - 测试 Agent 的 `fast_query` Schema 增加 `user_query`,并强制子进程使用 UTF-8 stdout,避免 Windows 中文 receipt 乱码。 - 新版开发 checkout 高于服务端已发布版本时,忽略过期的 `update_required`,避免错误提示降级并继续正常处理 QBV companion。 - 收紧分析措辞:价格与 PE 高相关必须提示指标定义中的机械关系,禁止把同步性写成估值驱动价格的因果结论。 - 增加 cwd、重复交接、委派失败终态、Schema、中文 stdout、版本倒挂和因果措辞回归测试。 ### 合入线上 QBV Agent 注册热修复 - canonical QBV 已是目标版本时仍检查当前 Agent 注册,不再因 `already_current` 提前结束。 - Windows symlink 权限不足时安全回退 Junction;正确链接保持 no-op,真实目录或错误链接不覆盖。 - QBV 安装或更新后继续注册当前 Agent;安装、更新或注册成功均返回 `reload_required=true` 并提示重新加载 Agent。 - Agent 注册失败作为旁路错误记录,不阻断 QBS 数据业务。 ## [4.25.8] — 2026-08-14 ### 单资产多指标活页快路径 - 新增 `visual-page-fast-path.md`:一个资产的 2~4 个标准历史字段明确要求同图时,路由提前到取数前,并固定为一次 `fast_query`;禁止进入 `quant-standard`、公式引擎、`renderChart` 或 `renderKLine`。 - `fetch_fastquery_csv.py` 支持把完整 CSV 序列安全写入 `skill/output` artifact,stdout 仅返回 SHA256、字段摘要和价格/估值对齐分析,避免完整时间序列占满模型上下文。 - `live_page_routing.py prepare-fast-query-page` 一次完成 hash-bound computation capsule、QBV Handoff 与幂等 Job,减少模型手工拼装和重复读取。 - 测试 Agent 放行上述两个受控入口,并限制 CSV URL 为 HTTPS、artifact 路径位于 `skill/output`;增加快路径、artifact、路径边界和真实用户表达回归测试。 ## [4.25.7] — 2026-08-14 ### QBS 计算胶囊复用到独立 QBV SOP - 新增 `qbs_computation_capsule_v1` 构建/校验脚本,把用户核心问题、主图意图、资产映射、可复现合同、合同 fingerprint、结果 artifact SHA256、字段映射、结论和收据一起交给 QBV,禁止只传 PNG 或一句总结。 - `qbs_qbv_handoff_v1` 增加可选 `computation_capsule`,并校验 task/turn/query lineage 与 artifact hash;旧 Handoff 字段保持兼容。 - 明确 `covered/partial/unusable` 复用边界:只跳过已覆盖的资产识别、同字段取数和同公式计算;QBV 的页面路由、ownership、构建、运行时注册、发布及验收保持独立。 - 增加长鑫科技价格/成交量/PE 同图真实句式的胶囊合同、篡改检测和 Handoff 回归测试。 ## [4.25.6] — 2026-08-13 ### 修复 QBV 路由成功但未启动子 Agent - 将 QBV 交接从写死的 OpenClaw `sessions_spawn` 改为宿主通用内部子 Agent 委派合同,优先使用 `spawn_agent`,兼容真实存在的等价委派工具;明确禁止用 `create_thread/fork_thread` 或口头声明代替真实工具调用。 - `source_skill_id` 调整为可空审计字段:有真实值时标记 `available`,缺失时记录 `unavailable + source_skill_name + source_skill_version`,不再阻断 Handoff、Job 和 QBV 委派。 - Job 增加通用 `delegation_tool/delegation_id` 回执字段;委派不可用或失败时必须写入 `DELEGATION_UNAVAILABLE` 可重试终态,不再静默留下未启动链路。 - 更新图表/K线工作流和合同测试,覆盖缺失运行时 Skill ID、真实子 Agent 委派提示、通用委派回执及用户可见新任务误用防护。 ## [4.25.5] — 2026-08-13 ### 修复真实图表表达漏触发 QBV - 路由分类器支持“放在一张图里”“画成一张图”“同一个图里比较”“做成图”“绘制成图表”等真实自然表达,同时保持普通查数、表格输出、弱“看看走势”和明确 PNG-only 为 `none`。 - 图表工作流新增低自由度硬闸门:数据/图片验证后、QBS 首答前必须实际执行 `live_page_routing.py route` 并保留 JSON;新增 PowerShell 安全的 `route --user-query` 调用,避免内联 JSON 引号被 Shell 改写;不得只读规则、仅生成 PNG 或由 Agent 自行口头判定。 - `create|existing_page` 必须继续 handoff → prepare → 非阻塞 spawn,仅等待 `accepted`;路由或交接失败仍软降级,不阻断 QBS 首答。 - 增加宁德时代价格、成交量、PE 同图比较等真实用户句式及表格/不要图反例回归测试。 ## [4.25.4] — 2026-08-13 ### QBS → QBV 非阻塞活页触发合同 - 新增 `workflows/live-page-routing.md` 与 `scripts/live_page_routing.py`,QBS 统一输出 `none / suggest / create / existing_page`;明确 K 线、分时、收益/净值/回撤曲线、多资产对比、排名图、热力图和动态看板进入强活页路由,弱“看看走势”和只要 PNG 保持 QBS。 - 新增 `qbs_qbv_handoff_v1`,强制保留真实 `source_skill_id + task_id + turn_id`,复用已验证输出/收据,禁止把 ownership、direct/fork/unmatched 等 QBV SOP 泄漏到 QBS。 - 新增本地持久 QBV Job registry,按 `task_id + turn_id + route + normalized_page_reference` 幂等,支持 queued/running/completed/failed、终态链接和可重试失败,避免同一 Turn 重复建页。 - 明确 OpenClaw `sessions_spawn` 非阻塞交接:只有真实 accepted 才提示“正在生成”,QBS 不等待 QBV;spawn 或页面失败不影响第一条业务答案。 - 持仓、成本、股数、止损/减仓和自动化条件未经确认时仅 `suggest`,禁止入队或持久化。 ## [4.25.3] — 2026-08-12 ### 单次版本检查与 QBV companion 管理 - `newSession` 的一次 QBS 版本检查可接收服务端 `companions`,在 QBS 已最新时自动安装或更新 `quant-buddy-view`;QBS 自身需要更新时保持 QBS 优先,reload 后再处理 QBV,禁止旧进程同时替换两个 Skill。 - 新增标准库实现的 Companion Manager:支持 canonical skills root、copy/symlink/junction、SemVer 防降级、三次下载、SHA-512、确定性 ZIP 路径、traversal/symlink 防护、共享锁、staging、原子替换、备份回滚和运行时文件保留。 - QBV 安装写入 `.managed-install.json`,失败只记录 `output/.companion_state.json` 并旁路返回,不复制 QBS API Key,也不阻断行情、财务、公式、筛选和回测。 - `call.py` 与 `QuantAPI.newSession` 复用同一 QBS-first companion 状态机,并增加环境禁用、显式 skills root 和强制 reconcile 开关。 ## [4.25.2] — 2026-08-12 ### Turn 追踪改为旁路软降级 - Turn 只用于审计与分析:普通工具遇到 `turn_id` 串号、问题上下文漂移或服务端 Turn 记录失败时,不再抛出 `TURN_CONTEXT_MISMATCH` 阻断业务。 - 不安全的 `turn_id` 会被取消,调用方本轮真实 `user_query` 继续随业务请求发送;诊断通过 warning / `tracking_recorded=false` 保留,禁止错误关联到其他 Session。 - `newSession` / `beginTurn` 的服务端记录失败时仍提交本地 Session/Turn 上下文,后续行情、公式、取数和页面工具可继续执行;真正的业务参数、权限和锁定 `task_id` 错误仍保持硬失败。 ## [4.25.1] — 2026-08-11 ### 清理未支持的数据源 - 移除不再支持的 `旧版财务数据类` 数据源,避免继续推荐无法使用的旧 旧版 财务字段。 - 同步系统数据名目录、导入总数和 Provider 使用规则。 ## [4.25.0] — 2026-08-07 ### 原生日内分钟行情工具 - 新增原生工具 `fast_query_minute`,调用单资产分钟行情接口;使用常规 API Key、trace 透传与共享 `ratePerMinute` 配额链路。 - 工具契约严格限制为 `asset + fields`:不支持多资产、历史日期、区间、window 或分钟聚合;支持 OHLCVA canonical 字段与中文别名。 - 返回共享 `dates[]` 与列式 `fields.<name>[]`,明确 `current_session` / `latest_completed`、交易日和市场时区语义。 - 同步测试 Agent 的 executor/tool schema/tool contract/local-tool registry,防止用脚本包装原生接口。 - 场景路由新增“单资产分钟/分时/逐分钟序列”优先分支;最新单点行情仍走 `fast_query(snapshot)`。 **变更文件**:`SKILL.md`、`CHANGELOG.md`、`scripts/executor.py`、`tools/fast_query_minute.md`、`tests/test_fast_query_minute_contract.py`、`../agent/tools_schema.py`、`../agent/tool_contracts.py`、`../agent/local_tools.py`。 ## [4.24.5] — 2026-08-07 更新资产、系统数据名和板块预设。 - 清理已下线的期货资产。 - 新增 `创业板指指数成分股` 和 `上证50`。 ## [4.24.4] — 2026-08-07 更新资产、系统数据名和板块预设,版本升至 `4.24.4`。 - A 股资产增至 5,565 条,新增 4 个资产并同步证券简称变化。 - 系统数据名增至 2,357 条,新增中证 A500、创业板指数成分股数据。 - 板块预设增至 741 条,新增中证 A500、创业板指、MLCC 和开盘啦题材。 ## [4.24.3] — 2026-08-06 ### 多轮追问 Turn 追踪 - `newSession` 创建 Session 与首个不可覆盖的 Turn;新增 `beginTurn`,同一 Session 的每条用户追问独立登记。 - 普通工具自动透传当前 `task_id + turn_id + user_query`;显式问题与当前 Turn 不一致时返回 `TURN_CONTEXT_MISMATCH`。 - `QuantAPI.begin_turn()` 与请求头 `x-turn-id` 支持多工具共享同一 Turn,服务端成功前不切换本地上下文。 ## [4.24.2] — 2026-08-01 ### 时效性事实核验门禁 - 新增 `workflows/external-fact-verification.md`,在最新上市/退市/更名/换代码、知识冲突,以及“本地资产库命中但平台返回 `ASSET_NOT_FOUND`”时强制核验外部事实。 - 明确区分 `external_fact_status` 与 `quant_buddy_data_status`;平台接口未识别资产不得推导为公司未上市,网页搜索结果也不得冒充 Quant Buddy 行情、公式、Grant 或验证收据。 - `stock-profile.md`、`quick-lookup.md` 接入 `asset_catalog_conflict` 分支;`event-study.md` 复用统一的搜索工具与来源优先级,同时保留事件研究专用的日期和收益计算门禁。 - 复用现有 Agent Web Search 与本地 `webSearch` / Bocha,不新增搜索接口、不修改配置优先级;增加 SpaceX 原故障回放契约测试和 Bocha 无网络 mock 测试。 **变更文件**:`SKILL.md`、`CHANGELOG.md`、`scripts/call.py`、`scripts/event_study_local.py`、`workflows/external-fact-verification.md`、`workflows/global-rules.md`、`workflows/stock-profile.md`、`workflows/quick-lookup.md`、`workflows/event-study.md`、`tests/test_external_fact_verification_contract.py`、`tests/test_web_search_fallback.py`、`tests/fixtures/spacex-asset-catalog-conflict.json`、`../docs/更新skill版本数据库sql语句.md` --- ## [4.24.1] — 2026-08-01 **变更文件**:`SKILL.md`、`CHANGELOG.md`、`presets/assets_db/stock_a.yaml`、`presets/assets_db/stock_hk.yaml`、`presets/assets_db/stock_us.yaml`、`../docs/更新skill版本数据库sql语句.md` 从 MongoDB `quanLiang.tkrsInfo` 刷新本地资产名称、ticker 与行业映射快照。本次数据库共 10,568 条文档,跳过 6 条无法分类或必要字段为空的记录;同步后再次 dry-run 为零差异,五类资产文件格式和 ticker 唯一性校验通过。 - **A 股资产库**:`stock_a.yaml` 保持 5,561 条,共刷新 227 个 ticker 的资料;其中 3 个名称更新(`SZ000793`:`*ST华闻` → `ST华闻`,`SZ000820`:`*ST节能` → `神雾节能`,`SZ301677`:`欣兴工具` → `C欣兴工具`),225 个 ticker 的行业字段按源库更新。 - **港股资产库**:`stock_hk.yaml` 从 3,067 条增至 3,068 条,新增 `HK2261 拿森科技`。 - **美股资产库**:`stock_us.yaml` 从 1,068 条增至 1,070 条,新增 `SPCX.N SpaceX`、`SKHY.N 海力士`。 - **指数与期货**:`index.yaml` 保持 606 条、`future.yaml` 保持 257 条,内容与源库一致,本次无 Git 差异。 - **发布配套**:`SKILL.md` 两处版本号升至 `4.24.1`;数据库版本 SQL 同步到 `4.24.1`,继续采用软升级策略。GitHub `v4.24.1` tag 和 zip 尚未生成时,SHA-512 必须保留为占位符,发布后使用真实归档哈希替换再执行。 --- ## [4.24.0] — 2026-07-31 **变更文件**:`SKILL.md`、`tools/dimension_indicators.md`(新增)、`scripts/executor.py`、`workflows/composition-select.md`、`CHANGELOG.md`、`../docs/更新skill版本数据库sql语句.md` 新增「维度指标库」能力:以前 skill 只能通过 `presets/dimensions.yaml` 看到**综合指标**(维度分)并拿去 `selectByComposition` 选股,看不到细分指标,也拿不到任何指标的口径公式。本次接入两个服务端只读接口,把整套指标库和公式定义打开。 - **新增工具 `listDimensionIndicators`**:按维度返回指标目录,细分 + 综合都有(当前 17 个维度 / 163 个指标)。支持 `dimension` / `asset_scope` / `indicator_type` / `output_type` / `keyword` 过滤。默认档只回答「用哪个指标」:维度层 3 个字段(名字/说明/指标数),每条指标 6 个字段(口径细节去 `getIndicatorFormulas` 拿);`status` 和 `has_formula:false` 是例外字段,只在不正常时出现。四档详略:`with_indicators:false` 仅目录(约 1.6KB)、`compact` 4 字段(约 20KB)、默认 6 字段(约 29KB)、`verbose` 全字段(约 58KB)。⚠️ 默认档不含 `indicator_type`,要拿去喂 `selectByComposition` 时必须带 `indicator_type:"综合"` 查(该接口只接受综合指标)。 - **新增工具 `getIndicatorFormulas`**:按指标名返回该指标的完整公式组。`name` / `ind_id` / `index_title` 三种写法都认,精确优先,歧义返回 `candidates` 不猜。返回的 `formulas` **依赖在前、目标在最后,可原样传给 `runMultiFormulaBatchStream`**;超 20 条时带 `batch_hint.layers` 分批方案和每批的 `force_reusable_array`。 - **SKILL.md 新增「维度指标库」概念段**(在「平台数据覆盖范围」下):讲清 维度 → 指标 → 公式 三层、`细分`/`综合` 的区别、`name` 与 `index_title` 从不相同这一坑,以及「选股走 dimensions.yaml,问口径走新工具」的分流。 - **场景路由新增一行**:「维度指标库查询 / 指标口径与公式」→ `tools/dimension_indicators.md`。 - **`presets/dimensions.yaml` 定位澄清**:目录树里标明它是本地快照、**只含综合指标**,要细分指标或公式需在线查。两者并存,选股路径不变。 - **`composition-select.md`**:补充「问口径不是选股题」的出口,以及 `INDICATOR_NOT_FOUND` 时用 `listDimensionIndicators` 核对服务端当前可用综合指标,区分本地快照过期与指标下线。 - **`executor.py`**:两个工具进 `TOOL_ROUTES`(`call.py` 白名单随之自动生效)与 `TOOL_TIMEOUTS`(各 120s,纯 DB 查询)。 - **发布配套**:数据库版本 SQL 同步到 `4.24.0`,继续采用软升级策略;GitHub `v4.24.0` tag 和 zip 尚未生成时,SHA-512 必须保留为占位符,发布后用真实归档哈希替换再执行。 --- ## [4.23.4] — 2026-07-15 - `output_mode:"summary"` 保留 deferred 的完整续传契约;缺少 `task_id/trace_id` 时返回 `DEFERRED_CONTINUATION_MISSING`,禁止重提原批次。 - QBV 编排通过 task-scoped `QBS_SESSION_KEY` 继承上游 task_id,避免并发 Agent 共用默认 session 导致 Trace 拆链。 - failed、部分失败和 deferred 继续不生成 `validation_receipt_file`。 --- ## [4.23.1] — 2026-07-07 **变更文件**:`SKILL.md`、`CHANGELOG.md`、`presets/functions.yaml`、`presets/assets_db/*.yaml`、`presets/index_info_catalog/*.yaml`、`presets/index_info_catalog/manifest.yaml`、`presets/index_info_catalog/README.md`、`dev-tools/import_index_info_catalog.py` 本次刷新本地预设快照,并把 `index_info_catalog` 的真实维护方式写入文档:全量系统数据名目录来自后台 `indexInfo` 页面人工筛选下载的 Excel,而不是直接从数据库或 `全部` Provider 导出。 - **函数库刷新**:`presets/functions.yaml` 从 `config_support_function.json` 更新为 170 条常用函数。 - **资产库刷新**:`presets/assets_db/` 从 `tkrsInfo` 更新,当前数量为 A 股 5557 条、港股 3067 条、美股及境外 ETF 1068 条、指数 604 条、期货 257 条。 - **系统数据名目录刷新**:`presets/index_info_catalog/` 用 7 个手工导出的 `indexInfo_*.xlsx` 重新生成,范围固定为旧版财务、行业分类、境外财务、宏观策略、龙虎榜、A 股财务和期权波动率七类数据,合计 2355 条,`warnings: []`。 - **新增数据名**:`guanzhao` 新增 `中证红利指数成分股`、`创业板50指数成分股`;`A 股财务数据类` 新增 `A股融券余额〔融资融券信息〕`、`A股融资融券融券资格〔融资融券信息〕`。 - **维护口径同步**:`README.md` 记录后台手工筛选下载地址与 Provider 选择规则;`dev-tools/import_index_info_catalog.py` 的期望总数同步为 2355,避免下次维护误报旧总数。 - `SKILL.md`:版本号升至 `4.23.1`,目录树中的函数库、资产库、系统数据名目录数量同步更新。 --- ## [4.23.0] — 2026-06-25 **变更文件**:`SKILL.md`、`presets/data_catalog.yaml`、`presets/index_info_catalog/*.yaml`、`presets/index_info_catalog/manifest.yaml`、`presets/index_info_catalog/README.md`、`tools/search_functions.md`、`tools/run_multi_formula.md`、`dev-tools/import_index_info_catalog.py` 本次把系统支持数据名目录沉淀为随 skill 发布的 YAML 预设,并同步收紧公式数据名与回测示例文档。`quant-buddy-skill` 不新增公式诊断中枢;公式真实出数验证仍以现有 `runMultiFormulaBatchStream` 流程为准。 - **新增系统支持数据名全量索引**:新增 `presets/index_info_catalog/`,按当时的数据分类拆分为 7 个 YAML 文件,覆盖期权波动率、A 股财务、龙虎榜、境外财务、宏观策略、行业分类和旧版财务数据,合计 2351 条系统支持 `index_title`。 - **新增导入审计清单**:`manifest.yaml` 记录来源 Excel、记录数、sha256、列名校验与总条数;`README.md` 说明全量目录的检索方式。原始 `.xlsx` 不进入 skill 发布包。 - **开发导入工具外置**:新增 `dev-tools/import_index_info_catalog.py`,只作为开发维护工具,不放进 `quant-buddy-skill/scripts/`,避免被误认为 skill 运行时能力。 - **明确数据目录分工**:`data_catalog.yaml` 定位为高频精选字段,新增港/美股 EBITDA、营业收入、龙虎榜净买额、GICS 所属指数等常用条目;全量系统数据名用 `rg "关键词" presets/index_info_catalog` 检索后,在公式中使用精确 `index_title`。 - **修正文档示例**:`tools/search_functions.md` 将旧 `MA("数据", N)` 示例改为 `平均("数据", N)`,避免引用平台不存在的 `MA()` 函数。 - **补充标准回测写法**:`tools/run_multi_formula.md` 增加单因子 TopN、多因子综合分 TopN、等权基准、指数基准/超额净值与交易成本签名确认说明,强调数据名必须来自 preset 中的精确 `index_title`。 - `SKILL.md`:版本号升至 `4.23.0`,目录树与 `presets/` 使用规则同步更新。 --- ## [4.22.2] — 2026-06-18 **变更文件**:`SKILL.md`、`scripts/executor.py`、`scripts/call.py`、`references/troubleshooting.md`、`tools/run_multi_formula.md`、`tools/resume_job.md`、`workflows/global-rules.md` 给批量公式接口(`runMultiFormulaBatchStream` / `resumeJob`)的错误处理加上统一的 `category` 标签 + 分层指引,让 Agent 分清「该改公式 / 后端超时 / 连接断了 / 后端崩」,不再瞎改公式或空转重试。新增字段向后兼容,缺字段有兜底。 - **修 bug**:服务端真错误码放在 `errorCode`,旧代码只读 `code` 永远拿到笼统 `"FATAL"`;现优先读 `errorCode`(`scripts/executor.py`)。 - **四类标签**:`input_error`(改公式)/ `server_timeout`(别改公式别马上重试)/ `server_error`(隔会儿试一次)/ `transport_recoverable`(唯一该 `resumeJob` 续传的),每个带 `retryable` + `guidance`。关键区分:`server_timeout`(任务多半已死)≠ `transport_recoverable`(任务还活着、线断了)。 - **客户端只透传不覆盖**:以服务端给的 `category` 为准,仅在服务端没给时补默认、给客户端自产码贴标签(`scripts/executor.py`、`scripts/call.py`)。权威分类表在服务端仓库,不在本包。 - **文档指引**:`references/troubleshooting.md`、`tools/run_multi_formula.md`、`tools/resume_job.md`、`workflows/global-rules.md` 各加按 `category` 写动作的对照表。 - **顺带修**:实时进度行(`✓`/`✗`)此前按提交顺序猜公式名、把报错贴错公式,改为按事件自带身份标注(`scripts/executor.py`)。 - `SKILL.md`:版本号升至 `4.22.2`。 --- ## [4.22.1] — 2026-06-17 **变更文件**:`SKILL.md`、`scripts/executor.py`、`scripts/call.py` 本次小版本修复 `runMultiFormulaBatchStream` 的一类**接口接收错位**:客户端把后端的报错吞掉、误报成"流中断",且成功结构与文档承诺不一致。纯客户端接收层修复,工具参数与 Agent 用法不变。 - **修复误报 `STREAM_INTERRUPTED`(`scripts/executor.py`,主修复)**:`_consume_sse` 此前默认所有响应都是 SSE 流,对后端用普通 JSON(HTTP 200)回的参数/格式校验错误(`INVALID_FORMULA` / `TASK_ID_REQUIRED` / `FORMULAS_REQUIRED` / `INVALID_FORCE_REUSABLE` / 公式数超限等**一整类**)逐行解析时匹配不到任何 SSE 帧,被当噪音丢弃,最终落到兜底分支误报"SSE 流中断,3 次续传仍失败"。现进入 SSE 循环前先按 `Content-Type` 分流:非 `text/event-stream` 的响应直接当 fatal **原样透出**后端 `error`(含 `code` / `message` / `usage` 用法提示),不再吞错。 - **兜底文案不再撒谎(`scripts/executor.py`)**:从未收到 `ready` 事件(续传循环根本没进)时,错误码改为 `NO_READY_EVENT`、文案改为"未收到 ready 事件即结束;请先检查公式格式/载荷与网络,再怀疑传输";已握手后续传耗尽才报 `STREAM_INTERRUPTED`,且续传次数用真实计数(`call_run_multi_formula_batch_stream` 与 `call_resume_job` 此前写死"3 次")。 - **成功/失败结构归一化成文档结构(`scripts/call.py`)**:SSE `done` 事件返回的是 compact 负载(顶层 `status`+`summary`+`results`、无 `code`/`data` 外壳、字段叫 `indexinfo_id`),与 `tools/run_multi_formula.md` 承诺的同步版结构不一致——`_process_run_multi_formula_batch` 此前因键名错配在 SSE 路径上是死代码。现重写为三态:fatal 透出 / compact 包成 `{code:0, success, data:{status,summary,results}, task_id}` 信封(注入 `data_id = indexinfo_id` 供 `readData` 串联、保留 `indexinfo_id` 向后兼容;失败/部分失败时顶层补 `message` 与 `errors[]`)/ 旧同步形态维持原失败提升逻辑。 - **`trace_id` 不再泄露给 LLM(`scripts/call.py`)**:除顶层/`data` 层外,部分失败时 `trace_id` 还会出现在 `results[]` 失败条目里。现一并从 `data.results[]`(及兼容旧 `results[]`)收集首个非空 `trace_id` 写入 session 供后续续传/调试,再从所有层级剥离;`deferred`(research_24h)响应按 spec 例外保留。 - `SKILL.md`:版本号升至 `4.22.1`。 --- ## [4.22.0] — 2026-06-12 **变更文件**:`SKILL.md`、`scripts/executor.py`、`presets/dimensions.yaml`(新增)、`tools/select_by_composition.md`(新增)、`workflows/composition-select.md`(新增) 新增「已物化维度组合选股」能力:对"某个已上线维度分 TopN / 排名 / 名单 / 推荐"类问题,直接读取 `presets/dimensions.yaml` 选择 `indicator_id`,调用 `selectByComposition` 完成横截面组合选股/筛选,不走公式引擎,响应更快。 - **新增 `selectByComposition` 工具**(`scripts/executor.py`):注册 `selectByComposition -> POST /selectByComposition`,超时 120s(轻量 DB 查询,无需按公式任务长时间等待)。 - **新增已物化维度目录**(`presets/dimensions.yaml`):从 `qw_indicator` / `qw_dimension` 导出,包含 A 股动量与反转、基本面质量、异动监控、形态健康、趋势结构、相对强度等 score/screen 指标,每条记录含 `indicator_id`、`direction`、`output_type`、`asset_scope`、`calculation` 说明;`selectByComposition` 直接用 `indicator_id` 查库。 - **新增工具文档**(`tools/select_by_composition.md`):说明 `selectByComposition` 的参数(`mode`/`universe`/`composition`/`screens`/`sort_by`/`top_n`/`with_breakdown`)、典型调用、输出要点与失败处理(`INDICATOR_NOT_FOUND` / 404 时退回 `quant-standard.md`)。 - **新增 leaf workflow**(`workflows/composition-select.md`):命中条件(当前截面 TopN + 语义匹配 `dimensions.yaml` + 不要求回测/历史/IC/下载)、执行步骤(`newSession` → 读 `dimensions.yaml` → `selectByComposition`)、失败退回 `quant-standard.md` 规则。 - **SKILL.md 路由与目录更新**: - 原生工具优先列表新增 `selectByComposition`; - `newSession` 前置工具列表新增 `selectByComposition`; - 目录树新增 `workflows/composition-select.md`、`tools/select_by_composition.md`、`presets/dimensions.yaml`; - 场景路由表新增「已物化维度选股 / 维度分 TopN / 推荐股票」行,路由至 `composition-select.md`;原「量化选股 / 回测」行补充"或目录无匹配维度、需要临时构造指标"的说明。 - `SKILL.md`:版本号升至 `4.22.0`。 --- ## [4.21.2] — 2026-06-11 **变更文件**:`SKILL.md`、`scripts/call.py`、`scripts/self_update.py`、`references/troubleshooting.md` 本次小版本完成「强拦截 → 软升级」闭环:旧版本用户不再因为服务端版本配置被业务接口硬拦截;普通接口成功响应携带版本心跳,客户端仅记录待更新状态,并在下一次 `newSession` 时再静默安装与激活新版,避免影响当前任务上下文。 - **成功响应版本心跳**(`scripts/call.py`):普通业务接口成功返回时识别 `skill_latest_version` / `skill_update_available` / `skill_update_enforced:false` / `skill_self_update`;命中新版时写入 `output/.self_update_state.json`,状态为 `pending`,激活策略为 `next_newSession`,当前业务结果照常返回。 - **长 session 不强制切换**(`scripts/call.py`):如果用户正在同一个 session 里连续使用,后台只沉淀待更新信息,不在当前上下文中替换文件或要求重读文档;等下一次 `newSession` 时再执行更新,并让新会话使用新版规则。 - **`newSession` 版本检查降频**(`scripts/call.py`):`/version/check` 仍作为主动检查与诊断入口,但默认按 TTL 去重,避免每次新建会话都重复触发远端检查;可通过 `QBS_FORCE_VERSION_CHECK` 强制检查。 - **禁止自动降级**(`scripts/call.py`、`scripts/self_update.py`):当服务端 `latest_version` 低于或等于本地版本时,客户端会忽略该更新信号并标注 `skill_update_ignored_reason`;`self_update.py` 直跑也会 no-op 跳过,避免测试/灰度环境配置滞后时把新版覆盖回旧版。 - **自更新更稳妥**(`scripts/self_update.py`):下载超时提升到 5 分钟,新增至少 3 次重试;安装流程使用 staging / lock / 原子替换思路,并保留 `config.json`、`config.local.json`、`output/`、`logs/` 等用户配置与运行态数据。 - **调用入口更健壮**(`scripts/call.py`):工具白名单运行时从 `executor.TOOL_ROUTES` 源解析,避免在 `call.py` 二次硬编码漂移;同时优化 stdin 读取,减少无输入或输入不规范时的挂起风险。 - **排错文档同步**(`references/troubleshooting.md`):补充软升级、业务 `code:-1`、版本心跳与待更新状态的判断口径,帮助 Agent 区分 HTTP 成功、业务错误与升级提示。 - **最终答案呈现补强**(`SKILL.md`、`workflows/fast-snapshot.md`):成功查数后必须把工具 JSON 转写成人类可读结论;默认隐藏 `task_id`、`_quota`、`skill_update_available`、`skill_self_update` 等运行态/升级字段,避免简单查询被原样 JSON 淹没。 - `SKILL.md`:版本号升至 `4.21.2`。 --- ## [4.21.1] — 2026-06-09 **变更文件**:`SKILL.md`、`scripts/formula_package.py`、`presets/assets_db/stock_a.yaml`、`presets/assets_db/stock_us.yaml` 本次小版本两件事:把公式任务包的调用流程并入与其它工具一致的统一规范;并扩充本地资产库支持的境内外 ETF。 - **公式任务包调用流程对齐统一规范**(`scripts/formula_package.py`):此前该脚本独立于 `call.py`,注册/取数/管理会绕过「先 newSession + 版本检查」前置,且请求不带 `task_id` / `user_query`。现复用 `call.py` 的 session 读取与版本守卫—— - 调用前统一做**版本守卫**:session 创建版本与当前 skill 版本不一致时返回 `SKILL_VERSION_MISMATCH`,提示先 `newSession` 再继续; - 每个请求自动注入 **`task_id` / `user_query`**(连同既有的 `x-skill-version` 头),与其它工具「每个请求带当前版本 + user_query」的固定口径一致; - `register` / `list` / `revoke` / `refresh` 等需 api_key 的管理类操作,未建 session 时直接返回 `SESSION_REQUIRED`,强制先 `newSession`; - 对外取数 `query` 仍保持**无凭证、无版本门禁**(浏览器 / 第三方只读直连行为不变),从 skill 内调用时附带 session 上下文供服务端审计。 > 配套:服务端 `registerFormulaPackage` / `refreshFormulaPackage` / `listFormulaPackages` / `revokeFormulaPackage` 同步要求 `task_id`(缺失返回 `TASK_ID_REQUIRED`);该改动在服务端仓库,不在本 skill 内。 - **新增支持资产:境内外 ETF**(`presets/assets_db/stock_a.yaml`、`presets/assets_db/stock_us.yaml`): - **A 股场内 ETF**:宽基(上证50 `SH510050` / 沪深300 `SH510300` / 中证500 `SH510500` / 中证1000 `SH512100` / 中证A500 `SZ159361` / 创业板 `SZ159915` / 科创50 `SH588000` / 科创芯片 `SH588990`)、行业(有色 `SH512400` / 钢铁 `SH515210` / 煤炭 `SH515220` / 化工 `SH516120` / 建材 `SZ159745` / 养殖 `SZ159865` / 半导体设备 `SZ159516` / 红利低波 `SH512890`)、跨境(恒生 `SH513210` / 恒生科技 `SH513180` / 中概互联 `SH513050` / 港股科技 `SH513010` / 港股通互联网 `SZ159792` / 中韩半导体 `SH513310`)、商品与债券(黄金 `SH518880` / 石油 `SH561360` / 十年国债 `SH511260` / 城投债 `SH511220`)。 - **美股及境外 ETF**:SPDR 系列(`SPY.A` / `XLF.A` / `XLE.A` / `XLK.A` / `XLV.A` / `KRE.A` / `KBE.A` / `FEZ.A`)、iShares 安硕系列(`SOXX.O` / `LQD.A` / `HYG.A` / `EWT.A` / `EWZ.A` / `EWJ.A` / `EWU.A`)、VanEck `SMH.O`、KraneShares `KWEB.A`、全球X `ARGT.A`,以及 `JETS.A` / `MAGS.BAT` / `IGV.BAT` / `DWPP.O` 等。 - ⚠️ ETF 仍按所在市场口径:A 股场内 ETF 行情正常,估值/财务以工具返回为准;境外 ETF **仅支持行情价格**,不支持估值/财务。 - `SKILL.md`:版本号升至 `4.21.1`;目录树资产计数同步为 `stock_a.yaml` 5540 条、`stock_us.yaml` 1068 条。 --- ## [4.21.0] — 2026-06-05 **变更文件**:`SKILL.md`、`scripts/formula_package.py`(新增)、`tools/formula_package.md`(新增)、`recipes/formula-package.md`(新增)、`config.json` 新增「公式任务包(Formula Package)」对外取数能力:把一组公式注册成长期任务包,得到 `package_id` + `signature`,之后**无需 API Key**即可凭凭证流式取数(SSE),底层数据更新自动重算。适合前端页面 / 第三方只读接入。 - `scripts/formula_package.py`(新增):公式包客户端,子命令 `register` / `query` / `list` / `revoke` / `refresh`;复用 `executor.py` 的 `load_config` / 无代理 opener / skill 版本头;`query` 走 SSE 解析并组装为 `outputs` dict,签名可由本地凭证自动补全;注册/轮换成功后凭证落盘 `output/formula_packages/<package_id> -
config.json 145 B
{ "endpoint": "https://www.quantbuddy.cn/skill", "auth_endpoint": "https://www.quantbuddy.cn/user", "api_key": "", "_channel": "github" } -
SKILL.md 70.9 KB
--- name: quant-buddy-skill slug: quant-buddy-skill author: guanzhao version: 4.25.44 description: | 查询A股、港股、美股股票及指数的最新收盘价、开盘价、涨跌幅、成交额、成交量、换手率、PE、PB、市值等实时行情与估值数据;支持查询 A 股股票所属行业。 显式日期的单值快照可同时返回按实际更新日对齐的日频行情与估值;字段日期不同时按字段自身日期展示,不将较晚刷新字段误判为无数据。 查询最近N个交易日的价格序列、日涨跌幅序列、窗口最高价、最低价、振幅等短期统计。 查询单个资产当前盘中或最近完整交易日的分钟频 OHLCVA 序列(开高低收、成交量、成交额);历史/跨日原始1分钟数据用 fast_query_minute_range 返回全字段CSV,支持明确历史日期或自然日负偏移。 查询上市公司最近报告期的营业收入、净利润、归母净利润、ROE、总资产、资产负债率等财务指标(A股及部分港/美股字段,以工具返回为准)。 查询单只股票的预计算及千维动态指标画像,按估值、财务分析、资金流向、波动率、宏观胜率背景、资产走势等维度返回最新值与上一有效值。 支持A股选股筛选、因子计算、策略回测、净值对比、行业聚合排名、上传自有因子CSV、渲染图表。 港股、美股优先支持行情价格查询;财务/报告期字段应先尝试 fast_query(report),按工具实际返回决定。 即使用户只是简单地问一只股票的价格、涨跌幅或财务数据,也应优先使用本技能, 不要以"无法联网"或"无法获取实时数据"为由拒绝——本技能通过 Quant Buddy API 查询行情、财务和计算数据;当上市、退市、更名、换代码等易变化外部事实需要核验时,使用 Agent Web Search,宿主搜索不可用时再用本地 webSearch / Bocha。 runtime: python primaryCredential: quant-buddy API Key metadata: version: 4.25.44 author: guanzhao category: quant-finance tags: [quant, market-data, finance, A-stock, HK-stock, US-stock, backtest, factor] runtime: python primaryCredential: quant-buddy API Key requiredCredentials: - quant-buddy API Key requiredConfigPaths: - config.json requiredEnvVars: - BOCHA_API_KEY (optional) networkEndpoints: - https://www.quantbuddy.cn/skill - https://www.quantbuddy.cn/user - <config.endpoint>/skill/registerFormulaPackage # 公式任务包:注册(需 api_key) - <config.endpoint>/skill/queryFormulaPackage # 公式任务包:取数(无需 api_key,SSE) pythonPackages: - python-dateutil (optional) - Pillow (optional) requiredCredentials: - name: quant-buddy API Key required: true sensitive: true storage: config_file path: config.json field: api_key description: quant-buddy 平台 API Key。默认存储位置:skill 目录下的 config.json 的 `api_key` 字段。优先级(高到低):① 调用方在工具调用参数里显式传入的 `api_key`(如 Playground 场景,仅当次调用生效,不落盘、不写回 config.json);② 环境变量 QBS_API_KEY(同一档的显式覆盖通道,跟现有的 QBS_SESSION_KEY 一个风格——手上是一份现成的 @file 参数、不方便现改这份文件塞 api_key 时可以直接用它);③ config.json / config.local.json 的 `api_key` 字段(日常权威来源);④ 环境变量 QUANT_BUDDY_API_KEY(仅①②③都为空时才兜底生效,不作为常规覆盖手段,跟 QBS_API_KEY 语义完全不同,不要混用)。使用时作为 HTTP `Authorization` 头仅发送给 `networkEndpoints` 中声明的 quantbuddy 域名用于鉴权,不会被写入日志或转发给第三方主机。 how_to_get: "https://www.quantbuddy.cn/login" requiredConfigPaths: - path: config.json required: true description: Skill 目录下的 API Key 配置文件,仅包含 quant-buddy api_key 和两个公开端点配置,由 skill 本地脚本读取;api_key 仅作为 HTTP `Authorization` 头发给 `networkEndpoints` 中声明的 quantbuddy 域名,不发送给其他主机。 requiredEnvVars: - name: BOCHA_API_KEY required: false sensitive: true description: 可选。scripts/event_study_local.py 的通用外部事实搜索读取,用于事件研究以及上市、退市、更名、换代码等时效性事实核验;Agent 宿主搜索可用时优先使用宿主能力。未配置时本地 Bocha 兜底自动禁用,其它功能不受影响。 how_to_get: "https://open.bochaai.com" networkAccess: true networkEndpoints: - https://www.quantbuddy.cn/skill - https://www.quantbuddy.cn/user runtimeRequirements: python: "3.8+" packages: - name: python-dateutil version: ">=2.8" required: false description: Used by scripts/event_study_local.py for optional Bocha-backed external fact verification, including event study and asset status changes. Not needed if BOCHA_API_KEY is not configured. - name: Pillow version: ">=9.0" required: false description: Used by scripts/call.py saveChart command to convert chart images to JPEG. Falls back gracefully (writes raw bytes) if not installed; no credential exposure risk. --- # 观照量化投研 > **首屏优先**:先读本文件前部的「平台工具参数速查」「硬规则」和「场景路由」。简单行情、窗口序列、最近报告期、固定区间收益、K 线图等高频任务命中 Fast Path 时,无需继续整本通读。 ## 平台工具参数速查(高频踩坑,先看这一段) > 下表是 LLM 最容易写错的三个 schema。任何调用前先核对,不要凭"看起来合理"猜参数名。 | 工具 | ✅ 正确参数 | ❌ 模型常见错误(已被 call.py 自动归一化或拦截,但仍应避免) | |------|------------|------------------------------------------------------------| | `confirmDataMulti` | `{"data_desc": "市盈率 TTM,股息率"}` — **逗号分隔字符串** | `{"queries": [...]}` / `{"query": "..."}` / `{"names": [...]}` | | `runMultiFormulaBatchStream` 公式中引用 session 中间变量 | 必须用**双引号**包裹:`排序値 = "A股股息率〔估値数据〕" * "条件合并"` | 裸变量名相乘:`"A股股息率…" * 条件合并` ← 平台直接报错 | | `readData` | `{"ids": ["69fe…<24位hex>"], "mode": "last_column_full"}` — **必须是 `runMultiFormulaBatchStream` 返回的 `data_id` 字段(hex)** | 传中文变量名 `{"ids": ["Top10股息"]}` / 用错参数名 `{"index_title": "..."}` / `{"variable_names": [...]}` / **传 `expression_id` 而非 `data_id`**(两者相邻易混,传错会返回 `"error": "IndexInfo {id}"`)| **口径转换(confirmDataMulti 查询词)**:用户写 `PE(TTM)` / `归母净利润` 等英文或缩写时,查询词应使用**中文规范名**(如 `市盈率 TTM` / `归母净利润`),而不是把用户原文照抄进 `data_desc`。详细规则见 `workflows/global-rules.md#指标口径精确匹配`。 --- ## 已有文件转活页:先交 QBV 静态托管 用户提供 JPG/PNG、HTML、PDF 等已有文件并要求转活页或公开分享时,优先进入 QBV `workflows/existing-file-static-first.md`:先 file_prepare 保存原件、生成原始承载页和持久发布参数,保留 file_publish_dir 执行静态发布,验收后在下一次工具调用前先向用户发送链接,file_confirm_delivery确认实际发出的消息,再自动继续已授权的研究、纠错、补指标和QBS接入。复合需求也不能先研究再发布。此顺序高于资产映射、查数、公式验证、图表分类器及“先计算再Handoff”;首版不要求计算胶囊,不先改写原报告。复用真实 task_id/turn_id 和同一个 page_id,不能另建用户可见任务。QBS查询/接入失败只影响增强,保留线上成功版本;未查到不等于平台不支持。纯文件分析、明确不发布不触发;真实公开授权、不可读文件、转换/首次托管问题须如实说明。不得将快照标为实时或已核验。 ## 硬规则(违反必失败) 0. **工具名与 unknown-tool 红线(最高优先级)**: - 公式执行唯一可调用工具名:`runMultiFormulaBatchStream`。 - 禁止调用或重试旧名/错名:`runMultiFormulaBatch` / `runMultiFormula` / `run_multi_formula`。 - 任何工具返回 `未知工具` / `Unknown tool` / `tool not found` 后,同名工具 **0 次重试**,也不得尝试名称变体。 - 若 workflow 已声明唯一正确原生工具,只允许切换到该工具 1 次;仍失败则立即输出受控失败答复。 - 若上一步结果已足够回答用户问题,必须直接收敛回答,禁止继续升级工具链。 1. **认证后验与 session 初始化**: - 不要在普通查数题第一步读取 `config.json`,也不要检查 `.session.json`、`output/.session*.json` 或任何本地 session 文件。 - 只要本轮准备调用平台原生工具,先直接调用原生 `newSession`;不得用 Bash / Glob / Read / ls 做 session 存在性探测。 - **quant-buddy-view 上游继承例外**:若当前任务由 quant-buddy-view 编排,且上游已经通过 `trace_context.py begin` 创建 task_id,不得再生成第二个 task_id。优先由 QBV 的 `scripts/qbs_bridge.py` 调用本技能;bridge 会传 `{"task_mode":"inherit","task_id":"<上游 task_id>","task_source":"quant-buddy-view","user_query":"<用户原始问题>"}` 并用 `QBS_SESSION_KEY=<task_id>` 隔离并发 session。此例外只用于跨 Skill 会话绑定。 - 继承 task_id 时必须使用显式 `task_mode=inherit`,不要通过 `qbv_` 等字符串前缀猜测来源。继承 session 会锁定 task_id;后续参数若传入不同值,必须按 `TASK_ID_CONTEXT_MISMATCH` 停止,不能静默拆链。 - quant-buddy-skill 独立使用时保持原行为:不传 `task_mode/task_id`,由 `newSession` 自动生成新的 UUID 并上报 session begin。 - 工具实际返回 `api_key 为空` / `code: 1` / 401/402 时才进入认证引导并停止当前查数任务。 - 同一对话追问可复用当前 session;新问题必须新建 session。 - 所有业务 HTTP/SSE 请求统一携带 `x-skill-name: quant-buddy-skill` 与当前 `x-task-id`,用于跨 Skill Trace 聚合;quant-buddy-view 上游任务不得切换 task_id。 2. **原生工具优先,禁止脚本包装**: - 平台已有原生工具时,必须直接调用原生工具:`fast_query`、`fast_query_minute`、`fast_query_minute_range`、`confirmDataMulti`、`selectByComposition`、`runMultiFormulaBatchStream`、`resumeJob`、`readData`、`renderKLine`、`renderChart` 等。 - 禁止用 Bash / shell / Python / `scripts/call.py` / `run_skill_script` 包装已有原生平台工具。唯一编排例外是 quant-buddy-view 的 `qbs_bridge.py`,它只负责继承 task_id 和隔离 session,不改写业务参数或结果。 - 只有平台明确不存在等价原生工具,且 workflow 明确允许脚本兜底时,才可使用本地脚本。 - **许可例外(csv 解析)**:当 `fast_query` 返回 `mode:"csv"` + `csv_url`(数据点 > 500 的正常交付)时,调用 `python scripts/fetch_fastquery_csv.py "<csv_url>"` 下载并解析该 csv 属于**许可路径**——这是消费工具返回的 OSS 产物(平台无等价原生解析工具),不算"包装原生工具"。历史分钟长表使用 `scripts/fetch_minute_range_csv.py @output/minute-manifest.json --output output/minute-data.json`,不能套用日频宽表解析器。但仍禁止用裸 `curl` / 自写临时脚本替代该脚本。 - 涉及资产时仍需先用 `grep presets/assets_db/{类型}.yaml` 搜索本地资产库,禁止整文件读取;命中多条先澄清,未命中再交给服务端兜底解析。 - 英文代码无市场后缀时必须先 grep 对应资产库确认 ticker 格式。 3. **工具失败熔断:同类错误不得重复** - 同一工具、同一参数结构、同一错误类型出现第 1 次后,只能按 workflow 声明的备用路径切换;无备用路径则受控失败。 - 禁止无新信息地重复调用失败工具;禁止尝试名称变体;禁止读更多文档代替执行;禁止用 shell/Python 包装绕过失败工具。 - `runMultiFormulaBatchStream` / `resumeJob` 只有最终 `completed` 且全部结果成功时才返回 `validation_receipt_file`;`failed`、部分失败、`deferred` 均不生成收据。QBV 编排必须以该收据作为进度完成证据。长结果可传 `output_mode:"summary"`:completed 保留 `data_id/expression_id/status`;deferred 额外完整保留 `status/task_id/trace_id/job_id/stream_url/_deferred`。deferred 缺 `task_id/trace_id` 时返回 `DEFERRED_CONTINUATION_MISSING`,禁止重提原批次。 4. **任何 workflow 失败退出时必须输出受控失败答复**:禁止以空白或纯过程日志结束对话。失败答复必须包含: - ①用户的原始问题(一句话复述) - ②失败卡在哪一步(工具名 + 错误摘要) - ③给用户的一句话说明("当前无法获取…,原因:…") - 可选④:用户可采取的下一步(如"稍后重试"或"换用完整链路") 5. **先读 workflow 再操作**:按下方「场景路由」表加载对应 workflow,不要自行猜测参数格式。 6. **配置/认证错误立即停止,不得在普通查数流程中转为认证收集**: - **工具返回 API Key 缺失错误**(含 `api_key 为空` 消息 / `code: 1`):立即停止查数,输出**新用户引导消息**(格式见「前置条件」章节模板),禁止继续执行查数;等待用户粘贴 Key 后再执行配置向导。 - **其他工具报错**(网络、服务端错误等):直接报告"内部工具异常",不做认证相关引导。 7. **最终答案首句必须是数据结论**:回答用户时,第一句话必须直接给出数据结论(如资产名+数值、表格、或"符合条件的共N只"),绝对禁止以"已成功获取""数据已获取""根据返回结果""让我来"等过程性陈述开头。违反此规则 = 必须删除过程话术后重新输出。 - **禁止原样粘贴工具 JSON**:工具返回 `code:0` / `success:true` 后,最终答复必须把 `data.results` 等业务字段转写成人类可读结论(一句话、短表格或名单)。除非用户明确要求"给我原始 JSON / 调试输出",否则不得把完整工具响应原样发给用户。 - **隐藏运行态字段**:最终答案默认忽略 `code`、`success`、`task_id`、`_quota`、`skill_latest_version`、`skill_update_available`、`skill_update_enforced`、`skill_self_update`、`auto_upgrade*`、`version_check` 等运行态/升级字段;这些字段只供 Agent 判断流程,不是给普通用户看的答案内容。 - **版本心跳不打断业务回答**:若业务 `data` 已成功返回,即使响应体带版本心跳,也必须先回答用户问题;只有工具明确返回业务失败或 `SKILL_VERSION_MISMATCH` 时才进入自愈/排错流程。 8. **用户条件冻结,不得改写**:执行前必须逐字核对用户原始条件,以下改写行为均属违规(一旦发现必须回退并重新确认): - **百分比↔小数互转**(如"股息率>3%"禁止改写为 `>0.03`) - **相对时间改为年份区间**(如"过去10年"禁止改写为"2015-2025") - **资产宇宙替换**(如"普通股票"禁止改写为"万得全A成分股"或"非ST股") - **事件口径扩大**(如"年报/半年报"禁止扩大为全部业绩披露类型) - **卡片附加条件继承**:命中知识卡片后,若卡片含用户未明确提出的"首次/非ST/封板/流动性门槛"等附加条件,必须先删除再执行,禁止默默继承进最终答案 9. **任务含糊时先反问,禁止猜测开干**:若用户的指令有 **2 种以上合理解读**(如"批量确认X"不清楚是确认指数本身还是全部成分股、"分析一下Y"不清楚要哪个维度),**第一步必须向用户提问澄清,不得凭推测选择一种解读自行执行**。反问应简洁列出各种可能(例:"您的意思是 ① … 还是 ② …?"),等用户确认后再继续。**唯一例外**:用户语义明确无歧义(如"给我贵州茅台今日收盘价"),无需反问。 **⚠️ 模糊词处理规则(先判断是否真歧义,再决定反问还是默认口径直行)**: 下列词在量化语义中存在多种定义,必须正确处理: - **技术分析类**:支撑位 / 阻力位 / 压力位 / 颈线位 / 关键位 / 关键点位 / 突破位 - **走势判断类**:趋势 / 趋势预测 / 后市判断 / 还能不能涨 / 会不会跌 / 短期看法 / 中线看法 - **盘面定性类**:异动 / 主力 / 主力流向 / 庄家动向 / 强势 / 弱势 / 抗跌 / 抗跌性 - **健康度类**:基本面好不好 / 估值贵不贵 / 财务健康 / 业绩怎么样 / 基本面 **判定流程(按顺序匹配,命中即停)**: 1. **综合分析请求 → 用默认口径直行,禁止反问阻塞**。 判定:用户在一句话里列出 ≥2 个分析维度(如"基本面 + 技术指标 + 趋势"、"估值 + 财务 + 走势"),或明确说"全面分析 / 综合看一下 / 给一份报告"。 做法:直接走 `stockProfile`(综合画像)+ 常规技术指标 + 默认趋势口径,**报告首句**告知用户使用的口径(例:"本次按以下默认口径输出:基本面=综合画像(估值+财务+资金流+波动率),技术=MACD/KDJ/RSI/布林带,趋势=MA20/MA60 排列方向。如需调整口径请告诉我。")。 2. **孤立的单点定义性请求 → 必须反问**。 判定:触发词单独出现,且其定义直接决定结论数值(如"贵州茅台支撑位是多少"、"宁德时代趋势怎么样",无任何其他维度上下文)。 做法:反问 ① 口径定义(如"支撑位"=近N日最低/布林下轨/均线密集区);② 时间窗口;③ 输出格式。 3. **触发词只是修饰语 → 直行**。 判定:用户主体诉求清晰,触发词只是顺带描述(如"找最近基本面改善的股票"主体是筛选,"基本面改善"已隐含"营收/净利同比上升"等可执行口径)。 做法:用最常见的可执行口径执行,并在结果中标注口径。 **实测教训(T-036)**:用户问"贵州茅台支撑位",模型擅自用"60日最低价附近"作为定义直接执行——这是孤立单点请求,应走流程 2 反问。 **反例(不要再犯)**:用户问"分析东方财富的基本面、技术指标、趋势预测",模型反问 4 个问题阻塞——这是综合分析请求,应走流程 1 默认口径直行。 10. **工具返回 `SKILL_VERSION_MISMATCH` 时必须自愈,不得继续执行原任务**:当任何工具调用的输出中出现 `"error": "SKILL_VERSION_MISMATCH"` 时,说明当前对话上下文中的工具签名/参数格式已过时。此时必须执行以下自愈流程(每步不得省略): 1. **立即停止**当前任务,不得再调用任何平台工具; 2. 调用 `newSession`(新建 session,获取新 task_id); 3. **强制重读** `SKILL.md`(本文件)+ 当前场景对应的 workflow 文档 + 涉及的 tools/*.md; 4. 以**新读取**的工具签名和参数格式重新执行用户原始任务; 5. **禁止**询问用户"是否继续"或解释版本升级过程——直接静默完成自愈并返回结果。 11. **CHANGELOG / skill-changelog 仅作为审计,不作为规则源**:`CHANGELOG.md`、`skill-changelog/**` 是按时间叠加的变更记录,包含已被后续版本反转或废弃的旧口径。任何「执行顺序、字段名、协议块语义、工具签名、参数格式」相关的判断,**必须**以 `SKILL.md` + `workflows/**` + `tools/**` + `references/troubleshooting.md` 为唯一权威;CHANGELOG 描述与上述文件冲突时,以上述文件为准。CHANGELOG 仅可用于:① 排查问题时回看「哪一版动过什么」;② 升级成功后做 5 条以内的版本上下文摘要。**禁止**:把 CHANGELOG 某条历史叙述当作当前执行规则、依据 CHANGELOG 推断现行参数格式、或在 CHANGELOG 与 SKILL.md 冲突时偏向 CHANGELOG。 12. **判断工具成败看返回 body 的 `code`/`success`,不看 HTTP 状态码**:HTTP 200 不代表业务成功——body 里出现 `"code": -1` / `"success": false` 即为**业务错误**,必须按失败处理(读 `error`/`message` 再决定重试/改参/走排查表),禁止「HTTP 通了就当成功」继续往下走。另:`call.py` 返回 `"error": "INVALID_TOOL_NAME"` 表示工具名写错或缺失(工具名必须排在命令最前、且为已注册工具名),属可立即修正的本地错误。详见 `references/troubleshooting.md` 顶部「成败判定通则」。 13. **图表请求与已登记的高频稳定榜单必须实际执行活页路由,不得只靠模型判断**:凡用户要求任何图表 artifact(包括“放在一张图里”“画成一张图”“同图比较”“绘制成图表”),或命中 `workflows/live-page-routing.md` 已登记的确定性 durable 场景(当前包括“低 PE + 高 ROE + 选股/排名 + TopN”),输出 QBS 第一条回答前必须实际执行 `python scripts/live_page_routing.py route ...` 并保留 route JSON。**不得把所有 TopN/选股都视为 durable**;只有路由合同明确列出的窄场景才触发。命中“单资产 + 2~4 个 fast_query 标准历史字段 + 明确同图”时,必须在取数和静态渲染前读取并执行 `workflows/visual-page-fast-path.md`;不得读取 `quant-standard.md` 或 `render-kline.md`,不得先生成静态图。其他图表与 durable 场景按各自 workflow 的路由检查点执行。不得因为用户没说“活页/网页”、已经生成 PNG、已经读过规则、或模型自行判断应为 `none/create` 而跳过命令;必须保留 route JSON 作为本轮 Trace 证据。`create|existing_page` 且 QBS 已有排名、对比、回测、热力图等结构化 artifact 时,优先把最小业务字段写入 `skill/output` 下的请求 JSON,并执行一次 `python scripts/live_page_routing.py prepare-validated-page @output/...json`;该命令原子完成 computation capsule、Handoff 和幂等 Job,成功后禁止再手工执行 `handoff` 或 `prepare`。无结构化 artifact 时才使用通用 handoff → prepare。随后使用宿主真实提供的内部子 Agent 委派工具(优先 `spawn_agent`),只等待即时成功回执,绝不在首答前等待 QBV 完成;不得用 `create_thread/fork_thread` 代替内部子 Agent,也不得只口头声称已启动。若宿主没有内部委派工具,必须执行 `python scripts/live_page_routing.py mark-delegation-unavailable --qbv-job-id <ID>` 把 Job 置为 `DELEGATION_UNAVAILABLE`,不得遗留 queued Job,也不得声称页面正在生成。`source_skill_id` 有真实值就记录,缺失则标记 `unavailable`,不得阻断 Handoff。`none|suggest` 按分类结果继续 QBS。页面 direct/fork/unmatched、本人原位更新、他人复制和权限判断全部由 QBV 执行。路由或委派失败必须记录 Job 失败终态,但不得阻断 QBS 正常答案。只有用户明确只要 PNG/本地图片/表格或不要网页时不创建页面;弱“看看走势”仍保持 QBS。 14. **QBS→QBV 只复用本轮已经算完的部分,不把 QBV 改成 QBS 专用渲染器**:`create|existing_page` 在 Handoff 前优先运行 `scripts/qbv_computation_capsule.py build @capsule-input.json`,生成 `qbs_computation_capsule_v1`。胶囊必须同时包含用户核心问题/主图意图、资产规范化结果、可复现查询或公式合同及 fingerprint、结果快照或 artifact SHA256、字段映射、结论与验证收据;禁止只交 PNG 或一句总结。 同一业务 role 对应多个已物化结果时可传 `data_ids`;构建器按原顺序展开为 `role__01`、`role__02`…,保留原始 ID 字符串并同步 `required_roles`,禁止 Agent 手工改写或复制 ID。QBV 的 thin adapter 判定 `covered` 时不得重复识别资产或重算相同 role,`partial` 时只补 `missing_roles`,`unusable` 时无损回退原 QBV→QBS bridge;direct/fork/unmatched、ownership、构建、运行时注册、发布和验收仍完全归 QBV。用户直接使用 QBV 时不依赖胶囊,原 SOP 不变。 15. **已跑通的公式执行合同必须原样交给 QBV,禁止二次改写**:`runMultiFormulaBatchStream` 成功后,以 Validation Receipt 中的 `qbs_formula_runtime_contract_v1` 为唯一执行合同,保留 `formulas` 的条数、顺序、完整指标名、引号、`begin_date`、`include_description`、`use_minute_data`、`force_reusable_array`、`reads` 和 fingerprint。`prepare-validated-page` 必须把该合同写入 computation capsule;不得把平台已确认的 `"A股市盈率(PE, TTM)〔估值数据〕"` / `"A股净资产收益率ROE"` 缩写成 `PE(TTM)` / `ROE` 后交给 QBV,也不得把多条已验证公式合并成一条新公式。显式合同与 Receipt 不一致、fingerprint 不一致或输出左值不完整时必须失败关闭,不得猜测修复。准备交接 JSON 时,Receipt 已含原始公式就不要在 `validated_roles[].formula` 手抄第二份,也不要在每个 role 重复同一 Receipt;优先在顶层 `validation_receipts` 传一次 Receipt 对象或 Receipt 文件路径字符串,也可以让 `prepare-validated-page` 按同任务全部 `data_id` 自动发现,避免引号转义失败和无效重试。 ## Fast Path / Leaf workflow 顶部硬闸门(每次进入 leaf 都生效) > 修复 T-001 / T-011 / T-024 等 leaf 没把 `newSession` 当成首条强制步骤、跳过直接调平台工具的问题。 无论路由进入 `fast-snapshot` / `fast-window` / `fast-report-period` / `render-kline` / 任何 leaf workflow: 1. 当前 Skill Session 尚未建立时,调用任何平台原生工具前必须先调用 `newSession`;`newSession` 同时登记本 Session 的首个 Turn。 2. 同一 Session 收到新的用户消息(包括追问)时,必须先调用 `beginTurn`,参数中的 `user_query` 必须是本轮原话;同一 Turn 内连续调用多个工具时复用当前上下文,不重复 `beginTurn`。 - 正常 Agent 调用 `newSession` / `beginTurn` 时必须同时生成可选 `agent_intent`:用简短文字写清本轮要解决的对象、动作、约束和期望产物,推荐 20~160 字。追问中的“那它呢/和上一个比/继续”等指代必须结合已有上下文展开,但不得覆盖用户原话。 - `agent_intent` 示例:首问 `分析贵州茅台的盈利质量、估值水平与主要风险。`;追问“那和五粮液比呢?” → `延续上一轮贵州茅台分析,对比五粮液的盈利能力、估值水平与主要风险。`;带约束问题“只要近三年,做成一张表” → `整理目标公司近三年的核心指标并输出单表对比,不扩展到其他期间。` - 禁止直接复制 `user_query` 充当 Intent;禁止记录 Chain of Thought、内部推理步骤或未经数据分析的结论。老客户端、自动测试和无法生成 Intent 的旁路允许省略,服务端按 `null` 处理,不得阻断业务。 3. 只有真正开始新的独立 Skill Session 才重新 `newSession`。不允许用“已读 SKILL.md / leaf workflow”跳过首个 `newSession`,也不允许用重复 `newSession` 代替追问的 `beginTurn`。 - 新版 `newSession` 的单次版本检查可能同时返回可选 companion(当前为 `quant-buddy-view`):QBS 自身需要升级时必须先只升级 QBS 并 reload;QBS 已最新时才可安装、更新或补齐当前 Agent 的 companion 注册。companion 失败属于旁路错误,不得阻断当前 QBS 数据业务;只要返回 `reload_required=true`,最终回复必须明确提示用户重新加载 Agent,禁止忽略该提示后声称活页能力已在当前会话生效。 - **建议在 `newSession` 同时传 `agent_model`**:填入当前 Agent 的真实模型标识(例如 `gpt-4o` / `claude-sonnet-4` / `gemini-2.5-pro`);拿不准就留空,禁止猜测。 4. 未建立 Session 直接调用平台工具仍会返回 `MISSING_NEW_SESSION`。若新问题未先 `beginTurn`,或显式 `turn_id/user_query` 与当前 Turn 漂移,客户端必须取消不安全的 Turn 关联、保留本轮真实 `user_query` 并继续业务工具;仅输出追踪诊断,禁止因 Turn 记录失败停止回答用户,也禁止把调用错误挂到其他 Session。 ## 最小充分原则(任何动作前自检) > 默认走最窄路径;只在收到"明确不够用"的证据后,才扩大范围。 **每次准备读文件、调工具、扩大读取范围前,回答三个问题**: 1. **这一步要解决的具体问题是什么?** — 必须能用一句话写成"为了 X,所以做 Y",其中 X 是**已经发生**的需求,不能是"可能会需要 X"、"以防万一"、"先准备着"。 2. **有没有更窄的选项能完成同样的 X?** — 更下游的输出 / 更精简的文件 / 更少的字段 / 不调用这个工具直接构造。 3. **当前选择如果失败,下一步是什么?** — 如果答不上来,说明还没想清楚就在动手。 任一回答含糊 → 不做这一步。 **扩大范围的唯一合法触发**:上一步工具明确返回了"缺数据 / 字段不存在 / 失败",且失败原因可以追溯。不允许用"为了更全面"、"为了更准确"、"为了避免遗漏"作为理由。 > 这条原则覆盖:要不要多读一个文档;readData 读哪个变量;要不要为某个字段调 confirmDataMulti;公式自己写还是查现成数据集;以及所有未来出现的同类决策。 **工具层面落地**:调用 `confirmDataMulti` / `readData` / `runMultiFormulaBatchStream` 或加载额外文档前,必须在心里完成工具清单自检;**不要为执行清单而搜索、加载或读取 `recipes/tool-call-checklist.md`**。无论该文件是否已在上下文中,只在心里完成以下三条最小自检即可(这三条已是清单的浓缩版,不需要再去查原文): 1. 这次调用是否直接服务于用户当前问题? 2. 是否有更窄的输出或更少的字段可读? 3. 如果调用失败,下一步是否明确且只改一个维度? 顶层原则管"要不要做",清单管"具体怎么做"。 ## Skill 包根目录 **本 SKILL.md 所在目录即为 skill 根目录(`SKILL_ROOT`)**,下文所有相对路径均以此为基准。 宿主已将命令工作目录(cwd)固定为本 Skill 根目录时,**禁止再次执行 `cd`**,直接使用相对路径运行;仅在人工终端或宿主没有设置 cwd 时,才先切换到本目录。 ``` SKILL_ROOT/ ├── config.json ← API Key 配置(按需读取;非每题必读) ├── SKILL.md ← 本文件(入口 + 路由) │ ├── workflows/ ← 业务流程编排(路由目标) │ ├── fast-snapshot.md Fast Path:最新时点行情/估值(≤1000资产,标量/CSV) │ ├── fast-window.md Fast Path:最近N日序列/窗口统计(≤2500日) │ ├── fast-report-period.md Fast Path:最近报告期财务(≤1000资产) │ ├── quick-lookup.md 快速查数路由器 + 共享基础规则 │ ├── quick-snapshot.md 最新时点行情/估值快照(字段齐即停) │ ├── quick-window.md 最近N日短窗序列/窗口统计 │ ├── quick-report-period.md 最近报告期财务指标 │ ├── period-return-compare.md 固定区间累计涨跌幅对比 │ ├── stock-profile.md 单股预计算指标画像 │ ├── external-fact-verification.md 上市/退市/更名/换代码及知识冲突核验 │ ├── composition-select.md 已物化维度组合选股(selectByComposition 快路径) │ ├── global-rules-lite.md 精简全局规则(quick-window/period-return-compare 专用) │ ├── quant-standard.md 选股/回测/因子/图表标准流程 │ ├── live-page-routing.md QBS→QBV 非阻塞活页路由、Handoff 与 Job 合同 │ ├── visual-page-fast-path.md 单资产标准历史字段同图的取数复用快路径 │ ├── event-study.md 事件研究(给定或可识别事件后的窗口表现) │ ├── regime-segmentation.md 阈值区间/连续阶段识别与区间统计 │ └── render-kline.md K线图渲染与交付 │ ├── recipes/ ← 公式模板 & 工具用法(被 workflow 引用) │ ├── ma-crossover-backtest.md 均线金叉策略 │ ├── value-pe-strategy.md PE估值选股 │ ├── upload-custom-data.md 上传自有数据 │ ├── render-chart.md 渲染图表 │ ├── download-data.md 下载数据 │ └── industry-aggregation.md 行业聚合排名 │ ├── references/ ← 参考文档 │ ├── environment.md 环境依赖 │ ├── troubleshooting.md 故障排查 │ └── ru-billing.md RU 计费 │ ├── tools/ ← API 工具完整参数文档(默认不读;workflow 标注「必读」或报错时再查) │ │ ⚠️ 下表列出所有可用工具的**实际调用名**,调用时必须使用此名,不得变体 │ ├── fast_query.md → 工具名 `fast_query` 快速合并查询(行情/估值/财务,≤1000资产,支持CSV) │ ├── fast_query_minute_range.md → 历史单资产跨日1分钟CSV全列(日期/自然日offset) │ ├── fast_query_minute.md → 工具名 `fast_query_minute` 单资产当前盘中/最近完整日分钟 OHLCVA 序列 │ ├── confirm_data_multi.md → 工具名 `confirmDataMulti` 批量确认数据项存在性与维度(写公式前必查) │ ├── run_multi_formula.md → 工具名 `runMultiFormulaBatchStream` 执行公式批次(选股/回测/因子计算) │ ├── read_data.md → 工具名 `readData` 读取公式计算结果(需传 data_id,非 expression_id) │ ├── render_kline.md → 工具名 `renderKLine` 渲染 K 线图(直接传 ticker,无需提前跑公式) │ ├── stock_profile.md → 工具名 `stockProfile` 单股预计算指标画像(估值/财务/资金/波动/走势) │ ├── select_by_composition.md → 工具名 `selectByComposition` 已物化维度组合选股/筛选(不走公式引擎) │ ├── dimension_indicators.md → 工具名 `listDimensionIndicators` 按维度列出指标目录(细分+综合) │ │ → 工具名 `getIndicatorFormulas` 按指标名取该指标的完整公式组 │ │ ⚠️ 二者非平台原生工具,走 `python scripts/call.py <工具名>`(硬规则 #2 许可路径) │ ├── render_chart.md → 工具名 `renderChart` 渲染折线/柱状/面积图(需先有 data_id) │ ├── get_card_formulas.md → 工具名 `getCardFormulas` 按卡片名拉取完整公式组(量化场景使用) │ ├── scan_dimensions.md → 工具名 `scanDimensions` 九维度 IC 扫描(单股多维度预测力分析) │ ├── search_similar_cases.md → 工具名 `searchSimilarCases` 向量检索相似案例(设计策略前的 fallback 查找) │ ├── search_functions.md → 工具名 `searchFunctions` 检索平台函数名称与调用格式 │ ├── download_data.md → 工具名 `downloadData` 按 data_id 下载一维时序到 CSV/JSON │ ├── upload_data.md → 工具名 `uploadData` 上传自有因子 CSV,上传后可在公式中引用 │ ├── refresh_snapshot_time.md → 工具名 `refreshSnapshotTime` 强制刷新分钟数据截止时间(盘中实时场景) │ ├── resume_job.md → 工具名 `resumeJob` 续传 deferred 后台任务(配合 research_24h 使用) │ └── formula_package.md → 脚本 `scripts/formula_package.py` 注册公式组为「任务包」→ 凭 package_id+signature 无 key 取数(对外只读/前端页面接入) │ ├── presets/ ← 已验证的常用数据(按需加载) │ ├── cases_index.yaml 106 张案例卡片目录(量化标准场景必读,快速查数无需) │ ├── assets.yaml 常用资产(99 行精选,可一次读完) │ ├── assets_db/ 全量资产字典(按类型分文件,⚠️ 仅 grep 检索,禁止 read_file 整文件;不含指数成分股映射) │ │ ├── stock_a.yaml A 股 5316 条(SH/SZ,含场内 ETF) │ │ ├── stock_hk.yaml 港股 2858 条(HK 前缀;行情优先,财务以 fast_query 返回为准) │ │ ├── stock_us.yaml 美股及境外ETF 1069 条(.N/.O/.A/.BAT;行情优先,财务以 fast_query 返回为准) │ │ ├── index.yaml 指数 513 条 │ │ └── future.yaml 期货 239 条 │ ├── functions.yaml 常用函数(170 条) │ ├── data_catalog.yaml 常用精选数据集(高频 index_title) │ ├── index_info_catalog/ 系统支持数据名全量索引(2566 条,按业务分类分 YAML,grep 检索) │ ├── dimensions.yaml 已物化指标候选的本地快照,用于 selectByComposition 的常见快速映射 │ │ ⚠️ 细分指标、快照未命中项和实时可选状态,用 `listDimensionIndicators` 在线确认;公式口径用 `getIndicatorFormulas` │ ├── sectors.yaml 行业板块(742 条,10 个分类) │ └── themes.yaml 题材板块 │ ├── scripts/ ← 执行脚本 │ ├── call.py 工具统一入口(所有命令通过它调用) │ ├── executor.py call.py 的底层(禁止直接调用) │ ├── formula_package.py 公式任务包客户端(register/query/list/revoke/refresh,取数走 SSE) │ ├── quant_api.py Python SDK(供其他脚本 import) │ ├── auth/ 认证脚本 │ └── eval/ 评测脚本 │ └── output/ ← 输出目录(自动创建) ├── .session.<key>.json 当前 session task_id(按 QBS_SESSION_KEY 派生,多会话隔离) ├── ic_data/ IC 扫描结果 └── *.png / *.csv 图表和数据文件 ``` --- **全局 429 处理(所有路径均适用)**: | error.code | 处理 | |---|---| | `RATE_LIMIT_EXCEEDED` / `CONCURRENT_LIMIT` | 读 `retryAfter` 秒后**静默重试**,不向用户暴露 | | `WINDOW_QUOTA_EXCEEDED` | **立即停止**,读 `references/troubleshooting.md` 配额限流段,输出提示 | | `DAILY_QUOTA_EXCEEDED` / `DAILY_SCAN_EXCEEDED` | **立即停止**,输出:`⚠️ 今日额度已满,次日 00:00 重置。` | | `SERVICE_OVERLOADED`(503) | `retryAfter` 秒后静默重试 1 次,仍失败则告知"系统繁忙,请稍后重试" | --- ## ⛔ 执行顺序(路由前必读,所有场景必须遵守) 已有文件转活页先执行上方静态托管优先例外,不进入下表 QBS leaf;首次静态交付后的数据增强才按本节加载查询规则。 **无论匹配到哪个 leaf workflow,执行顺序固定为:** ``` ① read_skill_file(global-rules 版本,见下表) → ② read_skill_file(leaf workflow) → ③ 执行 ``` **步骤 ① 全局规则文件选择(按目标 leaf workflow 确定)**: | 目标 leaf workflow | 步骤 ① 读取的文件 | |---|---| | `fast-snapshot.md` | 无(Fast Path,跳过步骤 ①,直接执行) | | `fast-window.md` | 无(Fast Path,跳过步骤 ①,直接执行) | | `fast-report-period.md` | 无(Fast Path,跳过步骤 ①,直接执行) | | `quick-window.md` | `workflows/global-rules-lite.md` | | `period-return-compare.md` | `workflows/global-rules-lite.md` | | 其他所有 workflow | `workflows/global-rules.md` | - **步骤 ① 是硬前置条件**。确定目标 leaf 后,先按上表选择并读取对应 global-rules 版本,再读 leaf workflow,最后执行。 - Fast Path(fast-*.md)直接从步骤 ② 开始,无需步骤 ①。 --- ## 场景路由 **先识别用户意图,确定目标 leaf workflow;然后按上方执行顺序加载**: | 场景 | 触发词 | 目标 leaf workflow | |------|--------|----------| | 单资产历史/跨日分钟 | 用户明确历史交易日或跨日区间的原始1分钟/分时CSV | 确认唯一资产 → `tools/fast_query_minute_range.md` → `fast_query_minute_range`;不传fields/format/remove_nan | | 单资产日内分钟 / 分时序列 | 明确要求分钟、分时、1分钟、每分钟、日内 OHLCV、逐分钟开高低收/成交量;不含历史日期、区间或多资产 | 先按资产库规则确认唯一资产 → 直接调用 `fast_query_minute` → 成功即停 | | 最新时点行情 / 估值 / 基础信息(快照) | 最新价、今日收盘、最新涨跌幅、当前换手率、最新PE/PB/市值、所属行业… | Fast Path 条件满足 → 只读 `fast-snapshot.md`;不满足/无法查询 → `global-rules.md` → `quick-snapshot.md` | | 最近N日序列 / 窗口统计 | 最近5日、最近20日、近N个交易日、窗口最高/最低/振幅…(仅单资产、最近N日) | Fast Path 条件满足 → 只读 `fast-window.md`;不满足/无法查询 → `global-rules-lite.md` → `quick-window.md` | | 最近报告期财务 | 营收、净利润、归母净利润、ROE、总资产、总负债、资产负债率… | Fast Path 条件满足 → 只读 `fast-report-period.md`;不满足/无法查询 → `global-rules.md` → `quick-report-period.md` | | 单股指标画像 / 个股综合分析 | 分析一下XX个股、看一下XX这只股票、个股画像、指标概览、估值财务资金走势综合看一下、基本面和估值怎么样… | `global-rules.md` → `stock-profile.md` | | 最新上市/退市/更名/换代码或资产状态冲突 | 现在上市了吗、最新代码、是否退市;或本地资产库命中但平台返回 `ASSET_NOT_FOUND` | `global-rules.md` → `external-fact-verification.md`;外部事实与 Quant Buddy 数据状态必须分开判断 | | 单资产标准历史字段同图 | 一个资产 + 2~4 个价格/成交/估值标准历史字段 + “放在一张图里/画成图/同图比较” | **只读 `visual-page-fast-path.md`**;先 route,再一次 `fast_query`,一次命令准备 capsule + Handoff + Job;禁止进入 `quant-standard.md`、`render-kline.md` 和静态渲染 | | 申万一级行业近N日涨跌幅排名图 | 申万一级行业/行业板块 + 最近N日/最近一个月 + 涨跌幅 + 排名图/柱状图/可视化 | **只读 `industry-ranking-fast.md`**;固定一条行业聚合公式,只读 `indexinfo_id`,直接准备已物化 QBV Job;禁止进入 `global-rules.md`、`quant-standard.md`、行业 recipe 和 `renderChart` | | K线图(可视化) | 明确出现 K线/K 线/蜡烛图/OHLC/开高低收;普通“股价、成交量、PE 放在一张图”不属于 K 线 | `global-rules.md` → `render-kline.md`;输出首答前必须实际运行 `live_page_routing.py route`;明确“只要 PNG/不要网页”时 route 为 `none` | | 固定区间累计涨跌幅 | 从A到B、某年某月至某年某月、区间收益、累计涨跌幅、区间表现、多资产区间对比 | `global-rules-lite.md` → `period-return-compare.md` | | 数据下载 / 导出本地 CSV | 下载成CSV、导出到本地、保存到本地、下载历史数据 | `global-rules.md` → `recipes/download-data.md`;单资产单字段时序优先 `runMultiFormulaBatchStream` → `downloadData` → `write_skill_file`,禁止 Bash 兜底 | | 已物化指标选股 / 维度分或细分指标 TopN / 推荐股票 | 分数最高、综合分最高、维度分,或由已物化细分 score/screen 指标组成的推荐/选出/筛选 TopN | `global-rules.md` → `composition-select.md`(`newSession` → 本地快照匹配或在线目录确认 → `selectByComposition`) | | 高频稳定因子榜单:低 PE + 高 ROE TopN | 同时出现低PE/低市盈率、高ROE/高净资产收益率、选股/筛选/排名、TopN/前N;即使没说图表或活页 | `global-rules.md` → `quant-standard.md` 的“高频默认口径”;验证 TopN 后必须实际 route,`create` 时用三个已物化 `data_id` 一次 `prepare-validated-page`,QBS 先答、QBV 后台补链接;明确“只要表格/不要网页”则 `none` | | 维度指标库查询 / 指标口径与公式 | 平台有哪些维度、XX 维度下有哪些指标、XX 指标怎么算的/口径是什么/公式是什么、想按现成指标改口径 | `tools/dimension_indicators.md`(用 `scripts/call.py` 调 `listDimensionIndicators` → `getIndicatorFormulas`,非平台原生工具;要拿改过的公式跑数再转 `quant-standard.md`) | | 量化选股 / 回测 / 因子 / 图表 / 上传下载 | 选股、回测、均线、PE选股、因子、净值、上传CSV、下载数据、画图、多个指标放进同一张图…;或目录无匹配维度、需要临时构造指标/历史曲线/自定义公式 | `global-rules.md` → `quant-standard.md`;任何图表 artifact 在首答前必须实际运行 `live_page_routing.py route`,命中 `create` 后非阻塞交接 QBV | | 直接运行用户给定的公式链文件 | 「运行/跑一遍/执行这个文件里的全部公式」「公式链文件」「formula chain」「按这个 md/json 跑」 | `global-rules.md` → `run-formula-chain.md` | | 事件研究 | 复盘、历次、涨价、降息、加息、事件窗口、随后表现、超预期、不及预期、政策后表现…(给定事件或需先识别事件日) | `global-rules.md` → `event-study.md` | | 阈值区间统计 / 连续阶段 | 历次、每次、平均、回撤超过、从高点下跌超过、熊市区间、连续阶段、regime | `global-rules.md` → `regime-segmentation.md` | | 对外发布公式组 / 做取数页面 / 注册任务包 | 注册公式包、package_id、签名取数、做个能直接打开的页面/看板、前端实时取数、对外只读接口、第三方接入 | `tools/formula_package.md` + `recipes/formula-package.md`(用 `scripts/formula_package.py`,非平台原生工具)| > 上传、下载、画图不是独立场景——它们是 workflow 内的子步骤,workflow 文档会在需要时指引你读对应的 `recipes/`。 ### 路由硬排除(优先于触发词匹配) 以下规则在触发词匹配**之前**检查,命中即强制改道,不得被触发词覆盖: | 用户意图特征 | 禁止进入 | 强制导向 | 判断依据 | |-------------|---------|---------|---------| | 盘中/实时/当前/现在/今天/今日/当日 + 查询日内行情(涨幅排名、涨停、日内跌幅等) | `quick-snapshot` `quick-window` | `quant-standard.md`(优先匹配分钟频卡片) | 需要分钟频卡片的专用公式;`use_minute_data: true` 已是全局默认 | | 盘中/实时/当前/今天/今日/当日 + 全市场/板块 + TopN/排名/阈值名单/选股/筛选/信号 | `quick-snapshot` `quick-window` | `quant-standard.md` → 优先命中"实时横截面 TopN 排名"或"盘中阈值筛选_名单查询"微流程 | 这类高频短题有专用封闭微流程 | | 给出明确起止日期,只问区间累计涨跌幅/收益 | `event-study` `quick-window` `quant-standard` | `period-return-compare.md` | 本质是固定区间收益比较,不是因果窗口分析,也不是复杂量化流程 | | 行业/板块聚合排名(如"申万行业涨幅前5") | `quick-window` `quick-snapshot` | `quant-standard.md` | 需要横截面聚合,不是单资产序列 | | 阈值触发型离散事件识别(如"跌幅超过X%的次数",问每次后表现) | — | `event-study.md`(阈值触发模式) | 需先识别阈值事件日,再做窗口分析 | | 由阈值条件定义连续区间(如"历次熊市""回撤超30%的阶段") | `event-study` | `regime-segmentation.md` | 研究的是连续阶段而非离散事件后的窗口 | | "创近N日新高/新低"(不含"首次"修饰词) | 不得加"昨日未满足"条件 | 按**当前状态**判断(state check),公式只比较当前值与昨日的N日极值 | 只有用户明确出现"首次突破/首次跌破""新晋""今日第一次"时,才允许追加首次触发条件;详见 `quant-standard.md` | 判断口诀: - **有明确起止日 + 只问区间数值** → `period-return-compare`(固定区间收益比较) - **有事件 + 问"随后N天/月表现"** → `event-study`(因果窗口) - **有阈值条件 + 问"每次发生后表现"** → `event-study`(阈值触发模式) - **有阈值条件 + 问"连续阶段/区间内表现"** → `regime-segmentation`(连续阶段统计) 若用户请求满足以下任一模式,应优先判定为【快速查数任务】,按以下路由直接跳转,不得先进入其他 workflow: **Fast Path 条件(同时满足以下 2 点才可走 Fast Path;否则走完整链路):** - 所有目标字段属于 fast_query whitelist(价格/估值/财务/衍生/资金流向·南北向持股/商品现货·库存,以及 A 股所属行业基础信息字段,详见 `tools/fast_query.md`),不涉及自定义公式/选股/排名 - 非全市场横截面查询(不是"全市场排名/前N只/行业筛选"等场景) > 资产数量不再限制 Fast Path 路由(服务端支持 ≤1000 个资产)。超过 500 数据点时服务端自动返回 CSV 格式(OSS 下载链接),详见 `tools/fast_query.md` 限流与 CSV 模式段落。 **快速查数路由(按优先级依次判断,首个匹配即停):** 0. 用户明确要求**一个资产的 2~4 个标准历史字段放在同一张图中**,且未明确只要 PNG → 只读 `workflows/visual-page-fast-path.md`;这是页面主图快路径,不得继续读取 `quant-standard.md` / `render-kline.md`。 0a. 用户明确要求**申万一级行业最近 N 个交易日涨跌幅排名图/柱状图/可视化**,且未明确只要 PNG → 只读 `workflows/industry-ranking-fast.md`;不得读取 `global-rules.md`、`quant-standard.md` 或行业 recipe,不得调用 `renderChart`。 **分钟覆盖前置检查**:先确认市场/资产类型并按 [分钟支持范围](references/minute-data-coverage.md) 判断。A股/美股股票、国内期货起于2026-05-13,港股股票起于2026-05-20,国内指数起于2026-08-13;美国/香港指数及期货暂不支持。历史窗口早于边界必须先提示:全窗口越界不查;部分覆盖明确缺失范围,不静默改日期或宣称完整覆盖。此限制不套用于日频行情。 0. 用户明确要单资产历史/跨日分钟序列或分钟CSV → `tools/fast_query_minute_range.md`;只支持历史日期,完整返回所有列;窗口/offset互斥,不得偷换成日频或当日分钟。 1. 用户明确要**单资产**完整分钟/分时/逐分钟序列或分钟 OHLCVA,且未指定历史日期、日期区间、分钟聚合或多个资产 → 先按资产库规则确认唯一资产,调用 `fast_query_minute`;按索引配对返回的 `dates[]` 与 `fields.<name>[]`,保留 `data_scope/trade_date/timezone` 语义,成功即停。只问最新标量仍走 snapshot;历史/区间改走 fast_query_minute_range;多资产不能塞进任一单资产工具。连续期货只按所选 trade_date 解读 contract_info,不将 inferred 说成已实时核验;附加 warnings 不影响成功行情。 2. 用户是开放式单股综合指标概览(如“分析一下XX个股”“看一下XX这只股票”“个股画像”“指标概览”“估值财务资金走势综合看一下”),且不是只问单字段/明确窗口/IC 预测力 → `workflows/global-rules.md` → `workflows/stock-profile.md` 3. 时间锚点是"最近 N 日窗口/序列",或用户明确给出起止日期要求返回区间序列(如"从X日到X日每日的…走势/序列/数据"),或用户只说"最近走势/看走势"但未明确要图片/K线 → Fast Path 条件满足时读 `workflows/fast-window.md`,不满足则 `workflows/global-rules-lite.md` → `workflows/quick-window.md`;未给 N 时默认按最近 20 个交易日 4. 时间锚点是"最近报告期"且字段属于财务类 → Fast Path 条件满足时读 `workflows/fast-report-period.md`,不满足则 `workflows/global-rules.md` → `workflows/quick-report-period.md` 5. 用户明确要“K线 / K 线 / 蜡烛图 / OHLC / 开高低收” → 直接加载 `workflows/render-kline.md`;普通多指标同图已由步骤 0 截止 6. 其余(明确是最近完成交易日或当日的行情/估值/多资产对比,且**不含** 排名/筛选/全市场 语义)→ Fast Path 条件满足时读 `workflows/fast-snapshot.md`,不满足则 `workflows/global-rules.md` → `workflows/quick-snapshot.md` > **说明**:含"今天/今日/当日/当前/现在/实时/盘中"但仅查单资产行情字段,属于日内刷新场景,`fast_query snapshot` 已自动启用盘中刷新(等效 `use_minute_data: true`),应直接走 Fast Path;上方"路由硬排除"已拦截"今天 + 全市场/板块 + 排名/筛选",此处无需重复排除。 > 上述路由不需要先读 `workflows/quick-lookup.md`。 ### 关键红线速查(即使未读 global-rules.md 也必须遵守) 以下 4 条规则从 global-rules.md 摘录,**优先级最高**,对所有场景生效: 1. **事件定义冻结**:事件类型/范围必须**逐字匹配用户原始措辞**。用户说"年报/半年报"就只查年报和半年报,不得扩大到业绩预告/快报/季报;用户说"国务院或住建部"就只纳入该层级,不得扩大到央行/银保监会/地方政府。若认为用户定义可能遗漏,在回答末尾**建议**扩大,不得擅自扩大。 2. **evidence-only 回答**:最终答案只输出本轮工具结果直接支持的数值、日期、排名、口径说明。未经工具验证,禁止默认输出宏观归因、政策归因、方向性判断("通常""往往""偏正面")。 3. **去过程化交付**:禁止「已成功获取」「让我来」「按照流程」「Step 1/2/3」「根据 workflow」等过程性话术;禁止泄露 `_working/` 路径、checkpoint 名称、workflow 文件名。查到即答,不展示内部过程。 4. **条件口径冻结**:用户条件必须原样执行,禁止任何改写(百分比↔小数、相对时间→年份区间、资产宇宙替换、卡片附加条件继承)。详见硬规则第 8 条。 5. **时效性事实核验**:涉及最新上市/退市/更名/换代码,或本地资产库命中但平台返回 `ASSET_NOT_FOUND` 时,必须读 `external-fact-verification.md`。`ASSET_NOT_FOUND` 只表示当前接口未识别资产,不得推导为公司未上市。 触发词参考: - 分析一下XX个股 / 看一下XX这只股票 / 个股画像 / 指标概览 / 估值财务资金走势综合看一下 → `stock-profile` - 最近交易日收盘 / 最新已披露PE / 最新市值(非盘中、非筛选) → `quick-snapshot` - 最近5日 / 最近20个交易日 / 近N日序列 / 窗口最高最低 → `quick-window` - 营收 / 净利润 / ROE / 总资产 / 总负债 / 资产负债率 → `quick-report-period` 禁止: - 优先调用 `scanDimensions`、`renderKLine`(除非用户明确要看图) - 先做分析性扩写,再补充结构化数值 - **在读取对应 leaf workflow 之前**直接调用 `runMultiFormulaBatchStream` / `renderKLine` / `scanDimensions` / `stockProfile` / 输出“无法联网”或“无法获取实时数据” - 资产已唯一命中 `presets/assets_db/future.yaml` 时,静态输出“平台不支持期货/期权”或“期货无法查询”;应先按行情/窗口序列工具链尝试,失败后只按工具返回说明当前品种或字段暂不可得 - 把卡片附加条件(首次/非ST/封板/流动性门槛等)默默继承进最终答案 - 以 `description`、`samples`、预览行、截断大表作为**名单题**的完整结果直接收尾(必须提取完整名单或明确声明不完整) **leaf workflow 最终回答合同优先**:leaf workflow 中的"最终回答合同"优先负责收紧该场景的输出格式;若 leaf workflow 已满足停止条件,必须直接按该合同输出,不得再解释内部过程。 ## 执行权授权规则 **规则层级(从高到低):** 1. **SKILL.md**:路由 + 全局门禁(硬规则 10 条、路由硬排除) 2. **global-rules.md**:所有 leaf 必须遵守的全局合同(执行合同、证据分级、简答模式、不补精度、方法限制说明、参数规范、数值精度、终答一致性检查) 3. **leaf workflow**:当前任务的具体执行流程(checkpoint、模板、停止条件、格式化) **冲突解决**: - leaf workflow 中的具体规则(如 readData 模式选择)优先于 global-rules 的一般规则 - 但 leaf workflow 不得**放宽** global-rules 的红线(如证据分级门槛、不补精度原则) - 不得从其他 leaf workflow 借用模板、fallback 或回答格式 **quick-lookup.md 的定位**: - 仅作为快查子流程的路由入口和规则参考总表 - 各 leaf workflow 已自包含所有执行规则,执行时无需回到 quick-lookup.md - quick-lookup.md 不定义任何 leaf 独有规则 ## 全局执行规则 > **全局合同详见 `workflows/global-rules.md`,进入任何 leaf workflow 时自动生效。** > leaf workflow 可在其内部添加更严格的约束,但不得豁免或放宽 global-rules 中的规则。 ## 平台数据覆盖范围 | ✅ 支持 | ⚠️ 有条件支持 | ❌ 不支持(短期内不会支持) | |------|------|------| | A股个股(沪深主板/创业板/科创板/北交所) | ETF / LOF / 场外基金(先 grep 本地资产库,能唯一命中则正常执行;未命中才告知不支持);期货行情/窗口序列(先 grep `presets/assets_db/future.yaml`,唯一命中后按工具返回尝试行情字段;不承诺估值/财务/K线图) | 期权 | | 港股个股(HK + 代码,如 HK0001) | | 台股 / 韩股 / 日股 / 德股等其他境外市场 | | 美股个股(NASDAQ: 代码.N;NYSE: 代码.O;AMEX: 代码.A) | | | | 主要宽基指数(沪深300、中证500、万得全A等) | | | > **港股 / 美股数据范围限制**: > - **行情价格类**(收盘价、开盘价、最高价、最低价、涨跌幅、成交量、成交额):A / HK / US 均支持。 > - **所属行业基础信息**:仅 A 股股票支持 `所属行业`,使用 `fast_query(snapshot/value)`;资产须为 `type=stock` 且 `market_id=1/2`。返
Comments (0)
Sign in to join the conversation.
Reviews (0)
No reviews yet.
No comments yet.