privora-a股-港股-黄金-基金-多资产-量化分析-量化回测-模拟盘-实时告警-风险监控-python-策略-ai-x

内容来源:clawhub · 原始地址 · 查看安装指南

原始内容


name: Privora · 数据驱动投资工作流平台 for AI Agents title: 🔬 Privora · AI Agent 投资工作流平台(A股/港股/美股/黄金/基金/财报数据 + Python 回测 + 模拟交易 + 组合归因 + 云端告警 + 流程编排) version: 1.0.47 updatedAt: 2026-07-30 keywords:

  • A股
  • 港股
  • 美股
  • 基金
  • 黄金
  • 财报数据
  • 业绩预告
  • 现金分红
  • 分钟K线
  • K线
  • 实时行情
  • 量化回测
  • 策略沙盒
  • Python回测
  • A股模拟盘
  • 模拟交易
  • 持仓监控
  • 组合归因
  • 净值曲线
  • AI Agent
  • 数据后端
  • 股票
  • 告警
  • 数据新鲜度 description: Privora · AI Agent 投资工作流平台 — Bearer Token 即接入 /agent/skills/execute 通用 dispatcher,覆盖 A 股/港股/美股/黄金/基金/财报数据 + Python 回测(含 sandbox)+ 模拟交易 + 组合归因(α/β TWR)+ 云端告警 + 流程编排。Hermes / Claude / GPT / OpenClaw 全兼容。 license: MIT-0 metadata: { "openclaw": { "emoji": "📈", "requires": { "env": ["LG_AGENT_BASE_URL", "LG_AGENT_TOKEN"] } } }

Privora · AI Agent 投资工作流平台(Bearer Token 即接入 · A股/港股/美股/黄金/基金 数据 + 回测 + 模拟交易 + 告警 + 流程编排)

给你的 AI Agent 一个统一的投资研究工作流后端 —— 数据查询 + 策略回测 + 组合归因 + 云端告警 + 流程编排一个 Token 全覆盖。

Hermes / Claude / GPT / OpenClaw 任何 Agent,通过一个 Bearer Token 即可访问:

  • 📊 多资产数据(按市场段分别发布,均 🟢 生产可用)日线 A 股 5500+ 股票(stock_day,含沪深300 / 上证综指 / 中证A500 / 深证成指 4 主要指数)+ 港股(stock_day_hk,如 00700.HK)+ 美股(stock_day_us,如 AAPL);分钟 K 线 A 股(stock_kline)+ 港股(stock_kline_hk),1/5/15/30/60 分钟;持仓黄金基金财报事件(业绩预告 / 快报)——一个 API 全覆盖,详见下方「数据资产可用性」表。
  • 🔔 7×24 云端监控:Serverless 策略托管,飞书 / 微信毫秒级预警,零服务器运维
  • 🧪 Python 策略回测:用同一份平台数据跑回测,输出 Sharpe / 最大回撤 / 交易明细
  • 🔒 加密静态、认证边界返明文:持仓数据在数据库中以 per-account 独立密钥密文存储(防 DB 层泄露 + 平台 admin 跨账户读取);持有你 Bearer Token 的 Agent 通过 API 认证后,平台按调用者身份解密并返回明文 —— 这不是 E2E 加密,Token 授权即数据访问权。
  • 🎯 1-click subscribe→alert:Agent 帮用户从"订阅 dashboard"到"配置 alert 上线"降到 1 step (2026-06-05 新增)
  • 🧾 模拟交易 (Paper Trading):MARKET / LIMIT 委托类型 + 调度器驱动 + 真实涨跌停 / 停牌信号,账户 + 订单 DB-level 幂等。

让普通人也能拥有私募级别的工作流——不需要私募的预算,就能像私募研究员一样在同一条流水线里跑数据 + 分析 + Agent + 告警。

🆕 最新版本 v1.0.45(2026-07-16)· lg_agent_exec.sh 支持命名参数扁平调用(key=value / key:=jsonvalue),不用再手拼 envelope JSON;旧 envelope 形式 100% 继续可用。历史版本变更见文末 §最近更新

🎯 最适合:想把整个投研工作流(数据查询 + 回测 + 模拟交易 + 告警 + 流程编排)交给 AI Agent 自动化的散户 / 小工作室;把 Hermes / Claude / GPT / OpenClaw 当量化助手用的开发者。注意:Bearer Token 是"工作流授权凭证",不只是"数据 API key" —— 授予前先按 §Scope 分类明白要给 Agent 哪些能力

🌐 产品主页https://privora.cn · 注册即拿 Token

演示


🌟 核心亮点

1. 🤖 兼容所有主流通用 AI Agent

打破生态壁垒,本技能不仅专供某一平台,而是完美兼容 Hermes、OpenClaw、Claude Code、GitHub Copilot 等所有支持外挂工具/技能的通用大模型 Agent。只需简单配置环境变量,您的通用 AI 助手瞬间化身专业量化分析师。

2. 🔒 加密静态 · 认证边界返明文(Encryption-at-rest, not E2E)

每个账户的持仓数据在数据库中以 per-account 独立密钥密文存储。这是防"库泄露 / 平台 admin 跨账户读取"的加密,不是 E2E 加密

  • 持有你 Bearer Token 的 Agent 通过 API 认证后,平台按调用者身份解密并返回明文 —— Token 是解密权的钥匙,保管好 Token 就是保管加密防线
  • 每个账户的加密密钥独立,平台管理员账号无法跨账户读取持仓明细(DB 层保证)
  • 订阅他人发布资产时,发布方看不到你的查询内容或账户信息(widget config 对订阅方 sanitize)
  • 给不可信 Agent 的 Token = 给它明文数据。按 §Scope 授予最小 scope,不要 bundle 无关能力

3. ⚡ Serverless 极速预警与零部署

策略云端托管运行,无需您购买第三方行情 API,无需自建服务器维护 Cron 任务,无 Token 消耗税。策略触发后,毫秒级推送到您的飞书机器人或微信 Webhook。


🛠️ 能做什么

核心功能 详细说明
资产盈亏巡航 一键查询持仓明细、当日盈亏、历史收益率,数据由 privora.cn 闭环处理。
云端自动盯盘 设置预警条件(突破均线、涨跌幅、换手率等),触发即通知,7x24小时云端值守。
多终端实时推送 策略触发毫秒级推送到飞书、微信 Webhook,不错过任何交易信号。
行情数据 日线按市场段分别发布,均 🟢 生产可用:A 股(stock_day,沪深京 5500+ 股票 + 4 指数)、港股(stock_day_hk,如 00700.HK)、美股(stock_day_us,如 AAPL);分钟 K 线:A 股(stock_kline)+ 港股(stock_kline_hk),1/5/15/30/60 分钟;基金日 NAV(fund_day);SGE 黄金日线(metal_day);财报事件(stock_forecast 业绩预告 + stock_express 业绩快报,11 年历史已回填)。详见下方「数据资产可用性」表。
Python 策略回测 用平台日线数据跑单股 / 多股组合回测,输出 Sharpe / 最大回撤 / 交易明细 / equity curve;结果持久化到 process_backtest_result,可通过 investment.stock.backtest.list 检索历史审计记录(平台已积累 44+ 次持久化回测)。
模拟交易 (Paper Trading) MARKET / LIMIT 两种委托类型,调度器驱动,模拟完整委托 → 成交 → 盈亏核算链路;账户按 user_name 唯一(DB-level UNIQUE),订单按 (user_name, client_order_id) 幂等,Agent 重复调用不重建。适合策略 6 阶段验证的最终纸面交易关卡。
用户声音收集 支持 Agent 代客户提交 Bug 和需求,无缝对接后台反馈系统。

数据资产可用性(2026-07-17 platform check)

发布模型说明:底层物理表按市场是合表存储的(同一张表可能物理上含多市场行),但对外发布是按市场段分别发布为独立 DataAsset 的stock_day 段族现已全市场覆盖并各自独立上线:A 股段 = stock_day,港股段 = stock_day_hk,美股段 = stock_day_us;分钟 K 线段族同理:A 股段 = stock_kline,港股段 = stock_kline_hk。三段各自独立发布、独立 asset id,调用时请用下表的 assetName,不要假设同一 asset 覆盖多市场。

DataAsset 状态 覆盖 频率 备注
stock_day 🟢 生产可用 A 股(沪深京)5500+ 股票日线 + 4 主要指数(沪深300 1B0300 / 上证综指 1A0001 / 中证A500 1B0510 / 深证成指 399001 id=1;000001 等 A 股 ticker 自动路由(segmentValues SH/SZ/BJ);399001 自 2026-03-17 起停更,请求会返回 meta.benchmarkWarning
stock_day_hk 🟢 生产可用 港股日线(如 00700.HK id=204;数据新鲜(2026-07-16 校验通过)
stock_day_us 🟢 生产可用 美股日线(如 AAPL id=206;由 stock_day_us_backfill_to_mc 任务从 PG 同步至 MC
stock_kline 🟢 生产可用 A 股 1/5/15/30/60 分钟 K 线(interval_type 区分周期) 分钟 id=202;日线 + 日内同步,由 stock_kline_daily_sync 等调度维护
stock_kline_hk 🟢 生产可用 港股 1/5/15/30/60 分钟 K 线 分钟 id=207;与 stock_kline 同结构,段独立
stock_minutes 已弃用 (旧) 分钟 K 线,已被 stock_kline / stock_kline_hk 取代 id=154;仅历史兼容保留,新集成请改用 stock_kline/stock_kline_hk
fund_day 🟢 生产可用 公募基金日 NAV 日 (T+1) 数据延迟约 1 个工作日
metal_day 🟢 生产可用 SGE 黄金 / 白银日线 上海黄金交易所
stock_forecast 🟢 生产可用 (NEW 2026-06-22) A 股上市公司业绩预告;11 年历史 82,457 行已回填 财报季 (1/4/7/10 月底前后) 集中发布
stock_express 🟢 生产可用 (NEW 2026-06-22) A 股上市公司业绩快报;11 年历史 19,945 行已回填 比业绩预告更精确但发布更稀疏
stock_dividend 🟢 生产可用 (NEW v1.0.32) A 股上市公司现金分红事件;进入 portfolio.attribution 归因 事件驱动 除权除息日发布

对 Agent 的指导:调用 dataasset.list 看完整列表;标 🔴 / ⚫ / ⚪ 的资产请避免在策略里硬编码依赖(⚪ = 已弃用,改用其后继 asset)。dataasset.metadata.get (2026-06-22 新上) 可查每张表的 lastUpdated / expectedUpdateCadence / cronExpression 来判断当前状态。

📌 本节是 2026-07-17 的一次平台盘点快照,随时间推移可能与实际覆盖漂移。各已发布资产的完整覆盖范围、分市场明细、更新频率与数据起始日期,见持续维护的公开清单页:privora.cn/features/realtime-minute-data-coverage(按六类分组,含 A股/港股/美股/北交所分市场明细)。


🛡️ Scope 与操作者责任

本 skill 是通过 Bearer Token 对接 Privora 平台的能力。操作类别的副作用不同,操作者负责按类别 scope token 并为需要的类别加入确认门槛

  • 只读(Read-only)—— 数据 API、回测结果查询、流程/调度/数据源/仪表盘/市场的 list/get。平台状态零副作用
  • 幂等写(Idempotent write)—— 模拟交易下单(DB 层 UNIQUE on user_name + client_order_id,同 key 重试返回同一记录)、marketplace subscribe(ON CONFLICT 返回已有订阅)、告警配置更新。同输入多次调用只产生一次逻辑效果,可安全重试
  • 流程状态转移(Workflow state transition)—— process.ingestion.execute 触发已授权 python_script 运行并写入 process_backtest_result 表;scheduler-instance 的 redo / hold / resume / reset-priority 转移 trigger row 状态。每次调用创建或修改持久化记录
  • 外发 webhook(Outbound webhook)—— schedule.job.plugin.webhook.trigger 与告警评估路径向操作者配置的外部端点(飞书 / 微信 / 通用 webhook)发送通知。副作用在 Privora 之外,平台不可撤销

本 skill 不暴露(需要人在 platform UI 手动完成):

  • 持久化记录的删除 / 撤销 / reset 操作
  • 调度器 online / offline 状态转移
  • Webhook 插件生命周期变更(删除 / 禁用)
  • 管理员级账户操作

本 skill 不预先声明任何操作是"agent-safe" —— 这个分类取决于操作者的风险偏好、agent 的可靠性、以及具体用例。推荐姿势:只读 + 幂等写允许 agent 自主调用,流程状态转移和外发 webhook 必须先经过用户确认门槛。

Token 使用建议

  1. privora.cn/profile/tokens 创建专用 Bearer Token
  2. 最小 scope 原则 —— 只授予当前 use case 需要的 scope(如:只读分析用 data.read;策略研究加 backtest.run;agent 真的要下模拟单才加 paper.trade)。不要为"以防万一"打包无关 scope
  3. 明确设置 LG_AGENT_BASE_URL=https://privora.cn
  4. Token 泄露立即 rotate —— Token Management 页面列出所有活跃 token 及最后使用时间戳和 revoke 按钮

🛑 绝对不要让你的 agent 代替你 mint token。Token 创建是 operator 动作,不是 agent 动作。Agent 应该消费 operator 签发的 Bearer Token,不应该自己调 POST /api/subscription/tokens

📑 输出仅供分析参考,不构成投资建议

本 skill 的输出(行情数据 / 组合分析 / 回测报告 / 模拟交易 / 告警评估)是供操作者审查的分析结果,不是投资建议、不是交易指令、也不能替代持牌财务咨询。

  • 把结果作为你自己决策过程的输入 —— 使用前请自行验证数据新鲜度、假设、边界情况
  • 实盘交易和不可逆的财务决策不应放在 agent 自动执行链路里 —— 模拟交易只是模拟;真钱交易必须走由操作者控制的券商链路并显式确认
  • 回测反映的是历史条件 —— 过去表现不预测未来结果。使用前请确认数据窗口、策略逻辑、以及生存偏差 / look-ahead 假设
  • 无监管咨询声明 —— 本平台是数据基础设施;下游任何投资决策由操作者本人(你)负责

🚀 快速接入 (Quick Start)

0) ⚡ 30 秒试一下(不需要注册 / 不需要 Token)

装完 skill 想立刻看看能干什么?打开 privora.cn/marketplace

  • 无需登录,直接浏览公开挂牌的 A 股 / 港股 / 美股 / 黄金 / 基金 / 财报事件等数据资产
  • 想一眼看完全部已发布资产的覆盖范围 / 更新频率 / 数据起始日期,不用一个个点开?看公开清单页 privora.cn/features/realtime-minute-data-coverage
  • 每个资产可以点进去看 25 行样本数据 + 20 字段元信息(lastUpdated / 数据源 / cron 表达式等)
  • 看到有价值的资产?记下 numeric asset id → 回到终端跑下面的 §4 First Call Recipe(3 步 / 约 2 分钟)立刻验证 Bearer Token 对同一资产能跑通 —— 无需额外配置,安装 skill 时的环境变量对同一资产完全生效
  • 觉得样本还不够?往下走 §1 - §4 注册生成 Bearer Token 拿完整访问权(分页 / 过滤 / 更高 rate limit / 写操作 / Agent 集成)

为什么先看再注册:Privora 是投研工作流平台,"你的数据是否值得订阅"应该 30 秒能判断 —— 不需要注册墙。marketplace UI 是发现工具,Bearer Token 是同一批数据的程序化访问入口,两者对应关系明确。

1) 获取您的专属 Token

  1. 注册并登录 privora.cn
  2. 在侧边栏点击你的用户名 → API Token Management,或直接访问 https://privora.cn/profile/tokens
  3. 创建一个仅包含所需 scopes 的专用 Token(建议先用只读或低权限 Token)
  4. 复制您的专属 LG_AGENT_TOKEN

2) 为您的 Agent 配置环境变量

在您使用的 Agent 终端(如 Hermes、Claude Code、GitHub Copilot 或 OpenClaw)中注入以下环境变量:

export LG_AGENT_BASE_URL="https://privora.cn"
export LG_AGENT_TOKEN="***"

公开版主要走以上 Bearer Token 方式;如果 Agent 只是浏览 marketplace / 预览已发布看板 & 资产,也可以走匿名模式(不需要 token,见下方 §🌐 匿名预览)。session cookie / CSRF 兼容调用不支持。

3) 唤醒 Agent,开始对话

现在,您可以直接用自然语言向您的 Agent 下达指令了!

4) ⚠️ 做出你的第一次成功 API 调用(3 步走 / 避免最常见的 500)

最容易踩的坑:URL 里的 {id} 必须是数字型 asset ID(如 42),不是 asset 名字(如 fund_day / stock_day)。传成名字后端 Spring 转 Long 失败会返回 500——错误信息不会明确告诉你原因。

正确的 3 步 recipe

# Step 1: 先 list 拿数字 id ← 别跳过这步
curl -H "Authorization: Bearer $LG_AGENT_TOKEN" \
  https://privora.cn/api/data-assets | jq '.data[] | {id, assetName}'
# 输出示例:
# {"id": 42, "assetName": "fund_day"}
# {"id": 8,  "assetName": "stock_day"}
# {"id": 15, "assetName": "stock_dividend"}

# Step 2: 用数字 id (不是 assetName!) 查元数据
curl -H "Authorization: Bearer $LG_AGENT_TOKEN" \
  https://privora.cn/api/data-assets/42/metadata

