@openfinclaw/openfinclaw-strategy 2026.3.272 → 2026.3.274
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SKILL.md +35 -34
- package/index.test.ts +15 -3
- package/index.ts +16 -0
- package/openclaw.plugin.json +1 -0
- package/package.json +1 -1
- package/src/cli.ts +2 -2
- package/src/http/gateway-proxy.ts +73 -0
- package/src/prompt-guidance.ts +11 -0
- package/src/strategy/tools.ts +21 -4
- package/web/index.html +677 -675
package/SKILL.md
CHANGED
|
@@ -7,6 +7,7 @@ metadata:
|
|
|
7
7
|
requires:
|
|
8
8
|
extensions: ["openfinclaw-strategy"]
|
|
9
9
|
---
|
|
10
|
+
|
|
10
11
|
# OpenFinClaw
|
|
11
12
|
|
|
12
13
|
统一金融工具平台,一个 API Key 即可使用所有功能:
|
|
@@ -121,42 +122,42 @@ export OPENFINCLAW_API_KEY=YOUR_API_KEY
|
|
|
121
122
|
|
|
122
123
|
### 行情数据工具
|
|
123
124
|
|
|
124
|
-
| 工具名
|
|
125
|
-
|
|
|
126
|
-
| `fin_price` | 价格查询(股票/加密/指数) | **是**
|
|
127
|
-
| `fin_kline` | K线/OHLCV 数据 | **是**
|
|
128
|
-
| `fin_crypto` | 加密市场数据(21个端点) | **是**
|
|
129
|
-
| `fin_compare` | 多资产价格对比(2-5个资产) | **是**
|
|
130
|
-
| `fin_slim_search` | 代码/名称搜索 | **是**
|
|
125
|
+
| 工具名 | 用途 | 需要 API Key |
|
|
126
|
+
| ----------------- | --------------------------- | ------------ |
|
|
127
|
+
| `fin_price` | 价格查询(股票/加密/指数) | **是** |
|
|
128
|
+
| `fin_kline` | K线/OHLCV 数据 | **是** |
|
|
129
|
+
| `fin_crypto` | 加密市场数据(21个端点) | **是** |
|
|
130
|
+
| `fin_compare` | 多资产价格对比(2-5个资产) | **是** |
|
|
131
|
+
| `fin_slim_search` | 代码/名称搜索 | **是** |
|
|
131
132
|
|
|
132
133
|
### 策略工具
|
|
133
134
|
|
|
134
|
-
| 工具名
|
|
135
|
-
|
|
|
135
|
+
| 工具名 | 用途 | 需要 API Key |
|
|
136
|
+
| ---------------------- | -------------------------------------- | ------------ |
|
|
136
137
|
| `skill_leaderboard` | 查询排行榜(综合/收益/风控/人气/新星) | 否 |
|
|
137
138
|
| `skill_get_info` | 获取 Hub 策略公开详情 | 否 |
|
|
138
139
|
| `skill_validate` | 本地验证策略包格式(FEP v2.0) | 否 |
|
|
139
140
|
| `skill_list_local` | 列出本地已下载的策略 | 否 |
|
|
140
|
-
| `skill_fork` | 从 Hub 下载公开策略到本地 | **是**
|
|
141
|
-
| `skill_publish` | 发布策略 ZIP 到 Hub,自动触发回测 | **是**
|
|
142
|
-
| `skill_publish_verify` | 查询发布状态和回测报告 | **是**
|
|
141
|
+
| `skill_fork` | 从 Hub 下载公开策略到本地 | **是** |
|
|
142
|
+
| `skill_publish` | 发布策略 ZIP 到 Hub,自动触发回测 | **是** |
|
|
143
|
+
| `skill_publish_verify` | 查询发布状态和回测报告 | **是** |
|
|
143
144
|
|
|
144
145
|
### Skills(指导文档)
|
|
145
146
|
|
|
146
147
|
#### 行情数据 Skills
|
|
147
148
|
|
|
148
|
-
| Skill
|
|
149
|
-
|
|
|
149
|
+
| Skill | 触发场景 | 说明 |
|
|
150
|
+
| ------------- | ------------------ | ---------------- |
|
|
150
151
|
| `price-check` | 快速查价、XX多少钱 | 最简单的价格查询 |
|
|
151
152
|
|
|
152
153
|
#### 策略 Skills
|
|
153
154
|
|
|
154
|
-
| Skill
|
|
155
|
-
|
|
|
156
|
-
| `strategy-builder` | 创建新策略、生成策略代码 | 自然语言 → FEP v2.0 策略包
|
|
155
|
+
| Skill | 触发场景 | 说明 |
|
|
156
|
+
| ------------------ | ------------------------ | ----------------------------- |
|
|
157
|
+
| `strategy-builder` | 创建新策略、生成策略代码 | 自然语言 → FEP v2.0 策略包 |
|
|
157
158
|
| `skill-publish` | 发布策略到服务器 | 验证 → 打包 → 发布 → 查询回测 |
|
|
158
|
-
| `strategy-fork` | 下载/克隆 Hub 策略 | Fork → 本地编辑 → 发布新版本
|
|
159
|
-
| `strategy-pack` | 创建回测策略包 | 生成 fep.yaml + strategy.py
|
|
159
|
+
| `strategy-fork` | 下载/克隆 Hub 策略 | Fork → 本地编辑 → 发布新版本 |
|
|
160
|
+
| `strategy-pack` | 创建回测策略包 | 生成 fep.yaml + strategy.py |
|
|
160
161
|
|
|
161
162
|
### 典型工作流
|
|
162
163
|
|
|
@@ -186,8 +187,8 @@ openclaw strategy leaderboard popular --offset 20 --limit 20
|
|
|
186
187
|
|
|
187
188
|
**榜单类型**:
|
|
188
189
|
|
|
189
|
-
| 榜单类型
|
|
190
|
-
|
|
|
190
|
+
| 榜单类型 | 说明 | 排序依据 |
|
|
191
|
+
| ----------- | -------------- | ---------------- |
|
|
191
192
|
| `composite` | 综合榜(默认) | FCS 综合分 |
|
|
192
193
|
| `returns` | 收益榜 | 收益率 |
|
|
193
194
|
| `risk` | 风控榜 | 风控分 |
|
|
@@ -252,8 +253,8 @@ openclaw strategy show 550e8400-e29b-41d4-a716-446655440001 --remote
|
|
|
252
253
|
|
|
253
254
|
看板包含 4 个标签页:
|
|
254
255
|
|
|
255
|
-
| 标签页 | 数据来源
|
|
256
|
-
| -------- |
|
|
256
|
+
| 标签页 | 数据来源 | 说明 |
|
|
257
|
+
| -------- | -------------------- | ------------------------------ |
|
|
257
258
|
| 活动日志 | `agent_activity_log` | 所有 12 个工具的每次执行记录 |
|
|
258
259
|
| 事件流 | `agent_events` | Fork、发布、回测完成等关键事件 |
|
|
259
260
|
| 策略列表 | `strategies` | 本地策略生命周期状态 |
|
|
@@ -261,8 +262,8 @@ openclaw strategy show 550e8400-e29b-41d4-a716-446655440001 --remote
|
|
|
261
262
|
|
|
262
263
|
**REST API**(供前端或其他工具调用):
|
|
263
264
|
|
|
264
|
-
| 端点
|
|
265
|
-
|
|
|
265
|
+
| 端点 | 说明 |
|
|
266
|
+
| --------------------------- | ------------------------ |
|
|
266
267
|
| `GET /api/activity-log` | 工具调用日志(分页) |
|
|
267
268
|
| `GET /api/agent-events` | 事件流(分页) |
|
|
268
269
|
| `GET /api/strategies` | 策略列表 |
|
|
@@ -283,19 +284,19 @@ openclaw strategy show 550e8400-e29b-41d4-a716-446655440001 --remote
|
|
|
283
284
|
|
|
284
285
|
当用户提到以下内容时,应引导阅读对应的 Skill:
|
|
285
286
|
|
|
286
|
-
| 触发关键词 | Skill
|
|
287
|
-
| ------------------------------ |
|
|
288
|
-
| XX多少钱、什么价格、查价 | `price-check` | 最简单的价格查询
|
|
287
|
+
| 触发关键词 | Skill | 说明 |
|
|
288
|
+
| ------------------------------ | ------------------ | ------------------- |
|
|
289
|
+
| XX多少钱、什么价格、查价 | `price-check` | 最简单的价格查询 |
|
|
289
290
|
| 创建策略、写策略、生成策略包 | `strategy-builder` | 自然语言 → FEP v2.0 |
|
|
290
|
-
| 发布策略、上传策略、提交策略 | `skill-publish` | 验证 → 打包 → 发布
|
|
291
|
-
| Fork 策略、下载策略、克隆策略 | `strategy-fork` | 从 Hub Fork 策略
|
|
292
|
-
| 策略包格式、FEP 规范、打包回测 | `strategy-pack` | FEP v2.0 规范详解
|
|
291
|
+
| 发布策略、上传策略、提交策略 | `skill-publish` | 验证 → 打包 → 发布 |
|
|
292
|
+
| Fork 策略、下载策略、克隆策略 | `strategy-fork` | 从 Hub Fork 策略 |
|
|
293
|
+
| 策略包格式、FEP 规范、打包回测 | `strategy-pack` | FEP v2.0 规范详解 |
|
|
293
294
|
|
|
294
295
|
## 配置选项
|
|
295
296
|
|
|
296
|
-
| 配置项
|
|
297
|
-
|
|
|
298
|
-
| `apiKey` | `OPENFINCLAW_API_KEY` | 统一 API Key | 必填
|
|
297
|
+
| 配置项 | 环境变量 | 说明 | 默认值 |
|
|
298
|
+
| ------------------- | ----------------------- | ---------------- | ---------------------------- |
|
|
299
|
+
| `apiKey` | `OPENFINCLAW_API_KEY` | 统一 API Key | 必填 |
|
|
299
300
|
| `hubApiUrl` | `HUB_API_URL` | Hub 服务地址 | `https://hub.openfinclaw.ai` |
|
|
300
301
|
| `datahubGatewayUrl` | `DATAHUB_GATEWAY_URL` | DataHub 网关地址 | `http://43.134.61.136:9080` |
|
|
301
302
|
| `requestTimeoutMs` | `REQUEST_TIMEOUT_MS` | 请求超时(毫秒) | `60000` |
|
package/index.test.ts
CHANGED
|
@@ -44,7 +44,7 @@ function createFakeApi(pluginConfig: Record<string, unknown>): {
|
|
|
44
44
|
},
|
|
45
45
|
registerHook() {},
|
|
46
46
|
registerHttpHandler() {},
|
|
47
|
-
registerHttpRoute()
|
|
47
|
+
registerHttpRoute: vi.fn(),
|
|
48
48
|
registerChannel() {},
|
|
49
49
|
registerGatewayMethod() {},
|
|
50
50
|
registerCli() {},
|
|
@@ -70,11 +70,23 @@ describe("openfinclaw plugin", () => {
|
|
|
70
70
|
expect(plugin.name).toBe("OpenFinClaw");
|
|
71
71
|
});
|
|
72
72
|
|
|
73
|
-
it("registers
|
|
73
|
+
it("registers gateway HTTP proxy for Control UI", () => {
|
|
74
|
+
const { api } = createFakeApi({});
|
|
75
|
+
plugin.register(api);
|
|
76
|
+
expect(api.registerHttpRoute).toHaveBeenCalledWith(
|
|
77
|
+
expect.objectContaining({
|
|
78
|
+
path: "/plugins/openfinclaw",
|
|
79
|
+
match: "prefix",
|
|
80
|
+
auth: "gateway",
|
|
81
|
+
}),
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("registers expected strategy and market tools", () => {
|
|
74
86
|
const { api, tools } = createFakeApi({});
|
|
75
87
|
plugin.register(api);
|
|
76
88
|
|
|
77
|
-
expect(tools.size).
|
|
89
|
+
expect(tools.size).toBeGreaterThanOrEqual(7);
|
|
78
90
|
expect(tools.has("skill_publish")).toBe(true);
|
|
79
91
|
expect(tools.has("skill_publish_verify")).toBe(true);
|
|
80
92
|
expect(tools.has("skill_validate")).toBe(true);
|
package/index.ts
CHANGED
|
@@ -13,7 +13,9 @@ import { registerStrategyCli } from "./src/cli.js";
|
|
|
13
13
|
import { resolvePluginConfig } from "./src/config.js";
|
|
14
14
|
import { registerDatahubTools } from "./src/datahub/tools.js";
|
|
15
15
|
import { getDb } from "./src/db/db.js";
|
|
16
|
+
import { createOpenFinclawGatewayProxy } from "./src/http/gateway-proxy.js";
|
|
16
17
|
import { startHttpServer } from "./src/http/server.js";
|
|
18
|
+
import { OPENFINCLAW_AGENT_GUIDANCE } from "./src/prompt-guidance.js";
|
|
17
19
|
import { registerStrategyTools } from "./src/strategy/tools.js";
|
|
18
20
|
|
|
19
21
|
const openfinclawPlugin = {
|
|
@@ -51,6 +53,20 @@ const openfinclawPlugin = {
|
|
|
51
53
|
// startHttpServer() closes any previous server from a prior hot-reload
|
|
52
54
|
// before binding, so EADDRINUSE is avoided without a try/catch here.
|
|
53
55
|
startHttpServer(config.httpPort, api.logger);
|
|
56
|
+
|
|
57
|
+
// Same-origin proxy for Control UI (avoids CSP connect-src to loopback)
|
|
58
|
+
// auth: "plugin" — no gateway auth required; upstream is loopback-only (127.0.0.1)
|
|
59
|
+
api.registerHttpRoute({
|
|
60
|
+
path: "/plugins/openfinclaw",
|
|
61
|
+
match: "prefix",
|
|
62
|
+
auth: "plugin",
|
|
63
|
+
handler: createOpenFinclawGatewayProxy({ port: config.httpPort, logger: api.logger }),
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
// Inject agent system prompt: prioritise tool calls so data lands in SQLite
|
|
67
|
+
api.on("before_prompt_build", async () => ({
|
|
68
|
+
prependSystemContext: OPENFINCLAW_AGENT_GUIDANCE,
|
|
69
|
+
}));
|
|
54
70
|
},
|
|
55
71
|
};
|
|
56
72
|
|
package/openclaw.plugin.json
CHANGED
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
"name": "OpenFinClaw",
|
|
4
4
|
"description": "Unified financial tools: market data (price/K-line/crypto/compare/search), strategy publishing, fork, and validation. Single API key for Hub and DataHub.",
|
|
5
5
|
"kind": "financial",
|
|
6
|
+
"enabledByDefault": true,
|
|
6
7
|
"version": "2026.3.25",
|
|
7
8
|
"skills": ["."],
|
|
8
9
|
"configSchema": {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openfinclaw/openfinclaw-strategy",
|
|
3
|
-
"version": "2026.3.
|
|
3
|
+
"version": "2026.3.274",
|
|
4
4
|
"description": "OpenFinClaw - Unified financial tools: market data (price/K-line/crypto/compare/search), strategy publishing, fork, and validation. Single API key for Hub and DataHub.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"backtest",
|
package/src/cli.ts
CHANGED
|
@@ -101,7 +101,7 @@ export function registerStrategyCli(params: {
|
|
|
101
101
|
const perf = s.performance || {};
|
|
102
102
|
const returnStr =
|
|
103
103
|
typeof perf.returnSincePublish === "number"
|
|
104
|
-
? `收益: ${
|
|
104
|
+
? `收益: ${perf.returnSincePublish.toFixed(1)}%`
|
|
105
105
|
: "收益: --";
|
|
106
106
|
const sharpeStr =
|
|
107
107
|
typeof perf.sharpeRatio === "number"
|
|
@@ -109,7 +109,7 @@ export function registerStrategyCli(params: {
|
|
|
109
109
|
: "夏普: --";
|
|
110
110
|
const ddStr =
|
|
111
111
|
typeof perf.maxDrawdown === "number"
|
|
112
|
-
? `回撤: ${
|
|
112
|
+
? `回撤: ${perf.maxDrawdown.toFixed(1)}%`
|
|
113
113
|
: "回撤: --";
|
|
114
114
|
const author = s.author?.displayName || "未知";
|
|
115
115
|
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Reverse-proxy OpenFinclaw loopback dashboard API through the gateway under
|
|
3
|
+
* `/plugins/openfinclaw/*` so the Control UI can use same-origin fetch (CSP connect-src 'self').
|
|
4
|
+
*/
|
|
5
|
+
import type { IncomingMessage, ServerResponse } from "node:http";
|
|
6
|
+
import type { DashboardLogger } from "./server.js";
|
|
7
|
+
|
|
8
|
+
export const OPENFINCLAW_GATEWAY_PROXY_PREFIX = "/plugins/openfinclaw";
|
|
9
|
+
|
|
10
|
+
const HOP_BY_HOP = new Set([
|
|
11
|
+
"connection",
|
|
12
|
+
"keep-alive",
|
|
13
|
+
"proxy-authenticate",
|
|
14
|
+
"proxy-authorization",
|
|
15
|
+
"te",
|
|
16
|
+
"trailers",
|
|
17
|
+
"transfer-encoding",
|
|
18
|
+
"upgrade",
|
|
19
|
+
]);
|
|
20
|
+
|
|
21
|
+
export function createOpenFinclawGatewayProxy(params: {
|
|
22
|
+
port: number;
|
|
23
|
+
logger: DashboardLogger;
|
|
24
|
+
}): (req: IncomingMessage, res: ServerResponse) => Promise<boolean> {
|
|
25
|
+
const upstream = `http://127.0.0.1:${params.port}`;
|
|
26
|
+
|
|
27
|
+
return async (req: IncomingMessage, res: ServerResponse): Promise<boolean> => {
|
|
28
|
+
const raw = req.url ?? "/";
|
|
29
|
+
const u = new URL(raw, "http://localhost");
|
|
30
|
+
if (!u.pathname.startsWith(OPENFINCLAW_GATEWAY_PROXY_PREFIX)) {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const subPath =
|
|
35
|
+
u.pathname === OPENFINCLAW_GATEWAY_PROXY_PREFIX
|
|
36
|
+
? "/"
|
|
37
|
+
: u.pathname.slice(OPENFINCLAW_GATEWAY_PROXY_PREFIX.length) || "/";
|
|
38
|
+
const targetUrl = `${upstream}${subPath.startsWith("/") ? subPath : `/${subPath}`}${u.search}`;
|
|
39
|
+
|
|
40
|
+
const method = req.method ?? "GET";
|
|
41
|
+
if (method !== "GET" && method !== "HEAD") {
|
|
42
|
+
res.statusCode = 405;
|
|
43
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
44
|
+
res.end("Method Not Allowed");
|
|
45
|
+
return true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
const r = await fetch(targetUrl, {
|
|
50
|
+
method,
|
|
51
|
+
headers: { Accept: (req.headers.accept as string) || "application/json" },
|
|
52
|
+
signal: AbortSignal.timeout(30_000),
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
r.headers.forEach((value, key) => {
|
|
56
|
+
if (!HOP_BY_HOP.has(key.toLowerCase())) {
|
|
57
|
+
res.setHeader(key, value);
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
res.statusCode = r.status;
|
|
61
|
+
res.end(Buffer.from(await r.arrayBuffer()));
|
|
62
|
+
return true;
|
|
63
|
+
} catch (e) {
|
|
64
|
+
params.logger.info(`[OpenFinClaw] Gateway proxy → ${targetUrl} failed: ${String(e)}`);
|
|
65
|
+
if (!res.headersSent) {
|
|
66
|
+
res.statusCode = 502;
|
|
67
|
+
res.setHeader("Content-Type", "text/plain; charset=utf-8");
|
|
68
|
+
res.end("Bad Gateway (OpenFinClaw loopback server unreachable)");
|
|
69
|
+
}
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
};
|
|
73
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent system prompt guidance for OpenFinClaw plugin.
|
|
3
|
+
* Injected via before_prompt_build hook so the AI prioritises tool calls
|
|
4
|
+
* over CLI/HTTP workarounds — only tool executions write to SQLite.
|
|
5
|
+
*/
|
|
6
|
+
export const OPENFINCLAW_AGENT_GUIDANCE = [
|
|
7
|
+
"当用户需要策略发布、验证、Fork、排行榜、策略详情查询等操作时,必须使用对应的 plugin tool(skill_publish, skill_publish_verify, skill_validate, skill_fork, skill_leaderboard, skill_get_info, skill_list_local),不要尝试通过 CLI 命令或 HTTP 请求替代。",
|
|
8
|
+
"当用户需要市场行情数据(价格、K线、加密货币、对比、搜索)时,必须使用 fin_price, fin_kline, fin_crypto, fin_compare, fin_slim_search 等 tool。",
|
|
9
|
+
"只有通过 tool 执行的操作才会写入 SQLite 数据库并在 Dashboard 中展示,绕过 tool 调用的数据不会落库。",
|
|
10
|
+
"tool 调用失败时优先检查 API Key 是否配置(OPENFINCLAW_API_KEY 环境变量或插件配置 apiKey)。",
|
|
11
|
+
].join("\n");
|
package/src/strategy/tools.ts
CHANGED
|
@@ -237,14 +237,31 @@ export function registerStrategyTools(
|
|
|
237
237
|
// Update backtest result metrics and strategy level
|
|
238
238
|
const verifyStrategyId = resp.entryId as string | undefined;
|
|
239
239
|
if (resp.backtestStatus === "completed" && backtestTaskId) {
|
|
240
|
-
const
|
|
241
|
-
|
|
240
|
+
const report = resp.backtestReport as Record<string, unknown> | undefined;
|
|
241
|
+
const perf = report?.performance as Record<string, unknown> | undefined;
|
|
242
242
|
updateBacktestResult(db, backtestTaskId, {
|
|
243
243
|
status: "completed",
|
|
244
244
|
total_return: typeof perf?.totalReturn === "number" ? perf.totalReturn : undefined,
|
|
245
245
|
sharpe: typeof perf?.sharpe === "number" ? perf.sharpe : undefined,
|
|
246
|
+
sortino: typeof perf?.sortino === "number" ? perf.sortino : undefined,
|
|
246
247
|
max_drawdown: typeof perf?.maxDrawdown === "number" ? perf.maxDrawdown : undefined,
|
|
247
248
|
win_rate: typeof perf?.winRate === "number" ? perf.winRate : undefined,
|
|
249
|
+
profit_factor:
|
|
250
|
+
typeof perf?.profitFactor === "number" ? perf.profitFactor : undefined,
|
|
251
|
+
total_trades: typeof perf?.totalTrades === "number" ? perf.totalTrades : undefined,
|
|
252
|
+
final_equity: typeof perf?.finalEquity === "number" ? perf.finalEquity : undefined,
|
|
253
|
+
equity_curve: Array.isArray(report?.equity_curve)
|
|
254
|
+
? JSON.stringify(report.equity_curve)
|
|
255
|
+
: undefined,
|
|
256
|
+
trade_journal: Array.isArray(report?.trade_journal)
|
|
257
|
+
? JSON.stringify(report.trade_journal)
|
|
258
|
+
: undefined,
|
|
259
|
+
monthly_returns:
|
|
260
|
+
report?.monthly_returns && typeof report.monthly_returns === "object"
|
|
261
|
+
? JSON.stringify(report.monthly_returns)
|
|
262
|
+
: undefined,
|
|
263
|
+
tearsheet_html:
|
|
264
|
+
typeof report?.tearsheet_html === "string" ? report.tearsheet_html : undefined,
|
|
248
265
|
completed_at: new Date().toISOString(),
|
|
249
266
|
});
|
|
250
267
|
// Backtest completed → strategy stays at L1
|
|
@@ -416,7 +433,7 @@ export function registerStrategyTools(
|
|
|
416
433
|
const perf = s.performance || {};
|
|
417
434
|
const returnStr =
|
|
418
435
|
typeof perf.returnSincePublish === "number"
|
|
419
|
-
? `收益: ${
|
|
436
|
+
? `收益: ${perf.returnSincePublish.toFixed(1)}%`
|
|
420
437
|
: "收益: --";
|
|
421
438
|
const sharpeStr =
|
|
422
439
|
typeof perf.sharpeRatio === "number"
|
|
@@ -622,7 +639,7 @@ export function registerStrategyTools(
|
|
|
622
639
|
lines.push("");
|
|
623
640
|
lines.push("绩效指标:");
|
|
624
641
|
if (typeof info.backtestResult.totalReturn === "number")
|
|
625
|
-
lines.push(`- 总收益率: ${
|
|
642
|
+
lines.push(`- 总收益率: ${info.backtestResult.totalReturn.toFixed(2)}%`);
|
|
626
643
|
if (typeof info.backtestResult.sharpe === "number")
|
|
627
644
|
lines.push(`- 夏普比率: ${info.backtestResult.sharpe.toFixed(3)}`);
|
|
628
645
|
}
|