@openfinclaw/openfinclaw-strategy 2026.3.25 → 2026.3.27

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/index.ts CHANGED
@@ -1,16 +1,20 @@
1
+ import type { Command } from "commander";
1
2
  /**
2
3
  * OpenFinClaw — Unified financial tools plugin.
3
4
  * Features:
4
5
  * - Strategy tools: publish, validate, fork, leaderboard
5
6
  * - Market data tools: price, K-line, crypto data, compare, search
7
+ * - SQLite persistence: all tool executions logged; domain tables for strategies and backtests
8
+ * - Dashboard: embedded HTTP server at http://127.0.0.1:<httpPort> (default 18792)
6
9
  * Supports FEP v2.0 protocol for strategy packages.
7
10
  */
8
- import type { OpenClawPluginApi } from "openfinclaw/plugin-sdk";
9
- import type { Command } from "commander";
10
- import { registerDatahubTools } from "./src/datahub/tools.js";
11
- import { registerStrategyTools } from "./src/strategy/tools.js";
11
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
12
12
  import { registerStrategyCli } from "./src/cli.js";
13
13
  import { resolvePluginConfig } from "./src/config.js";
14
+ import { registerDatahubTools } from "./src/datahub/tools.js";
15
+ import { getDb } from "./src/db/db.js";
16
+ import { startHttpServer } from "./src/http/server.js";
17
+ import { registerStrategyTools } from "./src/strategy/tools.js";
14
18
 
15
19
  const openfinclawPlugin = {
16
20
  id: "openfinclaw",
@@ -22,11 +26,14 @@ const openfinclawPlugin = {
22
26
  register(api: OpenClawPluginApi) {
23
27
  const config = resolvePluginConfig(api);
24
28
 
29
+ // Initialise SQLite database (creates tables on first run)
30
+ const db = getDb();
31
+
25
32
  // Register DataHub market data tools (fin_price, fin_kline, fin_crypto, fin_compare, fin_slim_search)
26
- registerDatahubTools(api, config);
33
+ registerDatahubTools(api, config, db);
27
34
 
28
35
  // Register strategy tools (skill_publish, skill_validate, skill_fork, skill_leaderboard, etc.)
29
- registerStrategyTools(api, config);
36
+ registerStrategyTools(api, config, db);
30
37
 
31
38
  // Register CLI commands
32
39
  api.registerCli(
@@ -38,7 +45,10 @@ const openfinclawPlugin = {
38
45
  }),
39
46
  { commands: ["strategy"] },
40
47
  );
48
+
49
+ // Start embedded dashboard HTTP server (loopback only, configurable port)
50
+ startHttpServer(db, config.httpPort, api.logger);
41
51
  },
42
52
  };
43
53
 
44
- export default openfinclawPlugin;
54
+ export default openfinclawPlugin;
@@ -3,7 +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
- "version": "0.1.0",
6
+ "version": "2026.3.25",
7
7
  "skills": ["./skills"],
8
8
  "configSchema": {
9
9
  "type": "object",
@@ -30,6 +30,13 @@
30
30
  "minimum": 5000,
31
31
  "maximum": 300000,
32
32
  "description": "HTTP request timeout in milliseconds"
33
+ },
34
+ "httpPort": {
35
+ "type": "number",
36
+ "default": 18792,
37
+ "minimum": 1024,
38
+ "maximum": 65535,
39
+ "description": "Dashboard HTTP server port (loopback only)"
33
40
  }
34
41
  }
35
42
  },
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfinclaw/openfinclaw-strategy",
3
- "version": "2026.3.25",
3
+ "version": "2026.3.27",
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",
@@ -34,7 +34,7 @@
34
34
  "registry": "https://registry.npmjs.org"
35
35
  },
36
36
  "dependencies": {
37
- "@sinclair/typebox": "^0.34.0",
37
+ "@sinclair/typebox": "0.34.48",
38
38
  "adm-zip": "^0.5.16"
39
39
  },
