@openfinclaw/findoo-datahub-plugin 2026.3.10 → 2026.3.12
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 +27 -9
- package/openclaw.plugin.json +11 -18
- package/package.json +1 -1
- package/skills/derivatives/skill.md +4 -4
- package/skills/risk-monitor/skill.md +11 -11
- package/test/e2e/l3-gateway-bootstrap.live.test.ts +246 -231
- package/test/e2e/l4-skill-tool-chain.live.test.ts +362 -366
- package/test/e2e/l5-browser/data-freshness.live.test.ts +379 -0
- package/test/e2e/l5-browser/market-data-chat.live.test.ts +259 -0
- package/test/e2e/l5-browser/skills-registry.test.ts +282 -0
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* L3 — Gateway Bootstrap E2E
|
|
3
3
|
*
|
|
4
4
|
* Verifies findoo-datahub-plugin loads correctly in a gateway-like environment:
|
|
5
|
-
* 1. Plugin registers all
|
|
5
|
+
* 1. Plugin registers all 13 tools
|
|
6
6
|
* 2. Plugin registers both services (fin-data-provider, fin-regime-detector)
|
|
7
7
|
* 3. Tools are callable and return well-formed responses
|
|
8
8
|
* 4. Services are consumable by other extensions
|
|
@@ -87,253 +87,268 @@ function parseResult(result: unknown): Record<string, unknown> {
|
|
|
87
87
|
|
|
88
88
|
/* ---------- tests ---------- */
|
|
89
89
|
|
|
90
|
-
describe.skipIf(SKIP)(
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
90
|
+
describe.skipIf(SKIP)("L3 — Gateway Bootstrap E2E", { timeout: 120_000 }, () => {
|
|
91
|
+
let tempDir: string;
|
|
92
|
+
let tools: Map<string, ToolDef>;
|
|
93
|
+
let services: Map<string, unknown>;
|
|
94
|
+
let logs: Array<{ level: string; msg: string }>;
|
|
95
|
+
|
|
96
|
+
beforeAll(async () => {
|
|
97
|
+
tempDir = mkdtempSync(join(tmpdir(), "l3-gateway-"));
|
|
98
|
+
const ctx = createGatewayApi(tempDir);
|
|
99
|
+
tools = ctx.tools;
|
|
100
|
+
services = ctx.services;
|
|
101
|
+
logs = ctx.logs;
|
|
102
|
+
// Simulate gateway calling plugin.register()
|
|
103
|
+
await findooDatahubPlugin.register(ctx.api);
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
afterAll(() => {
|
|
107
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
/* === Section 1: Plugin registration contract === */
|
|
111
|
+
|
|
112
|
+
describe("1. Registration contract", () => {
|
|
113
|
+
it("1.1 plugin has correct metadata", () => {
|
|
114
|
+
expect(findooDatahubPlugin.id).toBe("findoo-datahub-plugin");
|
|
115
|
+
expect(findooDatahubPlugin.name).toBe("Findoo DataHub");
|
|
116
|
+
expect(findooDatahubPlugin.kind).toBe("financial");
|
|
107
117
|
});
|
|
108
118
|
|
|
109
|
-
|
|
110
|
-
|
|
119
|
+
it("1.2 registers exactly 13 tools", () => {
|
|
120
|
+
expect(tools.size).toBe(13);
|
|
111
121
|
});
|
|
112
122
|
|
|
113
|
-
|
|
123
|
+
it("1.3 all 13 tool names match specification", () => {
|
|
124
|
+
const expected = [
|
|
125
|
+
"fin_stock",
|
|
126
|
+
"fin_index",
|
|
127
|
+
"fin_macro",
|
|
128
|
+
"fin_derivatives",
|
|
129
|
+
"fin_crypto",
|
|
130
|
+
"fin_market",
|
|
131
|
+
"fin_query",
|
|
132
|
+
"fin_data_ohlcv",
|
|
133
|
+
"fin_data_regime",
|
|
134
|
+
"fin_ta",
|
|
135
|
+
"fin_etf",
|
|
136
|
+
"fin_data_markets",
|
|
137
|
+
"fin_currency",
|
|
138
|
+
];
|
|
139
|
+
for (const name of expected) {
|
|
140
|
+
expect(tools.has(name), `Missing tool: ${name}`).toBe(true);
|
|
141
|
+
}
|
|
142
|
+
// No extra tools
|
|
143
|
+
for (const name of tools.keys()) {
|
|
144
|
+
expect(expected.includes(name), `Unexpected tool: ${name}`).toBe(true);
|
|
145
|
+
}
|
|
146
|
+
});
|
|
114
147
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
expect(
|
|
118
|
-
expect(
|
|
119
|
-
expect(
|
|
120
|
-
|
|
148
|
+
it("1.4 each tool has name, description, and execute function", () => {
|
|
149
|
+
for (const [name, tool] of tools) {
|
|
150
|
+
expect(typeof tool.name, `${name}.name`).toBe("string");
|
|
151
|
+
expect(typeof tool.description, `${name}.description`).toBe("string");
|
|
152
|
+
expect(tool.description!.length, `${name}.description length`).toBeGreaterThan(10);
|
|
153
|
+
expect(typeof tool.execute, `${name}.execute`).toBe("function");
|
|
154
|
+
}
|
|
155
|
+
});
|
|
121
156
|
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
157
|
+
it("1.5 registers both required services", () => {
|
|
158
|
+
expect(services.has("fin-data-provider")).toBe(true);
|
|
159
|
+
expect(services.has("fin-regime-detector")).toBe(true);
|
|
160
|
+
});
|
|
125
161
|
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
"fin_market",
|
|
134
|
-
"fin_query",
|
|
135
|
-
"fin_data_ohlcv",
|
|
136
|
-
"fin_data_regime",
|
|
137
|
-
"fin_ta",
|
|
138
|
-
"fin_etf",
|
|
139
|
-
"fin_data_markets",
|
|
140
|
-
];
|
|
141
|
-
for (const name of expected) {
|
|
142
|
-
expect(tools.has(name), `Missing tool: ${name}`).toBe(true);
|
|
143
|
-
}
|
|
144
|
-
// No extra tools
|
|
145
|
-
for (const name of tools.keys()) {
|
|
146
|
-
expect(expected.includes(name), `Unexpected tool: ${name}`).toBe(true);
|
|
147
|
-
}
|
|
148
|
-
});
|
|
162
|
+
it("1.6 fin-data-provider service has required methods", () => {
|
|
163
|
+
const provider = services.get("fin-data-provider") as Record<string, unknown>;
|
|
164
|
+
expect(typeof provider.getOHLCV).toBe("function");
|
|
165
|
+
expect(typeof provider.getTicker).toBe("function");
|
|
166
|
+
expect(typeof provider.detectRegime).toBe("function");
|
|
167
|
+
expect(typeof provider.getSupportedMarkets).toBe("function");
|
|
168
|
+
});
|
|
149
169
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
expect(typeof tool.execute, `${name}.execute`).toBe("function");
|
|
156
|
-
}
|
|
157
|
-
});
|
|
170
|
+
it("1.7 fin-regime-detector service has detect method", () => {
|
|
171
|
+
const detector = services.get("fin-regime-detector") as Record<string, unknown>;
|
|
172
|
+
expect(typeof detector.detect).toBe("function");
|
|
173
|
+
});
|
|
174
|
+
});
|
|
158
175
|
|
|
159
|
-
|
|
160
|
-
expect(services.has("fin-data-provider")).toBe(true);
|
|
161
|
-
expect(services.has("fin-regime-detector")).toBe(true);
|
|
162
|
-
});
|
|
176
|
+
/* === Section 2: Tool response format contract === */
|
|
163
177
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
178
|
+
describe("2. Tool response format", () => {
|
|
179
|
+
it("2.1 category tools return {success, endpoint, count, results}", async () => {
|
|
180
|
+
const tool = tools.get("fin_stock")!;
|
|
181
|
+
const raw = await tool.execute("fmt-1", {
|
|
182
|
+
symbol: "600519.SH",
|
|
183
|
+
endpoint: "price/historical",
|
|
184
|
+
limit: 3,
|
|
170
185
|
});
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
186
|
+
const res = parseResult(raw);
|
|
187
|
+
expect(res).toHaveProperty("success");
|
|
188
|
+
expect(res).toHaveProperty("endpoint");
|
|
189
|
+
expect(res).toHaveProperty("count");
|
|
190
|
+
expect(res).toHaveProperty("results");
|
|
191
|
+
expect(res.success).toBe(true);
|
|
192
|
+
expect(typeof res.count).toBe("number");
|
|
193
|
+
expect(Array.isArray(res.results)).toBe(true);
|
|
194
|
+
}, 30_000);
|
|
195
|
+
|
|
196
|
+
it("2.2 fin_data_ohlcv returns {symbol, market, timeframe, count, candles}", async () => {
|
|
197
|
+
const tool = tools.get("fin_data_ohlcv")!;
|
|
198
|
+
const raw = await tool.execute("fmt-2", {
|
|
199
|
+
symbol: "600519.SH",
|
|
200
|
+
market: "equity",
|
|
201
|
+
timeframe: "1d",
|
|
202
|
+
limit: 5,
|
|
175
203
|
});
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
it("2.2 fin_data_ohlcv returns {symbol, market, timeframe, count, candles}", async () => {
|
|
199
|
-
const tool = tools.get("fin_data_ohlcv")!;
|
|
200
|
-
const raw = await tool.execute("fmt-2", {
|
|
201
|
-
symbol: "600519.SH",
|
|
202
|
-
market: "equity",
|
|
203
|
-
timeframe: "1d",
|
|
204
|
-
limit: 5,
|
|
205
|
-
});
|
|
206
|
-
const res = parseResult(raw);
|
|
207
|
-
expect(res.symbol).toBe("600519.SH");
|
|
208
|
-
expect(res.market).toBe("equity");
|
|
209
|
-
expect(res.timeframe).toBe("1d");
|
|
210
|
-
expect(typeof res.count).toBe("number");
|
|
211
|
-
expect(Array.isArray(res.candles)).toBe(true);
|
|
212
|
-
const candle = (res.candles as Array<Record<string, unknown>>)[0]!;
|
|
213
|
-
expect(candle).toHaveProperty("timestamp");
|
|
214
|
-
expect(candle).toHaveProperty("open");
|
|
215
|
-
expect(candle).toHaveProperty("high");
|
|
216
|
-
expect(candle).toHaveProperty("low");
|
|
217
|
-
expect(candle).toHaveProperty("close");
|
|
218
|
-
expect(candle).toHaveProperty("volume");
|
|
219
|
-
}, 30_000);
|
|
220
|
-
|
|
221
|
-
it("2.3 fin_data_regime returns {symbol, market, timeframe, regime}", async () => {
|
|
222
|
-
const tool = tools.get("fin_data_regime")!;
|
|
223
|
-
const raw = await tool.execute("fmt-3", {
|
|
224
|
-
symbol: "600519.SH",
|
|
225
|
-
market: "equity",
|
|
226
|
-
timeframe: "1d",
|
|
227
|
-
});
|
|
228
|
-
const res = parseResult(raw);
|
|
229
|
-
expect(res.symbol).toBe("600519.SH");
|
|
230
|
-
expect(res.market).toBe("equity");
|
|
231
|
-
expect(res.timeframe).toBe("1d");
|
|
232
|
-
expect(["bull", "bear", "sideways", "volatile", "crisis"]).toContain(res.regime);
|
|
233
|
-
}, 30_000);
|
|
234
|
-
|
|
235
|
-
it("2.4 fin_data_markets returns {datahub, markets, categories, endpoints}", async () => {
|
|
236
|
-
const tool = tools.get("fin_data_markets")!;
|
|
237
|
-
const raw = await tool.execute("fmt-4", {});
|
|
238
|
-
const res = parseResult(raw);
|
|
239
|
-
expect(res).toHaveProperty("datahub");
|
|
240
|
-
expect(res).toHaveProperty("markets");
|
|
241
|
-
expect(res).toHaveProperty("categories");
|
|
242
|
-
expect(res.endpoints).toBe(172);
|
|
204
|
+
const res = parseResult(raw);
|
|
205
|
+
expect(res.symbol).toBe("600519.SH");
|
|
206
|
+
expect(res.market).toBe("equity");
|
|
207
|
+
expect(res.timeframe).toBe("1d");
|
|
208
|
+
expect(typeof res.count).toBe("number");
|
|
209
|
+
expect(Array.isArray(res.candles)).toBe(true);
|
|
210
|
+
const candle = (res.candles as Array<Record<string, unknown>>)[0]!;
|
|
211
|
+
expect(candle).toHaveProperty("timestamp");
|
|
212
|
+
expect(candle).toHaveProperty("open");
|
|
213
|
+
expect(candle).toHaveProperty("high");
|
|
214
|
+
expect(candle).toHaveProperty("low");
|
|
215
|
+
expect(candle).toHaveProperty("close");
|
|
216
|
+
expect(candle).toHaveProperty("volume");
|
|
217
|
+
}, 30_000);
|
|
218
|
+
|
|
219
|
+
it("2.3 fin_data_regime returns {symbol, market, timeframe, regime}", async () => {
|
|
220
|
+
const tool = tools.get("fin_data_regime")!;
|
|
221
|
+
const raw = await tool.execute("fmt-3", {
|
|
222
|
+
symbol: "600519.SH",
|
|
223
|
+
market: "equity",
|
|
224
|
+
timeframe: "1d",
|
|
243
225
|
});
|
|
226
|
+
const res = parseResult(raw);
|
|
227
|
+
expect(res.symbol).toBe("600519.SH");
|
|
228
|
+
expect(res.market).toBe("equity");
|
|
229
|
+
expect(res.timeframe).toBe("1d");
|
|
230
|
+
expect(["bull", "bear", "sideways", "volatile", "crisis"]).toContain(res.regime);
|
|
231
|
+
}, 30_000);
|
|
232
|
+
|
|
233
|
+
it("2.4 fin_data_markets returns {datahub, markets, categories, endpoints}", async () => {
|
|
234
|
+
const tool = tools.get("fin_data_markets")!;
|
|
235
|
+
const raw = await tool.execute("fmt-4", {});
|
|
236
|
+
const res = parseResult(raw);
|
|
237
|
+
expect(res).toHaveProperty("connected");
|
|
238
|
+
expect(res).toHaveProperty("markets");
|
|
239
|
+
expect(res).toHaveProperty("categories");
|
|
240
|
+
expect(res.endpoints).toBe(172);
|
|
241
|
+
});
|
|
244
242
|
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
});
|
|
243
|
+
it("2.5 error responses return {error: string}", async () => {
|
|
244
|
+
const tool = tools.get("fin_query")!;
|
|
245
|
+
const raw = await tool.execute("fmt-5", { path: "" });
|
|
246
|
+
const res = parseResult(raw);
|
|
247
|
+
expect(typeof res.error).toBe("string");
|
|
248
|
+
expect(res.success).toBeUndefined();
|
|
252
249
|
});
|
|
250
|
+
});
|
|
253
251
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
describe("3. Config edge cases", () => {
|
|
257
|
-
it("3.1 plugin warns when no API key configured", async () => {
|
|
258
|
-
const tmpDir2 = mkdtempSync(join(tmpdir(), "l3-nokey-"));
|
|
259
|
-
const saved = { ...process.env };
|
|
260
|
-
delete process.env.DATAHUB_API_KEY;
|
|
261
|
-
delete process.env.DATAHUB_PASSWORD;
|
|
262
|
-
delete process.env.OPENFINCLAW_DATAHUB_PASSWORD;
|
|
263
|
-
|
|
264
|
-
const ctx = createGatewayApi(tmpDir2, { datahubApiKey: undefined });
|
|
265
|
-
// Clear the pluginConfig entirely so resolveConfig can't find a key
|
|
266
|
-
(ctx.api as unknown as { pluginConfig: Record<string, unknown> }).pluginConfig = {};
|
|
267
|
-
await findooDatahubPlugin.register(ctx.api);
|
|
268
|
-
|
|
269
|
-
const warnLog = ctx.logs.find(
|
|
270
|
-
(l) => l.level === "warn" && l.msg.includes("no API key"),
|
|
271
|
-
);
|
|
272
|
-
expect(warnLog).toBeDefined();
|
|
273
|
-
|
|
274
|
-
// Restore
|
|
275
|
-
Object.assign(process.env, saved);
|
|
276
|
-
rmSync(tmpDir2, { recursive: true, force: true });
|
|
277
|
-
});
|
|
252
|
+
/* === Section 3: Config resolution === */
|
|
278
253
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
});
|
|
254
|
+
describe("3. Config edge cases", () => {
|
|
255
|
+
it("3.1 plugin warns when no API key configured", async () => {
|
|
256
|
+
const tmpDir2 = mkdtempSync(join(tmpdir(), "l3-nokey-"));
|
|
257
|
+
const saved = { ...process.env };
|
|
258
|
+
delete process.env.DATAHUB_API_KEY;
|
|
259
|
+
delete process.env.DATAHUB_PASSWORD;
|
|
260
|
+
delete process.env.OPENFINCLAW_DATAHUB_PASSWORD;
|
|
261
|
+
|
|
262
|
+
const ctx = createGatewayApi(tmpDir2, { datahubApiKey: undefined });
|
|
263
|
+
// Clear the pluginConfig entirely so resolveConfig can't find a key
|
|
264
|
+
(ctx.api as unknown as { pluginConfig: Record<string, unknown> }).pluginConfig = {};
|
|
265
|
+
await findooDatahubPlugin.register(ctx.api);
|
|
266
|
+
|
|
267
|
+
const errorLog = ctx.logs.find(
|
|
268
|
+
(l) => l.level === "error" && l.msg.includes("API key is required"),
|
|
269
|
+
);
|
|
270
|
+
expect(errorLog).toBeDefined();
|
|
271
|
+
|
|
272
|
+
// Restore
|
|
273
|
+
Object.assign(process.env, saved);
|
|
274
|
+
rmSync(tmpDir2, { recursive: true, force: true });
|
|
288
275
|
});
|
|
289
276
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
market: string;
|
|
299
|
-
timeframe: string;
|
|
300
|
-
limit: number;
|
|
301
|
-
}) => Promise<Array<{ timestamp: number; close: number }>>;
|
|
302
|
-
};
|
|
303
|
-
|
|
304
|
-
const data = await provider.getOHLCV({
|
|
305
|
-
symbol: "600519.SH",
|
|
306
|
-
market: "equity",
|
|
307
|
-
timeframe: "1d",
|
|
308
|
-
limit: 10,
|
|
309
|
-
});
|
|
310
|
-
expect(data.length).toBeGreaterThan(0);
|
|
311
|
-
expect(typeof data[0]!.timestamp).toBe("number");
|
|
312
|
-
expect(typeof data[0]!.close).toBe("number");
|
|
313
|
-
}, 30_000);
|
|
314
|
-
|
|
315
|
-
it("4.2 another extension can resolve fin-regime-detector and detect regime", async () => {
|
|
316
|
-
const provider = services.get("fin-data-provider") as {
|
|
317
|
-
getOHLCV: (p: {
|
|
318
|
-
symbol: string;
|
|
319
|
-
market: string;
|
|
320
|
-
timeframe: string;
|
|
321
|
-
limit: number;
|
|
322
|
-
}) => Promise<Array<{ timestamp: number; open: number; high: number; low: number; close: number; volume: number }>>;
|
|
323
|
-
};
|
|
324
|
-
const detector = services.get("fin-regime-detector") as {
|
|
325
|
-
detect: (bars: Array<{ timestamp: number; open: number; high: number; low: number; close: number; volume: number }>) => string;
|
|
326
|
-
};
|
|
327
|
-
|
|
328
|
-
const bars = await provider.getOHLCV({
|
|
329
|
-
symbol: "600519.SH",
|
|
330
|
-
market: "equity",
|
|
331
|
-
timeframe: "1d",
|
|
332
|
-
limit: 300,
|
|
333
|
-
});
|
|
334
|
-
const regime = detector.detect(bars);
|
|
335
|
-
expect(["bull", "bear", "sideways", "volatile", "crisis"]).toContain(regime);
|
|
336
|
-
}, 30_000);
|
|
277
|
+
it("3.2 plugin still registers all tools even without API key", async () => {
|
|
278
|
+
const tmpDir2 = mkdtempSync(join(tmpdir(), "l3-nokey2-"));
|
|
279
|
+
const ctx = createGatewayApi(tmpDir2, { datahubApiKey: undefined });
|
|
280
|
+
(ctx.api as unknown as { pluginConfig: Record<string, unknown> }).pluginConfig = {};
|
|
281
|
+
await findooDatahubPlugin.register(ctx.api);
|
|
282
|
+
expect(ctx.tools.size).toBe(13);
|
|
283
|
+
expect(ctx.services.size).toBe(2);
|
|
284
|
+
rmSync(tmpDir2, { recursive: true, force: true });
|
|
337
285
|
});
|
|
338
|
-
}
|
|
339
|
-
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
/* === Section 4: Cross-extension service consumption === */
|
|
289
|
+
|
|
290
|
+
describe("4. Cross-extension service consumption", () => {
|
|
291
|
+
it("4.1 another extension can resolve fin-data-provider and call getOHLCV", async () => {
|
|
292
|
+
// Simulate another extension calling the service
|
|
293
|
+
const provider = services.get("fin-data-provider") as {
|
|
294
|
+
getOHLCV: (p: {
|
|
295
|
+
symbol: string;
|
|
296
|
+
market: string;
|
|
297
|
+
timeframe: string;
|
|
298
|
+
limit: number;
|
|
299
|
+
}) => Promise<Array<{ timestamp: number; close: number }>>;
|
|
300
|
+
};
|
|
301
|
+
|
|
302
|
+
const data = await provider.getOHLCV({
|
|
303
|
+
symbol: "600519.SH",
|
|
304
|
+
market: "equity",
|
|
305
|
+
timeframe: "1d",
|
|
306
|
+
limit: 10,
|
|
307
|
+
});
|
|
308
|
+
expect(data.length).toBeGreaterThan(0);
|
|
309
|
+
expect(typeof data[0]!.timestamp).toBe("number");
|
|
310
|
+
expect(typeof data[0]!.close).toBe("number");
|
|
311
|
+
}, 30_000);
|
|
312
|
+
|
|
313
|
+
it("4.2 another extension can resolve fin-regime-detector and detect regime", async () => {
|
|
314
|
+
const provider = services.get("fin-data-provider") as {
|
|
315
|
+
getOHLCV: (p: {
|
|
316
|
+
symbol: string;
|
|
317
|
+
market: string;
|
|
318
|
+
timeframe: string;
|
|
319
|
+
limit: number;
|
|
320
|
+
}) => Promise<
|
|
321
|
+
Array<{
|
|
322
|
+
timestamp: number;
|
|
323
|
+
open: number;
|
|
324
|
+
high: number;
|
|
325
|
+
low: number;
|
|
326
|
+
close: number;
|
|
327
|
+
volume: number;
|
|
328
|
+
}>
|
|
329
|
+
>;
|
|
330
|
+
};
|
|
331
|
+
const detector = services.get("fin-regime-detector") as {
|
|
332
|
+
detect: (
|
|
333
|
+
bars: Array<{
|
|
334
|
+
timestamp: number;
|
|
335
|
+
open: number;
|
|
336
|
+
high: number;
|
|
337
|
+
low: number;
|
|
338
|
+
close: number;
|
|
339
|
+
volume: number;
|
|
340
|
+
}>,
|
|
341
|
+
) => string;
|
|
342
|
+
};
|
|
343
|
+
|
|
344
|
+
const bars = await provider.getOHLCV({
|
|
345
|
+
symbol: "600519.SH",
|
|
346
|
+
market: "equity",
|
|
347
|
+
timeframe: "1d",
|
|
348
|
+
limit: 300,
|
|
349
|
+
});
|
|
350
|
+
const regime = detector.detect(bars);
|
|
351
|
+
expect(["bull", "bear", "sideways", "volatile", "crisis"]).toContain(regime);
|
|
352
|
+
}, 30_000);
|
|
353
|
+
});
|
|
354
|
+
});
|