@openfinclaw/findoo-datahub-plugin 2026.3.2 → 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.
Files changed (70) hide show
  1. package/DESIGN.md +492 -151
  2. package/_vendor/claude-skills-finance/SKILL.md +192 -0
  3. package/_vendor/claude-skills-finance/assets/dcf_analysis_template.md +184 -0
  4. package/_vendor/claude-skills-finance/assets/expected_output.json +161 -0
  5. package/_vendor/claude-skills-finance/assets/forecast_report_template.md +177 -0
  6. package/_vendor/claude-skills-finance/assets/sample_financial_data.json +219 -0
  7. package/_vendor/claude-skills-finance/assets/variance_report_template.md +122 -0
  8. package/_vendor/claude-skills-finance/references/financial-ratios-guide.md +396 -0
  9. package/_vendor/claude-skills-finance/references/forecasting-best-practices.md +294 -0
  10. package/_vendor/claude-skills-finance/references/valuation-methodology.md +255 -0
  11. package/_vendor/claude-skills-finance/scripts/budget_variance_analyzer.py +406 -0
  12. package/_vendor/claude-skills-finance/scripts/dcf_valuation.py +449 -0
  13. package/_vendor/claude-skills-finance/scripts/forecast_builder.py +494 -0
  14. package/_vendor/claude-skills-finance/scripts/ratio_calculator.py +432 -0
  15. package/index.ts +356 -20
  16. package/openclaw.plugin.json +12 -19
  17. package/package.json +1 -1
  18. package/references/cn-market-specifics.md +165 -0
  19. package/references/crypto-analysis.md +635 -0
  20. package/references/financial-ratios-cn.md +452 -0
  21. package/references/hk-market-specifics.md +166 -0
  22. package/references/macro-cycle-cn.md +409 -0
  23. package/references/valuation-cn.md +427 -0
  24. package/skills/README.md +294 -0
  25. package/skills/a-concept-cycle/skill.md +200 -0
  26. package/skills/a-convertible-arb/skill.md +294 -0
  27. package/skills/a-dividend-king/skill.md +187 -0
  28. package/skills/a-earnings-season/skill.md +221 -0
  29. package/skills/a-index-timer/skill.md +192 -0
  30. package/skills/a-ipo-new/skill.md +297 -0
  31. package/skills/a-northbound-decoder/skill.md +185 -0
  32. package/skills/a-quant-board/skill.md +286 -0
  33. package/skills/a-share/skill.md +347 -0
  34. package/skills/a-share-radar/skill.md +185 -0
  35. package/skills/cross-asset/skill.md +202 -0
  36. package/skills/crypto/skill.md +269 -0
  37. package/skills/crypto-altseason/skill.md +208 -0
  38. package/skills/crypto-btc-cycle/skill.md +231 -0
  39. package/skills/crypto-defi-yield/skill.md +181 -0
  40. package/skills/crypto-funding-arb/skill.md +158 -0
  41. package/skills/crypto-stablecoin-flow/skill.md +149 -0
  42. package/skills/data-query/skill.md +124 -30
  43. package/skills/derivatives/skill.md +188 -35
  44. package/skills/etf-fund/skill.md +216 -0
  45. package/skills/factor-screen/skill.md +186 -0
  46. package/skills/hk-china-internet/skill.md +190 -0
  47. package/skills/hk-dividend-harvest/skill.md +192 -0
  48. package/skills/hk-hsi-pulse/skill.md +154 -0
  49. package/skills/hk-southbound-alpha/skill.md +163 -0
  50. package/skills/hk-stock/skill.md +295 -0
  51. package/skills/macro/skill.md +244 -53
  52. package/skills/risk-monitor/skill.md +171 -0
  53. package/skills/us-dividend/skill.md +162 -0
  54. package/skills/us-earnings/skill.md +149 -0
  55. package/skills/us-equity/skill.md +235 -0
  56. package/skills/us-etf/skill.md +261 -0
  57. package/skills/us-sector-rotation/skill.md +223 -0
  58. package/src/config.ts +4 -5
  59. package/src/datahub-client.test.ts +4 -7
  60. package/src/datahub-client.ts +6 -1
  61. package/src/register-tools.ts +720 -0
  62. package/src/tool-helpers.ts +89 -0
  63. package/test/e2e/l3-gateway-bootstrap.live.test.ts +354 -0
  64. package/test/e2e/l4-skill-tool-chain.live.test.ts +461 -0
  65. package/test/e2e/l5-browser/data-freshness.live.test.ts +379 -0
  66. package/test/e2e/l5-browser/market-data-chat.live.test.ts +259 -0
  67. package/test/e2e/l5-browser/skills-registry.test.ts +282 -0
  68. package/skills/crypto-defi/skill.md +0 -69
  69. package/skills/equity/skill.md +0 -64
  70. package/skills/market-radar/skill.md +0 -47