40
40
  "devDependencies": {
@@ -12,14 +12,15 @@ metadata: { "openclaw": { "emoji": "💰", "requires": { "extensions": ["openfin
12
12
 
13
13
  ### fin_price — 查当前价
14
14
 
15
- | Parameter | Type | Required | Example |
16
- | --------- | ------ | -------- | ---------- |
17
- | symbol | string | Yes | BTC/USDT |
18
- | market | string | No | crypto |
15
+ | Parameter | Type | Required | Example |
16
+ | --------- | ------ | -------- | -------- |
17
+ | symbol | string | Yes | BTC/USDT |
18
+ | market | string | No | crypto |
19
19
 
20
20
  Market 自动检测:含 `/` → crypto;`.SH/.SZ/.HK` 或纯字母 → equity。
21
21
 
22
22
  **返回字段:**
23
+
23
24
  - `price`: 最新价格
24
25
  - `volume24h`: 24小时成交量(加密货币)
25
26
  - `timestamp`: 数据时间
@@ -33,6 +34,7 @@ Market 自动检测:含 `/` → crypto;`.SH/.SZ/.HK` 或纯字母 → equity
33
34
  | limit | number | No | 30 | 10 |
34
35
 
35
36
  **返回字段:**
37
+
36
38
  - `bars[]`: K线数组,包含 date, open, high, low, close, volume
37
39
 
38
40
  ### fin_compare — 多资产对比
@@ -42,50 +44,52 @@ Market 自动检测:含 `/` → crypto;`.SH/.SZ/.HK` 或纯字母 → equity
42
44
  | symbols | string | Yes | BTC/USDT,ETH/USDT,600519.SH |
43
45
 
44
46
  **返回字段:**
47
+
45
48
  - `comparison[]`: 每个资产的 price, weekChange(周涨跌幅)
46
49
 
47
50
  ### fin_slim_search — 搜索代码
48
51
 
49
52
  当用户只说公司/币种名称,不确定 symbol 时使用:
50
53
 
51
- | Parameter | Type | Required | Example |
52
- | --------- | ------ | -------- | --------- |
53
- | query | string | Yes | 茅台 |
54
- | market | string | No | equity |
54
+ | Parameter | Type | Required | Example |
55
+ | --------- | ------ | -------- | ------- |
56
+ | query | string | Yes | 茅台 |
57
+ | market | string | No | equity |
55
58
 
56
59
  **触发场景:**
60
+
57
61
  - 用户说 "茅台多少钱" → 先搜索 `query="茅台"` → 找到 `600519.SH` → 再查价格
58
62
  - 用户说 "特斯拉股价" → 先搜索 `query="特斯拉"` → 找到 `TSLA` → 再查价格
59
63
 
60
64
  ### fin_crypto — 加密市场数据
61
65
 
62
- | Parameter | Type | Required | Example |
63
- | --------- | ------ | -------- | ---------------------------- |
64
- | endpoint | string | Yes | market/funding_rate |
65
- | symbol | string | No | BTC/USDT:USDT |
66
- | limit | number | No | 20 |
66
+ | Parameter | Type | Required | Example |
67
+ | --------- | ------ | -------- | ------------------- |
68
+ | endpoint | string | Yes | market/funding_rate |
69
+ | symbol | string | No | BTC/USDT:USDT |
70
+ | limit | number | No | 20 |
67
71
 
68
72
  **常用端点:**
69
73
 
70
- | 端点 | 用途 |
71
- | ----------------------- | -------------------- |
72
- | `market/ticker` | 单币种行情 |
73
- | `market/tickers` | 多币种行情 |
74
- | `market/funding_rate` | 资金费率 |
75
- | `coin/market` | CoinGecko 币种排行 |
76
- | `coin/trending` | 热门币种 |
77
- | `defi/protocols` | DeFi 协议 TVL 排行 |
78
- | `defi/yields` | DeFi 收益率 |
74
+ | 端点 | 用途 |
75
+ | --------------------- | ------------------ |
76
+ | `market/ticker` | 单币种行情 |
77
+ | `market/tickers` | 多币种行情 |
78
+ | `market/funding_rate` | 资金费率 |
79
+ | `coin/market` | CoinGecko 币种排行 |
80
+ | `coin/trending` | 热门币种 |
81
+ | `defi/protocols` | DeFi 协议 TVL 排行 |
82
+ | `defi/yields` | DeFi 收益率 |
79
83
 
80
84
  ## Symbol 格式速查
81
85
 
82
- | 格式 | 市场 | 示例 |
83
- | ------------------ | ------ | ------------------------ |
84
- | `XXX/YYY` | Crypto | `BTC/USDT`, `ETH/BTC` |
85
- | `6位数.SZ/SH` | A股 | `000001.SZ`, `600519.SH` |
86
- | `5位数.HK` | 港股 | `00700.HK`, `00941.HK` |
87
- | `1-5大写字母` | 美股 | `AAPL`, `NVDA`, `TSLA` |
88
- | `000xxx.SH` | 指数 | `000300.SH` (沪深300) |
86
+ | 格式 | 市场 | 示例 |
87
+ | ------------- | ------ | ------------------------ |
88
+ | `XXX/YYY` | Crypto | `BTC/USDT`, `ETH/BTC` |
89
+ | `6位数.SZ/SH` | A股 | `000001.SZ`, `600519.SH` |
90
+ | `5位数.HK` | 港股 | `00700.HK`, `00941.HK` |
91
+ | `1-5大写字母` | 美股 | `AAPL`, `NVDA`, `TSLA` |
92
+ | `000xxx.SH` | 指数 | `000300.SH` (沪深300) |
89
93
 
90
94
  ## Response Guidelines
91
95
 
@@ -101,7 +105,8 @@ Market 自动检测:含 `/` → crypto;`.SH/.SZ/.HK` 或纯字母 → equity
101
105
  **流程:** `fin_price(symbol="BTC/USDT")` → 返回 $69,552
102
106
 
103
107
  **用户:** 茅台现在什么价?
104
- **流程:**
108
+ **流程:**
109
+
105
110
  1. `fin_slim_search(query="茅台")` → 找到 `600519.SH`
106
111
  2. `fin_price(symbol="600519.SH")` → 返回 ¥1,856.00
107
112
 
@@ -115,4 +120,4 @@ Market 自动检测:含 `/` → crypto;`.SH/.SZ/.HK` 或纯字母 → equity
115
120
  **流程:** `fin_crypto(endpoint="market/funding_rate", symbol="BTC/USDT:USDT")` → 费率数据
116
121
 
117
122
  **用户:** 现在 DeFi 哪个协议 TVL 最高?
118
- **流程:** `fin_crypto(endpoint="defi/protocols", limit=5)` → TVL 排行
123
+ **流程:** `fin_crypto(endpoint="defi/protocols", limit=5)` → TVL 排行
package/src/config.ts CHANGED
@@ -48,10 +48,18 @@ export function resolvePluginConfig(api: OpenClawPluginApi): UnifiedPluginConfig
48
48
  ? Math.floor(Number(timeoutRaw))
49
49
  : DEFAULT_TIMEOUT_MS;
50
50
 
51
+ const httpPortRaw = raw?.httpPort ?? readEnv(["OPENFINCLAW_HTTP_PORT"]);
52
+ const httpPortNum = Number(httpPortRaw);
53
+ const httpPort =
54
+ Number.isFinite(httpPortNum) && httpPortNum >= 1024 && httpPortNum <= 65535
55
+ ? Math.floor(httpPortNum)
56
+ : 18792;
57
+
51
58
  return {
52
59
  apiKey: apiKey && apiKey.length > 0 ? apiKey : undefined,
53
60
  hubApiUrl: hubApiUrl.replace(/\/$/, ""),
54
61
  datahubGatewayUrl: datahubGatewayUrl.replace(/\/+$/, ""),
55
62
  requestTimeoutMs,
63
+ httpPort,
56
64
  };
57
65
  }
@@ -1,11 +1,13 @@
1
+ import type { DatabaseSync } from "node:sqlite";
1
2
  /**
2
3
  * DataHub market data tools registration.
3
4
  * Tools: fin_price, fin_kline, fin_crypto, fin_compare, fin_slim_search
4
5
  */
5
6
  import { Type } from "@sinclair/typebox";
6
- import type { OpenClawPluginApi } from "openfinclaw/plugin-sdk";
7
- import { DataHubClient, guessMarket } from "./client.js";
7
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
8
+ import { withLogging } from "../middleware/with-logging.js";
8
9
  import type { UnifiedPluginConfig, MarketType } from "../types.js";
10
+ import { DataHubClient, guessMarket } from "./client.js";
9
11
 
10
12
  /** JSON tool result helper. */
11
13
  function json(payload: unknown) {
@@ -24,14 +26,17 @@ function pick(params: Record<string, unknown>, ...keys: string[]): Record<string
24
26
  return out;
25
27
  }
26
28
 
27
- const NO_KEY = "API key not configured. Set apiKey in plugin config or OPENFINCLAW_API_KEY env var.";
29
+ const NO_KEY =
30
+ "API key not configured. Set apiKey in plugin config or OPENFINCLAW_API_KEY env var.";
28
31
 
29
32
  /**
30
33
  * Register DataHub market data tools.
34
+ * @param db - SQLite database for activity logging.
31
35
  */
32
36
  export function registerDatahubTools(
33
37
  api: OpenClawPluginApi,
34
38
  config: UnifiedPluginConfig,
39
+ db: DatabaseSync,
35
40
  ): void {
36
41
  const datahubClient = config.apiKey
37
42
  ? new DataHubClient(config.datahubGatewayUrl, config.apiKey, config.requestTimeoutMs)
@@ -86,7 +91,7 @@ export function registerDatahubTools(
86
91
  }),
87
92
  ),
88
93
  }),
89
- async execute(_id: string, params: Record<string, unknown>) {
94
+ execute: withLogging(db, "fin_price", "market-data", async (_id, params) => {
90
95
  try {
91
96
  if (!datahubClient) return json({ error: NO_KEY });
92
97
  const symbol = String(params.symbol);
@@ -102,7 +107,7 @@ export function registerDatahubTools(
102
107
  } catch (err) {
103
108
  return json({ error: err instanceof Error ? err.message : String(err) });
104
109
  }
105
- },
110
+ }),
106
111
  },
107
112
  { names: ["fin_price"] },
108
113
  );
