@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.
@@ -0,0 +1,69 @@
1
+ ---
2
+ name: fin-crypto-defi
3
+ description: "Crypto & DeFi data — CEX market data (tickers/orderbook/funding), DeFi protocol TVL/yields/stablecoins/DEX volumes, CoinGecko market cap/trending. All via DataHub."
4
+ metadata: { "openclaw": { "emoji": "🪙", "requires": { "extensions": ["findoo-datahub-plugin"] } } }
5
+ ---
6
+
7
+ # Crypto & DeFi
8
+
9
+ Use the **fin_crypto** tool for cryptocurrency and DeFi analysis via DataHub (works out of the box). For simple OHLCV data, use **fin_data_ohlcv** instead.
10
+
11
+ ## When to Use
12
+
13
+ - "BTC最新价格" / "Bitcoin ticker"
14
+ - "ETH永续资金费率" / "funding rate"
15
+ - "DeFi TVL排行" / "DeFi protocol ranking"
16
+ - "Aave收益率" / "DeFi yield opportunities"
17
+ - "USDT发行量" / "stablecoin market cap"
18
+ - "币圈热搜" / "trending coins"
19
+
20
+ ## CEX Market Data
21
+
22
+ | endpoint | Description | Example |
23
+ | --------------------- | ---------------------- | --------------------------------------------------------------- |
24
+ | `market/ticker` | Single ticker snapshot | `fin_crypto(endpoint="market/ticker", symbol="BTC/USDT")` |
25
+ | `market/tickers` | All tickers | `fin_crypto(endpoint="market/tickers")` |
26
+ | `market/orderbook` | Order book depth | `fin_crypto(endpoint="market/orderbook", symbol="BTC/USDT")` |
27
+ | `market/trades` | Recent trades | `fin_crypto(endpoint="market/trades", symbol="BTC/USDT")` |
28
+ | `market/funding_rate` | Perpetual funding rate | `fin_crypto(endpoint="market/funding_rate", symbol="BTC/USDT")` |
29
+
30
+ ## CoinGecko Market Intelligence
31
+
32
+ | endpoint | Description | Example |
33
+ | ------------------- | ---------------------- | ---------------------------------------------------------- |
34
+ | `coin/market` | Market cap ranking | `fin_crypto(endpoint="coin/market", limit=20)` |
35
+ | `coin/historical` | Coin historical data | `fin_crypto(endpoint="coin/historical", symbol="bitcoin")` |
36
+ | `coin/info` | Coin detail info | `fin_crypto(endpoint="coin/info", symbol="ethereum")` |
37
+ | `coin/categories` | Category rankings | `fin_crypto(endpoint="coin/categories")` |
38
+ | `coin/trending` | Trending / hot coins | `fin_crypto(endpoint="coin/trending")` |
39
+ | `coin/global_stats` | Global market overview | `fin_crypto(endpoint="coin/global_stats")` |
40
+
41
+ ## DeFi Protocol Data (DefiLlama)
42
+
43
+ | endpoint | Description | Example |
44
+ | --------------------- | --------------------------- | ------------------------------------------------------------ |
45
+ | `defi/protocols` | Protocol TVL ranking | `fin_crypto(endpoint="defi/protocols", limit=20)` |
46
+ | `defi/tvl_historical` | Full TVL history | `fin_crypto(endpoint="defi/tvl_historical")` |
47
+ | `defi/protocol_tvl` | Single protocol TVL history | `fin_crypto(endpoint="defi/protocol_tvl", symbol="aave")` |
48
+ | `defi/chains` | Blockchain TVL comparison | `fin_crypto(endpoint="defi/chains")` |
49
+ | `defi/yields` | Yield farming opportunities | `fin_crypto(endpoint="defi/yields")` |
50
+ | `defi/stablecoins` | Stablecoin market data | `fin_crypto(endpoint="defi/stablecoins")` |
51
+ | `defi/fees` | Protocol fees/revenue | `fin_crypto(endpoint="defi/fees")` |
52
+ | `defi/dex_volumes` | DEX trading volumes | `fin_crypto(endpoint="defi/dex_volumes")` |
53
+ | `defi/coin_prices` | DeFi token prices | `fin_crypto(endpoint="defi/coin_prices", symbol="ethereum")` |
54
+
55
+ ## Simple OHLCV (via CCXT)
56
+
57
+ Use **fin_data_ohlcv** for simple crypto candlestick data via CCXT:
58
+
59
+ ```
60
+ fin_data_ohlcv(symbol="BTC/USDT", market="crypto", timeframe="1d")
61
+ ```
62
+
63
+ ## Market Overview Pattern
64
+
65
+ 1. `fin_crypto(coin/global_stats)` — total market cap, BTC dominance
66
+ 2. `fin_crypto(coin/market)` — top coins by market cap
67
+ 3. `fin_crypto(coin/trending)` — what's hot
68
+ 4. `fin_crypto(defi/protocols)` — DeFi TVL leaders
69
+ 5. `fin_crypto(defi/chains)` — which chains are growing
@@ -0,0 +1,56 @@
1
+ ---
2
+ name: fin-data-query
3
+ description: "Generic DataHub query — directly call any of 172 financial data endpoints by path. Fallback when specialized tools don't cover the data need."
4
+ metadata: { "openclaw": { "emoji": "🔍", "requires": { "extensions": ["findoo-datahub-plugin"] } } }
5
+ ---
6
+
7
+ # Data Query (Fallback)
8
+
9
+ Use the **fin_query** tool as a generic fallback to access any of the 172 DataHub endpoints directly (works out of the box).
10
+
11
+ ## When to Use
12
+
13
+ - When specialized tools (fin_stock, fin_index, fin_macro, fin_derivatives, fin_crypto, fin_market) don't cover the specific data need
14
+ - When querying less common endpoints
15
+
16
+ ## DataHub Categories
17
+
18
+ | Category | Endpoints | Coverage |
19
+ | --------------- | --------- | ------------------------------------------------------------- |
20
+ | `equity/*` | 83 | A-share, HK, US — prices, fundamentals, ownership, money flow |
21
+ | `crypto/*` | 23 | CEX market data, CoinGecko, DeFi via DefiLlama |
22
+ | `economy/*` | 21 | China macro, rates, FX, World Bank |
23
+ | `derivatives/*` | 13 | Futures, options, convertible bonds |
24
+ | `index/*` | 12 | Index data, thematic indices |
25
+ | `etf/*` | 9 | ETF prices, NAV, fund data |
26
+ | `currency/*` | 3 | FX historical, search, snapshots |
27
+ | `news/*` | 1 | Company news |
28
+
29
+ ## Example Calls
30
+
31
+ ```
32
+ # ETF fund manager info
33
+ fin_query(path="etf/fund/manager", params={"symbol": "110011"})
34
+
35
+ # Currency historical
36
+ fin_query(path="currency/price/historical", params={"symbol": "USDCNH"})
37
+
38
+ # Company news
39
+ fin_query(path="news/company", params={"symbol": "AAPL"})
40
+
41
+ # Coverage metadata — see all available endpoints
42
+ fin_query(path="coverage/providers")
43
+ fin_query(path="coverage/commands")
44
+ ```
45
+
46
+ ## When to Prefer Specialized Tools
47
+
48
+ | Data Need | Use Instead |
49
+ | --------------------------- | ----------------- |
50
+ | Stock quote / financials | `fin_stock` |
51
+ | Index / ETF / Fund | `fin_index` |
52
+ | GDP / CPI / interest rates | `fin_macro` |
53
+ | Futures / options / CB | `fin_derivatives` |
54
+ | Crypto / DeFi | `fin_crypto` |
55
+ | Dragon-tiger / market radar | `fin_market` |
56
+ | Simple OHLCV (CCXT/Yahoo) | `fin_data_ohlcv` |
@@ -0,0 +1,53 @@
1
+ ---
2
+ name: fin-derivatives
3
+ description: "Derivatives analysis — futures (holdings/settlement/warehouse/term structure), options (chains/Greeks/IV), convertible bonds. All via DataHub."
4
+ metadata: { "openclaw": { "emoji": "📉", "requires": { "extensions": ["findoo-datahub-plugin"] } } }
5
+ ---
6
+
7
+ # Derivatives Analysis
8
+
9
+ Use the **fin_derivatives** tool for futures, options, and convertible bond analysis via DataHub (works out of the box).
10
+
11
+ ## When to Use
12
+
13
+ - "螺纹钢期货持仓" / "rebar futures holding"
14
+ - "IF2501 结算价" / "futures settlement"
15
+ - "铜仓单变化" / "warehouse receipts"
16
+ - "AAPL期权链" / "option chains with Greeks"
17
+ - "可转债转股溢价率" / "CB conversion premium"
18
+
19
+ ## Available Endpoints
20
+
21
+ ### Futures
22
+
23
+ | endpoint | Description | Example |
24
+ | -------------------- | ------------------------ | ------------------------------------------------------------------------------------------- |
25
+ | `futures/historical` | Futures historical OHLCV | `fin_derivatives(symbol="RB2501.SHF", endpoint="futures/historical")` |
26
+ | `futures/info` | Contract specification | `fin_derivatives(symbol="RB2501.SHF", endpoint="futures/info")` |
27
+ | `futures/holding` | Position ranking | `fin_derivatives(symbol="RB2501.SHF", endpoint="futures/holding", trade_date="2025-02-28")` |
28
+ | `futures/settle` | Daily settlement | `fin_derivatives(symbol="RB2501.SHF", endpoint="futures/settle")` |
29
+ | `futures/warehouse` | Warehouse receipts | `fin_derivatives(symbol="RB.SHF", endpoint="futures/warehouse")` |
30
+ | `futures/mapping` | Active contract mapping | `fin_derivatives(symbol="RB.SHF", endpoint="futures/mapping")` |
31
+
32
+ ### Options
33
+
34
+ | endpoint | Description | Example |
35
+ | ---------------- | ------------------------- | ----------------------------------------------------------------- |
36
+ | `options/basic` | Option contract list | `fin_derivatives(symbol="510050.SH", endpoint="options/basic")` |
37
+ | `options/daily` | Option daily prices | `fin_derivatives(symbol="10004537.SH", endpoint="options/daily")` |
38
+ | `options/chains` | Option chains with Greeks | `fin_derivatives(symbol="AAPL", endpoint="options/chains")` |
39
+
40
+ ### Convertible Bonds
41
+
42
+ | endpoint | Description | Example |
43
+ | ------------------- | --------------- | ------------------------------------------------------------------- |
44
+ | `convertible/basic` | CB basic info | `fin_derivatives(symbol="113xxx.SH", endpoint="convertible/basic")` |
45
+ | `convertible/daily` | CB daily prices | `fin_derivatives(symbol="113xxx.SH", endpoint="convertible/daily")` |
46
+
47
+ ## Futures Analysis Pattern
48
+
49
+ 1. `fin_derivatives(futures/info)` — contract specification
50
+ 2. `fin_derivatives(futures/historical)` — price trend
51
+ 3. `fin_derivatives(futures/holding)` — major institution positions
52
+ 4. `fin_derivatives(futures/settle)` — settlement and open interest
53
+ 5. `fin_derivatives(futures/warehouse)` — warehouse receipts (supply signal)
@@ -0,0 +1,64 @@
1
+ ---
2
+ name: fin-equity
3
+ description: "Equity research — A-share, HK, US stock analysis, financials, money flow, holders, dividends, index/ETF/fund, Stock Connect flows. All via DataHub."
4
+ metadata: { "openclaw": { "emoji": "📊", "requires": { "extensions": ["findoo-datahub-plugin"] } } }
5
+ ---
6
+
7
+ # Equity Research
8
+
9
+ Use **fin_stock**, **fin_index**, and **fin_market** tools for equity analysis across A-share, HK, and US markets. All data routes through DataHub (works out of the box).
10
+
11
+ ## When to Use
12
+
13
+ - "茅台行情" / "贵州茅台最新股价" / "AAPL earnings"
14
+ - "腾讯港股今天行情" / "00700.HK daily"
15
+ - "沪深300成分股" / "CSI 300 constituents"
16
+ - "50ETF净值" / "ETF NAV"
17
+ - "北向资金" / "Stock Connect flows"
18
+
19
+ ## Stock Data (fin_stock)
20
+
21
+ | endpoint | Description | Example |
22
+ | ------------------------- | --------------------- | ------------------------------------------------------------------- |
23
+ | `price/historical` | Historical OHLCV | `fin_stock(symbol="600519.SH", endpoint="price/historical")` |
24
+ | `fundamental/income` | Income statement | `fin_stock(symbol="600519.SH", endpoint="fundamental/income")` |
25
+ | `fundamental/balance` | Balance sheet | `fin_stock(symbol="600519.SH", endpoint="fundamental/balance")` |
26
+ | `fundamental/cash` | Cash flow statement | `fin_stock(symbol="AAPL", endpoint="fundamental/cash")` |
27
+ | `fundamental/ratios` | Financial ratios | `fin_stock(symbol="00700.HK", endpoint="fundamental/ratios")` |
28
+ | `fundamental/dividends` | Dividend history | `fin_stock(symbol="600519.SH", endpoint="fundamental/dividends")` |
29
+ | `ownership/top10_holders` | Top 10 shareholders | `fin_stock(symbol="600519.SH", endpoint="ownership/top10_holders")` |
30
+ | `moneyflow/individual` | Capital flow tracking | `fin_stock(symbol="600519.SH", endpoint="moneyflow/individual")` |
31
+ | `discovery/gainers` | Top gainers | `fin_stock(endpoint="discovery/gainers")` |
32
+
33
+ ## Index / ETF / Fund (fin_index)
34
+
35
+ | endpoint | Description | Example |
36
+ | -------------------- | ------------------------ | -------------------------------------------------------------- |
37
+ | `price/historical` | Index daily data | `fin_index(symbol="000300.SH", endpoint="price/historical")` |
38
+ | `constituents` | Index constituent stocks | `fin_index(symbol="000300.SH", endpoint="constituents")` |
39
+ | `daily_basic` | Index PE/PB valuation | `fin_index(symbol="000300.SH", endpoint="daily_basic")` |
40
+ | `thematic/ths_index` | THS concept index list | `fin_index(endpoint="thematic/ths_index")` |
41
+ | `thematic/ths_daily` | THS concept daily data | `fin_index(symbol="885760.TI", endpoint="thematic/ths_daily")` |
42
+
43
+ ## Cross-Border Flows (fin_market)
44
+
45
+ | endpoint | Description | Example |
46
+ | ----------------- | --------------------------------- | ----------------------------------------------------------------- |
47
+ | `flow/hsgt_flow` | Northbound/Southbound daily flows | `fin_market(endpoint="flow/hsgt_flow", start_date="2025-02-01")` |
48
+ | `flow/hsgt_top10` | Top 10 HSGT holdings | `fin_market(endpoint="flow/hsgt_top10", trade_date="2025-02-28")` |
49
+
50
+ ## Symbol Format
51
+
52
+ - A-shares: `600519.SH` (Shanghai), `000001.SZ` (Shenzhen)
53
+ - HK stocks: `00700.HK`
54
+ - US stocks: `AAPL`
55
+ - Index: `000300.SH`, ETF: `510050.SH`
56
+
57
+ ## Deep Analysis Pattern
58
+
59
+ 1. `fin_stock(price/historical)` — price trend
60
+ 2. `fin_stock(fundamental/income)` — profitability
61
+ 3. `fin_stock(fundamental/cash)` — cash quality
62
+ 4. `fin_stock(moneyflow/individual)` — institutional flow
63
+ 5. `fin_stock(ownership/top10_holders)` — ownership changes
64
+ 6. `fin_market(flow/hsgt_flow)` — cross-border capital
@@ -0,0 +1,60 @@
1
+ ---
2
+ name: fin-macro
3
+ description: "Macro economics and interest rates — China GDP/CPI/PPI/PMI/M2, global rates (Shibor/LPR/Libor/Treasury), World Bank data, FX rates. All via DataHub."
4
+ metadata: { "openclaw": { "emoji": "🏛️", "requires": { "extensions": ["findoo-datahub-plugin"] } } }
5
+ ---
6
+
7
+ # Macro & Rates
8
+
9
+ Use the **fin_macro** tool for macroeconomic indicators and interest rate data via DataHub (works out of the box).
10
+
11
+ ## When to Use
12
+
13
+ - "中国最新GDP" / "China GDP growth"
14
+ - "CPI数据" / "latest CPI"
15
+ - "Shibor利率" / "interbank rate"
16
+ - "LPR是多少" / "loan prime rate"
17
+ - "美国国债收益率" / "US Treasury yield"
18
+ - "世界银行GDP对比" / "World Bank comparison"
19
+
20
+ ## Available Endpoints
21
+
22
+ ### China Macro
23
+
24
+ | endpoint | Description | Example |
25
+ | ------------------ | --------------------- | ---------------------------------------- |
26
+ | `gdp/real` | China GDP | `fin_macro(endpoint="gdp/real")` |
27
+ | `cpi` | Consumer Price Index | `fin_macro(endpoint="cpi")` |
28
+ | `ppi` | Producer Price Index | `fin_macro(endpoint="ppi")` |
29
+ | `pmi` | Purchasing Managers | `fin_macro(endpoint="pmi")` |
30
+ | `money_supply` | Money supply M0/M1/M2 | `fin_macro(endpoint="money_supply")` |
31
+ | `social_financing` | Social financing | `fin_macro(endpoint="social_financing")` |
32
+
33
+ ### Interest Rates
34
+
35
+ | endpoint | Description | Example |
36
+ | ------------- | ------------------------ | ----------------------------------- |
37
+ | `shibor` | Shanghai Interbank Rate | `fin_macro(endpoint="shibor")` |
38
+ | `shibor_lpr` | Loan Prime Rate | `fin_macro(endpoint="shibor_lpr")` |
39
+ | `libor` | London Interbank Rate | `fin_macro(endpoint="libor")` |
40
+ | `hibor` | Hong Kong Interbank Rate | `fin_macro(endpoint="hibor")` |
41
+ | `treasury_cn` | China treasury yields | `fin_macro(endpoint="treasury_cn")` |
42
+ | `treasury_us` | US treasury yields | `fin_macro(endpoint="treasury_us")` |
43
+
44
+ ### Global (World Bank)
45
+
46
+ | endpoint | Description | Example |
47
+ | ---------------------- | --------------------- | ---------------------------------------------------------- |
48
+ | `worldbank/gdp` | World Bank GDP | `fin_macro(endpoint="worldbank/gdp", country="CN")` |
49
+ | `worldbank/population` | World Bank population | `fin_macro(endpoint="worldbank/population", country="US")` |
50
+ | `worldbank/inflation` | World Bank inflation | `fin_macro(endpoint="worldbank/inflation", country="CN")` |
51
+ | `worldbank/indicator` | Custom WB indicator | `fin_macro(endpoint="worldbank/indicator", country="CN")` |
52
+
53
+ ## Macro Cycle Analysis Pattern
54
+
55
+ 1. `fin_macro(gdp/real)` — growth trend
56
+ 2. `fin_macro(cpi)` — inflation
57
+ 3. `fin_macro(pmi)` — manufacturing activity
58
+ 4. `fin_macro(shibor)` — liquidity conditions
59
+ 5. `fin_macro(shibor_lpr)` — policy rate direction
60
+ 6. `fin_macro(treasury_cn)` — bond market signal
@@ -0,0 +1,47 @@
1
+ ---
2
+ name: fin-market-radar
3
+ description: "Market monitoring — dragon-tiger list, limit-up/down stats, block trades, sector money flow, margin trading, global index snapshots, IPO calendar. All via DataHub."
4
+ metadata: { "openclaw": { "emoji": "📡", "requires": { "extensions": ["findoo-datahub-plugin"] } } }
5
+ ---
6
+
7
+ # Market Radar
8
+
9
+ Use the **fin_market** tool for market-wide monitoring and anomaly detection via DataHub (works out of the box).
10
+
11
+ ## When to Use
12
+
13
+ - "今天龙虎榜" / "dragon-tiger list"
14
+ - "涨停板有哪些" / "limit up stocks"
15
+ - "大宗交易" / "block trades today"
16
+ - "板块资金流向" / "sector money flow"
17
+ - "融资融券余额" / "margin balance"
18
+ - "北向资金" / "northbound flow"
19
+ - "全球指数" / "global index snapshot"
20
+
21
+ ## Available Endpoints
22
+
23
+ | endpoint | Description | Key Params |
24
+ | ----------------------- | ------------------------------------ | ------------------------- |
25
+ | `market/top_list` | Dragon-tiger list (top movers) | `trade_date="2025-02-28"` |
26
+ | `market/top_inst` | Institutional trades on dragon-tiger | `trade_date` |
27
+ | `market/limit_list` | Limit-up/down stocks | `trade_date` |
28
+ | `market/suspend` | Trading suspensions | `trade_date` |
29
+ | `market/trade_calendar` | Exchange calendar | — |
30
+ | `moneyflow/individual` | Per-stock capital flow | `symbol` |
31
+ | `moneyflow/industry` | Sector capital flow | `trade_date` |
32
+ | `moneyflow/block_trade` | Block trade records | `trade_date` |
33
+ | `margin/summary` | Market margin summary | `trade_date` |
34
+ | `margin/detail` | Per-stock margin detail | `symbol` |
35
+ | `flow/hsgt_flow` | Northbound/Southbound flows | `start_date`, `end_date` |
36
+ | `flow/hsgt_top10` | Top HSGT holdings | `trade_date` |
37
+ | `discovery/gainers` | Top gainers | — |
38
+ | `discovery/losers` | Top losers | — |
39
+ | `discovery/active` | Most active | — |
40
+ | `discovery/new_share` | IPO calendar | — |
41
+
42
+ ## Post-market Review Pattern
43
+
44
+ 1. `fin_market(market/top_list)` — who made the dragon-tiger list
45
+ 2. `fin_market(market/limit_list)` — limit-up/down count
46
+ 3. `fin_market(margin/summary)` — margin trading changes
47
+ 4. `fin_market(flow/hsgt_flow)` — northbound capital trend
@@ -0,0 +1,103 @@
1
+ import type { OHLCVCache } from "../ohlcv-cache.js";
2
+ import type { OHLCV, Ticker } from "../types.js";
3
+
4
+ export type CcxtExchange = {
5
+ fetchTicker: (symbol: string) => Promise<Record<string, unknown>>;
6
+ fetchOHLCV: (
7
+ symbol: string,
8
+ timeframe: string,
9
+ since?: number,
10
+ limit?: number,
11
+ ) => Promise<Array<[number, number, number, number, number, number]>>;
12
+ };
13
+
14
+ export interface CryptoAdapter {
15
+ getOHLCV(params: {
16
+ symbol: string;
17
+ timeframe: string;
18
+ since?: number;
19
+ limit?: number;
20
+ exchangeId?: string;
21
+ }): Promise<OHLCV[]>;
22
+ getTicker(symbol: string, exchangeId?: string): Promise<Ticker>;
23
+ }
24
+
25
+ export function createCryptoAdapter(
26
+ cache: OHLCVCache,
27
+ getExchangeInstance: (id?: string) => Promise<CcxtExchange>,
28
+ defaultExchangeId?: string,
29
+ ): CryptoAdapter {
30
+ async function resolveExchange(exchangeId?: string): Promise<CcxtExchange> {
31
+ return getExchangeInstance(exchangeId ?? defaultExchangeId);
32
+ }
33
+
34
+ function ccxtToOHLCV(raw: [number, number, number, number, number, number]): OHLCV {
35
+ return {
36
+ timestamp: raw[0],
37
+ open: raw[1],
38
+ high: raw[2],
39
+ low: raw[3],
40
+ close: raw[4],
41
+ volume: raw[5],
42
+ };
43
+ }
44
+
45
+ return {
46
+ async getOHLCV(params) {
47
+ const { symbol, timeframe, since, limit, exchangeId } = params;
48
+ const market = "crypto";
49
+
50
+ // Check cache for existing data
51
+ const range = cache.getRange(symbol, market, timeframe);
52
+
53
+ if (range) {
54
+ // If since + limit are specified and we have enough data in cache, return cached
55
+ if (since != null && limit != null) {
56
+ const cached = cache.query(symbol, market, timeframe, since);
57
+ if (cached.length >= limit) {
58
+ return cached.slice(0, limit);
59
+ }
60
+ }
61
+
62
+ // Fetch data after the latest cached timestamp
63
+ const exchange = await resolveExchange(exchangeId);
64
+ const fetchSince = range.latest + 1;
65
+ const raw = await exchange.fetchOHLCV(symbol, timeframe, fetchSince, limit);
66
+ if (raw.length > 0) {
67
+ const newRows = raw.map(ccxtToOHLCV);
68
+ cache.upsertBatch(symbol, market, timeframe, newRows);
69
+ }
70
+
71
+ // Return all cached data (including newly stored)
72
+ return cache.query(symbol, market, timeframe, since);
73
+ }
74
+
75
+ // Full cache miss — fetch from exchange
76
+ const exchange = await resolveExchange(exchangeId);
77
+ const raw = await exchange.fetchOHLCV(symbol, timeframe, since, limit);
78
+ const rows = raw.map(ccxtToOHLCV);
79
+
80
+ if (rows.length > 0) {
81
+ cache.upsertBatch(symbol, market, timeframe, rows);
82
+ }
83
+
84
+ return rows;
85
+ },
86
+
87
+ async getTicker(symbol, exchangeId) {
88
+ const exchange = await resolveExchange(exchangeId);
89
+ const raw = await exchange.fetchTicker(symbol);
90
+
91
+ return {
92
+ symbol,
93
+ market: "crypto",
94
+ last: raw.last as number,
95
+ bid: raw.bid as number | undefined,
96
+ ask: raw.ask as number | undefined,
97
+ volume24h: raw.quoteVolume as number | undefined,
98
+ changePct24h: raw.percentage as number | undefined,
99
+ timestamp: (raw.timestamp as number) ?? Date.now(),
100
+ };
101
+ },
102
+ };
103
+ }
@@ -0,0 +1,11 @@
1
+ import type { OHLCV, Ticker } from "../types.js";
2
+
3
+ export interface EquityAdapter {
4
+ getOHLCV(params: {
5
+ symbol: string;
6
+ timeframe: string;
7
+ since?: number;
8
+ limit?: number;
9
+ }): Promise<OHLCV[]>;
10
+ getTicker(symbol: string): Promise<Ticker>;
11
+ }
@@ -0,0 +1,110 @@
1
+ import type { OHLCVCache } from "../ohlcv-cache.js";
2
+ import type { OHLCV, Ticker } from "../types.js";
3
+ import type { EquityAdapter } from "./equity-adapter.js";
4
+
5
+ /**
6
+ * Duck-typed client interface matching yahoo-finance2's API surface.
7
+ * Avoids hard dependency on the yahoo-finance2 package at type level.
8
+ */
9
+ export type YahooFinanceClient = {
10
+ chart: (
11
+ symbol: string,
12
+ options: { period1: string | number; period2?: string | number; interval?: string },
13
+ ) => Promise<{ quotes: Array<Record<string, unknown>> }>;
14
+ quote: (symbol: string) => Promise<Record<string, unknown>>;
15
+ };
16
+
17
+ /** Map our canonical timeframes to Yahoo Finance interval strings. */
18
+ const TIMEFRAME_MAP: Record<string, string> = {
19
+ "1m": "1m",
20
+ "5m": "5m",
21
+ "15m": "15m",
22
+ "1h": "60m",
23
+ "4h": "60m", // Yahoo has no 4h; use 1h as fallback
24
+ "1d": "1d",
25
+ "1W": "1wk",
26
+ "1M": "1mo",
27
+ };
28
+
29
+ /** Default lookback in ms (~1 year). */
30
+ const DEFAULT_LOOKBACK_MS = 365 * 24 * 60 * 60 * 1000;
31
+
32
+ /** Convert a Yahoo chart quote row to canonical OHLCV, or null if data is missing. */
33
+ function rowToOHLCV(row: Record<string, unknown>): OHLCV | null {
34
+ const date = row.date as Date | undefined;
35
+ if (!date) return null;
36
+
37
+ const open = row.open as number | null | undefined;
38
+ const high = row.high as number | null | undefined;
39
+ const low = row.low as number | null | undefined;
40
+ const close = row.close as number | null | undefined;
41
+ const volume = row.volume as number | null | undefined;
42
+
43
+ // Yahoo returns null values for non-trading days
44
+ if (open == null || high == null || low == null || close == null) return null;
45
+
46
+ return {
47
+ timestamp: date.getTime(),
48
+ open,
49
+ high,
50
+ low,
51
+ close,
52
+ volume: volume ?? 0,
53
+ };
54
+ }
55
+
56
+ export function createYahooAdapter(cache: OHLCVCache, client: YahooFinanceClient): EquityAdapter {
57
+ return {
58
+ async getOHLCV(params) {
59
+ const { symbol, timeframe, since, limit } = params;
60
+ const market = "equity";
61
+ const interval = TIMEFRAME_MAP[timeframe] ?? "1d";
62
+
63
+ // Check cache first
64
+ const range = cache.getRange(symbol, market, timeframe);
65
+ if (range) {
66
+ if (since != null && limit != null) {
67
+ const cached = cache.query(symbol, market, timeframe, since);
68
+ if (cached.length >= limit) {
69
+ return cached.slice(0, limit);
70
+ }
71
+ }
72
+ }
73
+
74
+ // Determine fetch window
75
+ const period1 = since ?? Date.now() - DEFAULT_LOOKBACK_MS;
76
+ const result = await client.chart(symbol, { period1, interval });
77
+
78
+ const rows = result.quotes
79
+ .map(rowToOHLCV)
80
+ .filter((r): r is OHLCV => r !== null)
81
+ .sort((a, b) => a.timestamp - b.timestamp);
82
+
83
+ if (rows.length > 0) {
84
+ cache.upsertBatch(symbol, market, timeframe, rows);
85
+ }
86
+
87
+ // Return from cache for consistency (merges with any existing data)
88
+ if (range || rows.length > 0) {
89
+ const all = cache.query(symbol, market, timeframe, since);
90
+ return limit ? all.slice(0, limit) : all;
91
+ }
92
+ return rows;
93
+ },
94
+
95
+ async getTicker(symbol) {
96
+ const raw = await client.quote(symbol);
97
+
98
+ return {
99
+ symbol,
100
+ market: "equity" as const,
101
+ last: (raw.regularMarketPrice as number) ?? 0,
102
+ bid: raw.bid as number | undefined,
103
+ ask: raw.ask as number | undefined,
104
+ volume24h: raw.regularMarketVolume as number | undefined,
105
+ changePct24h: raw.regularMarketChangePercent as number | undefined,
106
+ timestamp: (raw.regularMarketTime as Date)?.getTime?.() ?? Date.now(),
107
+ };
108
+ },
109
+ };
110
+ }
package/src/config.ts ADDED
@@ -0,0 +1,51 @@
1
+ import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
+
3
+ export type PluginConfig = {
4
+ datahubApiUrl: string;
5
+ datahubUsername: string;
6
+ datahubPassword: string;
7
+ requestTimeoutMs: number;
8
+ };
9
+
10
+ function readEnv(keys: string[]): string | undefined {
11
+ for (const key of keys) {
12
+ const value = process.env[key]?.trim();
13
+ if (value) return value;
14
+ }
15
+ return undefined;
16
+ }
17
+
18
+ // Public DataHub defaults — works out of the box
19
+ const DEFAULT_DATAHUB_URL = "http://43.134.61.136:8088";
20
+ const DEFAULT_DATAHUB_USERNAME = "admin";
21
+ const DEFAULT_DATAHUB_PASSWORD = "98ffa5c5-1ec6-4735-8e0c-715a5eca1a8d";
22
+
23
+ export function resolveConfig(api: OpenClawPluginApi): PluginConfig {
24
+ const raw = api.pluginConfig as Record<string, unknown> | undefined;
25
+
26
+ const datahubApiUrl =
27
+ (typeof raw?.datahubApiUrl === "string" ? raw.datahubApiUrl : undefined) ??
28
+ readEnv(["DATAHUB_API_URL", "OPENFINCLAW_DATAHUB_API_URL"]) ??
29
+ DEFAULT_DATAHUB_URL;
30
+
31
+ const datahubUsername =
32
+ (typeof raw?.datahubUsername === "string" ? raw.datahubUsername : undefined) ??
33
+ readEnv(["DATAHUB_USERNAME", "OPENFINCLAW_DATAHUB_USERNAME"]) ??
34
+ DEFAULT_DATAHUB_USERNAME;
35
+
36
+ const datahubPassword =
37
+ (typeof raw?.datahubPassword === "string" ? raw.datahubPassword : undefined) ??
38
+ (typeof raw?.datahubApiKey === "string" ? raw.datahubApiKey : undefined) ??
39
+ readEnv(["DATAHUB_PASSWORD", "OPENFINCLAW_DATAHUB_PASSWORD", "DATAHUB_API_KEY"]) ??
40
+ DEFAULT_DATAHUB_PASSWORD;
41
+
42
+ const timeoutRaw = raw?.requestTimeoutMs ?? readEnv(["OPENFINCLAW_DATAHUB_TIMEOUT_MS"]);
43
+ const timeout = Number(timeoutRaw);
44
+
45
+ return {
46
+ datahubApiUrl: datahubApiUrl.replace(/\/+$/, ""),
47
+ datahubUsername,
48
+ datahubPassword,
49
+ requestTimeoutMs: Number.isFinite(timeout) && timeout >= 1000 ? Math.floor(timeout) : 30_000,
50
+ };
51
+ }