package/index.ts CHANGED
@@ -20,10 +20,13 @@ function buildParams(params: Record<string, unknown>): Record<string, string> {
20
20
  if (params.start_date) out.start_date = String(params.start_date);
21
21
  if (params.end_date) out.end_date = String(params.end_date);
22
22
  if (params.trade_date) out.trade_date = String(params.trade_date);
23
+ if (params.date) out.date = String(params.date);
23
24
  if (params.limit) out.limit = String(params.limit);
24
25
  if (params.provider) out.provider = String(params.provider);
25
26
  if (params.country) out.country = String(params.country);
26
27
  if (params.indicator) out.indicator = String(params.indicator);
28
+ if (params.manager) out.manager = String(params.manager);
29
+ if (params.hs_type) out.hs_type = String(params.hs_type);
27
30
  return out;
28
31
  }
29
32
 
@@ -37,22 +40,34 @@ const findooDatahubPlugin = {
37
40
  "172 endpoints covering equity (A/HK/US), crypto, macro, derivatives, index, ETF.",
38
41
  kind: "financial" as const,
39
42
 
40
- async register(api: OpenClawPluginApi) {
43
+ register(api: OpenClawPluginApi) {
41
44
  const config = resolveConfig(api);
42
45
 
43
46
  // --- DataHub client ---
44
- const client = new DataHubClient(
45
- config.datahubApiUrl,
46
- config.datahubUsername,
47
- config.datahubPassword,
48
- config.requestTimeoutMs,
49
- );
47
+ if (!config.datahubApiKey) {
48
+ api.log?.(
49
+ "error",
50
+ "findoo-datahub-plugin: API key is required. Set DATAHUB_API_KEY env var or configure in Control UI → Plugins → Findoo DataHub.",
51
+ );
52
+ }
53
+
54
+ const client = config.datahubApiKey
55
+ ? new DataHubClient(
56
+ config.datahubApiUrl,
57
+ config.datahubUsername,
58
+ config.datahubApiKey,
59
+ config.requestTimeoutMs,
60
+ )
61
+ : null;
50
62
 
51
63
  // --- Local cache + regime detector ---
52
64
  const dbPath = api.resolvePath("state/findoo-ohlcv-cache.sqlite");
53
65
  const cache = new OHLCVCache(dbPath);
54
66
  const regimeDetector = new RegimeDetector();
55
67
 
68
+ const NO_KEY_ERROR =
69
+ "DataHub API key not configured. Set DATAHUB_API_KEY env var or configure in Control UI → Plugins → Findoo DataHub.";
70
+
56
71
  // --- Data provider service (exposed to other extensions) ---
57
72
  const dataProvider = {
58
73
  async getOHLCV(params: {
@@ -62,6 +77,8 @@ const findooDatahubPlugin = {
62
77
  since?: number;
63
78
  limit?: number;
64
79
  }) {
80
+ if (!client) throw new Error(NO_KEY_ERROR);
81
+
65
82
  // Check cache first
66
83
  const range = cache.getRange(params.symbol, params.market, params.timeframe);
67
84
  if (range && params.since != null && params.limit != null) {
@@ -82,6 +99,7 @@ const findooDatahubPlugin = {
82
99
  },
83
100
 
84
101
  async getTicker(symbol: string, market: MarketType) {
102
+ if (!client) throw new Error(NO_KEY_ERROR);
85
103
  return client.getTicker(symbol, market);
86
104
  },
87
105
 
@@ -118,7 +136,7 @@ const findooDatahubPlugin = {
118
136
  } as Parameters<typeof api.registerService>[0]);
119
137
 
120
138
  // ============================================================
121
- // AI Tools (10 total)
139
+ // AI Tools (13 total)
122
140
  // ============================================================
123
141
 
124
142
  // === Tool 1: fin_stock — Equity data ===
@@ -127,7 +145,7 @@ const findooDatahubPlugin = {
127
145
  name: "fin_stock",
128
146
  label: "Stock Data (A/HK/US)",
129
147
  description:
130
- "Fetch A-share, HK, or US equity data — quotes, historical prices, income, balance sheet, cashflow, ratios, money flow, holders, dividends, news.",
148
+ "Fetch A-share, HK, or US equity data — quotes, historical prices, financials (income/balance/cash/ratios/metrics + VIP variants), ownership (top10/shareholder_trade/repurchase/pledge), money flow, concept lists, calendar (earnings/ipo), discovery (gainers/losers), HK subset (hk/*), US subset (us/*). Notes: estimates/consensus is yfinance-only (US/HK, rate-limited, NOT for A-share — use fundamental/earnings_forecast instead). fundamental/stock_factor returns pre-computed MACD/KDJ/RSI/BOLL/CCI. market/stock_limit requires symbol param (no batch scan by date).",
131
149
  parameters: Type.Object({
132
150
  symbol: Type.String({
133
151
  description: "Stock code. A-shares: 600519.SH; HK: 00700.HK; US: AAPL",
@@ -136,17 +154,81 @@ const findooDatahubPlugin = {
136
154
  type: "string",
137
155
  enum: [
138
156
  "price/historical",
157
+ "price/quote",
158
+ "profile",
159
+ "search",
160
+ "screener",
161
+ // fundamentals — A-share
139
162
  "fundamental/income",
140
163
  "fundamental/balance",
141
164
  "fundamental/cash",
142
165
  "fundamental/ratios",
143
166
  "fundamental/metrics",
144
167
  "fundamental/dividends",
168
+ "fundamental/adj_factor",
169
+ "fundamental/earnings_forecast",
170
+ "fundamental/financial_audit",
171
+ "fundamental/financial_express",
172
+ "fundamental/forecast_vip",
173
+ "fundamental/revenue_per_segment",
174
+ "fundamental/management",
175
+ // ownership & structure
145
176
  "ownership/top10_holders",
177
+ "ownership/top10_float_holders",
178
+ "ownership/major_holders",
179
+ "ownership/shareholder_trade",
180
+ "ownership/repurchase",
181
+ "ownership/share_float",
182
+ "ownership/holder_number",
183
+ "ownership/share_statistics",
184
+ "pledge/stat",
185
+ "pledge/detail",
186
+ // money flow & market
146
187
  "moneyflow/individual",
147
188
  "market/top_list",
189
+ // discovery & calendar
148
190
  "discovery/gainers",
149
191
  "discovery/losers",
192
+ "discovery/active",
193
+ "discovery/undervalued_growth",
194
+ "discovery/undervalued_large_caps",
195
+ "discovery/growth_tech",
196
+ "discovery/aggressive_small_caps",
197
+ "discovery/name_change",
198
+ "calendar/earnings",
199
+ "calendar/ipo",
200
+ "compare/peers",
201
+ "shorts/short_volume",
202
+ "concept/concept_detail",
203
+ "concept/concept_list",
204
+ // estimates (yfinance only — US/HK stocks, not A-share; may hit rate limits)
205
+ "estimates/consensus",
206
+ // additional fundamentals (VIP / tushare-only)
207
+ "fundamental/backup_daily",
208
+ "fundamental/balance_vip",
209
+ "fundamental/cashflow_vip",
210
+ "fundamental/dividend_detail",
211
+ "fundamental/historical_splits",
212
+ "fundamental/income_vip",
213
+ "fundamental/revenue_segment_vip",
214
+ "fundamental/stock_factor",
215
+ // HK
216
+ "hk/income",
217
+ "hk/balancesheet",
218
+ "hk/cashflow",
219
+ "hk/fina_indicator",
220
+ "hk/basic",
221
+ "hk/hold",
222
+ "hk/adj_factor",
223
+ "hk/trade_cal",
224
+ // US
225
+ "us/income",
226
+ "us/balancesheet",
227
+ "us/cashflow",
228
+ "us/fina_indicator",
229
+ "us/basic",
230
+ "us/adj_factor",
231
+ "us/trade_cal",
150
232
  ],
151
233
  description: "DataHub equity endpoint path",
152
234
  }),
@@ -159,6 +241,7 @@ const findooDatahubPlugin = {
159
241
  }),
160
242
  async execute(_toolCallId: string, params: Record<string, unknown>) {
161
243
  try {
244
+ if (!client) return json({ error: NO_KEY_ERROR });
162
245
  const endpoint = String(params.endpoint ?? "price/historical");
163
246
  const qp = buildParams(params);
164
247
  const results = await client.equity(endpoint, qp);
@@ -182,7 +265,7 @@ const findooDatahubPlugin = {
182
265
  name: "fin_index",
183
266
  label: "Index / ETF / Fund",
184
267
  description:
185
- "Query index constituents, valuations, ETF prices/NAV, fund manager/portfolio.",
268
+ "Query index data — constituents, daily valuations (PE/PB via daily_basic), thematic/concept indices (ths_index/ths_daily/ths_member), classification, global indices. Use constituents (not members) for index composition.",
186
269
  parameters: Type.Object({
187
270
  symbol: Type.Optional(
188
271
  Type.String({ description: "Index/ETF/fund code. Index: 000300.SH; ETF: 510050.SH" }),
@@ -193,6 +276,10 @@ const findooDatahubPlugin = {
193
276
  "price/historical",
194
277
  "constituents",
195
278
  "daily_basic",
279
+ "available",
280
+ "info",
281
+ "classify",
282
+ "global_index",
196
283
  "thematic/ths_index",
197
284
  "thematic/ths_daily",
198
285
  "thematic/ths_member",
@@ -205,6 +292,7 @@ const findooDatahubPlugin = {
205
292
  }),
206
293
  async execute(_toolCallId: string, params: Record<string, unknown>) {
207
294
  try {
295
+ if (!client) return json({ error: NO_KEY_ERROR });
208
296
  const endpoint = String(params.endpoint ?? "price/historical");
209
297
  const qp = buildParams(params);
210
298
  const results = await client.index(endpoint, qp);
@@ -228,7 +316,7 @@ const findooDatahubPlugin = {
228
316
  name: "fin_macro",
229
317
  label: "Macro / Rates / FX",
230
318
  description:
231
- "China macro (GDP/CPI/PPI/PMI/M2), interest rates (Shibor/LPR/Libor/Hibor), treasury yields, FX, WorldBank data.",
319
+ "China macro (GDP/CPI/PPI/PMI/M2/social financing), interest rates (Shibor/LPR/Libor/Hibor), CN/US treasury yields, FX rates (currency/*), WorldBank data (worldbank/*), economic calendar, fixedincome rate aliases (fixedincome/rate/*). Notes: LIBOR terminated 2023 (data stops 2020-06). fixedincome/rate/* are aliases for shibor/libor/hibor. worldbank/country provides country classification for EM vs DM analysis.",
232
320
  parameters: Type.Object({
233
321
  endpoint: Type.Unsafe<string>({
234
322
  type: "string",
@@ -241,18 +329,29 @@ const findooDatahubPlugin = {
241
329
  "social_financing",
242
330
  "shibor",
243
331
  "shibor_lpr",
332
+ "shibor_quote",
244
333
  "libor",
245
334
  "hibor",
246
335
  "treasury_cn",
247
336
  "treasury_us",
248
337
  "index_global",
338
+ "wz_index",
249
339
  "calendar",
340
+ "currency/price/historical",
341
+ "currency/search",
342
+ "currency/snapshots",
343
+ "worldbank/country",
250
344
  "worldbank/gdp",
251
345
  "worldbank/population",
252
346
  "worldbank/inflation",
253
347
  "worldbank/indicator",
348
+ // fixedincome (dedicated DataHub paths, same data as shibor/libor/hibor)
349
+ "fixedincome/rate/shibor",
350
+ "fixedincome/rate/shibor_lpr",
351
+ "fixedincome/rate/libor",
352
+ "fixedincome/rate/hibor",
254
353
  ],
255
- description: "DataHub economy endpoint path",
354
+ description: "DataHub economy/currency endpoint path",
256
355
  }),
257
356
  symbol: Type.Optional(Type.String({ description: "Currency pair or indicator code" })),
258
357
  country: Type.Optional(Type.String({ description: "Country code for WorldBank" })),
@@ -262,12 +361,25 @@ const findooDatahubPlugin = {
262
361
  }),
263
362
  async execute(_toolCallId: string, params: Record<string, unknown>) {
264
363
  try {
364
+ if (!client) return json({ error: NO_KEY_ERROR });
265
365
  const endpoint = String(params.endpoint ?? "cpi");
266
366
  const qp = buildParams(params);
267
- const results = await client.economy(endpoint, qp);
367
+ // Route currency/* and fixedincome/* endpoints to their categories
368
+ let results: unknown[];
369
+ let category: string;
370
+ if (endpoint.startsWith("currency/")) {
371
+ results = await client.currency(endpoint.replace("currency/", ""), qp);
372
+ category = "currency";
373
+ } else if (endpoint.startsWith("fixedincome/")) {
374
+ results = await client.query(endpoint, qp);
375
+ category = "fixedincome";
376
+ } else {
377
+ results = await client.economy(endpoint, qp);
378
+ category = "economy";
379
+ }
268
380
  return json({
269
381
  success: true,
270
- endpoint: `economy/${endpoint}`,
382
+ endpoint: `${category}/${endpoint.startsWith("currency/") ? endpoint.replace("currency/", "") : endpoint}`,
271
383
  count: results.length,
272
384
  results,
273
385
  });
@@ -285,7 +397,7 @@ const findooDatahubPlugin = {
285
397
  name: "fin_derivatives",
286
398
  label: "Futures / Options / CB",
287
399
  description:
288
- "Futures (daily, holdings, settlement, warehouse, mapping), options (basic, daily, chains), convertible bonds.",
400
+ "Futures (daily OHLCV, holdings/OI, settlement, warehouse receipts, contract mapping, term structure curve), options (basic info, daily, chains with Greeks/IV), convertible bonds (basic, daily). Use futures/curve for contango/backwardation analysis.",
289
401
  parameters: Type.Object({
290
402
  symbol: Type.Optional(
291
403
  Type.String({ description: "Contract code, e.g. IF2501.CFX, 113xxx.SH (CB)" }),
@@ -299,6 +411,7 @@ const findooDatahubPlugin = {
299
411
  "futures/settle",
300
412
  "futures/warehouse",
301
413
  "futures/mapping",
414
+ "futures/curve",
302
415
  "options/basic",
303
416
  "options/daily",
304
417
  "options/chains",
@@ -314,6 +427,7 @@ const findooDatahubPlugin = {
314
427
  }),
315
428
  async execute(_toolCallId: string, params: Record<string, unknown>) {
316
429
  try {
430
+ if (!client) return json({ error: NO_KEY_ERROR });
317
431
  const endpoint = String(params.endpoint ?? "futures/historical");
318
432
  const qp = buildParams(params);
319
433
  const results = await client.derivatives(endpoint, qp);
@@ -337,7 +451,7 @@ const findooDatahubPlugin = {
337
451
  name: "fin_crypto",
338
452
  label: "Crypto & DeFi",
339
453
  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.",
454
+ "Crypto market data (ticker/orderbook/trades/funding_rate) via CEX, DeFi (protocols/TVL/yields/stablecoins/fees/dex_volumes/bridges/chains) via DefiLlama, market metrics (coin/market/info/categories/trending/global_stats) via CoinGecko. price/historical for crypto OHLCV, search for symbol lookup.",
341
455
  parameters: Type.Object({
342
456
  endpoint: Type.Unsafe<string>({
343
457
  type: "string",
@@ -361,19 +475,43 @@ const findooDatahubPlugin = {
361
475
  "defi/stablecoins",
362
476
  "defi/fees",
363
477
  "defi/dex_volumes",
478
+ "defi/bridges",
364
479
  "defi/coin_prices",
480
+ "price/historical",
481
+ "search",
365
482
  ],
366
483
  description: "DataHub crypto endpoint path",
367
484
  }),
368
485
  symbol: Type.Optional(
369
486
  Type.String({ description: "Coin ID, trading pair, or protocol slug" }),
370
487
  ),
488
+ start_date: Type.Optional(
489
+ Type.String({ description: "Start date for coin/historical (YYYY-MM-DD)" }),
490
+ ),
491
+ end_date: Type.Optional(
492
+ Type.String({ description: "End date for coin/historical (YYYY-MM-DD)" }),
493
+ ),
371
494
  limit: Type.Optional(Type.Number()),
372
495
  }),
373
496
  async execute(_toolCallId: string, params: Record<string, unknown>) {
374
497
  try {
498
+ if (!client) return json({ error: NO_KEY_ERROR });
375
499
  const endpoint = String(params.endpoint ?? "coin/market");
376
500
  const qp = buildParams(params);
501
+ // Endpoint-specific param mapping for crypto APIs
502
+ if (qp.symbol) {
503
+ const coinIdEndpoints = ["coin/historical", "coin/info"];
504
+ if (coinIdEndpoints.includes(endpoint)) {
505
+ qp.coin_id = qp.symbol;
506
+ delete qp.symbol;
507
+ } else if (endpoint === "defi/protocol_tvl") {
508
+ qp.protocol = qp.symbol;
509
+ delete qp.symbol;
510
+ } else if (endpoint === "defi/coin_prices") {
511
+ qp.coins = qp.symbol;
512
+ delete qp.symbol;
513
+ }
514
+ }
377
515
  const results = await client.crypto(endpoint, qp);
378
516
  return json({
379
517
  success: true,
@@ -395,7 +533,7 @@ const findooDatahubPlugin = {
395
533
  name: "fin_market",
396
534
  label: "Market Radar",
397
535
  description:
398
- "Market monitoring — dragon-tiger list, limit-up/down stats, block trades, sector money flow, margin, Stock Connect flows, global index, IPO calendar.",
536
+ "A-share market monitoring — dragon-tiger list (top_list/top_inst), limit-up/down stats (limit_list), block trades, sector money flow (moneyflow/industry), margin (summary/detail/trading), Stock Connect northbound (hsgt_flow/hsgt_top10) and southbound (ggt_daily/ggt_monthly), market snapshots, discovery (gainers/losers/active/new_share). Notes: flow/hsgt_top10 requires 'date' param (NOT trade_date). flow/hs_const requires 'hs_type' param (SH or SZ). market/stock_limit requires 'symbol' (no date-only scan). market_snapshots returns full market snapshot (12000+ records, no params needed).",
399
537
  parameters: Type.Object({
400
538
  endpoint: Type.Unsafe<string>({
401
539
  type: "string",
@@ -410,8 +548,14 @@ const findooDatahubPlugin = {
410
548
  "moneyflow/block_trade",
411
549
  "margin/summary",
412
550
  "margin/detail",
551
+ "margin/trading",
413
552
  "flow/hsgt_flow",
414
553
  "flow/hsgt_top10",
554
+ "flow/ggt_daily",
555
+ "flow/ggt_monthly",
556
+ "flow/hs_const",
557
+ "market/stock_limit",
558
+ "market_snapshots",
415
559
  "discovery/gainers",
416
560
  "discovery/losers",
417
561
  "discovery/active",
@@ -420,15 +564,39 @@ const findooDatahubPlugin = {
420
564
  description: "DataHub equity endpoint for market data",
421
565
  }),
422
566
  trade_date: Type.Optional(Type.String({ description: "Trade date, e.g. 2025-02-28" })),
567
+ date: Type.Optional(
568
+ Type.String({
569
+ description:
570
+ "Date param for hsgt_top10 (uses 'date' not 'trade_date'), e.g. 2025-02-28",
571
+ }),
572
+ ),
423
573
  symbol: Type.Optional(Type.String({ description: "Symbol for specific queries" })),
574
+ hs_type: Type.Optional(
575
+ Type.String({
576
+ description:
577
+ "Stock Connect type for hs_const: SH (northbound) or SZ (northbound Shenzhen)",
578
+ }),
579
+ ),
424
580
  start_date: Type.Optional(Type.String()),
425
581
  end_date: Type.Optional(Type.String()),
426
582
  limit: Type.Optional(Type.Number()),
427
583
  }),
428
584
  async execute(_toolCallId: string, params: Record<string, unknown>) {
429
585
  try {
586
+ if (!client) return json({ error: NO_KEY_ERROR });
430
587
  const endpoint = String(params.endpoint ?? "market/top_list");
431
588
  const qp = buildParams(params);
589
+ // Auto-alias: trade_date→date for endpoints that use 'date' param
590
+ const dateEndpoints = [
591
+ "market/top_list",
592
+ "market/top_inst",
593
+ "market/limit_list",
594
+ "flow/hsgt_top10",
595
+ ];
596
+ if (qp.trade_date && !qp.date && dateEndpoints.includes(endpoint)) {
597
+ qp.date = qp.trade_date;
598
+ delete qp.trade_date;
599
+ }
432
600
  const results = await client.equity(endpoint, qp);
433
601
  return json({
434
602
  success: true,
@@ -450,7 +618,7 @@ const findooDatahubPlugin = {
450
618
  name: "fin_query",
451
619
  label: "Raw DataHub Query",
452
620
  description:
453
- "Direct passthrough to any of 172 DataHub endpoints by path. Use when other tools don't cover the specific data.",
621
+ "Direct passthrough to any of 168 DataHub endpoints by path. Use when other tools don't cover the specific data. Accepts arbitrary query params. Example paths: equity/fundamental/income, crypto/defi/protocols, economy/cpi, fixedincome/rate/shibor.",
454
622
  parameters: Type.Object({
455
623
  path: Type.String({
456
624
  description:
@@ -464,6 +632,7 @@ const findooDatahubPlugin = {
464
632
  }),
465
633
  async execute(_toolCallId: string, params: Record<string, unknown>) {
466
634
  try {
635
+ if (!client) return json({ error: NO_KEY_ERROR });
467
636
  const path = String(params.path ?? "").trim();
468
637
  if (!path) throw new Error("path is required");
469
638
  const qp = (params.params ?? {}) as Record<string, string>;
@@ -575,7 +744,174 @@ const findooDatahubPlugin = {
575
744
  { names: ["fin_data_regime"] },
576
745
  );
577
746
 
578
- // === Tool 10: fin_data_marketsSupported Markets ===
747
+ // === Tool 10: fin_taTechnical Analysis Indicators ===
748
+ api.registerTool(
749
+ {
750
+ name: "fin_ta",
751
+ label: "Technical Analysis",
752
+ description:
753
+ "Calculate technical indicators (SMA, EMA, RSI, MACD, Bollinger Bands) for any symbol. DataHub fetches OHLCV and computes server-side.",
754
+ parameters: Type.Object({
755
+ symbol: Type.String({
756
+ description:
757
+ "Stock/crypto symbol. A: 600519.SH; HK: 00700.HK; US: AAPL; Crypto: BTC-USDT",
758
+ }),
759
+ indicator: Type.Unsafe<string>({
760
+ type: "string",
761
+ enum: ["sma", "ema", "rsi", "macd", "bbands"],
762
+ description: "Technical indicator to calculate",
763
+ }),
764
+ period: Type.Optional(
765
+ Type.Number({
766
+ description: "Indicator period (default: 20 for SMA/EMA/BBANDS, 14 for RSI)",
767
+ }),
768
+ ),
769
+ limit: Type.Optional(
770
+ Type.Number({ description: "Number of OHLCV bars to fetch (default: 200)" }),
771
+ ),
772
+ fast: Type.Optional(Type.Number({ description: "MACD fast period (default: 12)" })),
773
+ slow: Type.Optional(Type.Number({ description: "MACD slow period (default: 26)" })),
774
+ signal: Type.Optional(Type.Number({ description: "MACD signal period (default: 9)" })),
775
+ std: Type.Optional(
776
+ Type.Number({ description: "Bollinger Bands std dev (default: 2.0)" }),
777
+ ),
778
+ provider: Type.Optional(
779
+ Type.String({ description: "Data provider override (auto-detected if omitted)" }),
780
+ ),
781
+ }),
782
+ async execute(_toolCallId: string, params: Record<string, unknown>) {
783
+ try {
784
+ if (!client) return json({ error: NO_KEY_ERROR });
785
+ const indicator = String(params.indicator ?? "sma");
786
+ const qp: Record<string, string> = {};
787
+ if (params.symbol) qp.symbol = String(params.symbol);
788
+ if (params.period) qp.period = String(params.period);
789
+ if (params.limit) qp.limit = String(params.limit);
790
+ if (params.fast) qp.fast = String(params.fast);
791
+ if (params.slow) qp.slow = String(params.slow);
792
+ if (params.signal) qp.signal = String(params.signal);
793
+ if (params.std) qp.std = String(params.std);
794
+ if (params.provider) qp.provider = String(params.provider);
795
+ const results = await client.ta(indicator, qp);
796
+ return json({
797
+ success: true,
798
+ endpoint: `ta/${indicator}`,
799
+ count: results.length,
800
+ results,
801
+ });
802
+ } catch (err) {
803
+ return json({ error: err instanceof Error ? err.message : String(err) });
804
+ }
805
+ },
806
+ },
807
+ { names: ["fin_ta"] },
808
+ );
809
+
810
+ // === Tool 11: fin_etf — ETF & Fund Data ===
811
+ api.registerTool(
812
+ {
813
+ name: "fin_etf",
814
+ label: "ETF & Fund",
815
+ description:
816
+ "ETF and fund data — NAV history, fund info (type/size/fees), historical prices, portfolio holdings (top 10, quarterly), manager track record, dividends, share changes (subscription/redemption trends), adjusted NAV (dividend-reinvested), search. Use fund/manager with a known fund code to find manager info; manager param available for search.",
817
+ parameters: Type.Object({
818
+ symbol: Type.Optional(
819
+ Type.String({
820
+ description: "ETF/Fund code. ETF: 510050.SH; Fund: 110011",
821
+ }),
822
+ ),
823
+ manager: Type.Optional(
824
+ Type.String({
825
+ description: "Fund manager name for search endpoint (e.g. 张坤)",
826
+ }),
827
+ ),
828
+ endpoint: Type.Unsafe<string>({
829
+ type: "string",
830
+ enum: [
831
+ "nav",
832
+ "info",
833
+ "historical",
834
+ "fund/portfolio",
835
+ "fund/manager",
836
+ "fund/dividends",
837
+ "fund/share",
838
+ "fund/adj_nav",
839
+ "search",
840
+ ],
841
+ description: "DataHub ETF/fund endpoint path",
842
+ }),
843
+ start_date: Type.Optional(Type.String({ description: "Start date, e.g. 2025-01-01" })),
844
+ end_date: Type.Optional(Type.String({ description: "End date, e.g. 2025-12-31" })),
845
+ limit: Type.Optional(Type.Number({ description: "Max records to return" })),
846
+ }),
847
+ async execute(_toolCallId: string, params: Record<string, unknown>) {
848
+ try {
849
+ if (!client) return json({ error: NO_KEY_ERROR });
850
+ const endpoint = String(params.endpoint ?? "info");
851
+ const qp = buildParams(params);
852
+ const results = await client.etf(endpoint, qp);
853
+ return json({
854
+ success: true,
855
+ endpoint: `etf/${endpoint}`,
856
+ count: results.length,
857
+ results,
858
+ });
859
+ } catch (err) {
860
+ return json({ error: err instanceof Error ? err.message : String(err) });
861
+ }
862
+ },
863
+ },
864
+ { names: ["fin_etf"] },
865
+ );
866
+
867
+ // === Tool 12: fin_currency — FX & News ===
868
+ api.registerTool(
869
+ {
870
+ name: "fin_currency",
871
+ label: "FX & News",
872
+ description:
873
+ "Foreign exchange rates (price/historical, search, snapshots) and company news (news/company). Major pairs: USDCNH, EURUSD, USDJPY. Use search to find available currency pair symbols.",
874
+ parameters: Type.Object({
875
+ endpoint: Type.Unsafe<string>({
876
+ type: "string",
877
+ enum: ["price/historical", "search", "snapshots", "news/company"],
878
+ description: "DataHub currency/news endpoint path",
879
+ }),
880
+ symbol: Type.Optional(
881
+ Type.String({
882
+ description: "Currency pair (USDCNH, EURUSD) or stock symbol for news (AAPL)",
883
+ }),
884
+ ),
885
+ start_date: Type.Optional(Type.String({ description: "Start date, e.g. 2025-01-01" })),
886
+ end_date: Type.Optional(Type.String({ description: "End date, e.g. 2025-12-31" })),
887
+ limit: Type.Optional(Type.Number({ description: "Max records to return" })),
888
+ }),
889
+ async execute(_toolCallId: string, params: Record<string, unknown>) {
890
+ try {
891
+ if (!client) return json({ error: NO_KEY_ERROR });
892
+ const endpoint = String(params.endpoint ?? "price/historical");
893
+ const qp = buildParams(params);
894
+ // Route news/* to the generic query path
895
+ const results =
896
+ endpoint === "news/company"
897
+ ? await client.query(`news/company`, qp)
898
+ : await client.currency(endpoint, qp);
899
+ const category = endpoint === "news/company" ? "news" : "currency";
900
+ return json({
901
+ success: true,
902
+ endpoint: `${category}/${endpoint === "news/company" ? "company" : endpoint}`,
903
+ count: results.length,
904
+ results,
905
+ });
906
+ } catch (err) {
907
+ return json({ error: err instanceof Error ? err.message : String(err) });
908
+ }
909
+ },
910
+ },
911
+ { names: ["fin_currency"] },
912
+ );
913
+
914
+ // === Tool 13: fin_data_markets — Supported Markets ===
579
915
  api.registerTool(
580
916
  {
581
917
  name: "fin_data_markets",
@@ -584,7 +920,7 @@ const findooDatahubPlugin = {
584
920
  parameters: Type.Object({}),
585
921
  async execute() {
586
922
  return json({
587
- datahub: config.datahubApiUrl,
923
+ connected: !!client,
588
924
  markets: dataProvider.getSupportedMarkets(),
589
925
  categories: [
590
926
  "equity",
@@ -1,34 +1,27 @@
1
1
  {
2
2
  "id": "findoo-datahub-plugin",
3
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.",
4
+ "description": "Unified financial data source — 172 endpoints covering equity (A/HK/US), crypto, macro, derivatives, index, ETF. Works out of the box.",
5
5
  "kind": "financial",
6
6
  "version": "2026.3.2",
7
7
  "skills": ["./skills"],
8
8
  "configSchema": {
9
9
  "type": "object",
10
10
  "properties": {
11
- "datahubApiUrl": {
11
+ "datahubApiKey": {
12
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",
13
+ "minLength": 1,
14
+ "description": "DataHub API key (required). Also accepted via DATAHUB_API_KEY env var.",
24
15
  "sensitive": true
25
- },
26
- "requestTimeoutMs": {
27
- "type": "number",
28
- "default": 30000,
29
- "minimum": 1000,
30
- "maximum": 120000
31
16
  }
32
17
  }
18
+ },
19
+ "uiHints": {
20
+ "datahubApiKey": {
21
+ "label": "API Key",
22
+ "help": "DataHub API 密钥(必填)。",
23
+ "sensitive": true,
24
+ "placeholder": "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx"
25
+ }
33
26
  }
34
27
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openfinclaw/findoo-datahub-plugin",
3
- "version": "2026.3.2",
3
+ "version": "2026.3.12",
4
4
  "description": "Unified financial data source — free mode (CCXT/CoinGecko/DefiLlama/Yahoo) + full mode (172 DataHub endpoints)",
5
5
  "keywords": [
6
6
  "crypto",