@@ -130,7 +135,7 @@ export function registerDatahubTools(
130
135
  Type.Number({ description: "Number of bars to return (default: 30)" }),
131
136
  ),
132
137
  }),
133
- async execute(_id: string, params: Record<string, unknown>) {
138
+ execute: withLogging(db, "fin_kline", "market-data", async (_id, params) => {
134
139
  try {
135
140
  if (!datahubClient) return json({ error: NO_KEY });
136
141
  const symbol = String(params.symbol);
@@ -153,7 +158,7 @@ export function registerDatahubTools(
153
158
  } catch (err) {
154
159
  return json({ error: err instanceof Error ? err.message : String(err) });
155
160
  }
156
- },
161
+ }),
157
162
  },
158
163
  { names: ["fin_kline"] },
159
164
  );
@@ -204,7 +209,7 @@ export function registerDatahubTools(
204
209
  end_date: Type.Optional(Type.String({ description: "End date (YYYY-MM-DD)" })),
205
210
  limit: Type.Optional(Type.Number({ description: "Max results (default: 20)" })),
206
211
  }),
207
- async execute(_id: string, params: Record<string, unknown>) {
212
+ execute: withLogging(db, "fin_crypto", "market-data", async (_id, params) => {
208
213
  try {
209
214
  if (!datahubClient) return json({ error: NO_KEY });
210
215
  const endpoint = String(params.endpoint ?? "coin/market");
@@ -233,7 +238,7 @@ export function registerDatahubTools(
233
238
  } catch (err) {
234
239
  return json({ error: err instanceof Error ? err.message : String(err) });
235
240
  }
236
- },
241
+ }),
237
242
  },
