---
name: lg-agent-platform
version: 1.0.12
description: Privora · 私人专属数据管家 for AI Agents. Give Hermes / Claude / GPT a Bearer Token to multi-asset data — A股 / 港股 / 持仓 / 黄金 unified API + 24/7 portfolio monitoring + Python backtesting. 让普通人也能拥有私募级别的工作流。
license: MIT-0
metadata:
  {
    "openclaw": {
      "emoji": "📈",
      "requires": {
        "env": ["LG_AGENT_BASE_URL", "LG_AGENT_TOKEN"]
      }
    }
  }
---

# Privora · 多资产数据管家 for AI Agents

**给你的 AI Agent 一个像私募研究员一样工作的金融数据后端。**

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

- 📊 **多资产统一数据**：A 股 / 港股实时行情、分钟线、**持仓**、**黄金**——一个 API 全覆盖
- 🔔 **7×24 云端监控**：Serverless 策略托管，飞书 / 微信毫秒级预警，零服务器运维
- 🧪 **Python 策略回测**：用同一份平台数据跑回测，输出 Sharpe / 最大回撤 / 交易明细
- 🔒 **数据不落大模型**：你的真实持仓、账户数据通过 Token 安全调用，永不进入 LLM 训练

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

🎯 **最适合**：用 Hermes/Claude/GPT 做投资分析的散户、做量化策略想找稳定数据后端的个人开发者、希望"AI 帮我盯盘"的活跃交易者。