# Step 3: 用数字 id 查实际数据
curl -H "Authorization: Bearer $LG_AGENT_TOKEN" \
  "https://privora.cn/api/data-assets/42/data?page=1&size=10"

Agent 侧用 lg_agent_exec.sh 调用同理(v1.0.45 起支持命名参数扁平写法,不用手拼 JSON):

scripts/lg_agent_exec.sh dataasset.list
scripts/lg_agent_exec.sh dataasset.metadata.get id=42
scripts/lg_agent_exec.sh dataasset.data.get id=42 filter_column=stock_num filter_value=600519

id 必须是数字(先 dataasset.list 拿到再传,不是资产名字如 fund_day)。想看某个 skill 接受哪些 key,先 scripts/lg_agent_list.sh describe dataasset.data.get 看 schema + 示例,再照着填。

如果你已经踩到 500:不用改代码逻辑,只需把 {name} 换成对应的数字 id 即可。数据资产的可用列表见下方「数据资产可用性」表 —— 那里的名字对应 dataasset.list 返回的 assetName 字段,需要先 list 拿到本 team 里对应的数字 id。


🌐 匿名预览(无 token)

如果 Agent 只是想浏览 marketplace 或预览已发布的看板/数据资产/流程——比如帮用户看看 Privora 有什么数据源、有哪些现成看板可订阅、某个流程做什么用——不需要 Bearer Token 也能直接跑。这条通路和 privora.cn/marketplace 页面上未登录访客看到的内容是同一套数据,只是把它变成 machine-readable 的 skill 调用。

什么时候用

  • 用户还没注册,Agent 想先展示"这平台上有啥"
  • 用户已注册但当前 session 没配 token,你想让 Agent 先给个 marketplace 摘要
  • Agent 在做 discovery / recommendation,不需要写权限、也不涉及用户私有数据

怎么用

留空 LG_AGENT_TOKEN 或直接不传 Authorization header 即可:

# 无 token 调用 —— 直接返回 mode:"anonymous" + 10 个可用 skill
curl https://privora.cn/agent/skills

# 无 token 拿 marketplace 列表
curl -X POST https://privora.cn/agent/skills/execute \
  -H "Content-Type: application/json" \
  -d '{"skillId":"marketplace.item.list"}'

# 无 token 拿某个已发布看板的 widget 数据
curl -X POST https://privora.cn/agent/skills/execute \
  -H "Content-Type: application/json" \
  -d '{"skillId":"dashboard.data.get","params":{"pathParams":{"id":"<published-dashboard-uuid>"}}}'

响应体的 mode 字段会明确标 "anonymous"grantedScopes 列出下方 10 个允许的 skill。

匿名模式可用的 skill(全部只读)