238
243
  { names: ["fin_crypto"] },
239
244
  );
@@ -251,7 +256,7 @@ export function registerDatahubTools(
251
256
  description: "Comma-separated symbols (2-5). Example: BTC/USDT,ETH/USDT,600519.SH",
252
257
  }),
253
258
  }),
254
- async execute(_id: string, params: Record<string, unknown>) {
259
+ execute: withLogging(db, "fin_compare", "market-data", async (_id, params) => {
255
260
  try {
256
261
  if (!datahubClient) return json({ error: NO_KEY });
257
262
  const raw = String(params.symbols);
@@ -289,7 +294,7 @@ export function registerDatahubTools(
289
294
  } catch (err) {
290
295
  return json({ error: err instanceof Error ? err.message : String(err) });
291
296
  }
292
- },
297
+ }),
293
298
  },
294
299
  { names: ["fin_compare"] },
295
300
  );
@@ -312,7 +317,7 @@ export function registerDatahubTools(
312
317
  }),
313
318
  ),
314
319
  }),
315
- async execute(_id: string, params: Record<string, unknown>) {
320
+ execute: withLogging(db, "fin_slim_search", "market-data", async (_id, params) => {
316
321
  try {
317
322
  if (!datahubClient) return json({ error: NO_KEY });
318
323
  const q = String(params.query);
@@ -342,8 +347,8 @@ export function registerDatahubTools(
342
347
  } catch (err) {
343
348
  return json({ error: err instanceof Error ? err.message : String(err) });
344
349
  }
345
- },
350
+ }),
346
351
  },
