@dshtrading/kit-crypto 0.1.0
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/LICENSE +75 -0
- package/assets/skills/crypto-instrument-analysis.md +68 -0
- package/assets/skills/crypto-risk-checklist.md +39 -0
- package/assets/skills/indicator-authoring.md +218 -0
- package/assets/skills/knowledge-curation.md +98 -0
- package/assets/skills/trading-notes-setup.md +91 -0
- package/assets/skills/trading-strategy-paradigms.md +65 -0
- package/lib/api/lib/index.d.ts +720 -0
- package/lib/derivatives.d.ts +18 -0
- package/lib/derivatives.js +110 -0
- package/lib/fundamentals.d.ts +19 -0
- package/lib/fundamentals.js +154 -0
- package/lib/index.d.ts +27 -0
- package/lib/index.js +323 -0
- package/lib/news.d.ts +50 -0
- package/lib/news.js +196 -0
- package/package.json +39 -0
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { DerivativesData } from "./api/lib/index.js";
|
|
2
|
+
//#region src/derivatives.d.ts
|
|
3
|
+
interface DerivativesFetchOptions {
|
|
4
|
+
symbol: string;
|
|
5
|
+
fetch?: typeof globalThis.fetch;
|
|
6
|
+
}
|
|
7
|
+
interface DerivativesResult {
|
|
8
|
+
data?: DerivativesData;
|
|
9
|
+
unavailable?: string[];
|
|
10
|
+
}
|
|
11
|
+
/** 将输入 symbol 规范化为 Binance USDT-M 合约 symbol(如 BTCUSDT)。 */
|
|
12
|
+
declare function normalizeBinanceFuturesSymbol(raw: string): string;
|
|
13
|
+
/** 提取 base 资产名称(如 BTCUSDT → BTC)。 */
|
|
14
|
+
declare function extractBaseAsset(symbol: string): string;
|
|
15
|
+
declare function fetchCryptoDerivatives(options: DerivativesFetchOptions): Promise<DerivativesResult>;
|
|
16
|
+
declare function renderDerivativesData(result: DerivativesResult, requestedSymbol: string): string;
|
|
17
|
+
//#endregion
|
|
18
|
+
export { DerivativesFetchOptions, DerivativesResult, extractBaseAsset, fetchCryptoDerivatives, normalizeBinanceFuturesSymbol, renderDerivativesData };
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
//#region src/derivatives.ts
|
|
2
|
+
const BINANCE_FAPI_BASE = "https://fapi.binance.com";
|
|
3
|
+
const BINANCE_FUTURES_DATA_BASE = "https://fapi.binance.com/futures/data";
|
|
4
|
+
/** 将输入 symbol 规范化为 Binance USDT-M 合约 symbol(如 BTCUSDT)。 */
|
|
5
|
+
function normalizeBinanceFuturesSymbol(raw) {
|
|
6
|
+
const clean = raw.trim().toUpperCase().replace(/[-_]/g, "");
|
|
7
|
+
if (clean.endsWith("SWAP")) return clean.slice(0, -4);
|
|
8
|
+
return clean;
|
|
9
|
+
}
|
|
10
|
+
/** 提取 base 资产名称(如 BTCUSDT → BTC)。 */
|
|
11
|
+
function extractBaseAsset(symbol) {
|
|
12
|
+
const norm = normalizeBinanceFuturesSymbol(symbol);
|
|
13
|
+
for (const q of [
|
|
14
|
+
"USDT",
|
|
15
|
+
"USDC",
|
|
16
|
+
"BUSD",
|
|
17
|
+
"USD"
|
|
18
|
+
]) if (norm.endsWith(q) && norm.length > q.length) return norm.slice(0, -q.length);
|
|
19
|
+
return norm;
|
|
20
|
+
}
|
|
21
|
+
async function fetchCryptoDerivatives(options) {
|
|
22
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
23
|
+
const symbol = normalizeBinanceFuturesSymbol(options.symbol);
|
|
24
|
+
const unavailable = [];
|
|
25
|
+
let openInterest;
|
|
26
|
+
let openInterestValue;
|
|
27
|
+
let longShortRatio;
|
|
28
|
+
let topTraderLongShortRatio;
|
|
29
|
+
let takerBuySellRatio;
|
|
30
|
+
let fundingRate;
|
|
31
|
+
let timestamp = Date.now();
|
|
32
|
+
try {
|
|
33
|
+
const res = await fetchImpl(`${BINANCE_FAPI_BASE}/fapi/v1/openInterest?symbol=${encodeURIComponent(symbol)}`, { headers: { accept: "application/json" } });
|
|
34
|
+
if (res.ok) {
|
|
35
|
+
const json = await res.json();
|
|
36
|
+
if (json.openInterest) openInterest = Number(json.openInterest);
|
|
37
|
+
if (json.time) timestamp = json.time;
|
|
38
|
+
} else unavailable.push(`binance-oi: HTTP ${res.status}`);
|
|
39
|
+
} catch (err) {
|
|
40
|
+
unavailable.push(`binance-oi: ${err instanceof Error ? err.message : String(err)}`);
|
|
41
|
+
}
|
|
42
|
+
try {
|
|
43
|
+
const res = await fetchImpl(`${BINANCE_FUTURES_DATA_BASE}/globalLongShortAccountRatio?symbol=${encodeURIComponent(symbol)}&period=1h&limit=1`, { headers: { accept: "application/json" } });
|
|
44
|
+
if (res.ok) {
|
|
45
|
+
const arr = await res.json();
|
|
46
|
+
if (Array.isArray(arr) && arr.length > 0 && arr[0]?.longShortRatio) longShortRatio = Number(arr[0].longShortRatio);
|
|
47
|
+
} else unavailable.push(`binance-global-ls: HTTP ${res.status}`);
|
|
48
|
+
} catch (err) {
|
|
49
|
+
unavailable.push(`binance-global-ls: ${err instanceof Error ? err.message : String(err)}`);
|
|
50
|
+
}
|
|
51
|
+
try {
|
|
52
|
+
const res = await fetchImpl(`${BINANCE_FUTURES_DATA_BASE}/topLongShortPositionRatio?symbol=${encodeURIComponent(symbol)}&period=1h&limit=1`, { headers: { accept: "application/json" } });
|
|
53
|
+
if (res.ok) {
|
|
54
|
+
const arr = await res.json();
|
|
55
|
+
if (Array.isArray(arr) && arr.length > 0 && arr[0]?.longShortRatio) topTraderLongShortRatio = Number(arr[0].longShortRatio);
|
|
56
|
+
} else unavailable.push(`binance-top-ls: HTTP ${res.status}`);
|
|
57
|
+
} catch (err) {
|
|
58
|
+
unavailable.push(`binance-top-ls: ${err instanceof Error ? err.message : String(err)}`);
|
|
59
|
+
}
|
|
60
|
+
try {
|
|
61
|
+
const res = await fetchImpl(`${BINANCE_FUTURES_DATA_BASE}/takerlongshortRatio?symbol=${encodeURIComponent(symbol)}&period=1h&limit=1`, { headers: { accept: "application/json" } });
|
|
62
|
+
if (res.ok) {
|
|
63
|
+
const arr = await res.json();
|
|
64
|
+
if (Array.isArray(arr) && arr.length > 0 && arr[0]?.buySellRatio) takerBuySellRatio = Number(arr[0].buySellRatio);
|
|
65
|
+
} else unavailable.push(`binance-taker-vol: HTTP ${res.status}`);
|
|
66
|
+
} catch (err) {
|
|
67
|
+
unavailable.push(`binance-taker-vol: ${err instanceof Error ? err.message : String(err)}`);
|
|
68
|
+
}
|
|
69
|
+
try {
|
|
70
|
+
const res = await fetchImpl(`${BINANCE_FAPI_BASE}/fapi/v1/fundingRate?symbol=${encodeURIComponent(symbol)}&limit=1`, { headers: { accept: "application/json" } });
|
|
71
|
+
if (res.ok) {
|
|
72
|
+
const arr = await res.json();
|
|
73
|
+
if (Array.isArray(arr) && arr.length > 0 && arr[0]?.fundingRate) fundingRate = Number(arr[0].fundingRate);
|
|
74
|
+
} else unavailable.push(`binance-funding: HTTP ${res.status}`);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
unavailable.push(`binance-funding: ${err instanceof Error ? err.message : String(err)}`);
|
|
77
|
+
}
|
|
78
|
+
if (openInterest !== void 0 || longShortRatio !== void 0 || topTraderLongShortRatio !== void 0 || takerBuySellRatio !== void 0 || fundingRate !== void 0) return {
|
|
79
|
+
data: {
|
|
80
|
+
symbol: `${symbol}-SWAP`,
|
|
81
|
+
source: "binance-futures-public",
|
|
82
|
+
openInterest,
|
|
83
|
+
openInterestValue,
|
|
84
|
+
longShortRatio,
|
|
85
|
+
topTraderLongShortRatio,
|
|
86
|
+
takerBuySellRatio,
|
|
87
|
+
fundingRate,
|
|
88
|
+
timestamp
|
|
89
|
+
},
|
|
90
|
+
unavailable
|
|
91
|
+
};
|
|
92
|
+
return { unavailable };
|
|
93
|
+
}
|
|
94
|
+
function renderDerivativesData(result, requestedSymbol) {
|
|
95
|
+
const { data, unavailable = [] } = result;
|
|
96
|
+
if (!data) return `crypto_get_derivatives ${requestedSymbol}: no derivative data available.${unavailable.length > 0 ? ` (errors: ${unavailable.join("; ")})` : ""}`;
|
|
97
|
+
const lines = [`crypto_get_derivatives ${data.symbol} (source: ${data.source}, updated: ${new Date(data.timestamp).toISOString()}):`];
|
|
98
|
+
if (data.openInterest !== void 0) lines.push(`- Open Interest (OI): ${data.openInterest.toLocaleString()} base-coins`);
|
|
99
|
+
if (data.fundingRate !== void 0) {
|
|
100
|
+
const pct = (data.fundingRate * 100).toFixed(4);
|
|
101
|
+
lines.push(`- Latest Funding Rate: ${data.fundingRate} (${pct}%)`);
|
|
102
|
+
}
|
|
103
|
+
if (data.longShortRatio !== void 0) lines.push(`- Global Long/Short Account Ratio: ${data.longShortRatio.toFixed(2)} (Accounts)`);
|
|
104
|
+
if (data.topTraderLongShortRatio !== void 0) lines.push(`- Top Trader Position L/S Ratio: ${data.topTraderLongShortRatio.toFixed(2)} (Whale Positions)`);
|
|
105
|
+
if (data.takerBuySellRatio !== void 0) lines.push(`- Taker Buy/Sell Volume Ratio: ${data.takerBuySellRatio.toFixed(2)} (Market taker aggressiveness)`);
|
|
106
|
+
if (unavailable.length > 0) lines.push(` (partially unavailable sub-queries: ${unavailable.join("; ")})`);
|
|
107
|
+
return lines.join("\n");
|
|
108
|
+
}
|
|
109
|
+
//#endregion
|
|
110
|
+
export { extractBaseAsset, fetchCryptoDerivatives, normalizeBinanceFuturesSymbol, renderDerivativesData };
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import { CryptoFundamentals, FundamentalsPackage } from "./api/lib/index.js";
|
|
2
|
+
//#region src/fundamentals.d.ts
|
|
3
|
+
interface CryptoFundamentalsOptions {
|
|
4
|
+
symbol: string;
|
|
5
|
+
fetch?: typeof globalThis.fetch;
|
|
6
|
+
}
|
|
7
|
+
interface CryptoFundamentalsResult {
|
|
8
|
+
data?: CryptoFundamentals;
|
|
9
|
+
quoteVolume24h?: number;
|
|
10
|
+
priceChangePercent24h?: number;
|
|
11
|
+
tradesCount24h?: number;
|
|
12
|
+
unavailable?: string[];
|
|
13
|
+
}
|
|
14
|
+
declare function fetchCryptoFundamentals(options: CryptoFundamentalsOptions): Promise<CryptoFundamentalsResult>;
|
|
15
|
+
/** 获取完整加密资产基本面数据包。 */
|
|
16
|
+
declare function fetchCryptoFundamentalsPackage(symbol: string, fetchImpl?: typeof globalThis.fetch): Promise<FundamentalsPackage>;
|
|
17
|
+
declare function renderCryptoFundamentals(result: CryptoFundamentalsResult, requestedSymbol: string): string;
|
|
18
|
+
//#endregion
|
|
19
|
+
export { CryptoFundamentalsOptions, CryptoFundamentalsResult, fetchCryptoFundamentals, fetchCryptoFundamentalsPackage, renderCryptoFundamentals };
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
import { extractBaseAsset } from "./derivatives.js";
|
|
2
|
+
//#region src/fundamentals.ts
|
|
3
|
+
const COINCAP_API_BASE = "https://api.coincap.io/v2";
|
|
4
|
+
const BINANCE_API_BASE = "https://api.binance.com";
|
|
5
|
+
/** 上游超时(2026-09-02 整改):防公共端点挂起拖死桥请求。 */
|
|
6
|
+
const UPSTREAM_TIMEOUT_MS = 1e4;
|
|
7
|
+
const COMMON_COIN_ID_MAP = {
|
|
8
|
+
BTC: "bitcoin",
|
|
9
|
+
ETH: "ethereum",
|
|
10
|
+
SOL: "solana",
|
|
11
|
+
BNB: "binance-coin",
|
|
12
|
+
XRP: "ripple",
|
|
13
|
+
DOGE: "dogecoin",
|
|
14
|
+
ADA: "cardano",
|
|
15
|
+
AVAX: "avalanche-2",
|
|
16
|
+
DOT: "polkadot",
|
|
17
|
+
LINK: "chainlink",
|
|
18
|
+
MATIC: "polygon",
|
|
19
|
+
POL: "polygon-ecosystem-token",
|
|
20
|
+
NEAR: "near",
|
|
21
|
+
SUI: "sui",
|
|
22
|
+
APT: "aptos",
|
|
23
|
+
PEPE: "pepe",
|
|
24
|
+
SHIB: "shiba-inu",
|
|
25
|
+
UNI: "uniswap",
|
|
26
|
+
LTC: "litecoin",
|
|
27
|
+
BCH: "bitcoin-cash"
|
|
28
|
+
};
|
|
29
|
+
async function fetchCoinCapAsset(base, fetchImpl) {
|
|
30
|
+
const directId = COMMON_COIN_ID_MAP[base];
|
|
31
|
+
if (directId) try {
|
|
32
|
+
const res = await fetchImpl(`${COINCAP_API_BASE}/assets/${directId}`, {
|
|
33
|
+
headers: { accept: "application/json" },
|
|
34
|
+
signal: AbortSignal.timeout(UPSTREAM_TIMEOUT_MS)
|
|
35
|
+
});
|
|
36
|
+
if (res.ok) {
|
|
37
|
+
const json = await res.json();
|
|
38
|
+
if (json.data) return json.data;
|
|
39
|
+
}
|
|
40
|
+
} catch {}
|
|
41
|
+
const res = await fetchImpl(`${COINCAP_API_BASE}/assets?search=${encodeURIComponent(base)}&limit=5`, {
|
|
42
|
+
headers: { accept: "application/json" },
|
|
43
|
+
signal: AbortSignal.timeout(UPSTREAM_TIMEOUT_MS)
|
|
44
|
+
});
|
|
45
|
+
if (!res.ok) throw new Error(`HTTP ${res.status}`);
|
|
46
|
+
const list = (await res.json()).data ?? [];
|
|
47
|
+
return list.find((item) => item.symbol.toUpperCase() === base) ?? list[0];
|
|
48
|
+
}
|
|
49
|
+
async function fetchCryptoFundamentals(options) {
|
|
50
|
+
const fetchImpl = options.fetch ?? globalThis.fetch;
|
|
51
|
+
const rawSymbol = options.symbol.trim().toUpperCase();
|
|
52
|
+
const base = extractBaseAsset(rawSymbol);
|
|
53
|
+
const binancePair = `${base}USDT`;
|
|
54
|
+
const unavailable = [];
|
|
55
|
+
let coincapAsset;
|
|
56
|
+
try {
|
|
57
|
+
coincapAsset = await fetchCoinCapAsset(base, fetchImpl);
|
|
58
|
+
} catch (err) {
|
|
59
|
+
unavailable.push(`coincap: ${err instanceof Error ? err.message : String(err)}`);
|
|
60
|
+
}
|
|
61
|
+
let quoteVolume24h;
|
|
62
|
+
let priceChangePercent24h;
|
|
63
|
+
let tradesCount24h;
|
|
64
|
+
try {
|
|
65
|
+
const res = await fetchImpl(`${BINANCE_API_BASE}/api/v3/ticker/24hr?symbol=${encodeURIComponent(binancePair)}`, {
|
|
66
|
+
headers: { accept: "application/json" },
|
|
67
|
+
signal: AbortSignal.timeout(UPSTREAM_TIMEOUT_MS)
|
|
68
|
+
});
|
|
69
|
+
if (res.ok) {
|
|
70
|
+
const ticker = await res.json();
|
|
71
|
+
if (ticker.quoteVolume) quoteVolume24h = Number(ticker.quoteVolume);
|
|
72
|
+
if (ticker.priceChangePercent) priceChangePercent24h = Number(ticker.priceChangePercent);
|
|
73
|
+
if (ticker.count !== void 0) tradesCount24h = ticker.count;
|
|
74
|
+
} else unavailable.push(`binance-24hr: HTTP ${res.status}`);
|
|
75
|
+
} catch (err) {
|
|
76
|
+
unavailable.push(`binance-24hr: ${err instanceof Error ? err.message : String(err)}`);
|
|
77
|
+
}
|
|
78
|
+
if (coincapAsset) {
|
|
79
|
+
const rank = Number(coincapAsset.rank);
|
|
80
|
+
const marketCapUsd = Number(coincapAsset.marketCapUsd);
|
|
81
|
+
const circulatingSupply = Number(coincapAsset.supply);
|
|
82
|
+
const totalSupply = coincapAsset.maxSupply ? Number(coincapAsset.maxSupply) : void 0;
|
|
83
|
+
const volume24hUsd = Number(coincapAsset.volumeUsd24Hr);
|
|
84
|
+
const priceUsd = Number(coincapAsset.priceUsd);
|
|
85
|
+
const fdvUsd = totalSupply && Number.isFinite(priceUsd) ? totalSupply * priceUsd : void 0;
|
|
86
|
+
return {
|
|
87
|
+
data: {
|
|
88
|
+
symbol: rawSymbol,
|
|
89
|
+
name: coincapAsset.name,
|
|
90
|
+
rank: Number.isFinite(rank) ? rank : void 0,
|
|
91
|
+
marketCapUsd: Number.isFinite(marketCapUsd) ? marketCapUsd : void 0,
|
|
92
|
+
fdvUsd: fdvUsd && Number.isFinite(fdvUsd) ? fdvUsd : void 0,
|
|
93
|
+
circulatingSupply: Number.isFinite(circulatingSupply) ? circulatingSupply : void 0,
|
|
94
|
+
totalSupply: totalSupply && Number.isFinite(totalSupply) ? totalSupply : void 0,
|
|
95
|
+
volume24hUsd: Number.isFinite(volume24hUsd) ? volume24hUsd : void 0,
|
|
96
|
+
timestamp: Date.now()
|
|
97
|
+
},
|
|
98
|
+
quoteVolume24h,
|
|
99
|
+
priceChangePercent24h,
|
|
100
|
+
tradesCount24h,
|
|
101
|
+
unavailable
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
if (quoteVolume24h !== void 0) return {
|
|
105
|
+
data: {
|
|
106
|
+
symbol: rawSymbol,
|
|
107
|
+
name: base,
|
|
108
|
+
volume24hUsd: quoteVolume24h,
|
|
109
|
+
timestamp: Date.now()
|
|
110
|
+
},
|
|
111
|
+
quoteVolume24h,
|
|
112
|
+
priceChangePercent24h,
|
|
113
|
+
tradesCount24h,
|
|
114
|
+
unavailable
|
|
115
|
+
};
|
|
116
|
+
return { unavailable };
|
|
117
|
+
}
|
|
118
|
+
/** 获取完整加密资产基本面数据包。 */
|
|
119
|
+
async function fetchCryptoFundamentalsPackage(symbol, fetchImpl = globalThis.fetch) {
|
|
120
|
+
const quoteRes = await fetchCryptoFundamentals({
|
|
121
|
+
symbol,
|
|
122
|
+
fetch: fetchImpl
|
|
123
|
+
});
|
|
124
|
+
const base = extractBaseAsset(symbol);
|
|
125
|
+
return {
|
|
126
|
+
market: "crypto",
|
|
127
|
+
symbol,
|
|
128
|
+
crypto: quoteRes.data,
|
|
129
|
+
profile: {
|
|
130
|
+
symbol,
|
|
131
|
+
name: quoteRes.data?.name ?? base,
|
|
132
|
+
industry: "Blockchain / Cryptocurrency",
|
|
133
|
+
sector: "Digital Asset",
|
|
134
|
+
description: `${quoteRes.data?.name ?? base} 是全球主流加密数字资产,提供去中心化网络价值与交易结算能力。`
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
}
|
|
138
|
+
function renderCryptoFundamentals(result, requestedSymbol) {
|
|
139
|
+
const { data, quoteVolume24h, priceChangePercent24h, tradesCount24h, unavailable = [] } = result;
|
|
140
|
+
if (!data) return `crypto_get_fundamentals ${requestedSymbol}: no fundamental data available.${unavailable.length > 0 ? ` (errors: ${unavailable.join("; ")})` : ""}`;
|
|
141
|
+
const lines = [`crypto_get_fundamentals ${data.symbol}${data.name ? ` (${data.name})` : ""}:`];
|
|
142
|
+
if (data.rank !== void 0) lines.push(`- Global Market Cap Rank: #${data.rank}`);
|
|
143
|
+
if (data.marketCapUsd !== void 0) lines.push(`- Market Cap: \$${data.marketCapUsd.toLocaleString(void 0, { maximumFractionDigits: 0 })} USD`);
|
|
144
|
+
if (data.fdvUsd !== void 0) lines.push(`- Fully Diluted Valuation (FDV): \$${data.fdvUsd.toLocaleString(void 0, { maximumFractionDigits: 0 })} USD`);
|
|
145
|
+
if (data.circulatingSupply !== void 0) lines.push(`- Circulating Supply: ${data.circulatingSupply.toLocaleString()} coins`);
|
|
146
|
+
if (data.totalSupply !== void 0) lines.push(`- Total / Max Supply: ${data.totalSupply.toLocaleString()} coins`);
|
|
147
|
+
if (data.volume24hUsd !== void 0) lines.push(`- 24h Volume (Global): \$${data.volume24hUsd.toLocaleString(void 0, { maximumFractionDigits: 0 })} USD`);
|
|
148
|
+
if (priceChangePercent24h !== void 0) lines.push(`- 24h Price Change: ${priceChangePercent24h > 0 ? "+" : ""}${priceChangePercent24h.toFixed(2)}%`);
|
|
149
|
+
if (tradesCount24h !== void 0) lines.push(`- 24h Trades Count (Binance Spot): ${tradesCount24h.toLocaleString()} trades`);
|
|
150
|
+
if (unavailable.length > 0) lines.push(` (partially unavailable sources: ${unavailable.join("; ")})`);
|
|
151
|
+
return lines.join("\n");
|
|
152
|
+
}
|
|
153
|
+
//#endregion
|
|
154
|
+
export { fetchCryptoFundamentals, fetchCryptoFundamentalsPackage, renderCryptoFundamentals };
|
package/lib/index.d.ts
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { CryptoFundamentalsOptions, CryptoFundamentalsResult, fetchCryptoFundamentals, fetchCryptoFundamentalsPackage, renderCryptoFundamentals } from "./fundamentals.js";
|
|
2
|
+
import { DerivativesFetchOptions, DerivativesResult, extractBaseAsset, fetchCryptoDerivatives, normalizeBinanceFuturesSymbol, renderDerivativesData } from "./derivatives.js";
|
|
3
|
+
import { AggregateNewsOptions, AggregateNewsResult, NewsItem, NewsSource, aggregateNews, deriveSymbolTokens, parseCryptoPanic, parseRss2 } from "./news.js";
|
|
4
|
+
import Schema from "@deepseek-ai/schemastery";
|
|
5
|
+
import { SkillProvider } from "@deepseek-ai/dsh-skill";
|
|
6
|
+
import { Context } from "@deepseek-ai/cordis";
|
|
7
|
+
//#region src/index.d.ts
|
|
8
|
+
declare const provider: SkillProvider;
|
|
9
|
+
interface Config {
|
|
10
|
+
dryRun: boolean;
|
|
11
|
+
liveTrading: boolean;
|
|
12
|
+
}
|
|
13
|
+
declare const Config: Schema<Config>;
|
|
14
|
+
declare const inject: string[];
|
|
15
|
+
declare const name = "dsh-trading-crypto-kit";
|
|
16
|
+
declare function apply(ctx: Context, _config: Config): void;
|
|
17
|
+
declare function createGetDerivativesTool(options?: {
|
|
18
|
+
fetch?: typeof globalThis.fetch;
|
|
19
|
+
}): import("@deepseek-ai/dsh-tools").ToolDefinition;
|
|
20
|
+
declare function createGetFundamentalsTool(options?: {
|
|
21
|
+
fetch?: typeof globalThis.fetch;
|
|
22
|
+
}): import("@deepseek-ai/dsh-tools").ToolDefinition;
|
|
23
|
+
declare function createGetNewsTool(toolOptions?: {
|
|
24
|
+
cryptoPanicKey?: string;
|
|
25
|
+
}): import("@deepseek-ai/dsh-tools").ToolDefinition;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { AggregateNewsOptions, AggregateNewsResult, Config, CryptoFundamentalsOptions, CryptoFundamentalsResult, DerivativesFetchOptions, DerivativesResult, NewsItem, NewsSource, aggregateNews, apply, createGetDerivativesTool, createGetFundamentalsTool, createGetNewsTool, deriveSymbolTokens, extractBaseAsset, fetchCryptoDerivatives, fetchCryptoFundamentals, fetchCryptoFundamentalsPackage, inject, name, normalizeBinanceFuturesSymbol, parseCryptoPanic, parseRss2, provider, renderCryptoFundamentals, renderDerivativesData };
|
package/lib/index.js
ADDED
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
import { aggregateNews, deriveSymbolTokens, parseCryptoPanic, parseRss2 } from "./news.js";
|
|
2
|
+
import { extractBaseAsset, fetchCryptoDerivatives, normalizeBinanceFuturesSymbol, renderDerivativesData } from "./derivatives.js";
|
|
3
|
+
import { fetchCryptoFundamentals, fetchCryptoFundamentalsPackage, renderCryptoFundamentals } from "./fundamentals.js";
|
|
4
|
+
import { readFile } from "node:fs/promises";
|
|
5
|
+
import { fileURLToPath } from "node:url";
|
|
6
|
+
import Schema from "@deepseek-ai/schemastery";
|
|
7
|
+
import { BUNDLED_SKILL_RANK } from "@deepseek-ai/dsh-skill";
|
|
8
|
+
import { defineTool } from "@deepseek-ai/dsh-tools";
|
|
9
|
+
//#region src/index.ts
|
|
10
|
+
/**
|
|
11
|
+
* Crypto 工具箱插件(dsh-trading crypto 切片)。
|
|
12
|
+
*
|
|
13
|
+
* 包含:
|
|
14
|
+
* 1. skill provider:crypto-risk-checklist、crypto-instrument-analysis、indicator-authoring、trading-strategy-paradigms、knowledge-curation 与 trading-notes-setup 随包分发;
|
|
15
|
+
* 2. crypto_funding_rate(Binance 公共资金费率);
|
|
16
|
+
* 3. crypto_get_news(动态聚合新闻);
|
|
17
|
+
* 4. crypto_get_derivatives 与 crypto_get_fundamentals 工具;
|
|
18
|
+
* 5. indicator_author 创作工具(Issue #19);
|
|
19
|
+
* 6. knowledge_ingest 与 knowledge_search 知识库工具(Issue #24)。
|
|
20
|
+
*
|
|
21
|
+
* @module @dshtrading/kit-crypto
|
|
22
|
+
*/
|
|
23
|
+
const PROVIDER_NAME = "dsh-trading-crypto";
|
|
24
|
+
const SKILL_BODY_URL = new URL("../assets/skills/crypto-risk-checklist.md", import.meta.url);
|
|
25
|
+
const ANALYSIS_BODY_URL = new URL("../assets/skills/crypto-instrument-analysis.md", import.meta.url);
|
|
26
|
+
const AUTHORING_BODY_URL = new URL("../assets/skills/indicator-authoring.md", import.meta.url);
|
|
27
|
+
const STRATEGY_BODY_URL = new URL("../assets/skills/trading-strategy-paradigms.md", import.meta.url);
|
|
28
|
+
const KNOWLEDGE_CURATION_BODY_URL = new URL("../assets/skills/knowledge-curation.md", import.meta.url);
|
|
29
|
+
const JOURNAL_BODY_URL = new URL("../assets/skills/trading-notes-setup.md", import.meta.url);
|
|
30
|
+
const RESOURCE_BASE = {
|
|
31
|
+
kind: "directory",
|
|
32
|
+
path: fileURLToPath(new URL("../assets/skills/", import.meta.url))
|
|
33
|
+
};
|
|
34
|
+
const CANDIDATE = {
|
|
35
|
+
name: "crypto-risk-checklist",
|
|
36
|
+
description: "加密合约交易风控检查清单:开仓前逐项核对杠杆、仓位、资金费率与强平价。",
|
|
37
|
+
invocation: {
|
|
38
|
+
modelInvocable: true,
|
|
39
|
+
userInvocable: true
|
|
40
|
+
},
|
|
41
|
+
provider: PROVIDER_NAME,
|
|
42
|
+
source: "bundled",
|
|
43
|
+
resourceBase: RESOURCE_BASE,
|
|
44
|
+
rank: BUNDLED_SKILL_RANK,
|
|
45
|
+
locator: SKILL_BODY_URL
|
|
46
|
+
};
|
|
47
|
+
const SKILL_CANDIDATES = [
|
|
48
|
+
CANDIDATE,
|
|
49
|
+
{
|
|
50
|
+
name: "crypto-instrument-analysis",
|
|
51
|
+
description: "加密标的定性分析框架:趋势结构→量价→波动率→资金面→新闻面五步,输出带依据与反方情景的定性结论。",
|
|
52
|
+
invocation: {
|
|
53
|
+
modelInvocable: true,
|
|
54
|
+
userInvocable: true
|
|
55
|
+
},
|
|
56
|
+
provider: PROVIDER_NAME,
|
|
57
|
+
source: "bundled",
|
|
58
|
+
resourceBase: RESOURCE_BASE,
|
|
59
|
+
rank: BUNDLED_SKILL_RANK,
|
|
60
|
+
locator: ANALYSIS_BODY_URL
|
|
61
|
+
},
|
|
62
|
+
{
|
|
63
|
+
name: "indicator-authoring",
|
|
64
|
+
description: "自定义技术指标创作指南:根据用户自然语言需求生成符合契约的指标代码(TD9/SuperTrend/OBV+MA等),并通过 indicator_author 工具验证与落库。",
|
|
65
|
+
invocation: {
|
|
66
|
+
modelInvocable: true,
|
|
67
|
+
userInvocable: true
|
|
68
|
+
},
|
|
69
|
+
provider: PROVIDER_NAME,
|
|
70
|
+
source: "bundled",
|
|
71
|
+
resourceBase: RESOURCE_BASE,
|
|
72
|
+
rank: BUNDLED_SKILL_RANK,
|
|
73
|
+
locator: AUTHORING_BODY_URL
|
|
74
|
+
},
|
|
75
|
+
{
|
|
76
|
+
name: "trading-strategy-paradigms",
|
|
77
|
+
description: "经典交易策略参考范式指南:提供短线(唐奇安突破/RSI极值回归)、波段(EMA双均线/布林带下轨回归)、长线(200日均线基线/12月动量)6大策略原理、参数调优、8项回测指标研读与风险防范 SOP。",
|
|
78
|
+
invocation: {
|
|
79
|
+
modelInvocable: true,
|
|
80
|
+
userInvocable: true
|
|
81
|
+
},
|
|
82
|
+
provider: PROVIDER_NAME,
|
|
83
|
+
source: "bundled",
|
|
84
|
+
resourceBase: RESOURCE_BASE,
|
|
85
|
+
rank: BUNDLED_SKILL_RANK,
|
|
86
|
+
locator: STRATEGY_BODY_URL
|
|
87
|
+
},
|
|
88
|
+
{
|
|
89
|
+
name: "knowledge-curation",
|
|
90
|
+
description: "财经观点沉淀与知识库策展指南:基于 Content Insight 事实核查产物,规范化提取知识卡片字段、受控词表对齐、查重与关联建立,通过 knowledge_ingest 工具入库。",
|
|
91
|
+
invocation: {
|
|
92
|
+
modelInvocable: true,
|
|
93
|
+
userInvocable: true
|
|
94
|
+
},
|
|
95
|
+
provider: PROVIDER_NAME,
|
|
96
|
+
source: "bundled",
|
|
97
|
+
resourceBase: RESOURCE_BASE,
|
|
98
|
+
rank: BUNDLED_SKILL_RANK,
|
|
99
|
+
locator: KNOWLEDGE_CURATION_BODY_URL
|
|
100
|
+
},
|
|
101
|
+
{
|
|
102
|
+
name: "trading-notes-setup",
|
|
103
|
+
description: "交易日志建立与记录规范:检查/创建工作区 .trading-journal/ 双轨目录(agent 轨 + human 轨),分别记录 agent 与人类各自的操作。会话启动检查发现工作区没有交易日志目录时调用本技能建立骨架;记录条目格式以本技能为权威。",
|
|
104
|
+
invocation: {
|
|
105
|
+
modelInvocable: true,
|
|
106
|
+
userInvocable: true
|
|
107
|
+
},
|
|
108
|
+
provider: PROVIDER_NAME,
|
|
109
|
+
source: "bundled",
|
|
110
|
+
resourceBase: RESOURCE_BASE,
|
|
111
|
+
rank: BUNDLED_SKILL_RANK,
|
|
112
|
+
locator: JOURNAL_BODY_URL
|
|
113
|
+
}
|
|
114
|
+
];
|
|
115
|
+
const provider = {
|
|
116
|
+
name: PROVIDER_NAME,
|
|
117
|
+
list: () => Promise.resolve(SKILL_CANDIDATES),
|
|
118
|
+
async get(candidate) {
|
|
119
|
+
const target = SKILL_CANDIDATES.find((c) => c.name === candidate.name) ?? CANDIDATE;
|
|
120
|
+
return {
|
|
121
|
+
name: target.name,
|
|
122
|
+
description: target.description,
|
|
123
|
+
invocation: target.invocation,
|
|
124
|
+
provider: target.provider,
|
|
125
|
+
source: target.source,
|
|
126
|
+
resourceBase: RESOURCE_BASE,
|
|
127
|
+
content: await readFile(target.locator, "utf8")
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
};
|
|
131
|
+
const Config = Schema.object({
|
|
132
|
+
dryRun: Schema.boolean().default(true),
|
|
133
|
+
liveTrading: Schema.boolean().default(false)
|
|
134
|
+
});
|
|
135
|
+
const inject = ["skills", "tools"];
|
|
136
|
+
const name = "dsh-trading-crypto-kit";
|
|
137
|
+
const FUNDING_RATE_URL = "https://fapi.binance.com/fapi/v1/fundingRate";
|
|
138
|
+
const SYMBOL_PATTERN = /^[A-Z0-9]{4,20}$/;
|
|
139
|
+
const DEFAULT_FUNDING_LIMIT = 3;
|
|
140
|
+
const MAX_FUNDING_LIMIT = 1e3;
|
|
141
|
+
async function fetchFundingRates(symbol, limit) {
|
|
142
|
+
const url = new URL(FUNDING_RATE_URL);
|
|
143
|
+
url.searchParams.set("symbol", symbol);
|
|
144
|
+
url.searchParams.set("limit", String(limit));
|
|
145
|
+
const response = await fetch(url, { headers: { accept: "application/json" } });
|
|
146
|
+
if (!response.ok) {
|
|
147
|
+
const body = await response.text().catch(() => "");
|
|
148
|
+
throw new Error(`Binance futures API error: HTTP ${response.status}${body ? ` — ${body.slice(0, 200)}` : ""}`);
|
|
149
|
+
}
|
|
150
|
+
const data = await response.json();
|
|
151
|
+
if (!Array.isArray(data)) throw new Error("Binance futures API returned an unexpected payload (expected an array of funding records)");
|
|
152
|
+
return data;
|
|
153
|
+
}
|
|
154
|
+
function renderFundingRates(symbol, records) {
|
|
155
|
+
const lines = records.map((record) => {
|
|
156
|
+
const rate = Number(record.fundingRate);
|
|
157
|
+
const percent = Number.isFinite(rate) ? `${(rate * 100).toFixed(4)}%` : record.fundingRate;
|
|
158
|
+
const when = Number.isFinite(record.fundingTime) ? new Date(record.fundingTime).toISOString() : String(record.fundingTime);
|
|
159
|
+
const mark = record.markPrice === void 0 ? "" : ` markPrice=${record.markPrice}`;
|
|
160
|
+
return `- ${when} rate=${record.fundingRate} (${percent})${mark}`;
|
|
161
|
+
});
|
|
162
|
+
return [`crypto_funding_rate ${symbol} — last ${records.length} funding event(s):`, ...lines].join("\n");
|
|
163
|
+
}
|
|
164
|
+
function apply(ctx, _config) {
|
|
165
|
+
ctx.skills.registerProvider(() => provider);
|
|
166
|
+
const fundingTool = defineTool({
|
|
167
|
+
name: "crypto_funding_rate",
|
|
168
|
+
description: "Get recent funding rate history for a Binance USDⓈ-M perpetual futures symbol (public endpoint, no credentials). Returns the most recent funding events with rate and mark price.",
|
|
169
|
+
parameters: {
|
|
170
|
+
symbol: {
|
|
171
|
+
type: "string",
|
|
172
|
+
required: true,
|
|
173
|
+
description: "Perpetual futures symbol, e.g. BTCUSDT"
|
|
174
|
+
},
|
|
175
|
+
limit: {
|
|
176
|
+
type: "number",
|
|
177
|
+
description: `Number of most recent funding events to return (1-${MAX_FUNDING_LIMIT}, default ${DEFAULT_FUNDING_LIMIT})`,
|
|
178
|
+
default: DEFAULT_FUNDING_LIMIT
|
|
179
|
+
}
|
|
180
|
+
},
|
|
181
|
+
output: {
|
|
182
|
+
schema: { type: "string" },
|
|
183
|
+
render: (_args, value) => [{
|
|
184
|
+
type: "text",
|
|
185
|
+
text: String(value)
|
|
186
|
+
}]
|
|
187
|
+
},
|
|
188
|
+
async execute(raw) {
|
|
189
|
+
const args = raw ?? {};
|
|
190
|
+
const symbol = typeof args.symbol === "string" ? args.symbol.trim().toUpperCase() : "";
|
|
191
|
+
if (!SYMBOL_PATTERN.test(symbol)) throw new Error(`crypto_funding_rate: invalid symbol ${JSON.stringify(args.symbol)} — expected an uppercase Binance futures symbol like BTCUSDT`);
|
|
192
|
+
const requested = typeof args.limit === "number" && Number.isFinite(args.limit) ? Math.trunc(args.limit) : DEFAULT_FUNDING_LIMIT;
|
|
193
|
+
return renderFundingRates(symbol, await fetchFundingRates(symbol, Math.min(Math.max(requested, 1), MAX_FUNDING_LIMIT)));
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
const tools = ctx.tools;
|
|
197
|
+
const registerOnce = (tool) => {
|
|
198
|
+
if (tools.get(tool.name) !== void 0) {
|
|
199
|
+
ctx.logger("dsh-trading-crypto-kit").info("[dsh-trading-crypto-kit] tool %s already registered by another provider — skipped (mutual exclusion)", tool.name);
|
|
200
|
+
return;
|
|
201
|
+
}
|
|
202
|
+
tools.register(tool);
|
|
203
|
+
};
|
|
204
|
+
registerOnce(fundingTool);
|
|
205
|
+
const router = ctx.get?.("tradingMarketRouter", false);
|
|
206
|
+
registerOnce(createGetNewsTool({ cryptoPanicKey: router?.newsKey?.() }));
|
|
207
|
+
registerOnce(createGetDerivativesTool());
|
|
208
|
+
registerOnce(createGetFundamentalsTool());
|
|
209
|
+
const lifecycle = ctx;
|
|
210
|
+
lifecycle.inject?.(["tradingNewsRegistry"], (scope) => {
|
|
211
|
+
const registry = scope.tradingNewsRegistry;
|
|
212
|
+
if (registry && typeof registry.register === "function") lifecycle.effect?.(() => registry.register("crypto", aggregateNews), "kit-crypto news registration");
|
|
213
|
+
});
|
|
214
|
+
}
|
|
215
|
+
function createGetDerivativesTool(options = {}) {
|
|
216
|
+
return defineTool({
|
|
217
|
+
name: "crypto_get_derivatives",
|
|
218
|
+
description: "Get real-time crypto derivatives indicators (Open Interest, Long/Short Account Ratio, Top Trader Position Ratio, Taker Buy/Sell Volume Ratio, and latest Funding Rate) for a perpetual contract via Binance Futures public REST API. Accepts market-canonical (e.g. BTCUSDT, BTCUSDT-SWAP) or native symbols. No credentials required.",
|
|
219
|
+
parameters: { symbol: {
|
|
220
|
+
type: "string",
|
|
221
|
+
required: true,
|
|
222
|
+
description: "Perpetual contract symbol, market-canonical vocabulary, e.g. BTCUSDT or BTCUSDT-SWAP"
|
|
223
|
+
} },
|
|
224
|
+
output: {
|
|
225
|
+
schema: { type: "string" },
|
|
226
|
+
render: (_args, value) => [{
|
|
227
|
+
type: "text",
|
|
228
|
+
text: String(value)
|
|
229
|
+
}]
|
|
230
|
+
},
|
|
231
|
+
async execute(raw) {
|
|
232
|
+
const args = raw ?? {};
|
|
233
|
+
const symbol = typeof args.symbol === "string" ? args.symbol.trim() : "";
|
|
234
|
+
if (!symbol) throw new Error("crypto_get_derivatives: symbol parameter is required (e.g. BTCUSDT or BTCUSDT-SWAP)");
|
|
235
|
+
const result = await fetchCryptoDerivatives({
|
|
236
|
+
symbol,
|
|
237
|
+
fetch: options.fetch
|
|
238
|
+
});
|
|
239
|
+
return renderDerivativesData(result, symbol);
|
|
240
|
+
}
|
|
241
|
+
});
|
|
242
|
+
}
|
|
243
|
+
function createGetFundamentalsTool(options = {}) {
|
|
244
|
+
return defineTool({
|
|
245
|
+
name: "crypto_get_fundamentals",
|
|
246
|
+
description: "Get tokenomics and fundamental data (Market Cap Rank, Market Cap, Fully Diluted Valuation (FDV), Circulating Supply, Max Supply, 24h Volume and Price Change) for a crypto asset via CoinCap and Binance public REST APIs. Accepts symbol (BTCUSDT) or asset ticker (BTC). No credentials required.",
|
|
247
|
+
parameters: { symbol: {
|
|
248
|
+
type: "string",
|
|
249
|
+
required: true,
|
|
250
|
+
description: "Crypto symbol or asset ticker, e.g. BTCUSDT, BTC, ETH, SOL"
|
|
251
|
+
} },
|
|
252
|
+
output: {
|
|
253
|
+
schema: { type: "string" },
|
|
254
|
+
render: (_args, value) => [{
|
|
255
|
+
type: "text",
|
|
256
|
+
text: String(value)
|
|
257
|
+
}]
|
|
258
|
+
},
|
|
259
|
+
async execute(raw) {
|
|
260
|
+
const args = raw ?? {};
|
|
261
|
+
const symbol = typeof args.symbol === "string" ? args.symbol.trim() : "";
|
|
262
|
+
if (!symbol) throw new Error("crypto_get_fundamentals: symbol parameter is required (e.g. BTCUSDT or BTC)");
|
|
263
|
+
const result = await fetchCryptoFundamentals({
|
|
264
|
+
symbol,
|
|
265
|
+
fetch: options.fetch
|
|
266
|
+
});
|
|
267
|
+
return renderCryptoFundamentals(result, symbol);
|
|
268
|
+
}
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
const DEFAULT_NEWS_WINDOW_HOURS = 24;
|
|
272
|
+
const DEFAULT_NEWS_LIMIT = 20;
|
|
273
|
+
function renderNewsItem(item) {
|
|
274
|
+
return `[${item.source}] ${item.publishedAt} ${item.title}\n ${item.url}`;
|
|
275
|
+
}
|
|
276
|
+
function createGetNewsTool(toolOptions = {}) {
|
|
277
|
+
const description = "Get recent crypto news from public no-key sources (Binance listing/delisting/API announcements, OKX announcements, CoinDesk & The Block RSS). " + (toolOptions.cryptoPanicKey ? "CryptoPanic user key is set — the CryptoPanic free tier is queried as an additional source and degrades gracefully if it fails. " : "") + "Aggregates and sorts newest-first; each item carries source name, publish time and a link for traceability. Optionally filter by symbol (matched against item titles; note media headlines often use asset names like \"Bitcoin\" rather than tickers) and by a time window. Source failures are tolerated and reported instead of failing the whole call. No credentials required. Distinguish announcements (listing, delisting, regulatory) from opinion (media) when citing.";
|
|
278
|
+
return defineTool({
|
|
279
|
+
name: "crypto_get_news",
|
|
280
|
+
description,
|
|
281
|
+
parameters: {
|
|
282
|
+
symbol: {
|
|
283
|
+
type: "string",
|
|
284
|
+
description: "Optional symbol to filter by, market-canonical vocabulary, e.g. BTCUSDT or BTCUSDT-SWAP. Matched against item titles (case-insensitive substring of the symbol or its base asset)."
|
|
285
|
+
},
|
|
286
|
+
windowHours: {
|
|
287
|
+
type: "number",
|
|
288
|
+
description: `Only keep items published within the last N hours (1-168, default ${DEFAULT_NEWS_WINDOW_HOURS}).`,
|
|
289
|
+
default: DEFAULT_NEWS_WINDOW_HOURS
|
|
290
|
+
},
|
|
291
|
+
limit: {
|
|
292
|
+
type: "number",
|
|
293
|
+
description: `Max items to return (1-50, default ${DEFAULT_NEWS_LIMIT}).`,
|
|
294
|
+
default: DEFAULT_NEWS_LIMIT
|
|
295
|
+
}
|
|
296
|
+
},
|
|
297
|
+
output: {
|
|
298
|
+
schema: { type: "string" },
|
|
299
|
+
render: (_args, value) => [{
|
|
300
|
+
type: "text",
|
|
301
|
+
text: String(value)
|
|
302
|
+
}]
|
|
303
|
+
},
|
|
304
|
+
async execute(raw) {
|
|
305
|
+
const args = raw ?? {};
|
|
306
|
+
const options = {
|
|
307
|
+
symbol: typeof args.symbol === "string" ? args.symbol : void 0,
|
|
308
|
+
windowHours: typeof args.windowHours === "number" ? args.windowHours : void 0,
|
|
309
|
+
limit: typeof args.limit === "number" ? args.limit : void 0,
|
|
310
|
+
cryptoPanicKey: toolOptions.cryptoPanicKey
|
|
311
|
+
};
|
|
312
|
+
const { items, unavailable } = await aggregateNews(options);
|
|
313
|
+
if (items.length === 0 && unavailable.length === 0) return "crypto_get_news: no news items found within the requested window.";
|
|
314
|
+
const symbolNote = options.symbol ? ` symbol=${options.symbol.trim().toUpperCase()} (tokens: ${deriveSymbolTokens(options.symbol).join(", ")})` : "";
|
|
315
|
+
const keyNote = options.cryptoPanicKey ? " cryptopanicKey=set (B-source)" : "";
|
|
316
|
+
const lines = [`crypto_get_news — ${items.length} item(s)${symbolNote}${keyNote}, window=${options.windowHours ?? DEFAULT_NEWS_WINDOW_HOURS}h (newest-first):`, ...items.map(renderNewsItem)];
|
|
317
|
+
if (unavailable.length > 0) lines.push(" (source(s) unavailable this call: " + unavailable.join("; ") + ")");
|
|
318
|
+
return lines.join("\n");
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
}
|
|
322
|
+
//#endregion
|
|
323
|
+
export { Config, aggregateNews, apply, createGetDerivativesTool, createGetFundamentalsTool, createGetNewsTool, deriveSymbolTokens, extractBaseAsset, fetchCryptoDerivatives, fetchCryptoFundamentals, fetchCryptoFundamentalsPackage, inject, name, normalizeBinanceFuturesSymbol, parseCryptoPanic, parseRss2, provider, renderCryptoFundamentals, renderDerivativesData };
|