🌐 **产品主页**：[https://privora.cn](https://privora.cn?utm_source=clawhub&utm_medium=skill-readme&utm_campaign=lg-agent-platform) · 注册即拿 Token

![演示](./lg-data-demo.gif)

---

## 🌟 核心亮点

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

### 2. 🔒 银行级数据隐私隔离 (Privacy First)
**无需向大模型暴露您的敏感财务数据！** 
传统对话模式要求您手动输入持仓数量和成本，极易造成隐私泄露。本技能采用**云端托管架构**，您的真实持仓、账户数据全部安全储存在 `lg-data.cc` 平台。Agent 仅通过加密 Token 安全调用盈亏分析结果，彻底杜绝隐私数据被用于大模型训练的风险。

### 3. ⚡ Serverless 极速预警与零部署
策略云端托管运行，无需您购买第三方行情 API，无需自建服务器维护 Cron 任务，无 Token 消耗税。策略触发后，毫秒级推送到您的飞书机器人或微信 Webhook。

---

## 🛠️ 能做什么

| 核心功能 | 详细说明 |
| :--- | :--- |
| **资产盈亏巡航** | 一键查询持仓明细、当日盈亏、历史收益率，数据由 lg-data.cc 闭环处理。 |
| **云端自动盯盘** | 设置预警条件（突破均线、涨跌幅、换手率等），触发即通知，7x24小时云端值守。 |
| **多终端实时推送** | 策略触发毫秒级推送到飞书、微信 Webhook，不错过任何交易信号。 |
| **实时深度行情** | 获取 A 股、港股实时报价及分钟线数据，为 Agent 提供精准决策依据。 |
| **策略回测** ✨ NEW | 用 Python 写策略、用平台日线数据一键跑历史回测，输出 Sharpe / 最大回撤 / 交易明细 |
| **用户声音收集** | 支持 Agent 代客户提交 Bug 和需求，无缝对接后台反馈系统。 |

---

## 🚀 快速接入 (Quick Start)

只需三步，即可让您的 AI Agent 拥有量化能力：

### 1) 获取您的专属 Token
1. 注册并登录 [lg-data.cc](https://lg-data.cc)
2. 在侧边栏点击你的用户名 → API Token Management，或直接访问 `https://lg-data.cc/profile/tokens`
3. 创建一个仅包含所需 scopes 的专用 Token（建议先用只读或低权限 Token）
4. 复制您的专属 `LG_AGENT_TOKEN`

### 2) 为您的 Agent 配置环境变量
在您使用的 Agent 终端（如 Hermes、Claude Code、GitHub Copilot 或 OpenClaw）中注入以下环境变量：
```bash
export LG_AGENT_BASE_URL="https://lg-data.cc"
export LG_AGENT_TOKEN="***"
```
公开版仅支持以上 Bearer Token 方式，不支持 session cookie / CSRF 兼容调用。

### 3) 唤醒 Agent，开始对话
现在，您可以直接用自然语言向您的 Agent 下达指令了！

---

## 💬 典型应用场景

### 场景 1：查询私密资产盈亏（数据不落大模型）
> **您：** “帮我查下今天的账户盈亏情况。”
> 
> **Agent（调用 `dataasset.data.get`）：** 
> “为您同步 lg-data.cc 的最新分析结果：
> 💰 **当日盈亏：** +319 元 | **累计浮动：** -19,135 元
> 📊 **持仓明细：** 
> - 中国核电：+2.06%
> - 永和股份：-32.45%
> - 中国联通：-16.25%”

### 场景 2：设定云端智能监控
> **您：** “帮我监控贵州茅台，只要突破MA20均线就通知我。”
> 
> **Agent（调用监控接口）：** 
> “✅ 已在云端成功创建监控任务：
> - **标的**：贵州茅台 (SH600519)
> - **条件**：价格突破 MA20
> - **通知**：飞书/微信推送
> *任务将在 Serverless 云端静默运行，触发时您将立刻收到推送。*”

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

```bash
# 触发执行（异步），记下返回的 executionId
# 自定义参数放在 body 里：key=参数名（以 - / -- 开头），value=参数值
# 后端会自动注入 `-f <procName>` —— body 里不用传 -f（传了也会被忽略）
RESP=$(scripts/lg_agent_exec.sh '{
  "skillId": "process.ingestion.execute",
  "params": {
    "pathParams": {"id": "123"},
    "body": {
      "-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 "{
    \"skillId\": \"process.ingestion.execute.log.get\",
    \"params\": {
      \"pathParams\": {\"id\": \"123\", \"executionId\": \"$EXEC_ID\"},
      \"query\": {\"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')"
```

返回：`status` 由 `running` 过渡到 `completed` 或 `failed`，`exitCode` 为脚本退出码，`logLines` 为增量日志行。

### 场景 4：策略回测（双均线跑茅台）

> **您：** “用双均线（5日/20日）对茅台 SH600519 过去三年跑个回测”

在平台新建一个 `python_script` 流程节点，脚本如下（`lg_utils` 已预装）：

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

```python
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 表
```

**组合回测**（共享现金池、多标的同时跑）：

```python
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`）会被下游节点或监控面板消费。

## 技能列表

### REST 技能（`scripts/lg_agent_exec.sh` 调用）

> 当前公开版 skill 仅包含只读能力与常规非破坏性写操作。删除、终止、撤销、系统级评估、审批流等高风险/管理类操作不在该公开版 skill 范围内。
> 风险标记：🟢 low / 🟡 medium / 🔴 high。所有 `GET` 技能默认对会话用户开放；写操作需显式授予 scope。

> ⚠️ **Token mode limitation**：以下 7 个高风险 skill 在 token 模式下返回 `HTTP 409` + `"High-risk approval flow for token mode is not enabled yet"`。Approval flow 仅 session 模式支持。如需通过外部 agent 执行，请使用平台 UI 预先确认操作，或选用非破坏性替代 skill。
> - `subscription.token.revoke`
> - `metric.alert.delete`
> - `schedule.instance.kill` / `schedule.instance.cancel` / `schedule.instance.force_start` / `schedule.instance.mark_success`
> - `schedule.job.delete`

> 📦 **Request shape**: skill 网关只读 `params` 字段下的 `pathParams` / `query` / `body`。**顶层** `pathParams` / `body` 会被静默丢弃。所有调用都必须用 envelope 形式：
> ```json
> {"skillId": "...", "params": {"pathParams": {...}, "query": {...}, "body": {...}}}
> ```
> 历史踩坑：2026-05-07 一次 backfill 因为漏写 `params:` 包裹，`-target_day_id 20260506` 没到 broker，python_script 拿到 `target_day_id=None` 跑了一轮空 SELECT。

### 流程 (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（节点+组件+边） | 🟡 |
| `process.pipeline.update` | PUT | **全量更新已有 pipeline**（`PUT /api/ingestions/{id}`，同形 `BuildPipelineRequest`）。`nodes` 省略=仅改名/描述，保留现有步骤；`nodes=[]` 显式清空；`nodes=[...]` 全量替换。每次 PUT 自动写一条 `dacp_meta_proc_version`，可 `/versions/{n}/restore` 回滚。legacy `team_name IS NULL` 的流程会直接 403，需先 backfill。 | 🟡 |

### 调度 (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.create` 的 `jobScript` 字段用 | 🟢 |
| `schedule.job.create` | POST | 创建调度作业（`POST /api/schedule/jobs`）。**新建后 `state="0"`，broker 不会自动拉起**，必须再调 `schedule.job.online` 激活。 | 🟡 |
| `schedule.job.update` | PUT | 更新作业配置（`PUT /api/schedule/job/{jobId}`）。注意：payload 里的 `state` 字段会被**静默丢弃**，不要试图用它改上下线——上下线必须走 online/offline 端点。 | 🟡 |
| `schedule.job.online` | POST | 上线作业，`state=0/-1 → 1`，**body 是 `{"jobCode":"..."}`，用 jobCode 而不是 jobId**。broker 下一轮轮询会把这个 job 加载到运行域，cron 才会真正触发。 | 🟡 |
| `schedule.job.offline` | POST | 下线作业，`state=1 → -1`。下线后 broker 保留元信息但不再触发。 | 🟡 |
| `schedule.job.delete` | DELETE | 删除作业（按 jobId）。`state=1` 时拒绝，请先 `schedule.job.offline`。 | 🔴 |
| `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.kill` | POST | **强制终止**运行中的实例（状态→"-1" 不可逆） | 🔴 |
| `schedule.instance.cancel` | POST | **取消**等待队列里还没开跑的实例 | 🔴 |
| `schedule.instance.force_start` | POST | **强制启动**——绕过依赖检查（危险，仅限手动修复上游后） | 🔴 |
| `schedule.instance.mark_success` | POST | **标记成功**——不真跑，只改 trigger.state="1" 解锁下游（适合带外补数后使用） | 🔴 |
| `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 插件 | 🟡 |

> **⚠️ 调度上线陷阱**：`state` 从 `0→1` 必须经由 **`POST /api/schedule/job/online`**（body `{"jobCode":"..."}`），**不是** `PUT /api/schedule/job/{jobId}`——后者会静默丢弃 `state`。broker 的 `fetchJobInfo` SQL 是 `WHERE state IN ('1','-1')`，`state=0` 的新建作业对 broker 不可见，不会被调度，也不会出现在 `schedule.instance.list` 里。**另外：broker 只对"依赖已就绪"的作业产生触发**，大多数新建作业需要配一条 `schedule.job.depends.save` 再上线，否则 online 之后还是不会有 instance。

#### 调度作业字段契约

**外部 agent 在调 `schedule.job.create` 之前，先走一遍"发现"**（这几个字段没有硬编码枚举，值取决于当前部署）：

1. `schedule.workgroup.list` → 拿到 `{workgroups, namespaces}`，从中各选一个赋给 `workgroup` / `namespace`。**传一个没人认领的 workgroup 不会报错，但没 broker 会去跑**——这是最典型的"创建完成但永远不执行"陷阱。
2. `schedule.scripts.get` → 拿到 `{dp, sh, py}`，按 `jobType` 选对应字段赋给 `jobScript`（`dp` 作业用 `dp`，`python` 作业用 `py`，`shell` 作业用 `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 时静默丢弃**，请用 `schedule.job.online/offline` |
| 服务端自动填充 | — | `jobId`（UUID）、`state="0"`、`version=1`、`teamName` / `memberName` / `createUser`（取自会话） |

**`schedule.job.depends.save` 的 body**（JSON 数组，**全量替换**）：

```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(...)}`）
- 其他字段：`procName`、`output`、`isDefault`（`"1"` 标默认）都可选
- `dependId` 服务端生成（UUID16），不用自己传

**`schedule.job.plugins.save` 的 body**（JSON 数组，**全量替换**）：

```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. `schedule.job.online`（body `{"jobCode":...}`）→ 让 broker 把它纳入触发域

#### 作业运维决策手册

Ops 流程几乎总是先 `schedule.instance.list`（或 `schedule.job.by_process`→`schedule.instance.list`）拿到目标 `jobTriggerId`，再按下面这张表选动作：

| 场景 | 推荐 skill | 备注 |
|---|---|---|
| 失败了想重跑一次 | `schedule.instance.redo` | 保留依赖链；默认 `opType="3"`，带依赖重跑 |
| 运行中但想先停住等数据就绪 | `schedule.instance.hold` | 不杀进程，可 `schedule.instance.resume` 恢复 |
| 运行中出问题要强杀 | `schedule.instance.kill` | **不可逆**，trigger state → `-1`；下游会卡住，后续要配合 mark_success 或 redo |
| 还没跑起来（队列里）想撤 | `schedule.instance.cancel` | 已经在跑的要用 kill，不是这个 |
| 上游数据已手工修复，想绕过依赖检查直接跑 | `schedule.instance.force_start` | 危险，改动面窄时用 |
| 任务实际已经跑完（带外补数了），只是想解锁下游 | `schedule.instance.mark_success` | 仅改数据库 state，不真跑 |
| 等待太久想插队 | `schedule.instance.reset_priority` | 只对"在队列等待"的实例有效 |
| 查上下游会被哪些 job 影响 | `schedule.job.lineage` | 删作业 / offline 前先看一下 |
| 已知 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 |

> **高危 ops 的共同前提**：`kill` / `cancel` / `force_start` / `mark_success` 都会 `confirmRequired=true`，agent 触发后需人类审批才会执行。`redo` / `hold` 也默认 `confirmRequired=true`，因为都会改变下游可见状态。

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

| skillId | method | 功能 | 风险 |
|---|---|---|---|
| `datasource.list` | GET | 列出数据源 | 🟢 |
| `datasource.get` | GET | 获取数据源详情 | 🟢 |
| `datasource.list.active` | GET | 列出活跃数据源 | 🟢 |
| `datasource.connection.test` | POST | 测试数据源连接 | 🟡 |
| `dataasset.list` | GET | 列出数据资产 | 🟢 |
| `dataasset.get` | GET | 获取资产详情 | 🟢 |
| `dataasset.schema.get` | GET | 获取资产 schema | 🟢 |
| `dataasset.data.get` | GET | 查询资产数据（盈亏、行情等） | 🟢 |

### 看板 & 工作空间 (Dashboard / Workspace)

| skillId | method | 功能 | 风险 |
|---|---|---|---|
| `dashboard.list` | GET | 列出看板 | 🟢 |
| `dashboard.get` | GET | 获取看板详情 | 🟢 |
| `dashboard.data.get` | GET | 一次拿看板所有组件的数据（支持 `maxRows`，默认 100，上限 500） | 🟢 |
| `workspace.list` | GET | 列出工作空间 | 🟢 |
| `workspace.get` | GET | 获取工作空间详情 | 🟢 |

### 订阅 & Marketplace

| skillId | method | 功能 | 风险 |
|---|---|---|---|
| `subscription.token.list` | GET | 列出订阅 token | 🟢 |
| `subscription.token.create` | POST | 创建订阅 token | 🟡 |
| `subscription.token.revoke` | DELETE | Revoke a subscription token by its numeric `id`. Path param: `tokenId: <id>`. Get the id from `subscription.token.list` first. | 🔴 |
| `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 | 更新告警规则 | 🟡 |
| `metric.alert.toggle` | PUT | 启用/停用规则 | 🟡 |
| `metric.alert.test` | POST | 仅测试（无副作用） | 🟡 |
| `metric.alert.evaluate` | POST | 执行评估并按规则触发 webhook | 🟡 |
| `metric.alert.delete` | DELETE | 删除告警规则（按 `ruleCode`） | 🔴 |

### Webhook 插件

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

### 用户注册 & 反馈

| skillId | method | 功能 | 风险 |
|---|---|---|---|
| `auth.user.register` | POST | 注册新账号（`teamName` 自动生成为 `tenant_${username}`） | 🟢 |
| `feedback.submit` | POST | 提交反馈/Bug/需求 | 🟢 |
| `feedback.list` | GET | 查看历史反馈与官方回复 | 🟢 |

## Backtest API

`stock_day` 日线回测请使用 `run_stock_day_backtest`（单股）或 `run_stock_day_portfolio_backtest`（多股组合），均在 `lg_utils.backtest_examples.stock_day` 模块。完整参数说明见下方 Python 工具库表格及[场景 4 示例](#场景-4策略回测双均线跑茅台)。

结果可通过 `investment.stock.backtest.*` REST skill 检索：`list`（摘要）、`get`（全量 JSON）、`compare`（两次 metrics diff）。回测目前仅支持股票，基金/黄金 backtest 暂不支持。

### 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=...` | 🟢 |
| `investment.stock.portfolio.create` | POST | 新增股票持仓条目（body 含 `asset_class: "stock"`） | 🟡 |
| `investment.stock.portfolio.update` | PUT | 更新股票持仓 | 🟡 |
| `investment.stock.portfolio.delete` | DELETE | 删除股票持仓 | 🔴 |
| `investment.stock.trading.list` | GET | 查股票交易记录（query 传 `asset_class=stock`） | 🟢 |
| `investment.stock.trading.create` | POST | 录入新股票交易（自动更新持仓；body 含 `asset_class: "stock"`） | 🟡 |
| `investment.stock.trading.delete` | DELETE | 删除最近一笔股票交易（并反向更新持仓） | 🔴 |
| `investment.stock.backtest.list` | GET | 列当前团队的回测结果（`lg_utils.write_backtest_result` / `BacktestResult.persist` 写入的行），支持按 `name` 过滤。**Summary 视图**——返回 `totalReturn/sharpe/maxDrawdown/...` 等数值列，不含大 JSON | 🟢 |
| `investment.stock.backtest.get` | GET | 单条回测结果详情（按 id），包含 `paramsJson/metricsJson/tradesJson/equityCurveJson` 等全量 JSON 负载 | 🟢 |
| `investment.stock.backtest.compare` | GET | 两次回测结果 metrics 逐项 diff（`totalReturn/sharpe/maxDrawdown/...`） | 🟢 |

#### investment.fund.* — 基金持仓 / 交易（回测暂不支持）

| skillId | method | 功能 | 风险 |
|---|---|---|---|
| `investment.fund.portfolio.list` | GET | 查基金持仓（query 传 `asset_class=fund`；`market` 取值 OF/ETF/LOF） | 🟢 |
| `investment.fund.portfolio.create` | POST | 新增基金持仓（body 含 `asset_class: "fund"`） | 🟡 |
| `investment.fund.portfolio.update` | PUT | 更新基金持仓 | 🟡 |
| `investment.fund.portfolio.delete` | DELETE | 删除基金持仓 | 🔴 |
| `investment.fund.trading.list` | GET | 查基金交易记录（query 传 `asset_class=fund`） | 🟢 |
| `investment.fund.trading.create` | POST | 录入新基金交易（body 含 `asset_class: "fund"`） | 🟡 |
| `investment.fund.trading.delete` | DELETE | 删除最近一笔基金交易 | 🔴 |

#### investment.gold.* — 黄金持仓 / 交易（回测暂不支持）

| skillId | method | 功能 | 风险 |
|---|---|---|---|
| `investment.gold.portfolio.list` | GET | 查黄金持仓（query 传 `asset_class=gold`；`market` 取值 SGE/BANK；`stock_num` 取值 Au99.99/Au100g/AuTD） | 🟢 |
| `investment.gold.portfolio.create` | POST | 新增黄金持仓（body 含 `asset_class: "gold"`） | 🟡 |
| `investment.gold.portfolio.update` | PUT | 更新黄金持仓 | 🟡 |
| `investment.gold.portfolio.delete` | DELETE | 删除黄金持仓 | 🔴 |
| `investment.gold.trading.list` | GET | 查黄金交易记录（query 传 `asset_class=gold`） | 🟢 |
| `investment.gold.trading.create` | POST | 录入新黄金交易（body 含 `asset_class: "gold"`） | 🟡 |
| `investment.gold.trading.delete` | DELETE | 删除最近一笔黄金交易 | 🔴 |

> 技能源在 `app.js` 的 `SKILL_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)` | 分页拉团队有权限的资产数据；返回 `{success, data, totalElements, totalPages, ...}`。`filter_value` 支持 list/tuple → IN 查询；`filter_operator` 支持 `eq / ne / in / not_in / like / gt / gte / lt / lte / contains`，默认 `contains` |
| `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_AGENT_BASE_URL` - 平台地址（默认 `https://lg-data.cc`）
- `LG_AGENT_TOKEN` - Bearer Token（公开版唯一认证方式；建议使用最小权限、专用 Token）

## Security Notes

- 公开版 skill 仅支持 Bearer Token 模式，不接受会话 Cookie / CSRF。
- 首次安装建议使用测试账号或低权限 Token 验证读取类能力。
- 当前公开版 skill 仅面向只读与常规非破坏性写操作；删除、终止、审批与其他管理类能力应通过单独的 admin 工具或人工流程处理。
- 写操作应只授予明确需要的 scopes。
- 不要在脚本里硬编码 Token 凭据。

## 注意事项

- 公开版 skill 不包含删除、终止、撤销、审批等高风险管理操作。
- 公开版 helper scripts 只支持 Token 调用，不支持 session/cookie 兼容模式。
- `idempotencyKey` 用于幂等控制，写操作请保持稳定。
- Token 从平台获取，不要硬编码在脚本中。

---

**传送门：** [https://lg-data.cc](https://lg-data.cc)