347
352
  { names: ["fin_slim_search"] },
348
353
  );
349
- }
354
+ }
package/src/db/db.ts ADDED
@@ -0,0 +1,44 @@
1
+ import { mkdirSync } from "node:fs";
2
+ /**
3
+ * SQLite database singleton for OpenFinClaw plugin.
4
+ * Database path: ~/.openfinclaw/workspace/openfinclaw-plugin.db
5
+ */
6
+ import { createRequire } from "node:module";
7
+ import { homedir } from "node:os";
8
+ import { join } from "node:path";
9
+ import type { DatabaseSync } from "node:sqlite";
10
+ import { ensureSchema } from "./schema.js";
11
+
12
+ // Use createRequire to load the built-in node:sqlite in an ESM context.
13
+ const _require = createRequire(import.meta.url);
14
+
15
+ let _db: DatabaseSync | null = null;
16
+
17
+ /** Resolve the database file path under ~/.openfinclaw/workspace/. */
18
+ function resolveDbPath(): string {
19
+ const base = join(homedir(), ".openfinclaw", "workspace");
20
+ mkdirSync(base, { recursive: true });
21
+ return join(base, "openfinclaw-plugin.db");
22
+ }
23
+
24
+ /**
25
+ * Get (or lazily initialise) the SQLite database singleton.
26
+ * Calls ensureSchema on first access to create missing tables.
27
+ */
28
+ export function getDb(): DatabaseSync {
29
+ if (_db) return _db;
30
+ // node:sqlite is available in Node 22+
31
+ const { DatabaseSync } = _require("node:sqlite") as typeof import("node:sqlite");
32
+ const dbPath = resolveDbPath();
33
+ _db = new DatabaseSync(dbPath);
34
+ ensureSchema(_db);
35
+ return _db;
36
+ }
37
+
38
+ /** Close the database (used in tests / graceful shutdown). */
39
+ export function closeDb(): void {
40
+ if (_db) {
41
+ _db.close();
42
+ _db = null;
43
+ }
44
+ }