skillId 用途
marketplace.item.list 列出所有可订阅的 marketplace 条目(看板 / 资产 / 流程)—— discovery 入口
dashboard.get 按 id 拿某个已发布看板的元数据 + widget 定义
dashboard.data.get 一次拿某个已发布看板所有 widget 的数据
dataasset.get 拿某个 allowSubscription=true 资产的详情
dataasset.schema.get 拿该资产的列 schema
dataasset.metadata.get 拿该资产的富元数据(lastUpdated / expectedUpdateCadence / cron / 数据源描述等 20 字段)
dataasset.data.get 拿该资产的历史数据(预览有效范围内)
dataasset.data.getRealtime 拿该资产的实时镜像数据(若配置了 realtime mirror)
process.ingestion.get 拿某个 allowSubscription=true 流程的结构(不含 stepCfg 源码
process.component.list 列平台可用的步骤组件类型(rendering diagram preview 用)

硬性约束

  • 只读白名单仅上表 10 个 skill 可调,其余 skill(包括其它只读 GET,如 investment.stock.portfolio.list / dashboard.list / dataasset.list)无论是否存在都返回 403 missing-scope。任何写操作(subscribe / create / update / delete)同样 403;哪怕手工构造匿名 PUT/POST 到底层 /api/**,Node 代理层也会先 401 拦截,不会到 Spring。

  • 每 IP 限流(三桶,任一超限即 429)

    范围 限额 429 响应体
    通用桶 所有匿名 skill 60 / IP / 分钟 {"success":false, "message":"Too many anonymous agent requests, ..."} (无 bucket 字段)
    数据爆发桶 dataasset.data.get + dataasset.data.getRealtime 10 / IP / 分钟 {"success":false, "traceId":"...", "message":"Anonymous data-fetch burst-limit exhausted (10/min per IP). ...", "bucket":"burst"}
    数据日总桶 同上 100 / IP / 天 {"success":false, "traceId":"...", "message":"Anonymous data-fetch daily budget exhausted (100/day per IP). ...", "bucket":"daily"}

    收到 429 时读 response.body.bucket 判断:"burst" 等 60 秒重试;"daily" 今日不再放行该 skill,建议引导用户注册 Bearer Token;无 bucket 字段则是通用 60/min 桶命中。

    Rate-limit 存储: v1.0.37 起三桶都存 Redis sorted sets (anonratelimit:{burst,daily,general}:<ip>),通过 Lua 原子脚本实现 sliding window。fleet 内所有 Node worker + 所有 host 共享一份 counter,反爬承诺真的成立。Redis 不可用时 fail-open:静默放行 + ERROR 日志 event:"rate-limit-redis-fail",反爬承诺仅在 Redis 健康时有效(运维监控 fail-open 日志识别异常)。

  • preview token 服务端自动签:不用你手工去 /preview-token 拿;Node 按 skill 类型选:dashboard 类(dashboard.get / dashboard.data.get要求调用方在 params.pathParams.id 里传目标看板 id,Node 会用该 id 签 dashboardId-bound token;未传 id 或其它 skill 一律降级为 standalone sentinel(等同 dataasset 类行为)。

  • 无效 Bearer 不降级:如果传了 Authorization: Bearer <bogus>,返回 401 而不会悄悄退回匿名模式给你部分数据。要匿名就别传 header。

  • 无跨租户 leak:所有资产读都走 canReadAsset gate;只有 allowSubscription=true 的资产/看板/流程会被返回,其它一律 404。跟浏览器 /marketplace 未登录访客看到的是同一套子集。

  • Dashboard-scoped token 不能跨团枚举:dashboard-A(发布者 = team-A)签的 preview token 无法通过 dataasset.metadata.get 读到 team-B 的资产元信息,哪怕 team-B 资产 allowSubscription=true。仅 standalone sentinel token 可读所有公开挂牌资产。

匿名模式下的能力受限(订阅后才解锁)

数据获取类 skill(dataasset.data.get / dataasset.data.getRealtime / dataasset.get / dataasset.metadata.get / process.ingestion.get)在匿名模式下服务端会自动应用以下约束,参数会被静默改写或剥离——不是 400,你的 curl 依然能正常拿到响应,但拿到的不是你请求的形状:

  • 分页强制固定 page=1, size=25:传任何其他值都被服务端硬覆盖。响应 pageSize=25, currentPage=1。想拿更多请订阅后用 Bearer Token 调。
  • 过滤 / 排序参数被静默清空filterColumn / filterValue / filterOp / orderBy / orderDirection 一律置 null 再进服务层。想按条件过滤请订阅后再来。
  • 发布者身份字段被剥离dataasset.get):dataSource / realtimeDataSource / businessOwner / technicalOwner / teamName / jobCode / createdBy / createdDate / updatedBy / updatedDate 一律返回 null。Metadata map 中 teamName / dataSource / jobCode / createdBy / createdDate / sourceDescription 也被剥离。
  • process.ingestion.getstepCfg 被剥离:匿名调用者拿不到 process step 的源码 / SQL / Python 内容。
  • totalElements 是哨兵值 0,不是真实总数:匿名调用不消耗后端 COUNT(*) 查询(防止 JDBC 池 DoS)。分页导航请以 data.length 为准。

配合分页固定 + rate limit,匿名调用者理论上每 IP 每天最多拿到 2,500 行数据(100 次 × 25 行)——真的想跑分析请注册。

匿名模式下常见的误用

  • ❌ 不要基于匿名 preview 数据做投资决策 —— 25 行不是完整数据集,这只是"试读",不是"取样"。
  • ❌ 不要用多 IP 池绕 rate limit —— 我们记录并封 IP 池行为,正确路径是注册 Bearer Token。
  • ❌ 不要期望 totalElements 反映真实行数 —— 匿名调用永远返回 0,这是设计意图,不是 bug。
  • ❌ 不要在匿名模式下尝试 filterColumn=... —— 参数会被静默丢弃,返回的是无过滤的前 25 行,不是过滤后的结果。

技术契约锚点(review 用):匿名 skill 白名单 = Node app.jsANONYMOUS_SKILL_SCOPES 常量;匿名 rate limit = canAnonymousAgentCall (60/min) + canAnonymousDataFetchCall (10/min burst + 100/day daily);preview + HMAC 验证 = docs/auth-flow-invariants.md §1 + §2.6 + §2.6.1;能力锁定实现与残留 test 缺口 = docs/plans/2026-07-07-anon-preview-dataasset-lockdown.md

从匿名 → 注册的漏斗

匿名浏览完,如果用户想真正订阅一个看板 / 用私有数据 / 跑回测,需要注册并领 token

  • 引导用户去 https://privora.cn/registermarketplace.item.subscribe 是 🟡 写操作,不在匿名 scope 里)
  • 或直接调 auth.user.register skill(也在匿名 scope 之外 —— 需要一层 signup 意图确认,见 §用户注册 & 反馈)

💬 典型应用场景

场景 1:查询账户今日盈亏(个人数据,仅自己可见)

您: “帮我查下今天的账户盈亏情况。”

Agent(调用 dataasset.data.get): “为您同步 privora.cn 的最新分析结果: 💰 当日盈亏: +319 元 | 累计浮动: -19,135 元 📊 持仓明细:

  • 中国核电:+2.06%
  • 永和股份:-32.45%
  • 中国联通:-16.25%”

场景 2:设定云端智能监控

您: “帮我监控贵州茅台,只要突破MA20均线就通知我。”

Agent(调用监控接口): “✅ 已在云端成功创建监控任务:

  • 标的:贵州茅台 (SH600519)
  • 条件:价格突破 MA20
  • 通知:飞书/微信推送 任务将在 Serverless 云端静默运行,触发时您将立刻收到推送。

场景 3:测试流程并抓取执行日志

# 触发执行(异步),记下返回的 executionId
# 自定义 CLI 参数直接当 flat key 传(key 以 - / -- 开头,与 process.ingestion.execute
# 的 Map<String,String> body 逐字对应)。后端会自动注入 `-f <procName>` —— 不用自己传 -f。
RESP=$(scripts/lg_agent_exec.sh process.ingestion.execute id=123 \
  -start_date=20260419 -end_date=20260420 --env=dev)
EXEC_ID=$(echo "$RESP" | jq -r '.executionId')

# 轮询日志,直到 completed=true
OFFSET=0
while :; do
  LOG=$(scripts/lg_agent_exec.sh process.ingestion.execute.log.get \
    id=123 executionId="$EXEC_ID" offset="$OFFSET")
  echo "$LOG" | jq -r '.logLines[]'
  [ "$(echo "$LOG" | jq -r '.completed')" = "true" ] && break
  OFFSET=$(echo "$LOG" | jq -r '.nextOffset')
  sleep 1
done
echo "exitCode=$(echo "$LOG" | jq -r '.exitCode')"

返回:statusrunning 过渡到 completedfailedexitCode 为脚本退出码,logLines 为增量日志行。

场景 4:策略回测(双均线跑茅台)

您: “用双均线(5日/20日)对茅台 SH600519 过去三年跑个回测”

在平台新建一个 python_script 流程节点,脚本如下(lg_utils 已预装):

💡 stock_day 回测用现成的 run_stock_day_backtest 就好——它已经把列名大小写(STOCK_NUM / OPEN_PRICE / CLOSE_PRICE)和日期格式(day_idYYYYMMDD)配好了,别再手动传 price_columns={“open”:”open_price”,...} 或 ISO 日期,那些是 2026-04-21 踩过的坑。

from lg_utils import get_variable
from lg_utils.backtest_examples.dual_ma import DualMA
from lg_utils.backtest_examples.stock_day import run_stock_day_backtest

result = run_stock_day_backtest(
    strategy=DualMA(fast=5, slow=20),
    stock_num=”600519”,
    start=”20220101”,
    end=”20241231”,
    initial_cash=1_000_000,
    commission_bps=3, slippage_bps=1,
    benchmark_asset=”stock_day”,            # 可选:跟某只指数/股票对比
    benchmark_filter_column=”STOCK_NUM”,
    benchmark_filter_value=”000001”,
)
print(result.summary())
result.export_to_context(“maotai_ma520”)   # stdout 日志快照
result.persist(name=”maotai_ma520”)         # 持久化到 process_backtest_result 表

组合回测(共享现金池、多标的同时跑):

from lg_utils.backtest_examples.stock_day import run_stock_day_portfolio_backtest
from lg_utils.backtest_examples.dual_ma import DualMA

result = run_stock_day_portfolio_backtest(
    strategies={“600519”: DualMA(5, 20), “000001”: DualMA(10, 30)},
    stock_nums=[“600519”, “000001”],   # 决定 size='all' 结算先后
    start=”20240101”, end=”20241231”,
    initial_cash=1_000_000,
)
# result.metrics[“per_asset”] 给出每只股票的贡献度/回撤/交易数

任务日志里会出现:

=== Backtest Summary ===
asset           : stock_day
period          : 20220101 ~ 20241231  (bars=725)
total_return    : 23.1500%
sharpe          : 0.8412
max_drawdown    : 18.2300%
num_trades      : 14
win_rate        : 57.1429%
__LG_BACKTEST_RESULT__:maotai_ma520:{"metrics":...,"trades":...}

完整 JSON(含 trades / equity_curve)会被下游节点或监控面板消费。

场景 5:一键 subscribe→alert deeplink (NEW v1.0.13)

您: "帮我配个告警,招商银行股价跌破 30 通知我。"

Agent 调用流程(之前 6 步深埋,2026-06-05 起 1 步):

# 1) Agent 帮用户订阅相关 dashboard
RESP=$(scripts/lg_agent_exec.sh marketplace.item.subscribe itemId=dashboard-china-merchants-bank-watch)

# 2) 从 response 拿到本租户的 cloned dashboard ID
DASH_ID=$(echo "$RESP" | jq -r '.clonedDashboardId')

# 3) 构造 1-click deeplink — Agent 把这个 URL 给用户
DEEPLINK="https://privora.cn/dashboards?selectId=${DASH_ID}&openAlerts=true"
echo "请打开此链接配置告警:${DEEPLINK}"

用户点链接进去,metric alert modal 自动打开——已经对准刚订阅的 dashboard,剩下用户填阈值 + 选 webhook 渠道 finalize 就完。user-in-the-loop 边界保留(敏感操作仍需用户在 web 上确认),但 5 步导航 + 选 dashboard + 翻 toolbar 找 "Alerts" button 这些都省了。

这是平台活跃用户反馈最集中的需求——以前的路径是:订阅 → 跳到 dashboard 列表 → 找到目标 dashboard → 打开 toolbar → 找 "Alerts" button → (第一次还要去 /datasources 配 webhook,回来再继续)→ 配置 → 保存。这次更新把这条路径压到 1 步

场景 6:模拟交易(paper trading happy path)

您: "用模拟账户跑一笔 600519 的市价买单 100 股,看看现在能不能成交。"

Agent 调用:

# 1) Agent 确认 / 创建模拟账户(账户 UNIQUE on user_name,重复调用幂等)
ACCT=$(scripts/lg_agent_exec.sh paper.account.create 'initialCashCny:=1000000')

# 2) 提交 MARKET 买单(client_order_id 是幂等键 — Agent 自己生成 UUID)
ORDER_ID="$(uuidgen)"
RESP=$(scripts/lg_agent_exec.sh paper.order.place \
  clientOrderId="$ORDER_ID" symbol=600519 side=BUY orderType=MARKET 'quantity:=100')

# 3) 查订单状态(同 clientOrderId 重试拿同一订单,不会重复下单)
scripts/lg_agent_exec.sh paper.order.get clientOrderId="$ORDER_ID"

支持涨跌停 / 停牌 / suspended-stocks 信号、scheduler-driven 撮合。Bearer Token 必须挂 paper.* scope。

适合的 use case:策略上真实交易前 12 个月 paper trade 验证(per 6 阶段量化研究流水线最终关卡)。

技能列表

REST 技能(scripts/lg_agent_exec.sh 调用)

公开版 skill 覆盖 4 类操作,见 §🛡️ Scope & Operator Responsibility 完整的 read / idempotent-write / workflow-transition / outbound-webhook 分类。删除、撤销、系统级审批等破坏性/管理类操作不在本 skill 范围内,需通过 platform UI 或 admin 工具完成。 风险标记:🟢 low / 🟡 medium / 🔴 high。所有 GET 技能默认对会话用户开放;写操作需显式授予 scope。

📦 Request shape (v1.0.45+): 命名参数扁平写法 —— 直接用 key=value 传参,不用手拼 JSON:

scripts/lg_agent_exec.sh dataasset.data.get id=42 filter_column=code filter_value=000135

等价于旧 envelope 形式 {"skillId":"dataasset.data.get","params":{"pathParams":{"id":42},"query":{"filter_column":"code","filter_value":"000135"}}}——网关会按每个 skill 的 path 模板自动把 flat key 分类到 pathParams / query / bodykey=value 一律当字符串(保留 stock_num=000135 这类前导零);数字/布尔/数组用 key:=value(如 qty:=100)。旧 envelope 形式 100% 继续可用,两种写法可以在同一次调用里混用(例:数组 body 用 --json,path 参数用 flat key)。想看某个 skill 接受哪些 key,跑 scripts/lg_agent_list.sh describe <skillId>。完整规则 + 历史踩坑 + envelope 手工写法见文末 §高级 / 兼容性附录

流程 (Process / Ingestion)

skillId method 功能 风险
process.ingestion.list GET 列出所有流程 🟢
process.ingestion.get GET 根据 id 获取流程详情 🟢
process.ingestion.execute POST 异步触发流程执行(返回 executionId)。body 接收自定义 CLI 参数,如 {"-start_date":"20260419","--env":"dev"}。后端自动注入 -f <procName>,不要自己传 -f 🟡
process.ingestion.execute.log.get GET executionId 拉取日志+状态,支持 offset 增量轮询。记录持久化在 process_execution 表 + 磁盘文件,重启不丢。 🟢
process.component.list GET 列出当前团队可用的步骤组件(含 Markdown 使用说明) 🟢
process.pipeline.build POST 一次性创建完整 pipeline(节点+组件+边)。python_script 节点的 stepCfg 必须使用执行器字段名 "script"(不是 "pythonScript",例:{"script":"import sys\nprint(sys.version)","requirements":"pandas"}process.component.list 返回的 form-schema 中的显示名 pythonScript 是 UI 表单专用名,不等于执行器运行时 JSON key——混用会导致执行时报 "Python script is empty or NULL"(2026-07-10 incident,已在后端加翻译层向前兼容,但规范写法仍推荐用 script)。 🟡
process.create POST 业务字段封装层POST /api/ingestions/create-simple),比 process.pipeline.build 更简单:不需要知道 node id / stepSeq / aftId/sStep/nStep/fStep 拓扑字段 / 画布坐标 / 执行器 stepCfg 信封——只填 {name, description?, steps:[{kind, label?, ...}]},后端自动组装成 process.pipeline.build 同款请求并复用同一条持久化路径。kind 是封闭枚举,只支持单链路顺序执行(不支持分支/for 循环/if 节点,那些场景仍用 process.pipeline.build)。详见下方 process.create 详情 🟡
process.pipeline.update PUT 全量更新已有 pipelinePUT /api/ingestions/{id},同形 BuildPipelineRequest)。nodes 省略=仅改名/描述,保留现有步骤;nodes=[] 显式清空;nodes=[...] 全量替换。每次 PUT 自动写一条 dacp_meta_proc_version,可 /versions/{n}/restore 回滚。legacy team_name IS NULL 的流程会直接 403,需先 backfill。想只改一个步骤的 label/conf/remark 而保留 DAG?用 process.pipeline.update_node PATCH,避免 aftId 等拓扑字段被默认值 "-1" 误清空。想只改流程名称/描述?用 process.pipeline.patch_meta 🟡
process.pipeline.update_node PATCH 部分更新单个步骤(PATCH /api/processes/{procId}/steps/{stepId})。字段掩码语义:仅 stepLabel / stepConf / remark 三个安全字段可被更新;缺失字段、显式 null以及空字符串 "" 都视为"跳过"(清空)。严格拒绝aftId / sStep / nStep / fStep(DAG 拓扑)出现在 body 即返回 HTTP 200 success:false, code:"TOPOLOGY_FIELD_REJECTED" —— 要改变 DAG 拓扑请用 PUT process.pipeline.update 全量替换所有节点。stepName / stepSeq / parentId 静默丢弃。要清空 stepLabel/stepConf/remark 字段也必须走 PUT 全量替换 —— PATCH 设计为"只增改、不清空"。此 skill 需要 process.pipeline.update_node scope(独立于 process.pipeline.update),现有 token 须重新签发方可使用。 🟢
process.pipeline.patch_meta PATCH 部分更新流程级别元数据(PATCH /api/ingestions/{id}/meta)。字段掩码语义:仅 procLabel / procDescr 两个安全字段可被更新;缺失字段、显式 null以及空字符串 "" 都视为"跳过"(清空)。严格拒绝nodes(拓扑变更)/ procName(标识符)/ creater(归属)/ teamName 出现在 body 即返回 HTTP 200 success:false, code:"FIELD_NOT_PATCHABLE" —— 要改名称/DAG 结构请用 PUT process.pipeline.update此 skill 需要 process.pipeline.patch_meta scope(独立于 process.pipeline.update),现有 token 须重新签发方可使用。 🟢

process.create 详情

路径:POST /api/ingestions/create-simple,scope process.create(独立于 process.pipeline.build,现有 token 须重新签发方可使用)。

为什么需要它process.pipeline.build 要求调用方懂平台内部的 DAG 拓扑(node id、stepSeqaftId/sStep/nStep/fStep 边字段、画布 x/y/width/height)和执行器 stepCfg JSON 信封格式。process.create 只暴露业务字段——一个有序、有类型的步骤列表;封装层在服务端补平台管道,不生成任何业务代码(SQL/Python/消息文本仍由调用方自己写)。

请求体

{
  "name": "fund_dividend_wrapper_test",
  "description": "可选描述",
  "steps": [
    { "kind": "run_sql", "label": "create tables", "sql": "CREATE TABLE IF NOT EXISTS ...", "dataSourceName": "pg_main" },
    { "kind": "run_python", "label": "fetch akshare", "script": "import akshare as ak\nprint(ak.fund_fh_em())", "pipRequirements": ["akshare"] },
    { "kind": "run_python", "label": "tushare enrich", "script": "import tushare as ts\n# ...", "pipRequirements": ["tushare"] },
    { "kind": "print_summary", "label": "done", "message": "fund_dividend ingest complete" }
  ]
}

kind 封闭枚举,映射到真实平台组件(已核实执行器 *StepMeta 字段名,不是猜的):

kind 平台组件 必填业务字段 可选字段 组装出的 stepCfg 信封
run_sql sql sqldataSourceName {"sql":..,"dsName":..,"needSplit":"true"}
run_python python_script script pipRequirements(字符串数组,pip 包名) {"script":..,"requirements":".."}requirementspipRequirements 换行 join;未提供则省略该 key)。注意 key 是 script 不是 pythonScript —— 同 process.pipeline.build 那条 2026-07-10 坑,封装层已按执行器真实字段名组装,调用方不会踩到。
print_summary print message {"message":..}

body 里没有 teamName / userName 字段——不是被忽略,是这个 DTO 上根本没有这个属性。租户归属 100% 走鉴权上下文(同 process.pipeline.build),payload 传了也不会被采纳。

拓扑组装(调用方不需要关心,仅供理解结果):第 i 步(1-based)node id = "<name>_step<i>"stepSeq="<i>",画布自动横向布局(120×60,间距 220)。相邻两步之间只支持单链路顺序执行——step[i]sStepaftId 都指向 step[i+1]stepSeqnStep/fStep 保持 "-1"。这是 fail-fast 语义:任一步骤失败即中止整个 pipeline,不会静默跳到下一步。不支持分支 / for 循环 / if 节点 / 并行——那些场景请用 process.pipeline.build 或先用 process.create 建好线性骨架、再用 process.pipeline.update 全量替换加拓扑。

返回:与 process.pipeline.build 相同的信封 {success, data:{id,name,description,created,teamName}, message}name 冲突返回 HTTP 409 {success:false, code:"PROCESS_NAME_EXISTS"}steps 缺失某个 kind 必填字段返回 HTTP 400 {success:false, code:"VALIDATION_FAILED", message:"..."}

示例调用:

scripts/lg_agent_exec.sh process.create name=fund_dividend_wrapper_test --json '{"body":{
  "description":"fund dividend ingest",
  "steps":[
    {"kind":"run_sql","label":"create tables","sql":"CREATE TABLE IF NOT EXISTS fund_dividend (...)","dataSourceName":"pg_main"},
    {"kind":"run_python","label":"fetch akshare","script":"import akshare as ak\nprint(ak.fund_fh_em())","pipRequirements":["akshare"]},
    {"kind":"run_python","label":"tushare enrich","script":"import tushare as ts\n# ...","pipRequirements":["tushare"]},
    {"kind":"print_summary","label":"done","message":"fund_dividend ingest complete"}
  ]
}}'

调度 (Schedule)

skillId method 功能 风险
schedule.job.list GET 列出调度作业 🟢
schedule.job.get GET 获取调度作业详情 🟢
schedule.workgroup.list GET 发现 当前平台注册的 workgroup / namespace(从已注册 broker 聚合),是 schedule.job.create 两个必填字段的唯一合法来源 🟢
schedule.scripts.get GET 发现 平台配置的 jobScript 默认模板({dp, sh, py}),给 schedule.job.createjobScript 字段用 🟢
schedule.job.create POST 创建调度作业(POST /api/schedule/jobs)。新建后 state="0",上线操作需通过平台 UI 执行。 🟡
schedule.job.update PUT 全量替换作业配置(PUT /api/schedule/job/{jobId})。全部字段都会被覆盖——缺失字段会被写成 null,可能清空 cronExp 等关键字段。推荐用 schedule.job.patch 做部分更新;PUT 只在需要显式清空某字段时使用。state 字段静默丢弃。 🟡
schedule.job.patch PATCH 部分更新作业配置(PATCH /api/schedule/job/{jobId})。字段掩码语义:只有非 null 字段会被覆盖到已存在的行,缺失字段和显式 null 都视为"跳过"。要清空字段请用 PUT schedule.job.updatestate / teamName 同样静默丢弃(与 PUT 一致)。此 skill 需要 schedule.job.patch scope(独立于 schedule.job.update),现有 token 须重新签发方可使用。 🟢
schedule.job.depends.list GET 列出作业依赖(按 jobCode) 🟢
schedule.job.depends.save POST 全量替换作业依赖列表(旧的先删再写) 🟡
schedule.job.plugins.list GET 列出作业绑定的插件(按 jobCode) 🟢
schedule.job.plugins.save POST 全量替换作业插件列表(旧的先删再写) 🟡
schedule.instance.list GET 列出作业实例(一次运行=一条 trigger 行);ops 操作所需的 jobTriggerId 都从这里拿 🟢
schedule.instance.status.get GET (jobCode, batchNo) 查单条最新状态,用于轮询 🟢
schedule.instance.log.get GET jobTriggerId 拉取执行日志 🟢
schedule.instance.redo POST 重跑失败/已完成实例(保留依赖链语义) 🟡
schedule.instance.hold POST 暂停运行中的实例(不杀进程,可恢复) 🟡
schedule.instance.resume POST 恢复之前 hold 住的实例 🟡
schedule.instance.reset_priority POST 调等待队列里实例的优先级(priority 1-9,越小越先跑) 🟡
schedule.job.lineage GET 作业的上下游依赖图(includeAssets=true 时附带每个节点的输出资产) 🟢
schedule.job.by_process GET 用 process 名反查 jobCode(拿到后才能调 ops skill) 🟢
schedule.broker.list GET 列当前注册的 broker(排"无人认领 workgroup"类问题时用) 🟢
schedule.broker.latency GET Broker 队列长度 + 消费速率 + 推算的等待延迟(诊断"上线但跑得慢"类问题) 🟢
schedule.job.plugin.webhook.trigger POST 手动触发作业绑定的 webhook 插件 🟡

调度作业字段契约

外部 agent 在调 schedule.job.create 之前,先走一遍"发现"(这几个字段没有硬编码枚举,值取决于当前部署):

  1. schedule.workgroup.list → 拿到 {workgroups, namespaces},从中各选一个赋给 workgroup / namespace传一个没人认领的 workgroup 不会报错,但没 broker 会去跑——这是最典型的"创建完成但永远不执行"陷阱。
  2. schedule.scripts.get → 拿到 {dp, sh, py},按 jobType 选对应字段赋给 jobScriptdp 作业用 dppython 作业用 pyshell 作业用 sh;空字符串表示该类型没有在这套部署上配好)。
  3. 如需参考现有同类 job:schedule.job.list + schedule.job.get 挑一个已上线的作业 clone 一份。

schedule.job.create / schedule.job.update 的 body(DataflowJob 形):

字段 必填 说明
jobCode 后端强制 团队内唯一业务编码。已存在时 create 幂等返回旧 jobId。
jobLabel UI 强制 展示名
jobType UI 强制 枚举:dp / datastash / python / shell
workgroup UI 强制 集群组名。合法值来自 schedule.workgroup.list,不要自己编
namespace UI 强制 命名空间。合法值来自 schedule.workgroup.list
jobScript UI 强制 执行命令行。默认模板来自 schedule.scripts.get(按 jobType 取对应字段)
batchType UI 强制 枚举:monthly / daily / hourly / minutely / once / daemon
cronExp 条件 Quartz 6 段式(秒起头),如 0 5 15 * * ?
jobParam 条件 JSON 字符串 数组"[{\"paramName\":\"-f\",\"paramVal\":\"my_proc\"}, ...]"jobType=dp 时后端按 paramName="-f" 自动回写 procName
procName 可选 dp 作业通常交给后端从 jobParam 反推;其他 type 可显式传
runConstraint 可选 "1"=顺序执行(默认),"2"=并发执行
batchNo / batchOffset / batchStep 可选 批次计算相关
jobPriority 可选 1–9,数字越小越高(默认 5)
redoNum 可选 失败重试次数
lastdtOffset 可选 最晚启动偏移(秒),0 为不宽限
maxElapsed 可选 最长运行时间(秒)
jobExtCfg 可选 ≤1024 字符的扩展配置 JSON
tag 可选 自由标签
jobDescr 可选 描述
state update 时静默丢弃,上下线状态变更需通过平台 UI 操作
服务端自动填充 jobId(UUID)、state="0"version=1teamName / memberName / createUser(取自会话)

schedule.job.depends.save 的 body(JSON 数组,全量替换):

[
  { "dependCode": "upstream_job_code", "dependType": "10", "isDefault": "1" },
  { "dependCode": "20260424",          "dependType": "20",
    "batchCalExp": "${batchNo?calDate(-1,'d','yyyyMMdd')}" }
]
  • dependType="10" — 任务依赖,dependCode另一个 jobCode(同团队内可见)
  • dependType="20" — 时间/批次依赖,dependCode 是时间字符串,batchCalExp 是批次偏移表达式(${batchNo?calDate(...)}
  • 其他字段:procNameoutputisDefault"1" 标默认)都可选
  • dependId 服务端生成(UUID16),不用自己传

schedule.job.plugins.save 的 body(JSON 数组,全量替换):

[
  {
    "pluginCode": "webhook",
    "state": "1",
    "pluginCfg": "{\"webhookDsName\":\"feishu_ds\",\"dataSourceName\":\"feishu_ds\",\"triggerStates\":[\"1\",\"-2\"]}",
    "isBlock": "0",
    "isDefault": "1"
  }
]
  • pluginCode + pluginCfg(JSON 字符串)为必填
  • state 为要监听的任务状态:"1" 成功 / "-2" 失败 / "2" 结束 / "0" 启动 / "-1" 中止(dacp_dataflow_job_trigger.state 的子集)
  • webhook 插件pluginCfg必须webhookDsName,否则返回 {"success":false, "message":"Webhook plugin requires pluginCfg.webhookDsName"}
  • jobPluginId 服务端生成

典型的"从零到调度可跑"流程(外部 agent 视角):

  1. schedule.workgroup.list + schedule.scripts.get → 发现合法的 workgroup / namespace / jobScript
  2. schedule.job.create → 拿到 jobId
  3. schedule.job.depends.save(至少一条依赖,否则上线后不会产生 instance)
  4. (可选)schedule.job.plugins.save → 绑 webhook 等插件
  5. 通过平台 UI 上线作业(state: 0 → 1)→ 让 broker 把它纳入触发域

作业运维决策手册

Ops 流程几乎总是先 schedule.instance.list(或 schedule.job.by_processschedule.instance.list)拿到目标 jobTriggerId,再按下面这张表选动作:

场景 推荐 skill 备注
失败了想重跑一次 schedule.instance.redo 保留依赖链;默认 opType="3",带依赖重跑
运行中但想先停住等数据就绪 schedule.instance.hold 不杀进程,可 schedule.instance.resume 恢复
等待太久想插队 schedule.instance.reset_priority 只对"在队列等待"的实例有效
查上下游会被哪些 job 影响 schedule.job.lineage 在平台 UI 操作前先看一下上下游影响
已知 process 名找对应 jobCode schedule.job.by_process 常用于从 Process 页面反向调 ops
排查"作业没有 instance" schedule.broker.list → 看 workgroup 有没有 broker;schedule.job.lineage → 看 depend 是否还卡着 第二常见的"不跑"陷阱
排查"在跑但很慢 / 积压" schedule.broker.latency stalled / 队列长度;若是 broker 瓶颈就不是 job 的问题
想看这次跑得怎么样 schedule.instance.status.get(单点)或 schedule.instance.log.get(看日志) 轮询建议用 status.get,日志用 log.get

数据源 & 数据资产 (Datasource / Data Asset)

skillId method 功能 风险
datasource.list GET 列出数据源 🟢
datasource.get GET 获取数据源详情 🟢
datasource.list.active GET 列出活跃数据源 🟢
datasource.connection.test POST 测试数据源连接 🟡
datasource.update PUT 全量替换数据源(含连接配置 dsConf)。想只改描述或显示标签?用 datasource.patch,避免 dsConf/dsAuth 被覆盖。 🟡
datasource.patch PATCH 部分更新数据源元数据(PATCH /api/datasources/{dsId})。字段掩码语义:仅 dsDescr(描述)/ dsLabel(显示标签)两个安全字段可被更新;缺失字段、显式 null以及空字符串 "" 都视为"跳过"(清空)。严格拒绝dsConf(更改连接目标会破坏运行中的 ETL)/ dsType(标识符)/ dsAuth / dsId / dsName / teamName 出现在 body 即返回 HTTP 200 success:false, code:"FIELD_NOT_PATCHABLE"此 skill 需要 datasource.patch scope(独立于 datasource.update),现有 token 须重新签发方可使用。 🟢
dataasset.list GET 列出数据资产 🟢
dataasset.get GET 获取资产详情 🟢
dataasset.schema.get GET 获取资产 schema(列名/类型);支持 ?refresh=true 绕过缓存实时重新采集 🟢
dataasset.metadata.get GET 获取资产富元数据:20 个字段,包含 lastUpdated(最近刷新时间)、expectedUpdateCadence(调度批次类型,来自关联 Job)、cronExpression(cron 表达式)、sourceDescription(数据源描述)。Bug #50 后续,用于程序化判断资产新鲜度。 🟢
dataasset.data.get GET 查询资产数据(盈亏、行情等,全量历史);支持 filter_op 过滤运算符(见下方详情)。MC 数据源资产速度较慢(2-5s)但包含完整历史 🟢
dataasset.data.getRealtime GET 查询资产的 实时镜像数据源(PG 热窗口,最近 365 天);低延迟,适合仪表盘 / 实时 P&L。资产未配置 realtimeDataSource 时返回 {success:false, message:"This asset has no realtime mirror"},不抛异常。路径:GET /api/data-assets/{id}/data-realtime 🟢
dataasset.history.field-as-of GET 点状时间(PIT)股票状态字段查询:is_st(ST/暂停挂牌)、market_board(板块)、delisted(退市)、industry(申万行业分类)。返回指定股票在某日期上的历史状态快照。依赖 PR-B ETL 完成首次回填后方可返回实际数据;回填未完成前返回 data:null。详见下方「dataasset.history.field-as-of」节。 🟢
dataasset.update PUT 全量替换数据资产元数据(PUT /api/data-assets/{id})。想只改 description/tags/allowSubscription?用 dataasset.patch PATCH,避免误改 sensitivityLevel 等不可改字段。 🟢
dataasset.patch PATCH 部分更新数据资产元数据(PATCH /api/data-assets/{id})。字段掩码语义:仅 description / tags / allowSubscription 三个安全字段可被更新;缺失字段、显式 null以及空字符串 "" 都视为"跳过"。严格拒绝sensitivityLevel(含别名 sensitivity_level / securityLevel / level)— 敏感级是 MONOTONIC,调整必须走专用 sensitivity-change 路径;assetName / assetType / tableName / teamName 等标识符也拒绝。allowSubscription=true 在 INTERNAL 资产上要求 tags 包含 permission_field:<col>,否则返回 code:"PERMISSION_FIELD_REQUIRED"(INLINE 复刻 PUT 路径的 publish-validation guard)。此 skill 需要 dataasset.patch scope(独立于 dataasset.update),现有 token 须重新签发方可使用。 🟢

数据资产 API 详情

dataasset.schema.get?refresh 参数

路径:GET /api/data-assets/{id}/schema

参数 必填 默认 说明
refresh false false:返回注册时缓存的 schemaInfo JSON(快速,无 I/O)。true:绕过缓存,对底层表做实时列采集,结果持久化回 schemaInfo 并写 last_schema_sync_at

refresh=true 时响应的 meta 字段会包含 lastSchemaSyncAt(ISO-8601 UTC 时间戳),标示本次同步完成时间。

示例(refresh=true):

{
  "success": true,
  "data": {
    "columns": [
      {"name": "stock_num", "type": "varchar"},
      {"name": "day_id",    "type": "int4"},
      {"name": "close_price","type": "numeric"},
      "..."
    ]
  },
  "message": "success",
  "meta": {
    "lastSchemaSyncAt": "2026-06-17T03:12:45Z"
  }
}

业务错误(底层数据源不可达 / 序列化失败 / 保存失败):返回 HTTP 200,{"success": false, "message": "Schema refresh failed: <原因>"} —— 符合平台 {success, data, message, meta?} 合约,不抛 HTTP 500。


dataasset.metadata.get — 资产富元数据

路径:GET /api/data-assets/{id}/metadata

参数 必填 说明
id 数据资产 PK(Long)

无分页,无请求体。返回 data 对象包含 20 个字段:

字段 类型 来源 备注
assetId Long 直接 资产 PK
assetName String 直接 资产名称
assetType String 直接 "table"
assetCategory String 直接 "market-data"
description String 直接 可为 null
tags String 直接 CSV blob,调用方自行拆分
sensitivityLevel String 直接 PUBLIC / INTERNAL / CONFIDENTIAL / RESTRICTED
allowSubscription Boolean 直接 是否可订阅
teamName String 直接 所属团队
createdDate LocalDateTime 直接 创建时间
createdBy String 直接 创建人
recordCount Long 直接 行数(去规范化,可能滞后)
sizeBytes Long 直接 字节数(去规范化)
lastUpdated LocalDateTime 直接 最近数据刷新时间(与 dataasset.list 命名一致)
lastSchemaSyncAt OffsetDateTime 直接 最近 schema 同步时间(UTC)
jobCode String 直接 关联 Job 代码(标量,可为 null)
expectedUpdateCadence String 派生 来自 DataflowJob.batchType;多 Job 时取第一个有 cronExp 的 Job;无匹配 → null
cronExpression String 派生 来自 DataflowJob.cronExp;同上选取逻辑
dataSource String 直接 数据源名称(文本)
sourceDescription String 派生 来自 DataSource.dsDescr;team-scoped join,订阅方通常为 null

响应示例(成功):

{
  "success": true,
  "data": {
    "assetId": 46,
    "assetName": "stock_day",
    "assetType": "table",
    "assetCategory": "market-data",
    "description": "A-share daily OHLCV bars",
    "tags": "PUBLIC,equity,daily",
    "sensitivityLevel": "PUBLIC",
    "allowSubscription": true,
    "teamName": "tenant_lg_data",
    "createdDate": "2025-12-01T08:00:00",
    "createdBy": "admin",
    "recordCount": 5234567,
    "sizeBytes": 8923847234,
    "lastUpdated": "2026-06-17T15:42:18",
    "lastSchemaSyncAt": "2026-06-16T03:00:00Z",
    "jobCode": "stock_day_sync",
    "expectedUpdateCadence": "daily",
    "cronExpression": "0 0 18 * * ?",
    "dataSource": "lg_data_pro_pg",
    "sourceDescription": "Primary LG Postgres lakehouse (read-only mirror)"
  },
  "message": "OK"
}

业务错误(资产不存在 / 跨团队访问):返回 HTTP 200,{"success": false, "code": "ASSET_NOT_FOUND", "message": "<原因>"} —— 符合平台 {success, data, message} 合约,不抛 HTTP 4xx。


dataasset.data.getfilter_op 支持的运算符集合

路径:GET /api/data-assets/{id}/data

Gateway 支持 snake_case 别名(filter_column / filter_value / filter_op / order_by / order_direction)和 camelCase 两种写法,两者等价。

filter_op / filterOp 完整合法值:

运算符 SQL 语义 示例
eq = 'val' filterOp=eq&filterValue=600519
ne <> 'val' filterOp=ne&filterValue=600519
gt > 'val' filterOp=gt&filterValue=10.5
gte >= 'val' filterOp=gte&filterValue=10.5
lt < 'val' filterOp=lt&filterValue=10.5
lte <= 'val' filterOp=lte&filterValue=10.5
like LIKE '%val%'(平台自动加两侧 %,与 contains 等价) filterOp=like&filterValue=浦发
contains LIKE '%val%'(平台自动加两侧 %,与 like 等价) filterOp=contains&filterValue=浦发
in IN ('a','b','c') filterOp=in&filterValue=a,b,c
not_in NOT IN ('a','b','c') filterOp=not_in&filterValue=a,b,c
between ✨ 2026-07-28 BETWEEN 'start' AND 'end' filterOp=between&filterValue=20140101,20141231
  • 多值运算符in / not_in):filter_value 用英文逗号分隔,如 filter_value=600519,000001,601398 → 生成 IN ('600519','000001','601398')
  • likecontains 行为相同:平台都生成 LIKE '%val%' 子串匹配。如需自定义 LIKE 模式(前缀 / 后缀 / 通配符位置),目前需在调用方 SQL 端处理或走 POST /api/data-assets/{id}/query
  • between(issue #68,2026-07-28)filter_value 必须是恰好 2 个用英文逗号分隔的值(start,end),生成 field BETWEEN 'start' AND 'end'Fail-loud,无静默降级——以下任一情况直接返回 HTTP 200 {"success": false, "message": "Validation Failed: ..."},绝不会静默退化成单值过滤或悄悄改用最新分区:
    • 只给 1 个值或给了 3+ 个值;
    • 两个值类型不一致(如一个能解析成数字、另一个不能);
    • start > end(范围反了)。
  • 未知运算符拒绝(不静默降为 eq):传入不在上表中的值(如 filterOp=regex)→ HTTP 200 {"success": false, "message": "Validation Failed: Unsupported filter operator: regex"}
  • order_direction(snake_case;camelCase 写法为 orderDirection)接受 asc / desc(大小写不敏感),默认 asc

分页上限与截断信号(feedback #69,2026-07-29,v2 修正见下)dataasset.data.get 对 MaxCompute-backed 资产(fund_daystock_day_v2 等历史全量资产)的每页行数硬上限是 1000 行。PG-backed 资产(dataasset.data.getRealtime 的 365 天热窗口镜像)没有这个上限。

  • size 超过 1000 时必须同时传 order_by,否则直接报错(不看 page):MaxCompute 的分布式扫描在没有 order_by 时不保证行序一致——不仅是"翻页时前后两页可能对不上",更关键的是同一个 page=1,size=10000 请求重复调用两次,拿到的可能是完全不同的一批行(这正是 2026-07 一起线上事故的根因:某每日选股流程按 size=10000 无排序键拉全市场数据,30 天内每天实际拿到的股票代码集合都不一样,导致移动均线永远算不出来,且流程本身"成功退出"、日志正常,是一个完全静默的失败——事故的行为特征更像是"同一个请求形状每次返回任意子集",而不仅仅是"第二页跟第一页对不上")。因此判定条件是 size > 1000 且未传 order_by——第一次调用(page=1)就会报错,不会等到翻页才发现:{"success": false, "message": "Validation Failed: ... requires an 'orderBy' column ..."}(HTTP 200)。
  • size <= 1000 永远不受此限制,无论 page 多大、总行数多少——marketplace 匿名预览固定 page=1&size=2525 <= 1000 恒成立,天然、永久豁免,不需要任何特殊处理;普通小分页翻页(如 size=25&page=2)也不受影响,行为与本次修复前一致(这不是本次修复的范围)。
  • size 超过 1000 提供了 order_by(因此调用成功)时,响应里会带两个额外信号字段:truncated: trueeffectivePageSize: 1000(实际生效的每页行数)。不超限时这两个 key 不存在(不是 false/null,而是整个 key 缺失——沿用 totalCountApproximate/totalCountTruncated 的既有 additive-flag 惯例)。pageSize / totalPages 字段始终反映实际生效值,不会再出现"回显了 size=10000 但只拿到 1000 行"的自相矛盾。换句话说,truncated:true 只会出现在你自己提供了排序键的成功响应里——没有排序键的超限请求根本走不到返回结果那一步,直接报错。
    • 示例(第一次调用即可,不用先拿 page=1 再试探):scripts/lg_agent_exec.sh dataasset.data.get id=76 filter_column=fund_code filter_value=510300 filter_op=eq size=10000 order_by=day_id

dataasset.data.get — 复合过滤(业务列 + 分区列范围,issue #68,2026-07-28)

背景dataasset.data.get 底层对 MC(MaxCompute)分区表有一个守卫——只要请求里没有显式出现分区列(如 fund_dayday_id),后端会强行拼一个"最新分区"等值过滤,避免 ODPS 全表扫描报错。这个守卫本身不在本次改动范围内;本次新增的是第二个过滤槽位,让你能在一次调用里同时表达"业务列过滤 + 分区列范围",从根源避开这个守卫(守卫只看过滤条件里有没有出现分区列名,不关心运算符——你自己带了范围过滤,守卫就不会再覆盖它)。

第二过滤槽位参数filter_column_2 / filter_value_2 / filter_op_2(camelCase:filterColumn2 / filterValue2 / filterOp2),与主槽位 filter_column / filter_value / filter_op 参数形态完全一致、独立解析、用 AND 拼接。不传时行为与之前完全一样(不影响任何现有调用)。

示例 —— 取基金 510300 在 2014 全年的历史净值序列(原始 bug #68 场景):

scripts/lg_agent_exec.sh dataasset.data.get id=76 \
  filter_column=fund_code filter_op=eq filter_value=510300 \
  filter_column_2=day_id filter_op_2=between filter_value_2=20140101,20141231 \
  size=500

不需要业务列过滤、只想拉某个分区范围内全部记录时,between 也可以只用主槽位、不需要第二槽位:

scripts/lg_agent_exec.sh dataasset.data.get id=76 \
  filter_column=day_id filter_op=between filter_value=20140101,20141231 size=500

回测按年分块的使用指引(issue #68):对 MC 数据源支撑的分区资产(如 fund_day,assetId 76)做多年历史回测时,不要按天分块调用——十年历史按天拉需要约 41 小时(3650+ 次调用 × ~15-20s/次)。改为按自然年分块,用 filter_op_2=between 一年一次调用:day_id BETWEEN 20140101 AND 20141231,然后 20150101 AND 20151231,……。十年历史大约 10-12 次调用,每次 ~15-20s,总耗时约 3-4 分钟——比按天拉快两个数量级。客户端把每年返回的分页结果拼接起来即可得到完整序列。

dataasset.data.getRealtime 支持完全相同的第二过滤槽位 + between 运算符(走 PG 热窗口镜像,仅覆盖最近 365 天——超出该窗口的历史仍需用 dataasset.data.get)。


dataasset.data.get vs dataasset.data.getRealtime — 路由选择指南

用哪个?

  • dataasset.data.get / lg.get_asset_data — 分析型 / 历史全量 / 完整数据。底层存储是资产的主数据源(Step 4 切换后为 MC;速度较慢 ~2-5s,但包含完整历史和全列)。
  • dataasset.data.getRealtime / lg.get_asset_data_realtime — 实时型 / 低延迟 / 热窗口(最近 365 天)。底层存储是资产的 realtimeDataSource(PG 镜像)。对于未配置 realtimeDataSource 的资产返回 {success:false, message:"This asset has no realtime mirror"} — 这是正常业务返回,不是异常,调用方应处理而非视为错误。

两个函数接受完全相同的参数(asset_identifier, page, size, order_by, filter_column, filter_value, filter_operator),返回 shape 相同,差异仅在于:

  1. 路由目标不同(/data vs /data-realtime)。
  2. get_asset_data_realtime 永不抛出异常;所有错误(无镜像、类型不符、连接失败、认证失败)均以 {success:false, message:...} 返回,调用方 必须 检查 result["success"]

Prerequisite: .getRealtime requires the asset to have realtimeDataSource populated (via PUT /api/data-assets/{id}, an admin API). If not configured, .getRealtime returns {success:false, message:"This asset has no realtime mirror"}. Currently stock_day and fund_day are the only assets with realtime mirrors configured; other assets will return the "no mirror" response until an admin populates their realtimeDataSource.

Note for existing bearer token holders: if you receive 403 Forbidden: missing required scope 'dataasset.data.getRealtime' when calling this skill, your token was minted before this skill was added. Go to lg-data.cc → Token Management, edit your token, and add the dataasset.data.getRealtime scope. New tokens minted after 2026-07-02 automatically include all currently-registered scopes.


dataasset.history.field-as-of — 股票 PIT 状态字段查询 (#13 PR-A/C)

路径:GET /api/data-assets/history/field-as-of

Auth: 标准 Spring Security session 或 Bearer token(lgatk_…)。GET 请求无需 CSRF token。Bearer token 需要 scope dataasset.history.field-as-of(PR-A 新增;现有 token 须重新签发方可使用)。

依赖: 底层表(stock_st_history / stock_board_history / stock_delist_history / stock_industry_history)由 PR-B ETL 进程回填。ETL 未运行前所有查询均返回 data:null,不会报错。

参数 类型 必填 说明
field String 字段名,固定四选一:is_st / market_board / delisted / industry
stockNum String 股票代码,如 600519.SH / 00700.HK
asOfDate String 查询日期,ISO-8601 格式 YYYY-MM-DD
swVintage String field=industry 时有效:SW2014SW2021。缺省时自动按 asOfDate 选择(asOfDate < 2021-10-01 → SW2014;否则 SW2021)

field 语义说明:

field 返回类型 说明
is_st Boolean true = ST/特别处理/暂停挂牌;false = 正常;null = 无历史记录(ETL 未回填或日期早于数据)
market_board String 主板 / 创业板 / 科创板 / 北交所 / 中小板(v1 每日刷新,非事件驱动,精度至天)
delisted Boolean true = delist_date <= asOfDatefalse = 未退市或无记录(默认 False,不返回 null)
industry Object 申万行业分类对象(见下方结构),null = 无记录

industry 值对象结构:

{
  "l1_code":    "110000",
  "l1_name":    "食品饮料",
  "l2_code":    "110201",
  "l2_name":    "白酒",
  "sw_vintage": "SW2014"
}

响应示例(field=is_st,股票在 ST 状态):

{
  "success": true,
  "data": {
    "field":        "is_st",
    "stockNum":     "000666.SZ",
    "asOfDate":     "2022-06-01",
    "value":        true,
    "validFrom":    "2021-03-15",
    "validTo":      null,
    "source":       "namechange",
    "changeReason": "特别处理"
  },
  "message": "OK"
}

响应示例(field=industry,自动选择 SW2014):

{
  "success": true,
  "data": {
    "field":    "industry",
    "stockNum": "600519.SH",
    "asOfDate": "2020-06-15",
    "value": {
      "l1_code":    "110000",
      "l1_name":    "食品饮料",
      "l2_code":    "110201",
      "l2_name":    "白酒",
      "sw_vintage": "SW2014"
    },
    "validFrom": "2014-01-01",
    "validTo":   null,
    "source":    "index-member"
  },
  "message": "OK"
}

无历史记录(ETL 未回填 / 日期早于数据):

{
  "success": true,
  "data": null,
  "message": "No history record found for stockNum=600519.SH, field=is_st, asOfDate=2020-06-15. ETL may not yet have populated this field."
}

业务错误(非法 field / 非法日期格式):

{
  "success": false,
  "message": "Unsupported history field: 'foo'. Supported: [is_st, market_board, delisted, industry]"
}

Python 快捷用法(via lg_utils.history):

from datetime import date
from lg_utils.history import is_st_at, market_board_at, is_delisted_at, industry_at, load_st_history

# 单日点查
st      = is_st_at("000666.SZ", date(2022, 6, 1))           # True / False / None
board   = market_board_at("688001.SH", date(2022, 6, 1))    # "科创板" / None
delist  = is_delisted_at("000001.SZ", date(2023, 1, 1))     # True / False (never None)
ind     = industry_at("600519.SH", date(2020, 6, 15))       # dict with 5 keys / None

# 获取某股票全部 ST 历史事件列表(按 valid_from 升序)
events = load_st_history("000666.SZ")
# [{'valid_from': '2001-06-04', 'valid_to': '2001-09-18', 'is_st_value': True, ...}, ...]

lg_utils.history 模块通过 executor 内部 HTTP 调用同一个 REST endpoint,fail-soft 语义与 REST API 一致(无记录返回 None/[],不抛异常)。


看板 (Dashboard)

skillId method 功能 风险
dashboard.list GET 列出看板 🟢
dashboard.get GET 获取看板详情 🟢
dashboard.data.get GET 一次拿看板所有组件的数据(支持 maxRows,默认 100,上限 500) 🟢
dashboard.update PUT 全量替换看板(含 widgets/variables)。想只改名称或描述?用 dashboard.patch,避免 widgetsJson 被覆盖。 🟡
dashboard.patch PATCH 部分更新看板元数据(PATCH /api/dashboards/{id})。字段掩码语义:仅 name / description 两个安全字段可被更新;缺失字段、显式 null以及空字符串 "" 都视为"跳过"(清空)。严格拒绝widgetsJson / variablesJson / widgets / teamName / dashboardId / allowSubscription 出现在 body 即返回 HTTP 200 success:false, code:"FIELD_NOT_PATCHABLE"此 skill 需要 dashboard.patch scope(独立于 dashboard.update),现有 token 须重新签发方可使用。 🟢

订阅 & Marketplace

skillId method 功能 风险
subscription.token.list GET 列出订阅 token 🟢
marketplace.item.list GET 列出可订阅的看板/资产 🟢
marketplace.item.subscribe POST 订阅市场条目 🟡
marketplace.item.unsubscribe POST 取消订阅 🟡

指标告警 (Metric Alert)

skillId method 功能 风险
metric.alert.list GET dashboardId 列出告警规则 🟢
metric.alert.get GET ruleCode 获取规则 🟢
metric.alert.create POST 创建告警规则 🟡
metric.alert.update PUT 全量替换告警规则。想只改阈值/webhook/消息模板?用 metric.alert.patch 🟡
metric.alert.toggle PUT 启用/停用规则 🟡
metric.alert.test POST 仅测试(无副作用) 🟡
metric.alert.evaluate POST 执行评估并按规则触发 webhook 🟡
metric.alert.patch PATCH 部分更新告警规则元数据(PATCH /api/metric-alerts/{ruleCode})。字段掩码语义threshold(阈值字符串)/ webhookDsName(webhook 数据源名)/ messageTemplate(消息模板)/ templateEngine(模板引擎,'legacy''freemarker')四个安全字段可被更新;缺失字段、显式 null以及空字符串 "" 都视为"跳过"(清空)。严格拒绝enabled(用 metric.alert.toggle)/ ruleCode(标识符)/ metricKey(更改后测量对象变化 — 破坏性)/ teamName 出现在 body 即返回 HTTP 200 success:false, code:"FIELD_NOT_PATCHABLE"templateEngine 传入非 legacy/freemarker 值返回 HTTP 200 success:false, code:"INVALID_TEMPLATE_ENGINE"此 skill 需要 metric.alert.patch scope(独立于 metric.alert.update),现有 token 须重新签发方可使用。 🟢
metric.alert.snooze PUT 暂停指定 alert rule 直到 until (ISO 8601 本地时间戳);body: {"until": "2026-06-22T18:00:00"}. Snooze 优先级高于 silence + rate-limit;snooze 到期后自动恢复正常 evaluate()。until 缺失或非未来时间 → HTTP 200 success:false code:INVALID_UNTIL 🟢
metric.alert.unsnooze PUT 取消活跃的 snooze (snoozed_until → null);与 snooze 是独立 scope — 设置 snooze 的 agent 不会自动获得清除权限。幂等,已无 snooze 时也返回 success。 🟢
metric.alert.acknowledge PUT 标记 alert 已知 (acknowledged_at = now());v1 仅 info-only — 不影响下次 evaluate()(UI 显示"已知"状态);v2 可能扩展为"clears next trigger window"。 🟢

metric.alert.evaluate — 数据新鲜度门控(freshness gate,2026-06-22 重写)

freshnessConfig.sourceType = 'asset' 模式下,freshness gate 不再执行 SELECT MAX(field) 实时查询,而是直接读取 data_asset.last_data_refresh_at 列——该列由 registerAsset executor step 在每次 ETL 成功执行后自动写入。行为变化:

  • field 字段已废弃但向下兼容:现有配置中的 field 会被忽略(不校验、不查询),无需修改已有 freshnessConfig
  • 跨租户订阅规则publisherTeam != null):subscriber 侧直接通过 getAssetByIdInTeam 读取 publisher 资产的 last_data_refresh_at,无需 publisher 数据源凭证;跨租户 freshness gate 现已生效(此前 custom-sql 跨租户路径仍受 MVP 限制,保持 fail-open)。
  • NULL 值 = fail-open:资产首次注册后 executor 未重新部署前(或从未经过 registerAsset step),last_data_refresh_at 为 NULL → probe 返回 Optional.empty() → 评估照常进行,不触发 gate。
  • sourceType = 'custom-sql':行为不变(own-team 执行自定义 SQL;跨租户仍为 MVP 限制)。

已知限制:ETL job 执行成功但写入 0 行时,RegisterAssetStep 仍然会 bump last_data_refresh_at,导致 gate 不会跳过该次评估。若 webhook 频繁误触发,建议使用 metric.alert.snooze 或调高阈值;v2 会在 rowsWritten > 0 条件下才 bump(目前为规划中的 follow-up)。

metric.alert.snooze — evaluate() 跳过优先级(Triage #11)

evaluate() 内部跳过检查的优先级(先匹配先返回):

  1. snoozedUntilsnoozed_until > now() → SKIPPED "snoozed until <timestamp>"
  2. silenceMinuteslastTriggeredAt + silenceMinutes > now() → SKIPPED "in silence period"
  3. maxFiresPerDayfiresToday >= maxFiresPerDay → SKIPPED "daily limit reached"
  4. → 正常执行评估 + webhook

Snooze 到期后(snoozed_until < now())自动恢复,无需再调 unsnooze。

metric.alert.patch — templateEngine 字段(Triage #10)

templateEngine 控制 messageTemplate 的渲染方式:

行为 适用场景
"legacy"(默认) ${varName} 正则替换,与历史版本完全兼容 现有规则无需改动
"freemarker" FreeMarker 模板引擎:支持条件指令(<#if var??>...</#if>)、<#list> 迭代、null 安全访问(${var!})、rowFields 嵌套访问(${rowFields.stock_num!} 需要条件内容或丰富格式的飞书/微信消息

freemarker 模式下可用变量:

变量 类型 说明
${ruleName} String 规则名称
${value} String 当前触发值
${threshold} String 阈值
${fieldName} String 监控字段名
${assetName} String 资产名称
${operator} String 运算符(GT / LT / EQ 等)
${tenantName} String 告警规则所属团队
${publisherTeam} String 订阅资产发布方团队(自有资产为 null)
${timestamp} String ISO-8601 UTC 触发时间(freemarker 独有,legacy 不含
${rowFields.col_name!} String GROUP BY 分组字段值(如 ${rowFields.stock_num!}! 表示缺失时默认为空字符串)
${col_name} String rowFields 键同时提升为顶级变量,方便直接访问

freemarker 模板示例(飞书 Markdown):

<#if value??>
**告警 ${ruleName}** 触发
- 字段:`${fieldName}`
- 当前值:${value}
- 阈值:${threshold}(${operator})
<#if rowFields.stock_num??>- 股票:${rowFields.stock_num!}</#if>
<#if rowFields.region??>- 区域:${rowFields.region!}</#if>
触发时间:${timestamp}
</#if>

<#if value??>...</#if> 是 FreeMarker 空值检查指令:当 value 非 null 时渲染,等价于 if value is not None:${rowFields.stock_num!} 中的 ! 是默认值操作符 — 缺失时渲染为空字符串,避免 undefined variable 错误。<#if rowFields.stock_num??> 同理 — 仅当 GROUP BY 包含该列时渲染该行,避免多余的空行。

向下兼容说明: FreeMarker 的 ${var} 语法与 legacy 正则语法完全相同 — 将 templateEngine"legacy" 切换为 "freemarker" 后,现有的 ${ruleName} / ${value} 等模板无需任何改动即可继续工作。"freemarker" 模式额外支持 <#if> / <#list> 等高级指令。

切换引擎示例:

PATCH /api/metric-alerts/{ruleCode}
{
  "templateEngine": "freemarker",
  "messageTemplate": "${ruleName}: ${fieldName}=${value} > ${threshold}"
}

成功响应:{"success":true,"message":"Alert rule patched","ruleCode":"<ruleCode>"}

Webhook 插件

skillId method 功能 风险
plugin.webhook.send POST 通过数据源发送 webhook 🟡

用户注册 & 反馈

skillId method 功能 风险
auth.user.register POST 注册新账号(teamName 自动生成为 tenant_${username} 🟢
auth.token.introspect GET 查询当前 Bearer token 的身份、scope、状态与过期时间(无参数,仅限自身 token) 🟢
feedback.submit POST 提交反馈/Bug/需求 🟢
feedback.list GET 查看历史反馈与官方回复 🟢

反馈详情

feedback.submit — 提交反馈 / Bug 报告 / 功能需求

路径:POST /api/feedback

必填字段:

字段 类型 说明
category string 反馈分类,严格枚举,见下方
subject string 主题(最多 256 字符)
message string 详细描述

category 合法值(严格匹配,不接受缩写):

bug_report | feature_request | general | other

注意:"bug""feature" 等缩写形式均为非法 category,接口将返回 HTTP 200 + success:false

可选字段:

字段 类型 说明
contactEmail string 联系邮箱(可选,最多 180 字符)

请求示例:

{
  "category": "bug_report",
  "subject": "数据资产列表加载失败",
  "message": "在工作区 A 下点击「数据资产」后页面空白,控制台显示 JSON.parse 报错。浏览器 Chrome 125,复现率 100%。",
  "contactEmail": "user@example.com"
}

成功响应(HTTP 200):

{
  "success": true,
  "message": "Feedback submitted"
}

业务错误响应(HTTP 200 + success:false,category 非法示例):

{
  "success": false,
  "message": "Invalid category"
}

认证错误响应(HTTP 401,未登录):

{
  "success": false,
  "message": "Authentication required"
}

身份 & Token 详情

auth.token.introspect — 查询当前 Bearer token 的身份 + scope + 生命周期

路径:GET /api/public/agent/token-introspect

调用此接口可在不触发任何业务 401/403 的前提下,查看当前 Bearer token 的身份、所属团队、token id、用户 id、有效 scope 列表,以及 token 名称 / 状态 / 过期时间。自省范围限定为当前 token——接口不接受 tokenId 参数,永远只描述请求 header 中提供的那一个 Bearer。

输入:无 query 参数。Authorization: Bearer lgatk_... header 是唯一身份来源。

响应示例(200):

{
  "success": true,
  "username": "alice",
  "teamName": "team-alpha",
  "userId": 7,
  "tokenId": 42,
  "tokenName": "alice-clawhub-readonly",
  "status": "active",
  "expiresAt": "2026-09-17T10:30:45Z",
  "scopes": [
    "dataasset.list",
    "dataasset.get",
    "dataasset.schema.get",
    "dataasset.data.get",
    "process.ingestion.list"
  ]
}

expiresAt 在 token 无过期时为字面量 null(key 始终存在)。

响应示例(401,missing/invalid token):

{
  "success": false,
  "message": "Invalid token"
}

典型用法:在调用任何 /api/** 业务接口前先 introspect 一次,把当前 token 的 scopes 与计划调用的 skill 所需 scope 做集合对比;缺失则在客户端给出友好提示,避免逐个 skill 403 试探。


Backtest API

stock_day 日线回测请使用 run_stock_day_backtest(单股)或 run_stock_day_portfolio_backtest(多股组合),均在 lg_utils.backtest_examples.stock_day 模块。基金 / 黄金 / 港股日线回测也已 ship — lg_utils.backtest_examples.{fund_day,metal_day,hk_day}.run_*_backtest,shape 与 stock_day 一致(详见下方 Python 工具库表格)。完整参数说明见场景 4 示例

结果可通过 investment.stock.backtest.* REST skill 检索:list(摘要)、get(全量 JSON)、compare(两次 metrics diff)。REST skill 当前以 investment.stock.backtest.* 命名但是 asset-class-agnostic — 通过 Python 调用 lg_utils.backtest_examples.fund_day / metal_day / hk_day 持久化的结果同样可由这 3 个 REST skill 检索(persist(name=...) 调用统一走 process_backtest_result 表,按 name 索引)。专用的 investment.fund.backtest.* / investment.gold.backtest.* REST skill 命名空间是 v2 follow-up(目前 stock-class skill 已能 cover 所有资产类的回测结果检索)。

Wealth Studio(需开通 investment_studio 解决方案权限)

Phase 4 重命名说明:skill id 前缀由 stockstudio.* 改为 investment.stock.*(同时新增 investment.fund.* / investment.gold.*)。旧 stockstudio.* id 通过 Express alias 表自动转发,新代码优先使用 investment.stock.*

investment.stock.* — 股票持仓 / 交易 / 回测

skillId method 功能 风险
investment.stock.portfolio.list GET 查股票持仓(query 传 asset_class=stock)。每行附带 recommendation 字段:最新一条 per-stock 推荐;完整历史走 /api/profile/portfolio-positions/recommendations?stock_num=...。新增 ?accountType=real|paper|all 参数,默认 real;显式传 paperall 才会包含模拟交易数据 🟢
investment.stock.portfolio.create POST 新增股票持仓条目(body 含 asset_class: "stock" 🟡
investment.stock.portfolio.update PUT 更新股票持仓 🟡
investment.stock.trading.list GET 查股票交易记录(query 传 asset_class=stock 🟢
investment.stock.trading.create POST 录入新股票交易(自动更新持仓;body 含 asset_class: "stock" 🟡
investment.stock.backtest.list GET 列当前团队的回测结果,支持 ?name= / ?tag= / ?programGroup= 过滤。Summary 视图——返回 totalReturn/sharpe/maxDrawdown/... 数值列 + tags / programGroup 元数据,不含大 JSON 🟢
investment.stock.backtest.get GET 单条回测结果详情(按 id),包含 paramsJson/metricsJson/tradesJson/equityCurveJson 等全量 JSON 负载 🟢
investment.stock.backtest.compare GET 两次回测结果 metrics 逐项 diff(totalReturn/sharpe/maxDrawdown/... 🟢
investment.stock.portfolio.nav_history.get GET 组合 NAV 日历史 + 日度时间加权收益率(TWR) + 基准对比 α;详见下方「理财组合 NAV 历史」 🟢
investment.stock.portfolio.nav_history.benchmarks.list GET 返回平台支持的基准指数目录(4 条),附带每个指数最新可用日期与 active/stale 状态;可用于 UI 下拉选择器 🟢
investment.stock.portfolio.attribution.get GET 组合 α/β v1 归因:一次调用返回 alpha(日度 + 年化)、beta、R²、波动率、跟踪误差、信息比率;详见下方「理财组合归因」 🟢

investment.stock.backtest.list — 过滤参数 (#15)

路径:GET /api/profile/backtest-results

参数 必填 默认 说明
name 精确匹配 name 字段
tag 子串包含匹配:返回 tags 数组包含此 tag 的所有结果
programGroup 精确匹配 program_group 字段

多个过滤条件 AND 组合。无过滤参数时返回当前团队最近 50 条。

响应示例(截取):

{
  "success": true,
  "total": 2,
  "items": [
    {
      "id": 123,
      "name": "dual_ma_5_20",
      "totalReturn": 0.1842,
      "sharpe": 1.34,
      "tags": ["live-mock-2026Q3", "tutorial-walkthrough"],
      "programGroup": "dual-ma-series"
    },
    {
      "id": 122,
      "name": "dual_ma_5_30",
      "totalReturn": 0.1751,
      "sharpe": 1.21,
      "tags": [],
      "programGroup": "dual-ma-series"
    }
  ]
}

tags 是 JSON 数组(无 tag 时为 [],不是 null 也不省略 key)。programGroup 是字符串(未设置时为 null,但 key 始终存在)。

写入 tags / programGroup:调用 POST /api/internal/backtest-results 时在 body 里加 "tags": ["...","..."]"programGroup": "..." 字段。约束:tag 单条最长 64 字符;最多 20 条;空白 tag 自动剔除;programGroup 最长 128 字符。


理财组合 NAV 历史

skillId: investment.stock.portfolio.nav_history.get
路径: GET /api/profile/nav-history
权限: 需开通 investment_studio 解决方案权限(与持仓 / 交易系列相同)

查询参数:

参数 必填 默认 说明
start 组合最早快照日期 起始日期,格式 YYYY-MM-DD
end 今天 结束日期,格式 YYYY-MM-DD
asset_class stock 持仓资产类别(stock / fund / gold
benchmark TeamVariable benchmark_index → 默认 1B0300 基准指数代码(见下方指数目录)。优先级:请求参数 > 团队变量 > 默认 1B0300
accountType real 账户类型筛选:real(仅真实账户,默认)/ paper(仅模拟账户)/ all(全部)。默认 real;显式传 paperall 才会包含模拟交易数据。

基准指数解析优先级:

  1. ?benchmark=<code> 请求参数(最高优先级)
  2. 团队变量 benchmark_index(团队管理员在 TeamVariable 里配置的默认基准)
  3. 硬编码默认值:1B0300(沪深300)

支持的基准指数(stock_day 内的 legacy 编码):

代码 名称 市场
1B0300 沪深300(默认) SH
1A0001 上证综指 SH
1B0510 中证A500 SH
399001 深证成指(数据停更于 2026-03-17,请求时会降级并返回 meta.benchmarkWarning SZ

注意:这些是平台 stock_day 表里使用的 legacy 编码,与第三方常见的 xxxxxx.SH / xxxxxx.SZ 格式不同,不要混用。

EOD 快照与 accountType(v1.0.30 修复):

每日 EOD 快照作业(daily_pnl_snapshot)写入 daily_position_pnl 时,从 portfolio_positions.account_type 读取 accountType 并原样写入快照行。v1.0.30 之前,快照行始终以实体默认值 REAL 写入,导致模拟账户头寸被误标记为 REAL,使真实账户 NAV 曲线出现虚高。v1.0.30 通过同步写路径修复了此问题,并通过 Flyway 迁移回填了历史数据。如在 v1.0.30 发布前存入的快照行出现异常 NAV 尖峰,请在后端重启后检查 daily_position_pnlaccount_id LIKE 'paper-%' 的行是否均为 account_type = 'PAPER'

收益率计算方法(日度时间加权收益率 TWR):

平台以每日 SUM(market_value) 作为组合净值 V_t,从 trading_records 提取当日净流量 F_t = Σ(BUY 金额) − Σ(SELL 金额),按 r_t = (V_t − F_t) / V_{t−1} − 1 计算日收益率,再链乘为累积收益率 R_t = Π(1 + r_i) − 1。通过减去当日净流入,"增仓 / 减仓"带来的资金变动不计入收益,只保留纯粹的价格涨跌信号。完整数学推导见设计文档 docs/plans/2026-06-16-portfolio-nav-history-api-design.md §3a。

响应示例(含基准):

{
  "success": true,
  "data": {
    "dates": ["2026-01-02", "2026-01-03", "2026-01-06"],
    "portfolio_nav": [100000.00, 102000.00, 101500.00],
    "portfolio_cumulative_return": [0.00000000, 0.02000000, 0.01500000],
    "benchmark_code": "1B0300",
    "benchmark_cumulative_return": [0.00000000, 0.01000000, 0.00800000],
    "alpha_spread": [0.00000000, 0.01000000, 0.00700000]
  },
  "message": "ok",
  "meta": {
    "filledDates": [],
    "benchmarkSource": "default",
    "benchmarkWarning": null,
    "benchmarkStaleAt": null
  }
}

meta 字段说明:

字段 说明
filledDates market_value=null(当日无行情)而向前填充的日期列表;不影响收益率计算,仅作信息提示
benchmarkSource "request" / "team_variable" / "default" — 说明基准来源
benchmarkWarning 基准数据停更时的告警文案(如"Benchmark 399001 series stale; last available 2026-03-17");为 null 时省略
benchmarkStaleAt 基准最后有效日期(ISO);基准截断时此字段告知截断位置,alpha_spread 此后各元素为 null

业务错误合约:

  • ?benchmark=BADCODE → HTTP 200,{"success": false, "message": "Unknown benchmark index: BADCODE (...)"}(不抛 500,也不返 HTML)
  • 基准数据停更(如 399001)→ 仍返回完整 NAV,meta.benchmarkWarning 描述停更,alpha_spread 截断到最后有效日;不失败整个调用
  • 无持仓快照记录 → HTTP 200,{"success": true, "data": {"dates": [], ...}, "message": "No snapshot history found for this portfolio"}

理财组合归因

skillId: investment.stock.portfolio.attribution.get
路径: GET /api/profile/attribution
权限: 需开通 investment_studio 解决方案权限(与持仓 / 交易系列相同)

一次调用即可得到"我的策略跑赢市场了吗?"的核心数字:年化 alpha(超额收益)、beta(市场敏感度)、(拟合度)、波动率、跟踪误差和信息比率。底层数据来自已有 nav_history.get 的日度 TWR 累积收益序列,无额外 DB 查询。

查询参数:

参数 必填 默认 说明
start 组合最早快照日期 起始日期,格式 YYYY-MM-DD
end 今天 结束日期,格式 YYYY-MM-DD
asset_class stock 持仓资产类别(stock / fund / gold
benchmark TeamVariable benchmark_index → 默认 1B0300 基准指数代码,优先级与 nav_history.get 相同
accountType real real(仅真实账户)/ paper(仅模拟)/ all(全部)

计算方法(α/β v1):

nav_history.get 返回的日度累积收益序列,经链式反推 R_p[i] = (1+CR[i])/(1+CR[i-1]) - 1 得到日度收益率序列,再以 OLS 回归计算:

指标 公式
beta Cov(R_p, R_b) / Var(R_b)(样本方差,N-1 分母)
alpha mean(R_p) - beta × mean(R_b)(日度)
alphaAnnualized alpha × 252
rSquared Cov(R_p, R_b)² / (Var(R_p) × Var(R_b))
portfolioVolatility stddev(R_p) × √252(年化)
benchmarkVolatility stddev(R_b) × √252(年化)
trackingError stddev(R_p − R_b) × √252(年化主动风险)
informationRatio (mean(R_p) − mean(R_b)) × 252 / trackingError

所有数值字段精度为 4 位小数(BigDecimal scale 4)。

响应示例(含基准,合成数字):

{
  "success": true,
  "data": {
    "start": "2026-01-01",
    "end": "2026-06-17",
    "tradingDays": 115,
    "benchmarkCode": "1B0300",
    "benchmarkSource": "team_variable",
    "portfolioCumulativeReturn": "-0.1319",
    "benchmarkCumulativeReturn": "0.0528",
    "alpha": "-0.1847",
    "alphaAnnualized": "-0.4123",
    "beta": "0.8742",
    "rSquared": "0.6418",
    "portfolioVolatility": "0.2156",
    "benchmarkVolatility": "0.1834",
    "trackingError": "0.0941",
    "informationRatio": "-1.9628"
  },
  "meta": {
    "benchmarkWarning": null,
    "benchmarkStaleAt": null,
    "sampleSize": 115,
    "minSampleWarning": null,
    "computationWarning": null
  },
  "message": "OK"
}

22 个字段总览(D12 文档锁):

15 data 字段: start, end, tradingDays, benchmarkCode, benchmarkSource, portfolioCumulativeReturn, benchmarkCumulativeReturn, alpha, alphaAnnualized, beta, rSquared, portfolioVolatility, benchmarkVolatility, trackingError, informationRatio.

5 meta 字段: benchmarkWarning, benchmarkStaleAt, sampleSize, minSampleWarning, computationWarning.

2 envelope 字段: success, message.

meta 字段说明:

字段 说明
benchmarkWarning 基准缺失 / 对齐窗口过短时的告警文案;null 表示正常
benchmarkStaleAt 基准最后有效日期(ISO);基准截断时告知截断位置
sampleSize 对齐后的原始序列天数(等于 tradingDays
minSampleWarning 对齐后日度收益点数 < 5 时的告警 (OLS 最小有效样本)(含实际点数);null 表示正常;fires when aligned daily returns < 5 (OLS minimum)
computationWarning 基准方差接近零(β 无法定义)时的告警;null 表示正常

边界行为合约:

  • 对齐窗口 ≤ 1 天(基准数据只有首日)→ beta/rSquared/trackingError/informationRatio 全为 null,meta.benchmarkWarning = "Benchmark series too short for α/β computation after alignment"
  • 日度收益点数 < 5(OLS 最小样本,tradingDays 太短)→ 同样置 null,meta.minSampleWarning 含实际点数
  • 基准方差近零(常数基准)→ β 无意义,置 null,meta.computationWarning = "Benchmark variance near zero; β undefined"
  • 无持仓数据 → 与 nav_history.get 行为一致,返回空序列 + success:true
  • ?benchmark=BADCODE → HTTP 200 {"success": false, "message": "..."}

investment.fund.* — 基金持仓 / 交易 (回测通过 lg_utils.backtest_examples.fund_day Python 入口,REST skill investment.stock.backtest.* 可检索结果)

skillId method 功能 风险
investment.fund.portfolio.list GET 查基金持仓(query 传 asset_class=fundmarket 取值 OF/ETF/LOF) 🟢
investment.fund.portfolio.create POST 新增基金持仓(body 含 asset_class: "fund" 🟡
investment.fund.portfolio.update PUT 更新基金持仓 🟡
investment.fund.trading.list GET 查基金交易记录(query 传 asset_class=fund 🟢
investment.fund.trading.create POST 录入新基金交易(body 含 asset_class: "fund" 🟡

investment.gold.* — 黄金持仓 / 交易 (回测通过 lg_utils.backtest_examples.metal_day Python 入口,REST skill investment.stock.backtest.* 可检索结果)

skillId method 功能 风险
investment.gold.portfolio.list GET 查黄金持仓(query 传 asset_class=goldmarket 取值 SGE/BANK;stock_num 取值 Au99.99/Au100g/AuTD) 🟢
investment.gold.portfolio.create POST 新增黄金持仓(body 含 asset_class: "gold" 🟡
investment.gold.portfolio.update PUT 更新黄金持仓 🟡
investment.gold.trading.list GET 查黄金交易记录(query 传 asset_class=gold 🟢
investment.gold.trading.create POST 录入新黄金交易(body 含 asset_class: "gold" 🟡

investment.paper.* — 模拟盘交易(Paper Trading,单户 ¥1,000,000 沙盘)

⚠️ Paper-trading tokens are platform-issued, NOT self-mintable. The paper.* scope namespace is reserved — calling POST /api/subscription/tokens with scopes: "paper.account.read" (etc.) returns HTTP 400 + {"code":"RESERVED_SCOPE"}. Paper-execution tokens are auto-minted by the platform when a strategy is bound to a paper account via the UI (PaperExecutionTokenService.mintForExecution). If you want to drive paper trading from an external agent, mint the token via the strategy-binding flow first, then call the endpoints below with that token.

新 v1.1:单户模拟盘,行情打通 stock_day 实时价;MARKET 同步成交,LIMIT 走每 60s 撮合 + 15:00 自动 EXPIRED;T+1、一手 100 股、A 股标准手续费全部强制。与真金账户完全隔离——真金交易记录走 investment.stock.trading.*,二者数据库行级互不影响。

skillId method 功能 风险
investment.paper.account.get GET 查模拟账户当前状态:现金 / 初始资金 / 累计重置次数 / 总市值。首次调用 lazy-create 🟢
investment.paper.orders.submit POST 下单:{stock_num, market, side: BUY/SELL, order_type: MARKET/LIMIT, qty, limit_price?, client_order_id}client_order_id 必传(同 user + 同 client_order_id 幂等)。MARKET 同步返回成交价;LIMIT 返回 SUBMITTED,撮合后由 scheduler 推到 FILLED/EXPIRED 🟡
investment.paper.orders.list GET 查订单(支持 status= / from= / to= 过滤);返回字段含 status / source / filledPrice / filledQty / fees / rejectReason 🟢
investment.paper.positions.list GET 查模拟持仓(只返 account_id LIKE 'paper-%' 的行,不会和真金混在一起) 🟢

Scope 集(process-execution 默认)paper.orders.write paper.account.read dataasset.read。这是后端在 process 起跑时自动注入的 scope-limited 短期 Bearer。

Scope guard 边界:带 paper scope 的 Bearer 只能访问 /api/wealth/paper/**/api/data-assets/**/api/auth/current/api/public/agent/token-introspect;其他 namespace(如 /api/trading-records 真金路径)一律 403 scope_insufficient。如需在自己的 PAT 上启用 paper 调用,去 个人设置 → Token 管理 创建一个带这套 scope 的长期 PAT。

典型用法(在 Process Python 节点里跑量化策略)

import lg

execution_id = lg.get_variable("execution_id")
quote_resp = lg.get_asset_data_realtime("stock_day",
    filter_column="stock_num", filter_value="600000",
    filter_operator="eq", size=1)
if not quote_resp["success"]:
    print(quote_resp["message"])
    return
quote_rows = quote_resp["data"]
positions = {p["stockNum"] for p in lg.paper.get_positions()["items"]}

if quote_rows and quote_rows[0]["close_price"] < 9.50 and "600000" not in positions:
    # Preferred form: bare stock code + explicit market.
    # The dotted form ("600000.SH") is also accepted — the SDK and backend both
    # auto-strip it via PaperOrderService.normalizeStockNum (Bug #51-A).
    lg.paper.submit_order(
        symbol="600000", market="SH", side="BUY", qty=100, order_type="MARKET",
        client_order_id=f"{execution_id}-pufa-entry",  # 用 execution_id 做幂等键
    )

资源市场已上线 starter_paper_trade_strategy 模板,「免费接入」一键复制到你的 tenant 即可改写。

实时热窗口查询(lg.get_asset_data_realtime

# Real-time hot-window query (last 365 days, PG mirror, low-latency)
result = lg.get_asset_data_realtime(
    "stock_day",
    page=1, size=50,
    filter_column="day_id",
    filter_value="20260701",
    filter_op="eq",
    order_by="day_id",
    order_direction="desc",
)

if not result.get("success"):
    print(f"Realtime query failed: {result.get('message')}")
    # Common message: "This asset has no realtime mirror"
    # → Ask an admin to populate `realtimeDataSource` on the asset via
    # PUT /api/data-assets/{id}
else:
    rows = result["data"]
    total = result["totalElements"]
    print(f"Got {len(rows)} rows of {total} total")

回测模式(Ship 5-H,同一份脚本两种跑法):在 Process 执行环境上额外设置 LG_PAPER_MODE=backtest + LG_PAPER_BACKTEST_FROM=YYYY-MM-DD + LG_PAPER_BACKTEST_TO=YYYY-MM-DD同一脚本不改一行代码即对历史 stock_day 回放。此模式下:

  • 不发 HTTP —— submit_order 等调用走 lg_utils.paper_sim 进程内模拟器;后端 /api/wealth/paper/** 不会收到任何请求;paper_order 表不会写入新行。PaperScopeGuard / process_execution_secret / __LG_PAPER_TOKEN__ 全部不被消耗(token 若存在则 DEBUG 日志一行后忽略)。
  • 规则完全一致 —— Java 后端 FeeCalculator / PriceLimitValidator / PaperOrderService (T+1 + 一手) 为规则正本;Python 模拟器有 fixture 平价测试 (paper_sim_fixtures.yml + PaperSimRuleParityTest) 守护,任何规则方法 PR 必须同步重新生成 fixture + 更新 Python,否则 CI 红。
  • 不消费 scope —— 因为根本不发出 HTTP,所以 paper.orders.write 等 scope 在 backtest 模式下完全用不到;agent / PAT / process-execution token 的 scope 集合不需要任何调整。
  • 持久化 —— 脚本末尾必须显式调 lg.paper.persist_backtest(name=...)(无 atexit 隐式持久化;防止脚本异常中断时写入「半拉子结果」造成数据污染)。结果写入 process_backtest_result,UI 走 /profile/backtest-results
  • suspension 不查 —— dacp_suspended_stocks 是前向运维表,回测刻意不查;这是文档化的 KNOWN_DIVERGENCE,不是 bug。

资源市场已上线 starter_paper_trade_strategy_backtest 模板。设计文档:docs/plans/2026-06-11-paper-trading-ship-5-h.md(LA-APPROVED v3)。

lg.paper 完整 API 速查表(live + backtest 两种模式共用同一函数签名;运行时由 LG_PAPER_MODE env var 决定走 HTTP 还是走进程内模拟器):

函数 模式 签名 返回 备注
lg.paper.submit_order(symbol, side, qty, order_type="MARKET", limit_price=None, client_order_id=None, market=None, asset_class=None) live + backtest 见左 dict两端共有id主键 — 不是 orderId,两端一致)、clientOrderIdstockNummarketsideqtyorderTypelimitPriceassetClassstatusrejectReasonfeesfilledQty已成交价键名两端不一致:backtest 用 fillPrice,live 用 filledPrice(pre-existing 命名分歧;agent 代码需做 r.get("fillPrice") or r.get("filledPrice") 兼容写法)。时间戳键两端不同:backtest 仅返回 tradeDate(推进到的模拟交易日);live 额外返回 submittedAt/filledAt(PaperOrder 实体时间戳)但不返回 tradeDatelive 独有键accountIdsubmittedAtfilledAtsource — backtest 路径无此四键。 symbol 推荐裸代码 + 显式 market="SH"/"SZ"/"BJ"/"HK";带 .SH/.SZ/.BJ/.HK 后缀也接受(SDK 客户端 + 后端 PaperOrderService.normalizeStockNum 都会自动剥离 — Bug #51-A)。client_order_id 用作幂等键。order_type="LIMIT"limit_price 必填。
lg.paper.get_account() live + backtest () dict回测{"initialCapital", "cashBalance", "positionValue", "totalEquity", "unrealizedPnl"}live{"id", "accountId", "initialCapital", "cashBalance", "baseCurrency", "status", ...} — 注意 live 路径不返回 positionValue / totalEquity / unrealizedPnl(这些是模拟器进程内汇总,不是后端持久化字段)。 回测模式下来自模拟器持仓估值;live 模式查询后端 /api/wealth/paper/accountPaperTradingController.toAccountMap()
lg.paper.get_positions() live + backtest () dict{"items": [...], "total": N} 每条 item 关键键名因模式不完全统一回测返回 {stockNum, qty, avgCost, lastBuyDate}(精简);live 返回 {id, stockNum, market, stockName, assetClass, positionQty, avgCost, lastBuyDate, currency}(包含 positionQty 而非 qty)。marketValue / unrealizedPnl 两端都不返回 — 如需 market value / P&L 请自行用 get_asset_data("stock_day", ...)close_price × qty/positionQty 算。两端通用 stockNum(camelCase)— 不是 stock_num
lg.paper.get_orders(status=None) live + backtest (status=None) dict{"items": [...], "total": N} status 可选过滤值:SUBMITTED / FILLED / CANCELLED / REJECTED / EXPIRED(5 个;EXPIRED 覆盖 DAY-TIF 限价单未触发收盘自动失效)。不支持 PARTIALLY_FILLED(此后端未实现 partial fill 语义)。
lg.paper.cancel_order(order_id) live + backtest (order_id) dict(更新后的订单对象) 已成交订单 422 拒绝。
lg.paper.advance_day(day) backtest only (day: str "YYYY-MM-DD") None 推进模拟时钟到指定交易日;触发挂单成交评估、价格限制检查、T+1 规则。live 模式调用此函数会抛 RuntimeError(函数级文案,例如 "advance_day() is only available in backtest mode (LG_PAPER_MODE=backtest). In live mode, the current day is inferred from the market calendar.")。
lg.paper.record_eod_equity() backtest only () None 在当前模拟日的收盘后调用,记录该日的 equity curve 点(用于 Sharpe / MaxDD 等指标聚合)。
lg.paper.persist_backtest(name=None, force=False) backtest only (name=None, force=False) dict{"id": int, "name": str} — 仅 2 个 key 必须显式调用 — 没有 atexit 隐式持久化(防异常中断写入半拉子结果)。name=None 时使用 LG_PAPER_BACKTEST_PERSIST_NAME env 或自动生成时间戳名。force=True 允许覆盖同名 result。metrics 不在返回里 — 需要 metrics 摘要请查 process_backtest_result 表或 /profile/backtest-results UI。

回测模式完整脚本示范(同一脚本与 live 模式只差三个 env var):

Pattern note: use lg.get_asset_data for backtest / historical-window loops (full history via MC after Step 4 flip); use lg.get_asset_data_realtime for live / paper-trade single-row lookups (hot 365-day window on PG). .getRealtime returns success:false on data older than 365 days — never use it inside backtest loops that may extend beyond a year.

import lg
from datetime import date, timedelta

execution_id = lg.get_variable("execution_id")

# 走历史 stock_day(回放);不发任何 HTTP 给后端。
start = date(2026, 1, 1)
end   = date(2026, 1, 31)
day   = start
while day <= end:
    lg.paper.advance_day(day.isoformat())

    # 策略示例:每日抓 600000 的 stock_day 当日收盘价;< 9.50 且无持仓时买 100 股。
    # 注意:lg.get_asset_data 是单列过滤;如需 (stock_num, day_id) 联合过滤,
    # 在 SQL 化的策略里把 day_id 作为 filter_column、由 advance_day 控制时间游标。
    quote_resp = lg.get_asset_data("stock_day",
        filter_column="day_id", filter_value=int(day.strftime("%Y%m%d")),
        filter_operator="eq", size=500)
    quote_rows = [r for r in quote_resp["data"] if r.get("stock_num") == "600000"]
    positions = {p["stockNum"] for p in lg.paper.get_positions()["items"]}

    if quote_rows and quote_rows[0]["close_price"] < 9.50 and "600000" not in positions:
        lg.paper.submit_order(
            symbol="600000", market="SH", side="BUY", qty=100, order_type="MARKET",
            client_order_id=f"{execution_id}-day{day.isoformat()}",
        )

    lg.paper.record_eod_equity()    # 收盘记账
    day += timedelta(days=1)

# 显式持久化 — 没有这一行结果不会写 process_backtest_result
result = lg.paper.persist_backtest(name="600000_low_entry_jan2026")
print(f"backtest persisted: id={result['id']}, name={result['name']}")

关键差异 vs live 模式(同一份用户代码, env var 区别):

  • live 模式不需要 advance_day / record_eod_equity / persist_backtest(实时撮合 + 真实交易日时钟)— 这三个函数在 live 模式下会抛 RuntimeError(错误文案按函数定制,例如 advance_day 文案明确指出"live mode 由 market calendar 推进当前日"),便于 fail-fast 发现脚本误配。
  • backtest 模式下 submit_order 不会写 paper_order 表(进程内模拟器 only);live 模式下每次成功调用都对应后端一行 paper_order 记录。
  • backtest 模式 fixture 平价测试(paper_sim_fixtures.yml + PaperSimRuleParityTest Python 端 + PaperOrderServiceSymbolFormatTest Java 端)守护规则一致性 — 任何 fee / price-limit / T+1 / 一手规则的 PR 必须同步重新生成 fixture,否则 CI 红。

技能源在 app.jsSKILL_CATALOG,运行时可通过 GET /agent/skills 查询当前 token 实际可用的列表(会过滤 scope)。

Python 工具库 lg_utils(在平台 python_script 流程节点里 import 使用)

平台的 python_script 执行器会自动把 lg_utils 注入到用户脚本的 PYTHONPATH,无需安装。

模块 / 函数 功能
lg_utils.get_variable(key, default) / lg_utils.get_variables() 读取流程上下文变量(由前端/调度器传入;get_variables() 返回全集 dict)
lg_utils.put_variable(key, value) ✨ NEW 把变量回写到当前 session 的 JobPool,下游 step 的 ${key} 替换能解析到。value 必须 JSON-serializable,单个值 ≤ 64 KB。key 不能以 _lg_ 开头(保留给系统)。同 step 内多次调用累积;用于把 Python 脚本计算出的字符串/数字/小型 dict 传给后续 step(webhook messageTemplate / SQL where 子句等)
lg_utils.get_context() 当前团队快照:assets / datasources / dashboards / processes
lg_utils.get_asset_data(asset_identifier, page, size, order_by, filter_column, filter_value, filter_operator=None, filter_column_2=None, filter_value_2=None, filter_operator_2=None) 分页拉团队有权限的资产数据(历史全量,步骤 4 切换后走 MC);返回 {success, data, totalElements, totalPages, ...}filter_value 支持 list/tuple → IN 查询;filter_operator 支持 eq / ne / in / not_in / like / gt / gte / lt / lte / contains / between,默认 containsbetween 需要恰好 2 个值(start, end,经同一 list/tuple→逗号拼接机制传入 filter_value),值数量不对或区间反转时后端 fail-loud 拒绝。第二过滤槽位(2026-07-28,issue #68 executor 侧 follow-up):filter_column_2/filter_value_2/filter_operator_2,与主槽位参数形态完全一致、独立解析、AND 拼接;底层打 GET /api/internal/asset-data/{assetId}InternalPythonExecuteController,本轮已加第二槽位参数透传)。典型用法是主槽位业务列等值过滤 + 第二槽位分区列 between(例如 fund_code eq + day_id between),避免触发 MC "最新分区" 自动注入 guard——一次调用即可拿到跨分区的完整历史区间,无需按天拆调用。三个新参数均可选,不传时行为与之前完全一样。失败时 抛出 RuntimeError
lg_utils.get_asset_data_realtime(asset_identifier, page, size, order_by, filter_column, filter_value, filter_operator=None, filter_column_2=None, filter_value_2=None, filter_operator_2=None) get_asset_data 参数完全相同(含 between 与第二过滤槽位);内部走 GET /api/internal/asset-data-realtime/{assetId},查询资产的 实时镜像数据源(PG 热窗口,最近 365 天)。永不抛出异常——所有错误(含无效 between 区间等业务校验错误)以 {success:false, message:...} 返回(无镜像 / MC 类型误配 / 连接失败 / 认证失败);调用方须检查 result["success"]。适合仪表盘、实时 P&L 等低延迟场景。与 dataasset.data.getRealtime skill 对应。
lg_utils.get_portfolio_positions(stock_num=None, page=1, size=500) 当前团队持仓(每行附带最新的一条 per-stock 推荐 recommendation,由内部 API 按 update_time 取最近)
lg_utils.get_trading_records(account_id=None, market=None, stock_num=None, trade_type=None, page=1, size=50) ✨ NEW 拉团队的交易记录(分页 dict,字段 Jackson camelCase 如 tradeDate / stockNum / tradeType
lg_utils.write_recommendations(items, process_id=None, execution_id=None) Python 脚本把 per-stock 推荐(action/priority/add1/add2/reduce1/reduce2/noMoreAdd/market追加process_stock_recommendation(历史保留,不 upsert);前端持仓页"推荐"按时间倒序展示历史
lg_utils.get_connection(ds_name) / get_db_config(ds_name) 按团队数据源名取 JDBC 连接
lg_utils.backtest(strategy, asset, ...) 单资产回测引擎:long-only、整数股;输出 Sharpe / Sortino / MaxDD / 胜率 / profit_factor / 交易明细 / equity_curve / 年度拆分;可选 benchmark_asset= 对比并输出 alpha/beta;可选 persist=True 持久化到 process_backtest_result
lg_utils.backtest_portfolio(strategies, assets, ...) ✨ NEW 组合回测:多标的共享现金池;额外输出 per_asset 贡献度/回撤;同样支持 benchmark / persist
lg_utils.write_backtest_result(result, name=...) ✨ NEW BacktestResult 持久化到 process_backtest_result 表(append-only,按团队隔离)。BacktestResult.persist(name=...) 是同义糖
lg_utils.log 子模块 (from lg_utils.log import info, warn, error) 标准化日志 helper:info → stdout,warn / error → stderr,自动加 [INFO]/[WARN]/[ERROR] 前缀。注意是子模块,不在 lg_utils.__all__ 里,必须按子模块路径 import
lg_utils.backtest_examples.dual_ma.DualMA 内置双均线参考策略
lg_utils.backtest_examples.stock_day.run_stock_day_backtest 针对平台 stock_day 日线表(OPEN_PRICE/CLOSE_PRICE/day_id/STOCK_NUM)的单股快捷封装
lg_utils.backtest_examples.stock_day.run_stock_day_portfolio_backtest ✨ NEW 多只股票组合的快捷封装
lg_utils.backtest_examples.fund_day.run_fund_day_backtest ✨ NEW 基金日线回测快捷封装。fund_day 表的 unit_nav 同时映射到 open/close(基金无 OHLC,只有单日净值);fund_code 作为 filter;nav_date (YYYY-MM-DD) 作为日期列。其余参数 (strategy / start / end / capital / benchmark_asset) 与 stock_day 同形。
lg_utils.backtest_examples.metal_day.run_metal_day_backtest ✨ NEW SGE 黄金/白银日线回测快捷封装。metal_day.close_price 同时映射到 open/close;metal_code 作为 filter。常用 metal_code: Au99.99 (黄金 9999)、Au100g (黄金 100g)、Ag(T+D) (白银 T+D)。
lg_utils.backtest_examples.hk_day.run_hk_day_backtest 港股日线回测快捷封装。底层是对物理 stock_day 表按 HK ticker 过滤读取(港股数据现已作为独立 DataAsset stock_day_hk 🟢 生产可用发布,见「数据资产可用性」表)。结构同 stock_day 模板。

高级 / 兼容性附录

日常调用只需要上面 Quick Start 的 key=value 写法。这一节是给需要手写 JSON envelope、排查参数没生效、或给数组/嵌套 body 的少数几个 skill(schedule.job.depends.save / .plugins.save / process.pipeline.build / .update)用的参考。

lg_agent_exec.sh 完整参数语法

lg_agent_exec.sh <skillId> [key=value ...] [key:=jsonvalue ...] [--json '<raw params json>']
lg_agent_exec.sh '<完整 envelope JSON>'          # 旧用法,仍 100% 支持
  • key=value —— 一律当 JSON 字符串处理(保留 stock_num=000135day_id=20260419 这类前导零;不要用 key:=value 传这些字段,会被解析成数字丢掉前导零)。

  • key:=value —— 值按 raw JSON 解析(数字 / 布尔 / 数组 / 对象),例:qty:=100ok:=truetags:=["a","b"]解析失败直接报错退出(非零 exit code + stderr 说明),不会静默退化成字符串——如果你想要的其实是字符串,改用 key=value

  • --json '<obj>' —— 给一个 JSON 对象当 params 的一部分,和 flat key 共存。多用于 body 是数组/嵌套结构的 skill(schedule.job.depends.saveschedule.job.plugins.saveprocess.pipeline.buildprocess.pipeline.update):

    scripts/lg_agent_exec.sh schedule.job.depends.save jobCode=my_job \
      --json '{"body":[{"dependCode":"upstream_job","dependType":"10"}]}'
    

    jobCode 是 flat key(对应 path 模板 {jobCode}),--json 提供的 body 数组原样传递。冲突时 --json 提供的字段优先(同名 key 既在 --json 里又作为 flat key 传,--json 的值生效)。

  • Shell 引号提醒--json 的值本身是一段 JSON,请用单引号包裹整段(避免 shell 展开双引号内的 $/反引号);JSON 内部的双引号无需在 shell 层再转义。数组/对象值同理。

  • 旧用法完全兼容lg_agent_exec.sh '<整坨 envelope JSON>'(唯一一个参数、以 { 开头)按老路径直接透传,不受本次改动影响。

lg_agent_list.sh describe — 查一个 skill 接受哪些 key

scripts/lg_agent_list.sh                       # 列出当前 token 可见的全部 skill
scripts/lg_agent_list.sh describe dataasset.data.get   # 只看这一个 skill 的 schema + 示例

响应 JSON 里每个 skill 都带 params{name, in, required, type, example?, aliases?} 数组)和 exampleInvocation(一行可直接粘贴的 flat 调用示例)——这是权威、随后端部署自动更新的参数说明,比任何静态文档都新。describe 的过滤在服务端按调用者的 granted scope 生效,看不到就是没权限调,不会因为传了 ?skillId= 就多暴露信息。

Envelope 形式 & 历史踩坑(手写 JSON 时适用)

canonical envelope —— params 字段下嵌套 pathParams / query / body

{"skillId": "...", "params": {"pathParams": {...}, "query": {...}, "body": {...}}}

向后兼容(自 2026-06-29 起):顶层 pathParams / query / body 会被网关自动折叠进 params(仅当 params.<field> 缺失时;非对象值跳过)。嵌套和顶层字段同时出现时,嵌套字段优先生效。

历史踩坑:2026-05-07 一次 backfill 因为漏写 params: 包裹(且当时 body 字段没有 fallback),-target_day_id 20260506 没到 broker,python_script 拿到 target_day_id=None 跑了一轮空 SELECT。2026-06-29 把 query/body 的 fallback 加齐;同期发现的 dataasset.data.get filter 被静默丢弃就是同源问题(用旧顶层 shape 时 filter 没注入 URL,看着 SUCCESS 但实际未过滤)。v1.0.45 起推荐用 flat key 写法从根源避免这一整类踩坑——flat key 由网关按 skill 的 path 模板自动分类,不存在"忘记包裹"的问题。

paramAliases(2026-05-21 起):网关支持逐 skill 的 snake_case ↔ camelCase query 参数翻译。dataasset.data.get 是第一个开通的 skill(filter_column / filter_value / filter_op / order_by / order_direction 两种写法都接受)。触发原因是 LE 2026-05-21 晚间事故:fund_day007722 过滤但静默返回未过滤数据(后端 @RequestParam 要 camelCase,agent 传了 snake_case,两边不匹配又没有报错)。flat key 写法同样受益于这层翻译——filter_column=code 这种 flat 调用会先分类进 query,再走 paramAliases 翻译,行为和手写 envelope 一致。

环境要求

必需

  • LG_AGENT_BASE_URL - 平台地址(默认 https://privora.cn
  • LG_AGENT_TOKEN - Bearer Token(公开版唯一认证方式;建议使用最小权限、专用 Token)

Security Notes

  • 公开版 skill 仅支持 Bearer Token 模式,不接受会话 Cookie / CSRF。
  • 首次安装建议使用测试账号或低权限 Token 验证读取类能力。
  • 公开版 skill 覆盖数据查询 + 回测 + 模拟交易 + 告警 + 流程编排四大类,完整 per-category 分类(read / idempotent-write / workflow-transition / outbound-webhook)见 §Scope。删除、撤销、审批等破坏性/管理类操作不在 skill 范围内,需通过 platform UI 完成。
  • 写操作应只授予明确需要的 scopes。
  • 不要在脚本里硬编码 Token 凭据。

注意事项

  • 公开版 skill 覆盖数据 + 回测 + 模拟交易 + 告警 + 流程编排;破坏性/管理类操作(删除 / 撤销 / 审批)不在 skill 范围内,走 platform UI。
  • 公开版 helper scripts 只支持 Token 调用,不支持 session/cookie 兼容模式。
  • idempotencyKey 用于幂等控制,写操作请保持稳定。
  • Token 从平台获取,不要硬编码在脚本中。

最近更新

v1.0.47 (2026-07-30)

  • 🔗 「数据资产可用性」快照节 + Quick Start §0 各加一条指引:完整覆盖范围 / 分市场明细 / 更新频率 / 数据起始日期见持续维护的公开清单页 privora.cn/features/realtime-minute-data-coverage(按六类分组)。快照节本身标注为 2026-07-17 的一次性盘点,不再是权威口径。

v1.0.46 (2026-07-17)

  • 🌏 数据覆盖按市场段完整化:stock_day 段族全部 🟢 生产可用——A股 stock_day、港股 stock_day_hk、美股 stock_day_us(由 stock_day_us_backfill_to_mc PG→MC 同步);分钟K线 stock_kline(A股)+ stock_kline_hk(港股)live;旧 stock_minutes 弃用。文档明确「物理合表、按市场段分别发布为独立 DataAsset」模型。

v1.0.45 (2026-07-16)

  • 🚀 lg_agent_exec.sh 支持命名参数扁平调用lg_agent_exec.sh dataasset.data.get id=42 filter_column=code filter_value=000135,不用再手拼嵌套 envelope JSON。key=value 一律字符串(保留前导零);key:=value 按 raw JSON 解析(数字/布尔/数组),解析失败 fail-loud。--json '<obj>' 支持数组/嵌套 body 的 skill(schedule.job.depends.save 等)与 flat key 混用。旧 envelope 形式 100% 继续可用,两种写法可在同一次调用里混用。
  • 🔍 discoverabilityGET /agent/skills 每个 skill 现在带 params schema({name,in,required,type,example?,aliases?})+ exampleInvocation 一行示例;lg_agent_list.sh describe <skillId> 只看一个 skill 的 schema。
  • 📖 Quick Start / 典型场景示例全部改为新命名参数写法;envelope / fold-in / paramAliases 踩坑说明压缩进文末 §高级 / 兼容性附录

v1.0.44 (2026-07-11)

  • 🔄 内容 identical to v1.0.42(同 UX),v1.0.43 短暂回滚到 v1.0.40 SAFE 内容后发现 SkillSpector v2.3.5 在今天有 non-determinism,同 content 不同 scan 结果。既然 rollback 无法回 SAFE badge,选择保留 v1.0.42 的可读性改进(Chinese-fy Scope + What'''s-New 移底部 + marketplace §0)。

v1.0.43 (2026-07-11)

  • ⏪ 短暂 rollback 到 v1.0.40 内容尝试回 SAFE badge —— 失败(scanner non-determinism)。v1.0.44 恢复 v1.0.42 的可读性改进。

v1.0.42 (2026-07-11)

  • 📐 文档结构重组:4 个 What's-New 顶部块合并到本节;§Scope 与 §投资建议免责改为中文首要(不再中英双语介绍)。
  • 🚀 Quick Start §0 30 秒试用改为指向 marketplace UI(无需注册直接浏览公开挂牌资产),移除 top-of-doc dispatcher endpoint 示例。

v1.0.41 (2026-07-11)

  • 🚀 首版 Quick Start §0 匿名试用(后在 v1.0.42 改为 marketplace UI 路径)。

v1.0.40 (2026-07-10)

  • 🔒 SkillSpector 响应:primary framing 从"金融数据后端"翻转到"AI Agent 投资工作流平台";§2 加密段重写为"encryption at rest / 认证边界返明文";删除 3 处矛盾的"只读能力与常规非破坏性写操作"声明。

v1.0.38 (2026-07-10)

  • 📖 Quick Start §4 加 First Call Recipe(3 步 curl:list → 拿 numeric id → data.get)说明 URL {id} 必须是数字型资产 ID(如 42),不是资产名字(如 fund_day)。

v1.0.37 (2026-07-08)

  • 🛡️ 匿名 rate limit 迁移到 Redis 集群共享:10/IP/分钟 burst + 100/IP/天 daily 承诺现在真的成立(v1.0.36 时因 PM2 cluster 被放大 ~4×)。Redis 断连时 fail-open。

v1.0.36 (2026-07-08)

  • 🌐 匿名 marketplace preview 模式全接入:/agent/skills*Authorization header 自动进入 anonymous 模式,10 个只读 skill 白名单。三桶 rate limit(60/IP/分 general + 10/IP/分 data burst + 100/IP/天 data daily),任一超限 429 with bucket field。匿名调用强制 page=1, size=25,剥离发布者身份字段。

v1.0.32 (2026-07-02)

  • 📊 组合归因 (α/β):新增 investment.stock.portfolio.attribution.get,跟基准做 TWR 对比。
  • 📈 组合净值曲线:portfolio.nav_history.get 输出 NAV + 基准 overlay。
  • 💰 现金分红归因:新增 stock_dividend 数据资产。
  • 🧪 策略回测 sandbox 模式:process.ingestion.execute --mode=backtest
  • 🔔 告警治理 v1:alert.snooze / alert.acknowledge + Freemarker webhook 自定义 payload + 基于新鲜度的 stale 屏蔽。
  • ⚡ 实时行情双路由:dataasset.data.getRealtime 新 skill。

v1.0.35 (2026-07-03)

  • 📝 dataasset.metadata.get skill 注册:Node SKILL_CATALOG 补录 dataasset.metadata.get 条目,覆盖 GET /api/data-assets/{id}/metadata(20 字段富元数据 + 新鲜度字段)。此前该 skill 在 @RequireScope 和 SKILL.md 均已存在,但 Node catalog 缺失导致 Agent 调用返回 HTTP 400 "Skill not found"。(feedback ID=61 gap 4)

v1.0.30 (2026-06-23)

  • 📑 Added explicit analysis-not-investment-advice disclaimer in the Token Recommendation section: outputs are analytical inputs for operator review, not regulated advice; live trading and irreversible decisions stay outside autonomous execution.
  • 🔒 Streamlined the agent skill catalog: token-management write operations are documented as operator-only actions performed via the Privora token-management UI, consistent with the existing "operator-issued Bearer Token" guidance.

v1.0.29 (2026-06-23)

  • 🔒 Documentation cleanup based on ClawHub security audit feedback. Internal token-management surfaces are now exposed only through the operator UI rather than documented in the agent manifest; runtime behavior unchanged. (Note: this cleanup was queued for v1.0.28 but missed in the squash-merge; v1.0.29 ships the queued change.)

v1.0.28 (2026-06-23)

  • 📌 文案修正:实时 skill manifest 三个 public 端点的域名从 lg-data.cc 改为 privora.cn(与平台主域名一致)。旧域名仍 302 重定向到 privora.cn,已 in-flight 的调用不受影响,但建议把 /skill-version / /skill-manifest / /capabilities 的 baseURL 切到 https://privora.cn
  • 🔒 Token 推荐章节加强:显式强调 minimum-scope 原则 + "Do NOT have your agent create tokens on your behalf" 顶级规则(token mint 是 operator 动作,不是 agent 动作)。

v1.0.26 (2026-06-23)

  • 🔧 schedule.job.online / schedule.job.offline 响应清理:retired legacy code / msg alias 字段;统一使用 {success, message, data} 标准包络(PR #385 加的临时 alias 至此完成迁移)。
  • ✅ Frontend public/js/jobs.js 切到 result.success

v1.0.27 (2026-06-23)

  • 🔒 内部 token 校验加强:POST /api/subscription/tokens mint 路径增加额外服务端校验(最长有效期上限 + WARN 级别 audit log),由 operator 在 Token Management UI 上感知;不影响 agent-facing API 行为。

v1.0.26 (2026-06-23)

  • (internal) alertness freshness gate reads data_asset.last_data_refresh_at (no SQL probe). No user-facing behavior change.

v1.0.25 (2026-06-23)

  • 📝 Paper trading: clarified that paper.* tokens are platform-issued only.
  • ✅ Skill discovery: /api/public/agent/capabilities now enumerates 7 paper-trading endpoints (previously omitted because yml didn't register them).

v1.0.24 (2026-06-22)

  • 🔄 Live skill discovery:新增三个 public 端点(/api/public/agent/skill-version/api/public/agent/skill-manifest/api/public/agent/capabilities),已安装的 agent 可直接从 privora.cn 获取最新 manifest 和 endpoint catalog,不再依赖 ClawHub 缓存。/skill-version 返回 {version, updatedAt} 轻量级探针,用于判断本地缓存是否过期。
  • 📋 Capabilities catalog/capabilities 返回从 agent-scope-mapping.yml 解析的完整 {method, path, scope} 映射表(含 PR #382 snooze/ack/unsnooze 新 scope),agent 可程序化发现所有 endpoint 而无需手动翻 SKILL.md。
  • 📌 Frontmatter updatedAt 字段:从本版本起,每次 SKILL.md 发布都同步更新 frontmatter updatedAt 字段(ISO 日期字符串)。/skill-version 端点以此字段为权威版本时间,不受 Node 重部署 mtime 漂移影响。

v1.0.23 (2026-06-22)

  • 📊 新数据资产stock_forecast (业绩预告) + stock_express (业绩快报) 接入,11 年历史已回填 (82,457 + 19,945 行),每日 19:30 增量更新。
  • 🧹 覆盖声明收紧 (T-1 audit):原 headline 多资产统一数据:A 股 / 港股日线行情 改为按状态分类:stock_day / fund_day / metal_day / stock_forecast / stock_express 标 🟢 生产可用;stock_hk / stock_minutes 标 🔴 建设中(预计 Q3 开放);stock_us 标 ⚫ 未实现。新增「数据资产可用性」表 + 同步更新 frontmatter title / description / keywords 移除未交付的 港股 顶层标签。
  • 🔬 新 skilldataasset.metadata.get (Triage #14) — 单资产元数据查询,含 lastUpdated / expectedUpdateCadence / cronExpression / sourceDescription,可程序化判断数据新鲜度。
  • 📈 新 skillinvestment.stock.portfolio.attribution.get (Triage #9) — α/β 归因分析,复用 nav_history 时序,输出 alpha / beta / R² / 波动率 / tracking error / information ratio。配套前端 dashboard widget (#9 PR-B 已 ship)。
  • 🎯 新 skillmetric.alert.patch 支持 templateEngine 字段 — 选 freemarker 后 webhook 模板可用 <#if> 条件分支 + <#list> 循环 + 内置 ?string 格式化等。原 ${var} 字面替换仍是默认 templateEngine='legacy',向后兼容。
  • 🧰 执行器修复 (PR-D)StepMeta.parseParam 移除破坏性 replaceAll("[\\t\\n]","") strip — Python 多行脚本、pip 多行 requirements、SQL 多行子句存在 JSON 字符串里现在保留真实换行字符。修复 2026-05-26 / 2026-06-09 / 2026-06-15 三起相关事故。

v1.0.21 (2026-06-12)

  • 🔍 Display title expansion — adds 3 more 0-competitor empty-market terms to title: 量化分析 / 多资产 / 风险监控. Cumulative #1 唯一 long-tail wins after this release: 9 queries.

v1.0.20 (2026-06-12)

  • 🔍 Display title expansion — adds "模拟盘 + 实时告警" to surface the paper-trading and alert capabilities in clawhub search (both query terms were 0-competitor empty markets pre-v1.0.20).

v1.0.19 (2026-06-12)

  • 🔍 Display title experiment — pass --name on publish to test if the CLI flag can set a Chinese-keyword-rich display title (auto-generated slug-based titles miss Chinese query intent like "A股 / 量化回测" / "Python 策略").
  • No content change vs v1.0.18.

v1.0.22 (2026-06-12)

  • 📝 Documentation language tightened — replaced absolute "agent-safe" framing with precise per-category descriptions (read, idempotent write, workflow state transition, outbound webhook). Operators should configure least-privilege tokens and apply their own confirmation gates for state-changing operations.

v1.0.18 (2026-06-12)

  • 📝 Documented operation surface refined — read, idempotent write (subscribe / portfolio entries / token rotation), workflow state transition (redo / hold / resume / reset-priority), and outbound webhook trigger. Operators should evaluate risk per category and scope tokens accordingly.

v1.0.17 (2026-06-12)

  • 📝 Scope & Operator Responsibility section added — token recommendation + per-category side-effect description.
  • No new capabilities. No API surface change.

v1.0.16 (2026-06-12)

  • 🔍 文档元数据补全 — 加 title: + keywords: frontmatter 字段,提升 clawhub vector search 对中文量化 / 股票 / A股 / 港股 / 回测关键词的命中率。功能无变化(v1.0.15 已 ship 模拟交易 + 回测升级)。

v1.0.15 (2026-06-12)

  • 🧾 模拟交易 (Paper Trading) ✨ — MARKET / LIMIT 委托 + scheduler-driven 撮合 + 真实涨跌停 / 停牌信号;账户 UNIQUE on user_name,订单按 client_order_id 幂等。适合策略 12 月 paper trade 验证。
  • 📊 策略回测升级 — 平台已积累 44+ 次持久化回测,可通过 investment.stock.backtest.list 检索历史审计记录;新增 lg_utils.backtest_examples.stock_day.run_stock_day_portfolio_backtest 多股组合回测。
  • 📝 description + 文档清理 — 移除外链 + 简化加密叙事 + 强化产品能力描述,跟最新平台状态对齐。

v1.0.14 (2026-06-10)

  • 📝 Description 重排:把"字段级加密 GA"前置到前 100 字,Hermes / clawhub 列表视图能立刻看到核心差异化。功能本身无变化(v1.0.13 已 ship)。

v1.0.13 (2026-06-05)

两件 headline 更新:

  • 🔒 字段级加密 GA — 持仓量、成本价、交易价格等字段密文化存储,per-account 密钥隔离 (Ship 5, 2026-06-04)。
  • 🎯 1-click subscribe→alert deeplinkmarketplace.item.subscribedashboard-* item 现在返回 clonedDashboardId。Agent 用这个 ID 构造 /dashboards?selectId=<id>&openAlerts=true,把用户从订阅一步带到 alert 配置 modal。详见上方场景 5

📌 环境变更:平台主域名 lg-data.cc → privora.cnLG_AGENT_BASE_URL 默认值已更新。旧域名仍重定向工作,无需立即修改你的环境变量。

v1.0.12 (2026-05-24)

  • 多资产数据 + Python 策略回测能力 + 监控告警 webhook plugin 模板(飞书 / 微信 / 任意 HTTP 端点)。
  • 60+ REST skills 跨 Process / Schedule / Datasource / Dashboard / Marketplace / MetricAlert / Webhook plugins / Investment studio。

**量化回测 / 模拟交易 / 告警仅需此三项能力?**请见窄范围版:privora-cn-quant(无管理操作)。


⭐ 觉得这个 skill 有用?

如果它帮你的 AI Agent 少踩了几个坑、省了几天工程时间——欢迎去 ClawHub 给个 star,让更多散户找到它:

👉 clawhub.ai/guangfuwu/skills/privora-cn-quant (右上角 ⭐)

每个 star 都让 ClawHub 算法把这个 skill 排到更多寻找量化数据后端的开发者面前。3 秒钟的事,对维护者帮助巨大。

也欢迎在 issue 区反馈用得不爽的地方 / 想要的新能力——我会逐条跟进。


附录 · 已安装 agent 的实时 manifest 探测

此段是给已安装 agent 的元数据接入指南,不是 first-time discovery 内容。新读者可跳过。

已安装的 agent 可以直接从 privora.cn 拉最新版本(不经 ClawHub 缓存):

端点 用途
GET https://privora.cn/api/public/agent/skill-version 轻量级版本探测,返回 {version, name, description, updatedAt}
GET https://privora.cn/api/public/agent/skill-manifest 拿 SKILL.md 全文 markdown
GET https://privora.cn/api/public/agent/capabilities 结构化 endpoint catalog(scope ↔ HTTP path 映射)

三个端点都是 public,no auth。建议每次会话开始时 hit /skill-version 一次,如果版本号高于本地缓存就重新拉 manifest。

产品主页: https://privora.cn