@openfinclaw/findoo-datahub-plugin 2026.3.2

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 ADDED
@@ -0,0 +1,608 @@
1
+ import { Type } from "@sinclair/typebox";
2
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
3
+ import { resolveConfig } from "./src/config.js";
4
+ import { DataHubClient } from "./src/datahub-client.js";
5
+ import { OHLCVCache } from "./src/ohlcv-cache.js";
6
+ import { RegimeDetector } from "./src/regime-detector.js";
7
+ import type { MarketType } from "./src/types.js";
8
+
9
+ /* ---------- helpers ---------- */
10
+
11
+ const json = (payload: unknown) => ({
12
+ content: [{ type: "text" as const, text: JSON.stringify(payload, null, 2) }],
13
+ details: payload,
14
+ });
15
+
16
+ /** Build query params from user-facing tool params. */
17
+ function buildParams(params: Record<string, unknown>): Record<string, string> {
18
+ const out: Record<string, string> = {};
19
+ if (params.symbol) out.symbol = String(params.symbol);
20
+ if (params.start_date) out.start_date = String(params.start_date);
21
+ if (params.end_date) out.end_date = String(params.end_date);
22
+ if (params.trade_date) out.trade_date = String(params.trade_date);
23
+ if (params.limit) out.limit = String(params.limit);
24
+ if (params.provider) out.provider = String(params.provider);
25
+ if (params.country) out.country = String(params.country);
26
+ if (params.indicator) out.indicator = String(params.indicator);
27
+ return out;
28
+ }
29
+
30
+ /* ---------- plugin ---------- */
31
+
32
+ const findooDatahubPlugin = {
33
+ id: "findoo-datahub-plugin",
34
+ name: "Findoo DataHub",
35
+ description:
36
+ "Unified financial data source powered by OpenBB DataHub — " +
37
+ "172 endpoints covering equity (A/HK/US), crypto, macro, derivatives, index, ETF.",
38
+ kind: "financial" as const,
39
+
40
+ async register(api: OpenClawPluginApi) {
41
+ const config = resolveConfig(api);
42
+
43
+ // --- DataHub client ---
44
+ const client = new DataHubClient(
45
+ config.datahubApiUrl,
46
+ config.datahubUsername,
47
+ config.datahubPassword,
48
+ config.requestTimeoutMs,
49
+ );
50
+
51
+ // --- Local cache + regime detector ---
52
+ const dbPath = api.resolvePath("state/findoo-ohlcv-cache.sqlite");
53
+ const cache = new OHLCVCache(dbPath);
54
+ const regimeDetector = new RegimeDetector();
55
+
56
+ // --- Data provider service (exposed to other extensions) ---
57
+ const dataProvider = {
58
+ async getOHLCV(params: {
59
+ symbol: string;
60
+ market: MarketType;
61
+ timeframe: string;
62
+ since?: number;
63
+ limit?: number;
64
+ }) {
65
+ // Check cache first
66
+ const range = cache.getRange(params.symbol, params.market, params.timeframe);
67
+ if (range && params.since != null && params.limit != null) {
68
+ const cached = cache.query(params.symbol, params.market, params.timeframe, params.since);
69
+ if (cached.length >= params.limit) return cached.slice(0, params.limit);
70
+ }
71
+
72
+ const rows = await client.getOHLCV(params);
73
+ if (rows.length > 0) {
74
+ cache.upsertBatch(params.symbol, params.market, params.timeframe, rows);
75
+ }
76
+
77
+ if (range || rows.length > 0) {
78
+ const all = cache.query(params.symbol, params.market, params.timeframe, params.since);
79
+ return params.limit ? all.slice(0, params.limit) : all;
80
+ }
81
+ return rows;
82
+ },
83
+
84
+ async getTicker(symbol: string, market: MarketType) {
85
+ return client.getTicker(symbol, market);
86
+ },
87
+
88
+ async detectRegime(params: { symbol: string; market: MarketType; timeframe: string }) {
89
+ const ohlcv = await dataProvider.getOHLCV({
90
+ symbol: params.symbol,
91
+ market: params.market,
92
+ timeframe: params.timeframe,
93
+ limit: 300,
94
+ });
95
+ return regimeDetector.detect(ohlcv);
96
+ },
97
+
98
+ getSupportedMarkets() {
99
+ return [
100
+ { market: "crypto" as const, symbols: [], available: true },
101
+ { market: "equity" as const, symbols: [], available: true },
102
+ { market: "commodity" as const, symbols: [], available: true },
103
+ ];
104
+ },
105
+ };
106
+
107
+ // --- Register services ---
108
+ api.registerService({
109
+ id: "fin-data-provider",
110
+ start: () => {},
111
+ instance: dataProvider,
112
+ } as Parameters<typeof api.registerService>[0]);
113
+
114
+ api.registerService({
115
+ id: "fin-regime-detector",
116
+ start: () => {},
117
+ instance: regimeDetector,
118
+ } as Parameters<typeof api.registerService>[0]);
119
+
120
+ // ============================================================
121
+ // AI Tools (10 total)
122
+ // ============================================================
123
+
124
+ // === Tool 1: fin_stock — Equity data ===
125
+ api.registerTool(
126
+ {
127
+ name: "fin_stock",
128
+ label: "Stock Data (A/HK/US)",
129
+ description:
130
+ "Fetch A-share, HK, or US equity data — quotes, historical prices, income, balance sheet, cashflow, ratios, money flow, holders, dividends, news.",
131
+ parameters: Type.Object({
132
+ symbol: Type.String({
133
+ description: "Stock code. A-shares: 600519.SH; HK: 00700.HK; US: AAPL",
134
+ }),
135
+ endpoint: Type.Unsafe<string>({
136
+ type: "string",
137
+ enum: [
138
+ "price/historical",
139
+ "fundamental/income",
140
+ "fundamental/balance",
141
+ "fundamental/cash",
142
+ "fundamental/ratios",
143
+ "fundamental/metrics",
144
+ "fundamental/dividends",
145
+ "ownership/top10_holders",
146
+ "moneyflow/individual",
147
+ "market/top_list",
148
+ "discovery/gainers",
149
+ "discovery/losers",
150
+ ],
151
+ description: "DataHub equity endpoint path",
152
+ }),
153
+ start_date: Type.Optional(Type.String({ description: "Start date, e.g. 2025-01-01" })),
154
+ end_date: Type.Optional(Type.String({ description: "End date, e.g. 2025-12-31" })),
155
+ limit: Type.Optional(Type.Number({ description: "Max records to return" })),
156
+ provider: Type.Optional(
157
+ Type.String({ description: "Data provider override (tushare, yfinance, polygon)" }),
158
+ ),
159
+ }),
160
+ async execute(_toolCallId: string, params: Record<string, unknown>) {
161
+ try {
162
+ const endpoint = String(params.endpoint ?? "price/historical");
163
+ const qp = buildParams(params);
164
+ const results = await client.equity(endpoint, qp);
165
+ return json({
166
+ success: true,
167
+ endpoint: `equity/${endpoint}`,
168
+ count: results.length,
169
+ results,
170
+ });
171
+ } catch (err) {
172
+ return json({ error: err instanceof Error ? err.message : String(err) });
173
+ }
174
+ },
175
+ },
176
+ { names: ["fin_stock"] },
177
+ );
178
+
179
+ // === Tool 2: fin_index — Index / ETF / Fund ===
180
+ api.registerTool(
181
+ {
182
+ name: "fin_index",
183
+ label: "Index / ETF / Fund",
184
+ description:
185
+ "Query index constituents, valuations, ETF prices/NAV, fund manager/portfolio.",
186
+ parameters: Type.Object({
187
+ symbol: Type.Optional(
188
+ Type.String({ description: "Index/ETF/fund code. Index: 000300.SH; ETF: 510050.SH" }),
189
+ ),
190
+ endpoint: Type.Unsafe<string>({
191
+ type: "string",
192
+ enum: [
193
+ "price/historical",
194
+ "constituents",
195
+ "daily_basic",
196
+ "thematic/ths_index",
197
+ "thematic/ths_daily",
198
+ "thematic/ths_member",
199
+ ],
200
+ description: "DataHub index endpoint path",
201
+ }),
202
+ start_date: Type.Optional(Type.String()),
203
+ end_date: Type.Optional(Type.String()),
204
+ limit: Type.Optional(Type.Number()),
205
+ }),
206
+ async execute(_toolCallId: string, params: Record<string, unknown>) {
207
+ try {
208
+ const endpoint = String(params.endpoint ?? "price/historical");
209
+ const qp = buildParams(params);
210
+ const results = await client.index(endpoint, qp);
211
+ return json({
212
+ success: true,
213
+ endpoint: `index/${endpoint}`,
214
+ count: results.length,
215
+ results,
216
+ });
217
+ } catch (err) {
218
+ return json({ error: err instanceof Error ? err.message : String(err) });
219
+ }
220
+ },
221
+ },
222
+ { names: ["fin_index"] },
223
+ );
224
+
225
+ // === Tool 3: fin_macro — Economy / Rates / FX ===
226
+ api.registerTool(
227
+ {
228
+ name: "fin_macro",
229
+ label: "Macro / Rates / FX",
230
+ description:
231
+ "China macro (GDP/CPI/PPI/PMI/M2), interest rates (Shibor/LPR/Libor/Hibor), treasury yields, FX, WorldBank data.",
232
+ parameters: Type.Object({
233
+ endpoint: Type.Unsafe<string>({
234
+ type: "string",
235
+ enum: [
236
+ "gdp/real",
237
+ "cpi",
238
+ "ppi",
239
+ "pmi",
240
+ "money_supply",
241
+ "social_financing",
242
+ "shibor",
243
+ "shibor_lpr",
244
+ "libor",
245
+ "hibor",
246
+ "treasury_cn",
247
+ "treasury_us",
248
+ "index_global",
249
+ "calendar",
250
+ "worldbank/gdp",
251
+ "worldbank/population",
252
+ "worldbank/inflation",
253
+ "worldbank/indicator",
254
+ ],
255
+ description: "DataHub economy endpoint path",
256
+ }),
257
+ symbol: Type.Optional(Type.String({ description: "Currency pair or indicator code" })),
258
+ country: Type.Optional(Type.String({ description: "Country code for WorldBank" })),
259
+ start_date: Type.Optional(Type.String()),
260
+ end_date: Type.Optional(Type.String()),
261
+ limit: Type.Optional(Type.Number()),
262
+ }),
263
+ async execute(_toolCallId: string, params: Record<string, unknown>) {
264
+ try {
265
+ const endpoint = String(params.endpoint ?? "cpi");
266
+ const qp = buildParams(params);
267
+ const results = await client.economy(endpoint, qp);
268
+ return json({
269
+ success: true,
270
+ endpoint: `economy/${endpoint}`,
271
+ count: results.length,
272
+ results,
273
+ });
274
+ } catch (err) {
275
+ return json({ error: err instanceof Error ? err.message : String(err) });
276
+ }
277
+ },
278
+ },
279
+ { names: ["fin_macro"] },
280
+ );
281
+
282
+ // === Tool 4: fin_derivatives — Futures / Options / CB ===
283
+ api.registerTool(
284
+ {
285
+ name: "fin_derivatives",
286
+ label: "Futures / Options / CB",
287
+ description:
288
+ "Futures (daily, holdings, settlement, warehouse, mapping), options (basic, daily, chains), convertible bonds.",
289
+ parameters: Type.Object({
290
+ symbol: Type.Optional(
291
+ Type.String({ description: "Contract code, e.g. IF2501.CFX, 113xxx.SH (CB)" }),
292
+ ),
293
+ endpoint: Type.Unsafe<string>({
294
+ type: "string",
295
+ enum: [
296
+ "futures/historical",
297
+ "futures/info",
298
+ "futures/holding",
299
+ "futures/settle",
300
+ "futures/warehouse",
301
+ "futures/mapping",
302
+ "options/basic",
303
+ "options/daily",
304
+ "options/chains",
305
+ "convertible/basic",
306
+ "convertible/daily",
307
+ ],
308
+ description: "DataHub derivatives endpoint path",
309
+ }),
310
+ trade_date: Type.Optional(Type.String({ description: "Trade date, e.g. 2025-02-28" })),
311
+ start_date: Type.Optional(Type.String()),
312
+ end_date: Type.Optional(Type.String()),
313
+ limit: Type.Optional(Type.Number()),
314
+ }),
315
+ async execute(_toolCallId: string, params: Record<string, unknown>) {
316
+ try {
317
+ const endpoint = String(params.endpoint ?? "futures/historical");
318
+ const qp = buildParams(params);
319
+ const results = await client.derivatives(endpoint, qp);
320
+ return json({
321
+ success: true,
322
+ endpoint: `derivatives/${endpoint}`,
323
+ count: results.length,
324
+ results,
325
+ });
326
+ } catch (err) {
327
+ return json({ error: err instanceof Error ? err.message : String(err) });
328
+ }
329
+ },
330
+ },
331
+ { names: ["fin_derivatives"] },
332
+ );
333
+
334
+ // === Tool 5: fin_crypto — Crypto & DeFi ===
335
+ api.registerTool(
336
+ {
337
+ name: "fin_crypto",
338
+ label: "Crypto & DeFi",
339
+ description:
340
+ "Crypto market data (tickers, orderbook, funding rates) via CEX, DeFi (TVL, yields, stablecoins, fees, DEX volumes) via DefiLlama, market cap rankings via CoinGecko.",
341
+ parameters: Type.Object({
342
+ endpoint: Type.Unsafe<string>({
343
+ type: "string",
344
+ enum: [
345
+ "market/ticker",
346
+ "market/tickers",
347
+ "market/orderbook",
348
+ "market/trades",
349
+ "market/funding_rate",
350
+ "coin/market",
351
+ "coin/historical",
352
+ "coin/info",
353
+ "coin/categories",
354
+ "coin/trending",
355
+ "coin/global_stats",
356
+ "defi/protocols",
357
+ "defi/tvl_historical",
358
+ "defi/protocol_tvl",
359
+ "defi/chains",
360
+ "defi/yields",
361
+ "defi/stablecoins",
362
+ "defi/fees",
363
+ "defi/dex_volumes",
364
+ "defi/coin_prices",
365
+ ],
366
+ description: "DataHub crypto endpoint path",
367
+ }),
368
+ symbol: Type.Optional(
369
+ Type.String({ description: "Coin ID, trading pair, or protocol slug" }),
370
+ ),
371
+ limit: Type.Optional(Type.Number()),
372
+ }),
373
+ async execute(_toolCallId: string, params: Record<string, unknown>) {
374
+ try {
375
+ const endpoint = String(params.endpoint ?? "coin/market");
376
+ const qp = buildParams(params);
377
+ const results = await client.crypto(endpoint, qp);
378
+ return json({
379
+ success: true,
380
+ endpoint: `crypto/${endpoint}`,
381
+ count: results.length,
382
+ results,
383
+ });
384
+ } catch (err) {
385
+ return json({ error: err instanceof Error ? err.message : String(err) });
386
+ }
387
+ },
388
+ },
389
+ { names: ["fin_crypto"] },
390
+ );
391
+
392
+ // === Tool 6: fin_market — Market Radar ===
393
+ api.registerTool(
394
+ {
395
+ name: "fin_market",
396
+ label: "Market Radar",
397
+ description:
398
+ "Market monitoring — dragon-tiger list, limit-up/down stats, block trades, sector money flow, margin, Stock Connect flows, global index, IPO calendar.",
399
+ parameters: Type.Object({
400
+ endpoint: Type.Unsafe<string>({
401
+ type: "string",
402
+ enum: [
403
+ "market/top_list",
404
+ "market/top_inst",
405
+ "market/limit_list",
406
+ "market/suspend",
407
+ "market/trade_calendar",
408
+ "moneyflow/individual",
409
+ "moneyflow/industry",
410
+ "moneyflow/block_trade",
411
+ "margin/summary",
412
+ "margin/detail",
413
+ "flow/hsgt_flow",
414
+ "flow/hsgt_top10",
415
+ "discovery/gainers",
416
+ "discovery/losers",
417
+ "discovery/active",
418
+ "discovery/new_share",
419
+ ],
420
+ description: "DataHub equity endpoint for market data",
421
+ }),
422
+ trade_date: Type.Optional(Type.String({ description: "Trade date, e.g. 2025-02-28" })),
423
+ symbol: Type.Optional(Type.String({ description: "Symbol for specific queries" })),
424
+ start_date: Type.Optional(Type.String()),
425
+ end_date: Type.Optional(Type.String()),
426
+ limit: Type.Optional(Type.Number()),
427
+ }),
428
+ async execute(_toolCallId: string, params: Record<string, unknown>) {
429
+ try {
430
+ const endpoint = String(params.endpoint ?? "market/top_list");
431
+ const qp = buildParams(params);
432
+ const results = await client.equity(endpoint, qp);
433
+ return json({
434
+ success: true,
435
+ endpoint: `equity/${endpoint}`,
436
+ count: results.length,
437
+ results,
438
+ });
439
+ } catch (err) {
440
+ return json({ error: err instanceof Error ? err.message : String(err) });
441
+ }
442
+ },
443
+ },
444
+ { names: ["fin_market"] },
445
+ );
446
+
447
+ // === Tool 7: fin_query — Raw DataHub Query (fallback) ===
448
+ api.registerTool(
449
+ {
450
+ name: "fin_query",
451
+ label: "Raw DataHub Query",
452
+ description:
453
+ "Direct passthrough to any of 172 DataHub endpoints by path. Use when other tools don't cover the specific data.",
454
+ parameters: Type.Object({
455
+ path: Type.String({
456
+ description:
457
+ "Full endpoint path after /api/v1/, e.g. equity/fundamental/income, crypto/defi/protocols, economy/cpi",
458
+ }),
459
+ params: Type.Optional(
460
+ Type.Record(Type.String(), Type.String(), {
461
+ description: "Query parameters as key-value pairs",
462
+ }),
463
+ ),
464
+ }),
465
+ async execute(_toolCallId: string, params: Record<string, unknown>) {
466
+ try {
467
+ const path = String(params.path ?? "").trim();
468
+ if (!path) throw new Error("path is required");
469
+ const qp = (params.params ?? {}) as Record<string, string>;
470
+ const results = await client.query(path, qp);
471
+ return json({ success: true, endpoint: path, count: results.length, results });
472
+ } catch (err) {
473
+ return json({ error: err instanceof Error ? err.message : String(err) });
474
+ }
475
+ },
476
+ },
477
+ { names: ["fin_query"] },
478
+ );
479
+
480
+ // === Tool 8: fin_data_ohlcv — OHLCV with caching ===
481
+ api.registerTool(
482
+ {
483
+ name: "fin_data_ohlcv",
484
+ label: "OHLCV Data",
485
+ description: "Fetch OHLCV candle data with local SQLite caching via DataHub.",
486
+ parameters: Type.Object({
487
+ symbol: Type.String({
488
+ description: "Trading pair (e.g. BTC/USDT, AAPL, 600519.SH)",
489
+ }),
490
+ market: Type.Optional(
491
+ Type.Unsafe<"crypto" | "equity" | "commodity">({
492
+ type: "string",
493
+ enum: ["crypto", "equity", "commodity"],
494
+ description: "Market type (default: crypto)",
495
+ }),
496
+ ),
497
+ timeframe: Type.Optional(
498
+ Type.Unsafe<"1m" | "5m" | "1h" | "4h" | "1d">({
499
+ type: "string",
500
+ enum: ["1m", "5m", "1h", "4h", "1d"],
501
+ description: "Candle timeframe (default: 1h)",
502
+ }),
503
+ ),
504
+ since: Type.Optional(Type.Number({ description: "Start timestamp in Unix ms" })),
505
+ limit: Type.Optional(Type.Number({ description: "Number of candles to return" })),
506
+ }),
507
+ async execute(_id: string, params: Record<string, unknown>) {
508
+ try {
509
+ const symbol = params.symbol as string;
510
+ const market = (params.market as MarketType | undefined) ?? "crypto";
511
+ const timeframe = (params.timeframe as string | undefined) ?? "1h";
512
+ const since = params.since as number | undefined;
513
+ const limit = params.limit as number | undefined;
514
+
515
+ const ohlcv = await dataProvider.getOHLCV({ symbol, market, timeframe, since, limit });
516
+
517
+ return json({
518
+ symbol,
519
+ market,
520
+ timeframe,
521
+ count: ohlcv.length,
522
+ candles: ohlcv.map((bar) => ({
523
+ timestamp: new Date(bar.timestamp).toISOString(),
524
+ open: bar.open,
525
+ high: bar.high,
526
+ low: bar.low,
527
+ close: bar.close,
528
+ volume: bar.volume,
529
+ })),
530
+ });
531
+ } catch (err) {
532
+ return json({ error: err instanceof Error ? err.message : String(err) });
533
+ }
534
+ },
535
+ },
536
+ { names: ["fin_data_ohlcv"] },
537
+ );
538
+
539
+ // === Tool 9: fin_data_regime — Market Regime Detection ===
540
+ api.registerTool(
541
+ {
542
+ name: "fin_data_regime",
543
+ label: "Market Regime",
544
+ description:
545
+ "Detect market regime (bull/bear/sideways/volatile/crisis) using SMA/ATR analysis on DataHub OHLCV data.",
546
+ parameters: Type.Object({
547
+ symbol: Type.String({ description: "Trading pair (e.g. BTC/USDT, 600519.SH)" }),
548
+ market: Type.Optional(
549
+ Type.Unsafe<"crypto" | "equity" | "commodity">({
550
+ type: "string",
551
+ enum: ["crypto", "equity", "commodity"],
552
+ description: "Market type (default: crypto)",
553
+ }),
554
+ ),
555
+ timeframe: Type.Optional(
556
+ Type.Unsafe<"1m" | "5m" | "1h" | "4h" | "1d">({
557
+ type: "string",
558
+ enum: ["1m", "5m", "1h", "4h", "1d"],
559
+ description: "Candle timeframe (default: 4h)",
560
+ }),
561
+ ),
562
+ }),
563
+ async execute(_id: string, params: Record<string, unknown>) {
564
+ try {
565
+ const symbol = params.symbol as string;
566
+ const market = (params.market as MarketType | undefined) ?? "crypto";
567
+ const timeframe = (params.timeframe as string | undefined) ?? "4h";
568
+ const regime = await dataProvider.detectRegime({ symbol, market, timeframe });
569
+ return json({ symbol, market, timeframe, regime });
570
+ } catch (err) {
571
+ return json({ error: err instanceof Error ? err.message : String(err) });
572
+ }
573
+ },
574
+ },
575
+ { names: ["fin_data_regime"] },
576
+ );
577
+
578
+ // === Tool 10: fin_data_markets — Supported Markets ===
579
+ api.registerTool(
580
+ {
581
+ name: "fin_data_markets",
582
+ label: "Supported Markets",
583
+ description: "List all supported market types and data categories",
584
+ parameters: Type.Object({}),
585
+ async execute() {
586
+ return json({
587
+ datahub: config.datahubApiUrl,
588
+ markets: dataProvider.getSupportedMarkets(),
589
+ categories: [
590
+ "equity",
591
+ "crypto",
592
+ "economy",
593
+ "derivatives",
594
+ "index",
595
+ "etf",
596
+ "currency",
597
+ "coverage",
598
+ ],
599
+ endpoints: 172,
600
+ });
601
+ },
602
+ },
603
+ { names: ["fin_data_markets"] },
604
+ );
605
+ },
606
+ };
607
+
608
+ export default findooDatahubPlugin;
@@ -0,0 +1,34 @@
1
+ {
2
+ "id": "findoo-datahub-plugin",
3
+ "name": "Findoo DataHub",
4
+ "description": "Unified financial data source — 172 endpoints covering equity (A/HK/US), crypto, macro, derivatives, index, ETF via OpenBB DataHub. Works out of the box.",
5
+ "kind": "financial",
6
+ "version": "2026.3.2",
7
+ "skills": ["./skills"],
8
+ "configSchema": {
9
+ "type": "object",
10
+ "properties": {
11
+ "datahubApiUrl": {
12
+ "type": "string",
13
+ "default": "http://43.134.61.136:8088",
14
+ "description": "DataHub REST API base URL. Change only for self-hosted OpenBB instances"
15
+ },
16
+ "datahubUsername": {
17
+ "type": "string",
18
+ "default": "admin",
19
+ "description": "DataHub HTTP Basic Auth username"
20
+ },
21
+ "datahubPassword": {
22
+ "type": "string",
23
+ "description": "DataHub HTTP Basic Auth password",
24
+ "sensitive": true
25
+ },
26
+ "requestTimeoutMs": {
27
+ "type": "number",
28
+ "default": 30000,
29
+ "minimum": 1000,
30
+ "maximum": 120000
31
+ }
32
+ }
33
+ }
34
+ }
package/package.json ADDED
@@ -0,0 +1,32 @@
1
+ {
2
+ "name": "@openfinclaw/findoo-datahub-plugin",
3
+ "version": "2026.3.2",
4
+ "description": "Unified financial data source — free mode (CCXT/CoinGecko/DefiLlama/Yahoo) + full mode (172 DataHub endpoints)",
5
+ "keywords": [
6
+ "crypto",
7
+ "datahub",
8
+ "defi",
9
+ "finance",
10
+ "macro",
11
+ "stock"
12
+ ],
13
+ "homepage": "https://github.com/cryptoSUN2049/openFinclaw#readme",
14
+ "type": "module",
15
+ "dependencies": {
16
+ "yahoo-finance2": "^3.13.0"
17
+ },
18
+ "devDependencies": {
19
+ "openfinclaw": "workspace:*"
20
+ },
21
+ "peerDependencies": {
22
+ "openclaw": ">=2026.1.0"
23
+ },
24
+ "openclaw": {
25
+ "extensions": [
26
+ "./index.ts"
27
+ ],
28
+ "install": {
29
+ "npmSpec": "@openfinclaw/findoo-datahub-plugin"
30
+ }
31
+ }
32
+ }