@dshtrading/kit-us 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.
@@ -0,0 +1,28 @@
1
+ import { CompanyProfile, FinancialReportMatrix, FundamentalsPackage, StockFundamentals } from "./api/lib/index.js";
2
+ //#region src/fundamentals.d.ts
3
+ interface UsFundamentalsOptions {
4
+ symbol: string;
5
+ fetch?: typeof globalThis.fetch;
6
+ }
7
+ interface UsFundamentalsResult {
8
+ data?: StockFundamentals;
9
+ beta?: number;
10
+ fiftyTwoWeekChangePercent?: number;
11
+ avgVolume3Month?: number;
12
+ currency?: string;
13
+ exchange?: string;
14
+ unavailable?: string[];
15
+ }
16
+ /** US ticker 白名单校验(2026-09-02 整改):CN/HK 有严格正则,US 原先裸插值进 URL path。 */
17
+ declare function normalizeUsSymbol(input: string): string;
18
+ declare function fetchUsFundamentals(options: UsFundamentalsOptions): Promise<UsFundamentalsResult>;
19
+ /** 从 Yahoo Finance quoteSummary 动态拉取美股多期财务报表与指标矩阵。 */
20
+ declare function fetchUsFinancialMatrix(symbol: string, fetchImpl?: typeof globalThis.fetch): Promise<{
21
+ matrix?: FinancialReportMatrix;
22
+ profile?: CompanyProfile;
23
+ }>;
24
+ /** 获取完整美股基本面数据包。 */
25
+ declare function fetchUsFundamentalsPackage(symbol: string, fetchImpl?: typeof globalThis.fetch): Promise<FundamentalsPackage>;
26
+ declare function renderUsFundamentals(result: UsFundamentalsResult, requestedSymbol: string): string;
27
+ //#endregion
28
+ export { UsFundamentalsOptions, UsFundamentalsResult, fetchUsFinancialMatrix, fetchUsFundamentals, fetchUsFundamentalsPackage, normalizeUsSymbol, renderUsFundamentals };
@@ -0,0 +1,267 @@
1
+ //#region src/fundamentals.ts
2
+ const YAHOO_QUOTE_URL = "https://query1.finance.yahoo.com/v7/finance/quote";
3
+ const YAHOO_CHART_URL = "https://query2.finance.yahoo.com/v8/finance/chart";
4
+ const YAHOO_UA = "Mozilla/5.0";
5
+ /** 上游超时(2026-09-02 整改):对齐 connector 模式,防挂起拖死桥请求。 */
6
+ const UPSTREAM_TIMEOUT_MS = 1e4;
7
+ /** US ticker 白名单校验(2026-09-02 整改):CN/HK 有严格正则,US 原先裸插值进 URL path。 */
8
+ function normalizeUsSymbol(input) {
9
+ const sym = input.trim().toUpperCase();
10
+ if (!/^[A-Z0-9.\-^=]{1,12}$/.test(sym)) throw new Error(`us_get_fundamentals: invalid US symbol ${JSON.stringify(input)} — expected e.g. AAPL, BRK.B, ^GSPC`);
11
+ return sym;
12
+ }
13
+ const usFundamentalsCache = /* @__PURE__ */ new Map();
14
+ const US_FUNDAMENTALS_TTL_MS = 3e5;
15
+ async function fetchUsFundamentals(options) {
16
+ const fetchImpl = options.fetch ?? globalThis.fetch;
17
+ const symbol = normalizeUsSymbol(options.symbol);
18
+ const unavailable = [];
19
+ const cached = usFundamentalsCache.get(symbol);
20
+ if (cached && cached.expiresAt > Date.now()) return cached.data;
21
+ const fmpApiKey = process.env.FMP_API_KEY;
22
+ if (fmpApiKey) try {
23
+ const fmpRes = await fetchImpl(`https://financialmodelingprep.com/api/v3/profile/${encodeURIComponent(symbol)}?apikey=${fmpApiKey}`, {
24
+ headers: { accept: "application/json" },
25
+ signal: AbortSignal.timeout(UPSTREAM_TIMEOUT_MS)
26
+ });
27
+ if (fmpRes.ok) {
28
+ const p = (await fmpRes.json())?.[0];
29
+ if (p) {
30
+ let fiftyTwoWeekLow;
31
+ let fiftyTwoWeekHigh;
32
+ if (p.range) {
33
+ const parts = p.range.split("-");
34
+ if (parts.length === 2) {
35
+ fiftyTwoWeekLow = Number(parts[0]);
36
+ fiftyTwoWeekHigh = Number(parts[1]);
37
+ }
38
+ }
39
+ const result = {
40
+ data: {
41
+ symbol: p.symbol ?? symbol,
42
+ ...p.companyName ? { name: p.companyName } : {},
43
+ ...p.mktCap !== void 0 ? { marketCap: p.mktCap } : {},
44
+ ...Number.isFinite(fiftyTwoWeekHigh) ? { fiftyTwoWeekHigh } : {},
45
+ ...Number.isFinite(fiftyTwoWeekLow) ? { fiftyTwoWeekLow } : {},
46
+ timestamp: Date.now()
47
+ },
48
+ ...p.beta !== void 0 ? { beta: p.beta } : {},
49
+ ...p.volAvg !== void 0 ? { avgVolume3Month: p.volAvg } : {},
50
+ ...p.currency ? { currency: p.currency } : {},
51
+ ...p.exchangeShortName ? { exchange: p.exchangeShortName } : {},
52
+ unavailable
53
+ };
54
+ usFundamentalsCache.set(symbol, {
55
+ data: result,
56
+ expiresAt: Date.now() + US_FUNDAMENTALS_TTL_MS
57
+ });
58
+ return result;
59
+ }
60
+ }
61
+ } catch (err) {
62
+ unavailable.push(`fmp-official: ${err instanceof Error ? err.message : String(err)}`);
63
+ }
64
+ try {
65
+ const url = new URL(YAHOO_QUOTE_URL);
66
+ url.searchParams.set("symbols", symbol);
67
+ const response = await fetchImpl(url.toString(), {
68
+ headers: {
69
+ accept: "application/json",
70
+ "user-agent": YAHOO_UA
71
+ },
72
+ signal: AbortSignal.timeout(UPSTREAM_TIMEOUT_MS)
73
+ });
74
+ if (response.ok) {
75
+ const item = (await response.json()).quoteResponse?.result?.[0];
76
+ if (item) {
77
+ const dividendRate = item.dividendYield ?? item.trailingAnnualDividendYield;
78
+ return {
79
+ data: {
80
+ symbol: item.symbol ?? symbol,
81
+ name: item.shortName ?? item.longName,
82
+ marketCap: item.marketCap,
83
+ peTtm: item.trailingPE,
84
+ peDynamic: item.forwardPE,
85
+ pb: item.priceToBook,
86
+ eps: item.epsTrailingTwelveMonths,
87
+ dividendYield: dividendRate,
88
+ fiftyTwoWeekHigh: item.fiftyTwoWeekHigh,
89
+ fiftyTwoWeekLow: item.fiftyTwoWeekLow,
90
+ timestamp: Date.now()
91
+ },
92
+ beta: item.beta,
93
+ fiftyTwoWeekChangePercent: item.fiftyTwoWeekChangePercent,
94
+ avgVolume3Month: item.averageDailyVolume3Month,
95
+ currency: item.currency,
96
+ exchange: item.fullExchangeName,
97
+ unavailable
98
+ };
99
+ }
100
+ } else unavailable.push(`yahoo-quote: HTTP ${response.status}`);
101
+ } catch (err) {
102
+ unavailable.push(`yahoo-quote: ${err instanceof Error ? err.message : String(err)}`);
103
+ }
104
+ try {
105
+ const chartRes = await fetchImpl(`${YAHOO_CHART_URL}/${encodeURIComponent(symbol)}?interval=1d`, {
106
+ headers: {
107
+ accept: "application/json",
108
+ "user-agent": YAHOO_UA
109
+ },
110
+ signal: AbortSignal.timeout(UPSTREAM_TIMEOUT_MS)
111
+ });
112
+ if (chartRes.ok) {
113
+ const meta = (await chartRes.json()).chart?.result?.[0]?.meta;
114
+ if (meta) return {
115
+ data: {
116
+ symbol,
117
+ name: meta.shortName ?? meta.longName,
118
+ fiftyTwoWeekHigh: meta.fiftyTwoWeekHigh,
119
+ fiftyTwoWeekLow: meta.fiftyTwoWeekLow,
120
+ timestamp: Date.now()
121
+ },
122
+ fiftyTwoWeekChangePercent: meta.regularMarketChangePercent !== void 0 ? meta.regularMarketChangePercent / 100 : void 0,
123
+ avgVolume3Month: meta.regularMarketVolume,
124
+ currency: meta.currency,
125
+ exchange: meta.fullExchangeName,
126
+ unavailable
127
+ };
128
+ } else unavailable.push(`yahoo-chart-meta: HTTP ${chartRes.status}`);
129
+ } catch (err) {
130
+ unavailable.push(`yahoo-chart-meta: ${err instanceof Error ? err.message : String(err)}`);
131
+ }
132
+ return { unavailable };
133
+ }
134
+ const usMatrixCache = /* @__PURE__ */ new Map();
135
+ const US_MATRIX_TTL_MS = 864e5;
136
+ /** 从 Yahoo Finance quoteSummary 动态拉取美股多期财务报表与指标矩阵。 */
137
+ async function fetchUsFinancialMatrix(symbol, fetchImpl = globalThis.fetch) {
138
+ try {
139
+ const sym = normalizeUsSymbol(symbol);
140
+ const cached = usMatrixCache.get(sym);
141
+ if (cached && cached.expiresAt > Date.now()) return cached.data;
142
+ const res = await fetchImpl(`https://query1.finance.yahoo.com/v10/finance/quoteSummary/${encodeURIComponent(sym)}?modules=financialData,defaultKeyStatistics,incomeStatementHistoryQuarterly,balanceSheetHistoryQuarterly,cashflowStatementHistoryQuarterly,assetProfile`, {
143
+ headers: {
144
+ accept: "application/json",
145
+ "user-agent": YAHOO_UA
146
+ },
147
+ signal: AbortSignal.timeout(UPSTREAM_TIMEOUT_MS)
148
+ });
149
+ if (!res.ok) return {};
150
+ const item = (await res.json()).quoteSummary?.result?.[0];
151
+ if (!item) return {};
152
+ const profile = {
153
+ symbol: sym,
154
+ industry: item.assetProfile?.industry,
155
+ sector: item.assetProfile?.sector,
156
+ description: item.assetProfile?.longBusinessSummary,
157
+ website: typeof item.assetProfile?.website === "string" && /^https?:/i.test(item.assetProfile.website) ? item.assetProfile.website : void 0,
158
+ executives: item.assetProfile?.companyOfficers?.slice(0, 5).map((o) => ({
159
+ name: o.name,
160
+ title: o.title
161
+ }))
162
+ };
163
+ const incomes = item.incomeStatementHistoryQuarterly?.incomeStatementHistory ?? [];
164
+ if (incomes.length === 0) return { profile };
165
+ const sortedIncomes = [...incomes].filter((i) => i.endDate?.fmt);
166
+ const periods = sortedIncomes.map((i) => i.endDate.fmt.slice(0, 7).replace("-", "/"));
167
+ for (let i = 1; i < periods.length; i++) if (periods[i] === periods[i - 1]) periods[i] = `${periods[i]}#${i}`;
168
+ const latestPeriod = periods[periods.length - 1] ?? "";
169
+ const latestReportTitle = latestPeriod ? `${latestPeriod} 季报` : void 0;
170
+ const revValues = {};
171
+ const netValues = {};
172
+ const grossMarginValues = {};
173
+ sortedIncomes.forEach((inc, idx) => {
174
+ const p = periods[idx];
175
+ const rev = inc.totalRevenue?.raw;
176
+ const net = inc.netIncome?.raw;
177
+ const gross = inc.grossProfit?.raw;
178
+ revValues[p] = { value: rev };
179
+ netValues[p] = { value: net };
180
+ const margin = rev && gross ? gross / rev * 100 : void 0;
181
+ grossMarginValues[p] = { value: margin };
182
+ });
183
+ const result = {
184
+ matrix: {
185
+ currency: "USD",
186
+ latestReportTitle,
187
+ periods,
188
+ groups: [{
189
+ id: "profitability",
190
+ title: "盈利与收益能力",
191
+ rows: [
192
+ {
193
+ id: "gross_margin",
194
+ name: "毛利率",
195
+ unit: "%",
196
+ values: grossMarginValues
197
+ },
198
+ {
199
+ id: "revenue",
200
+ name: "营业总收入",
201
+ unit: "USD",
202
+ values: revValues
203
+ },
204
+ {
205
+ id: "net_income",
206
+ name: "净利润",
207
+ unit: "USD",
208
+ values: netValues
209
+ }
210
+ ]
211
+ }]
212
+ },
213
+ profile
214
+ };
215
+ usMatrixCache.set(sym, {
216
+ data: result,
217
+ expiresAt: Date.now() + US_MATRIX_TTL_MS
218
+ });
219
+ return result;
220
+ } catch {
221
+ return {};
222
+ }
223
+ }
224
+ /** 获取完整美股基本面数据包。 */
225
+ async function fetchUsFundamentalsPackage(symbol, fetchImpl = globalThis.fetch) {
226
+ const sym = normalizeUsSymbol(symbol);
227
+ const [quoteRes, { matrix, profile }] = await Promise.all([fetchUsFundamentals({
228
+ symbol: sym,
229
+ fetch: fetchImpl
230
+ }), fetchUsFinancialMatrix(sym, fetchImpl)]);
231
+ return {
232
+ market: "us",
233
+ symbol: sym,
234
+ stock: quoteRes.data,
235
+ matrix,
236
+ profile: {
237
+ symbol: sym,
238
+ name: quoteRes.data?.name ?? sym,
239
+ ...profile
240
+ }
241
+ };
242
+ }
243
+ function renderUsFundamentals(result, requestedSymbol) {
244
+ const { data, beta, fiftyTwoWeekChangePercent, avgVolume3Month, currency = "USD", exchange, unavailable = [] } = result;
245
+ if (!data) return `us_get_fundamentals ${requestedSymbol}: no fundamental data available.${unavailable.length > 0 ? ` (errors: ${unavailable.join("; ")})` : ""}`;
246
+ const lines = [`us_get_fundamentals ${data.symbol}${data.name ? ` (${data.name})` : ""}${exchange ? ` [${exchange}]` : ""}:`];
247
+ if (data.marketCap !== void 0) lines.push(`- Market Cap: \$${data.marketCap.toLocaleString(void 0, { maximumFractionDigits: 0 })} ${currency}`);
248
+ if (data.peTtm !== void 0) lines.push(`- Trailing PE (TTM): ${data.peTtm.toFixed(2)}`);
249
+ if (data.peDynamic !== void 0) lines.push(`- Forward PE: ${data.peDynamic.toFixed(2)}`);
250
+ if (data.pb !== void 0) lines.push(`- Price to Book (PB): ${data.pb.toFixed(2)}`);
251
+ if (data.eps !== void 0) lines.push(`- Diluted EPS (TTM): \$${data.eps.toFixed(2)}`);
252
+ if (data.dividendYield !== void 0) {
253
+ const pct = (data.dividendYield * (data.dividendYield < 1 ? 100 : 1)).toFixed(2);
254
+ lines.push(`- Dividend Yield: ${pct}%`);
255
+ }
256
+ if (beta !== void 0) lines.push(`- Beta (5Y Monthly): ${beta.toFixed(2)}`);
257
+ if (data.fiftyTwoWeekLow !== void 0 && data.fiftyTwoWeekHigh !== void 0) lines.push(`- 52-Week Range: \$${data.fiftyTwoWeekLow.toFixed(2)} - \$${data.fiftyTwoWeekHigh.toFixed(2)}`);
258
+ if (fiftyTwoWeekChangePercent !== void 0) {
259
+ const pct = (fiftyTwoWeekChangePercent * (Math.abs(fiftyTwoWeekChangePercent) < 1 ? 100 : 1)).toFixed(2);
260
+ lines.push(`- 52-Week Change: ${Number(pct) > 0 ? "+" : ""}${pct}%`);
261
+ }
262
+ if (avgVolume3Month !== void 0) lines.push(`- Volume / Avg Volume: ${avgVolume3Month.toLocaleString()} shares`);
263
+ if (unavailable.length > 0) lines.push(` (partially unavailable details: ${unavailable.join("; ")})`);
264
+ return lines.join("\n");
265
+ }
266
+ //#endregion
267
+ export { fetchUsFinancialMatrix, fetchUsFundamentals, fetchUsFundamentalsPackage, normalizeUsSymbol, renderUsFundamentals };
package/lib/index.d.ts ADDED
@@ -0,0 +1,21 @@
1
+ import { UsFundamentalsOptions, UsFundamentalsResult, fetchUsFinancialMatrix, fetchUsFundamentals, fetchUsFundamentalsPackage, normalizeUsSymbol, renderUsFundamentals } from "./fundamentals.js";
2
+ import { AggregateNewsOptions, AggregateNewsResult, NewsItem, NewsSource, aggregateNews, parseGoogleNewsRss, parseSecEdgarAtom } from "./news.js";
3
+ import Schema from "@deepseek-ai/schemastery";
4
+ import { SkillProvider } from "@deepseek-ai/dsh-skill";
5
+ import { Context } from "@deepseek-ai/cordis";
6
+ //#region src/index.d.ts
7
+ declare const provider: SkillProvider;
8
+ interface Config {
9
+ dryRun: boolean;
10
+ liveTrading: boolean;
11
+ }
12
+ declare const Config: Schema<Config>;
13
+ declare const inject: string[];
14
+ declare const name = "dsh-trading-us-kit";
15
+ declare function apply(ctx: Context, _config: Config): void;
16
+ declare function createGetNewsTool(): import("@deepseek-ai/dsh-tools").ToolDefinition;
17
+ declare function createGetFundamentalsTool(options?: {
18
+ fetch?: typeof globalThis.fetch;
19
+ }): import("@deepseek-ai/dsh-tools").ToolDefinition;
20
+ //#endregion
21
+ export { AggregateNewsOptions, AggregateNewsResult, Config, NewsItem, NewsSource, UsFundamentalsOptions, UsFundamentalsResult, aggregateNews, apply, createGetFundamentalsTool, createGetNewsTool, fetchUsFinancialMatrix, fetchUsFundamentals, fetchUsFundamentalsPackage, inject, name, normalizeUsSymbol, parseGoogleNewsRss, parseSecEdgarAtom, provider, renderUsFundamentals };
package/lib/index.js ADDED
@@ -0,0 +1,224 @@
1
+ import { aggregateNews, parseGoogleNewsRss, parseSecEdgarAtom } from "./news.js";
2
+ import { fetchUsFinancialMatrix, fetchUsFundamentals, fetchUsFundamentalsPackage, normalizeUsSymbol, renderUsFundamentals } from "./fundamentals.js";
3
+ import { readFile } from "node:fs/promises";
4
+ import { fileURLToPath } from "node:url";
5
+ import Schema from "@deepseek-ai/schemastery";
6
+ import { BUNDLED_SKILL_RANK } from "@deepseek-ai/dsh-skill";
7
+ import { defineTool } from "@deepseek-ai/dsh-tools";
8
+ import { createGetIndicatorsTool } from "@dshtrading/indicators/tool";
9
+ //#region src/index.ts
10
+ /**
11
+ * US 工具箱插件(dsh-trading us 切片)。
12
+ *
13
+ * 包含:
14
+ * 1. skill provider:us-risk-checklist、indicator-authoring、trading-strategy-paradigms、knowledge-curation 与 trading-notes-setup 随包分发;
15
+ * 2. us_get_news 与 us_get_fundamentals 工具;
16
+ * 3. indicator_author 创作工具(Issue #19);
17
+ * 4. knowledge_ingest 与 knowledge_search 知识库工具(Issue #24)。
18
+ *
19
+ * @module @dshtrading/kit-us
20
+ */
21
+ const PROVIDER_NAME = "dsh-trading-us";
22
+ const SKILL_BODY_URL = new URL("../assets/skills/us-risk-checklist.md", import.meta.url);
23
+ const AUTHORING_BODY_URL = new URL("../assets/skills/indicator-authoring.md", import.meta.url);
24
+ const STRATEGY_BODY_URL = new URL("../assets/skills/trading-strategy-paradigms.md", import.meta.url);
25
+ const KNOWLEDGE_CURATION_BODY_URL = new URL("../assets/skills/knowledge-curation.md", import.meta.url);
26
+ const JOURNAL_BODY_URL = new URL("../assets/skills/trading-notes-setup.md", import.meta.url);
27
+ const RESOURCE_BASE = {
28
+ kind: "directory",
29
+ path: fileURLToPath(new URL("../assets/skills/", import.meta.url))
30
+ };
31
+ const CANDIDATE = {
32
+ name: "us-risk-checklist",
33
+ description: "美股交易风控检查清单:开仓前逐项核对盘前盘后流动性、熔断与停牌、做空规则、T+1 与 PDT 日内限制、财报跳空风险。",
34
+ invocation: {
35
+ modelInvocable: true,
36
+ userInvocable: true
37
+ },
38
+ provider: PROVIDER_NAME,
39
+ source: "bundled",
40
+ resourceBase: RESOURCE_BASE,
41
+ rank: BUNDLED_SKILL_RANK,
42
+ locator: SKILL_BODY_URL
43
+ };
44
+ const SKILL_CANDIDATES = [
45
+ CANDIDATE,
46
+ {
47
+ name: "indicator-authoring",
48
+ description: "自定义技术指标创作指南:根据用户自然语言需求生成符合契约的指标代码(TD9/SuperTrend/OBV+MA等),并通过 indicator_author 工具验证与落库。",
49
+ invocation: {
50
+ modelInvocable: true,
51
+ userInvocable: true
52
+ },
53
+ provider: PROVIDER_NAME,
54
+ source: "bundled",
55
+ resourceBase: RESOURCE_BASE,
56
+ rank: BUNDLED_SKILL_RANK,
57
+ locator: AUTHORING_BODY_URL
58
+ },
59
+ {
60
+ name: "trading-strategy-paradigms",
61
+ description: "经典交易策略参考范式指南:提供短线(唐奇安突破/RSI极值回归)、波段(EMA双均线/布林带下轨回归)、长线(200日均线基线/12月动量)6大策略原理、参数调优、8项回测指标研读与风险防范 SOP。",
62
+ invocation: {
63
+ modelInvocable: true,
64
+ userInvocable: true
65
+ },
66
+ provider: PROVIDER_NAME,
67
+ source: "bundled",
68
+ resourceBase: RESOURCE_BASE,
69
+ rank: BUNDLED_SKILL_RANK,
70
+ locator: STRATEGY_BODY_URL
71
+ },
72
+ {
73
+ name: "knowledge-curation",
74
+ description: "财经观点沉淀与知识库策展指南:基于 Content Insight 事实核查产物,规范化提取知识卡片字段、受控词表对齐、查重与关联建立,通过 knowledge_ingest 工具入库。",
75
+ invocation: {
76
+ modelInvocable: true,
77
+ userInvocable: true
78
+ },
79
+ provider: PROVIDER_NAME,
80
+ source: "bundled",
81
+ resourceBase: RESOURCE_BASE,
82
+ rank: BUNDLED_SKILL_RANK,
83
+ locator: KNOWLEDGE_CURATION_BODY_URL
84
+ },
85
+ {
86
+ name: "trading-notes-setup",
87
+ description: "交易日志建立与记录规范:检查/创建工作区 .trading-journal/ 双轨目录(agent 轨 + human 轨),分别记录 agent 与人类各自的操作。会话启动检查发现工作区没有交易日志目录时调用本技能建立骨架;记录条目格式以本技能为权威。",
88
+ invocation: {
89
+ modelInvocable: true,
90
+ userInvocable: true
91
+ },
92
+ provider: PROVIDER_NAME,
93
+ source: "bundled",
94
+ resourceBase: RESOURCE_BASE,
95
+ rank: BUNDLED_SKILL_RANK,
96
+ locator: JOURNAL_BODY_URL
97
+ }
98
+ ];
99
+ const provider = {
100
+ name: PROVIDER_NAME,
101
+ list: () => Promise.resolve(SKILL_CANDIDATES),
102
+ async get(candidate) {
103
+ const target = SKILL_CANDIDATES.find((c) => c.name === candidate.name) ?? CANDIDATE;
104
+ return {
105
+ name: target.name,
106
+ description: target.description,
107
+ invocation: target.invocation,
108
+ provider: target.provider,
109
+ source: target.source,
110
+ resourceBase: RESOURCE_BASE,
111
+ content: await readFile(target.locator, "utf8")
112
+ };
113
+ }
114
+ };
115
+ const Config = Schema.object({
116
+ dryRun: Schema.boolean().default(true),
117
+ liveTrading: Schema.boolean().default(false)
118
+ });
119
+ const inject = ["skills", "tools"];
120
+ const name = "dsh-trading-us-kit";
121
+ function apply(ctx, _config) {
122
+ ctx.skills.registerProvider(() => provider);
123
+ const newsTool = createGetNewsTool();
124
+ const fundamentalsTool = createGetFundamentalsTool();
125
+ const tools = ctx.tools;
126
+ const registerOnce = (tool) => {
127
+ if (tools.get(tool.name) !== void 0) {
128
+ ctx.logger("dsh-trading-us-kit").info("[dsh-trading-us-kit] tool %s already registered by another provider — skipped (mutual exclusion)", tool.name);
129
+ return;
130
+ }
131
+ tools.register(tool);
132
+ };
133
+ registerOnce(newsTool);
134
+ registerOnce(fundamentalsTool);
135
+ const serviceGetter = ctx;
136
+ const marketData = (serviceGetter.get?.("tradingMarketDataRegistry", false))?.active("us")?.service ?? serviceGetter.get?.("tradingUsMarketData", false);
137
+ if (marketData !== void 0) registerOnce(createGetIndicatorsTool({
138
+ marketData,
139
+ market: "us"
140
+ }));
141
+ const lifecycle = ctx;
142
+ lifecycle.inject?.(["tradingNewsRegistry"], (scope) => {
143
+ const registry = scope.tradingNewsRegistry;
144
+ if (registry && typeof registry.register === "function") lifecycle.effect?.(() => registry.register("us", aggregateNews), "kit-us news registration");
145
+ });
146
+ }
147
+ const DEFAULT_NEWS_WINDOW_HOURS = 24;
148
+ const DEFAULT_NEWS_LIMIT = 20;
149
+ function renderNewsItem(item) {
150
+ return `[${item.source}] ${item.publishedAt} ${item.title}\n ${item.url}`;
151
+ }
152
+ function createGetNewsTool() {
153
+ return defineTool({
154
+ name: "us_get_news",
155
+ description: "Get recent US stock market news from Yahoo Finance RSS and CNBC RSS feeds. Aggregates and sorts newest-first; each item carries source name, publish time and a link for traceability. Optionally filter by symbol (e.g. AAPL, TSLA, NVDA) and by a time window. Source failures are tolerated and reported instead of failing the whole call. No credentials required.",
156
+ parameters: {
157
+ symbol: {
158
+ type: "string",
159
+ description: "Optional US stock symbol to filter by (e.g. AAPL, TSLA, NVDA). Matched against item titles."
160
+ },
161
+ windowHours: {
162
+ type: "number",
163
+ description: `Only keep items published within the last N hours (1-168, default ${DEFAULT_NEWS_WINDOW_HOURS}).`,
164
+ default: DEFAULT_NEWS_WINDOW_HOURS
165
+ },
166
+ limit: {
167
+ type: "number",
168
+ description: `Max items to return (1-50, default ${DEFAULT_NEWS_LIMIT}).`,
169
+ default: DEFAULT_NEWS_LIMIT
170
+ }
171
+ },
172
+ output: {
173
+ schema: { type: "string" },
174
+ render: (_args, value) => [{
175
+ type: "text",
176
+ text: String(value)
177
+ }]
178
+ },
179
+ async execute(raw) {
180
+ const args = raw ?? {};
181
+ const options = {
182
+ symbol: typeof args.symbol === "string" ? args.symbol : void 0,
183
+ windowHours: typeof args.windowHours === "number" ? args.windowHours : void 0,
184
+ limit: typeof args.limit === "number" ? args.limit : void 0
185
+ };
186
+ const { items, unavailable } = await aggregateNews(options);
187
+ if (items.length === 0 && unavailable.length === 0) return "us_get_news: no news items found within the requested window.";
188
+ const symbolNote = options.symbol ? ` symbol=${options.symbol.trim().toUpperCase()}` : "";
189
+ const lines = [`us_get_news — ${items.length} item(s)${symbolNote}, window=${options.windowHours ?? DEFAULT_NEWS_WINDOW_HOURS}h (newest-first):`, ...items.map(renderNewsItem)];
190
+ if (unavailable.length > 0) lines.push(" (source(s) unavailable this call: " + unavailable.join("; ") + ")");
191
+ return lines.join("\n");
192
+ }
193
+ });
194
+ }
195
+ function createGetFundamentalsTool(options = {}) {
196
+ return defineTool({
197
+ name: "us_get_fundamentals",
198
+ description: "Get fundamental valuation and financial indicators for US stocks (Market Cap, Trailing P/E, Forward P/E, P/B, Dividend Yield, Beta, 52-Week High/Low, EPS, 50-Day & 200-Day Moving Averages) via Stooq public equity data. Accepts market-canonical symbol (e.g. AAPL.US, TSLA.US) or pure ticker (AAPL, TSLA). No credentials required.",
199
+ parameters: { symbol: {
200
+ type: "string",
201
+ required: true,
202
+ description: "US stock symbol or ticker, market-canonical vocabulary, e.g. AAPL.US, TSLA.US, AAPL, TSLA"
203
+ } },
204
+ output: {
205
+ schema: { type: "string" },
206
+ render: (_args, value) => [{
207
+ type: "text",
208
+ text: String(value)
209
+ }]
210
+ },
211
+ async execute(raw) {
212
+ const args = raw ?? {};
213
+ const symbol = typeof args.symbol === "string" ? args.symbol.trim() : "";
214
+ if (!symbol) throw new Error("us_get_fundamentals: symbol parameter is required (e.g. AAPL.US or AAPL)");
215
+ const result = await fetchUsFundamentals({
216
+ symbol,
217
+ fetch: options.fetch
218
+ });
219
+ return renderUsFundamentals(result, symbol);
220
+ }
221
+ });
222
+ }
223
+ //#endregion
224
+ export { Config, aggregateNews, apply, createGetFundamentalsTool, createGetNewsTool, fetchUsFinancialMatrix, fetchUsFundamentals, fetchUsFundamentalsPackage, inject, name, normalizeUsSymbol, parseGoogleNewsRss, parseSecEdgarAtom, provider, renderUsFundamentals };
package/lib/news.d.ts ADDED
@@ -0,0 +1,45 @@
1
+ //#region src/news.d.ts
2
+ /**
3
+ * us_get_news 取数层(WS4 #1:#6 子工作流,spike/s impl-uscnhk-news EVIDENCE 推荐)。
4
+ *
5
+ * 两源均为单端点、无鉴权、无状态公共 GET(本出口实测):
6
+ * - Yahoo Finance news API(v8 家族,与既有 connector-yahoo 同族)——JSON,news[] 带 publisher/link/publishTime
7
+ * - Google News RSS(news.google.com/rss/search)——RSS 2.0,<source> 为原始媒体名;link 为 Google 跳转链接
8
+ * 本模块只做取数 + 归一化为 NewsItem[];时间窗/币种过滤 + 排序截尾;defineTool 装配在 index.ts。
9
+ * 铁律 #5:输出只带元数据(来源名/标题/链接/发布时间),不取正文,不缓存,不再分发。
10
+ * 每源独立容错:单源失败不炸整体,失败源在 `unavailable` 中注明,fail-soft。
11
+ */
12
+ type NewsSource = 'yahoo' | 'googlenews';
13
+ interface NewsItem {
14
+ /** 来源名(铁律 #5 的来源标注)。 */
15
+ source: string;
16
+ title: string;
17
+ url: string;
18
+ /** ISO 8601 发布时间。 */
19
+ publishedAt: string;
20
+ }
21
+ interface AggregateNewsOptions {
22
+ /** 标的(市场规范词汇,如 AAPL);缺省 = 通用市场主题。 */
23
+ symbol?: string | undefined;
24
+ /** 时间窗(小时):只保留 now - windowHours 内的条目;缺省 24。 */
25
+ windowHours?: number | undefined;
26
+ /** 输出条数上限;缺省 20。 */
27
+ limit?: number | undefined;
28
+ /** 依赖注入的 fetch(测试用 mock;缺省 globalThis.fetch)。 */
29
+ fetch?: typeof globalThis.fetch | undefined;
30
+ /** 注入当前时间戳(ms,测试用);缺省 Date.now()。 */
31
+ now?: number | undefined;
32
+ /** CryptoPanic API token(桥面透传,us 聚合器忽略;对齐 api 契约形状)。 */
33
+ cryptoPanicKey?: string | undefined;
34
+ }
35
+ interface AggregateNewsResult {
36
+ items: NewsItem[];
37
+ /** 取数失败的源:`<匿名>`(如 'yahoo: HTTP 500 — ...'),工具输出需注明缺席。 */
38
+ unavailable: string[];
39
+ }
40
+ /** 解析 Google News RSS(title/link/pubDate/source;link 为 Google 跳转,溯源用 source)。 */
41
+ declare function parseGoogleNewsRss(xml: string): NewsItem[];
42
+ declare function parseSecEdgarAtom(xml: string): NewsItem[];
43
+ declare function aggregateNews(options?: AggregateNewsOptions): Promise<AggregateNewsResult>;
44
+ //#endregion
45
+ export { AggregateNewsOptions, AggregateNewsResult, NewsItem, NewsSource, aggregateNews, parseGoogleNewsRss, parseSecEdgarAtom };