@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.
- package/LICENSE +75 -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/assets/skills/us-risk-checklist.md +1 -0
- package/lib/api/lib/index.d.ts +720 -0
- package/lib/fundamentals.d.ts +28 -0
- package/lib/fundamentals.js +267 -0
- package/lib/index.d.ts +21 -0
- package/lib/index.js +224 -0
- package/lib/news.d.ts +45 -0
- package/lib/news.js +166 -0
- package/package.json +39 -0
package/lib/news.js
ADDED
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
//#region src/news.ts
|
|
2
|
+
const YAHOO_SEARCH_URL = "https://query1.finance.yahoo.com/v1/finance/search";
|
|
3
|
+
const GOOGLE_NEWS_RSS_URL = "https://news.google.com/rss/search";
|
|
4
|
+
const DEFAULT_WINDOW_HOURS = 24;
|
|
5
|
+
const DEFAULT_LIMIT = 20;
|
|
6
|
+
const MAX_LIMIT = 50;
|
|
7
|
+
const UA = "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (dsh-trading/us_get_news)";
|
|
8
|
+
/** 下钻 fetch 统一 10s 超时(docs/replication.md §9;上游挂起不得拖垮 60s 轮询链)。 */
|
|
9
|
+
const UPSTREAM_TIMEOUT_MS = 1e4;
|
|
10
|
+
/** 无 symbol 时的通用市场主题(避免空 query)。 */
|
|
11
|
+
const DEFAULT_QUERY_TOPIC = "US stock market";
|
|
12
|
+
async function fetchText(url, fetchImpl, name) {
|
|
13
|
+
const response = await fetchImpl(url, {
|
|
14
|
+
headers: {
|
|
15
|
+
accept: "application/json, text/xml, */*",
|
|
16
|
+
"user-agent": UA
|
|
17
|
+
},
|
|
18
|
+
signal: AbortSignal.timeout(UPSTREAM_TIMEOUT_MS)
|
|
19
|
+
});
|
|
20
|
+
if (!response.ok) {
|
|
21
|
+
const body = await response.text().catch(() => "");
|
|
22
|
+
throw new Error(`${name}: HTTP ${response.status}${body ? ` — ${body.slice(0, 160)}` : ""}`);
|
|
23
|
+
}
|
|
24
|
+
return response.text();
|
|
25
|
+
}
|
|
26
|
+
async function fetchYahooNews(fetchImpl, topic, limit) {
|
|
27
|
+
const url = new URL(YAHOO_SEARCH_URL);
|
|
28
|
+
url.searchParams.set("q", topic);
|
|
29
|
+
url.searchParams.set("newsCount", String(limit));
|
|
30
|
+
url.searchParams.set("quotesCount", "0");
|
|
31
|
+
url.searchParams.set("enableFuzzyQuery", "false");
|
|
32
|
+
const text = await fetchText(url.toString(), fetchImpl, "yahoo");
|
|
33
|
+
const news = JSON.parse(text).news;
|
|
34
|
+
if (!Array.isArray(news)) throw new Error("yahoo: unexpected payload (expected news[])");
|
|
35
|
+
const items = [];
|
|
36
|
+
for (const n of news) {
|
|
37
|
+
if (!n.title || !n.link) continue;
|
|
38
|
+
const ts = typeof n.providerPublishTime === "number" ? n.providerPublishTime * 1e3 : NaN;
|
|
39
|
+
if (!Number.isFinite(ts)) continue;
|
|
40
|
+
items.push({
|
|
41
|
+
source: n.publisher || "yahoo",
|
|
42
|
+
title: n.title,
|
|
43
|
+
url: n.link,
|
|
44
|
+
publishedAt: new Date(ts).toISOString()
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
return items;
|
|
48
|
+
}
|
|
49
|
+
function decodeXmlText(raw) {
|
|
50
|
+
return raw.replace(/<!\[CDATA\[([\s\S]*?)\]\]>/g, (_, m) => m).replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, "\"").replace(/'/g, "'").replace(/&#([0-9]+);/g, (_, n) => String.fromCodePoint(Number(n))).replace(/&#x([0-9a-fA-F]+);/g, (_, n) => String.fromCodePoint(parseInt(n, 16))).trim();
|
|
51
|
+
}
|
|
52
|
+
function extractTag(block, tag) {
|
|
53
|
+
const re = new RegExp(`<${tag}[\\s>][\\s\\S]*?<\\/${tag}>`, "i");
|
|
54
|
+
const match = block.match(re);
|
|
55
|
+
if (!match) return "";
|
|
56
|
+
return decodeXmlText(match[0].replace(new RegExp(`<\\/?${tag}[^>]*>`, "gi"), ""));
|
|
57
|
+
}
|
|
58
|
+
/** 解析 Google News RSS(title/link/pubDate/source;link 为 Google 跳转,溯源用 source)。 */
|
|
59
|
+
function parseGoogleNewsRss(xml) {
|
|
60
|
+
const items = [];
|
|
61
|
+
const blocks = xml.match(/<item[\s>][\s\S]*?<\/item>/gi) ?? [];
|
|
62
|
+
for (const block of blocks) {
|
|
63
|
+
const title = extractTag(block, "title");
|
|
64
|
+
const link = extractTag(block, "link");
|
|
65
|
+
if (!title || !link) continue;
|
|
66
|
+
const pubDate = extractTag(block, "pubDate");
|
|
67
|
+
const ts = Date.parse(pubDate);
|
|
68
|
+
if (!Number.isFinite(ts)) continue;
|
|
69
|
+
const source = extractTag(block, "source") || "googlenews";
|
|
70
|
+
items.push({
|
|
71
|
+
source,
|
|
72
|
+
title,
|
|
73
|
+
url: link,
|
|
74
|
+
publishedAt: new Date(ts).toISOString()
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
return items;
|
|
78
|
+
}
|
|
79
|
+
async function fetchGooglenews(fetchImpl, topic, limit) {
|
|
80
|
+
const url = new URL(GOOGLE_NEWS_RSS_URL);
|
|
81
|
+
url.searchParams.set("q", topic);
|
|
82
|
+
url.searchParams.set("hl", "en-US");
|
|
83
|
+
url.searchParams.set("gl", "US");
|
|
84
|
+
url.searchParams.set("ceid", "US:en");
|
|
85
|
+
return parseGoogleNewsRss(await fetchText(url.toString(), fetchImpl, "googlenews")).slice(0, limit);
|
|
86
|
+
}
|
|
87
|
+
function parseSecEdgarAtom(xml) {
|
|
88
|
+
const items = [];
|
|
89
|
+
const entries = xml.match(/<entry[\s>][\s\S]*?<\/entry>/gi) ?? [];
|
|
90
|
+
for (const entry of entries) {
|
|
91
|
+
const title = extractTag(entry, "title");
|
|
92
|
+
let link = extractTag(entry, "link");
|
|
93
|
+
if (!link) {
|
|
94
|
+
const hrefMatch = entry.match(/<link[^>]+href="([^">]+)"/i);
|
|
95
|
+
if (hrefMatch) link = hrefMatch[1] ?? "";
|
|
96
|
+
}
|
|
97
|
+
if (!title || !link) continue;
|
|
98
|
+
const updated = extractTag(entry, "updated") || extractTag(entry, "published");
|
|
99
|
+
const ts = Date.parse(updated);
|
|
100
|
+
if (!Number.isFinite(ts)) continue;
|
|
101
|
+
items.push({
|
|
102
|
+
source: "sec-edgar",
|
|
103
|
+
title: title.replace(/\s+/g, " ").trim(),
|
|
104
|
+
url: link,
|
|
105
|
+
publishedAt: new Date(ts).toISOString()
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
return items;
|
|
109
|
+
}
|
|
110
|
+
async function fetchSecEdgarAnnouncements(fetchImpl, symbol, limit) {
|
|
111
|
+
const clean = symbol.trim().toUpperCase();
|
|
112
|
+
if (!clean || clean === DEFAULT_QUERY_TOPIC.toUpperCase()) return [];
|
|
113
|
+
const url = new URL("https://www.sec.gov/cgi-bin/browse-edgar");
|
|
114
|
+
url.searchParams.set("action", "getcompany");
|
|
115
|
+
url.searchParams.set("CIK", clean);
|
|
116
|
+
url.searchParams.set("type", "");
|
|
117
|
+
url.searchParams.set("dateb", "");
|
|
118
|
+
url.searchParams.set("owner", "exclude");
|
|
119
|
+
url.searchParams.set("start", "0");
|
|
120
|
+
url.searchParams.set("count", String(Math.max(limit, 20)));
|
|
121
|
+
url.searchParams.set("output", "atom");
|
|
122
|
+
return parseSecEdgarAtom(await fetchText(url.toString(), fetchImpl, "sec-edgar")).slice(0, limit);
|
|
123
|
+
}
|
|
124
|
+
function inWindow(publishedAt, nowMs, windowMs) {
|
|
125
|
+
const ts = Date.parse(publishedAt);
|
|
126
|
+
if (!Number.isFinite(ts)) return false;
|
|
127
|
+
return ts >= nowMs - windowMs && ts <= nowMs + 6e4;
|
|
128
|
+
}
|
|
129
|
+
function matchesSymbol(item, rawSymbol) {
|
|
130
|
+
if (!rawSymbol) return true;
|
|
131
|
+
const needle = rawSymbol.trim().toUpperCase();
|
|
132
|
+
if (!needle) return true;
|
|
133
|
+
const title = item.title.toUpperCase();
|
|
134
|
+
if (item.source === "sec-edgar") return true;
|
|
135
|
+
return title.includes(needle);
|
|
136
|
+
}
|
|
137
|
+
function clampNumber(value, fallback, min, max) {
|
|
138
|
+
if (typeof value !== "number" || !Number.isFinite(value)) return fallback;
|
|
139
|
+
return Math.min(Math.max(Math.trunc(value), min), max);
|
|
140
|
+
}
|
|
141
|
+
async function aggregateNews(options = {}) {
|
|
142
|
+
const fetchImpl = options.fetch ?? globalThis.fetch.bind(globalThis);
|
|
143
|
+
const now = options.now ?? Date.now();
|
|
144
|
+
const windowMs = clampNumber(options.windowHours, DEFAULT_WINDOW_HOURS, 1, 168) * 36e5;
|
|
145
|
+
const limit = clampNumber(options.limit, DEFAULT_LIMIT, 1, MAX_LIMIT);
|
|
146
|
+
const topic = options.symbol?.trim() || DEFAULT_QUERY_TOPIC;
|
|
147
|
+
const fetchers = [fetchYahooNews(fetchImpl, topic, limit), fetchGooglenews(fetchImpl, topic, limit)];
|
|
148
|
+
if (options.symbol && options.symbol.trim()) fetchers.push(fetchSecEdgarAnnouncements(fetchImpl, options.symbol, limit));
|
|
149
|
+
const results = await Promise.allSettled(fetchers);
|
|
150
|
+
const items = [];
|
|
151
|
+
const unavailable = [];
|
|
152
|
+
for (const result of results) if (result.status === "fulfilled") for (const item of result.value) {
|
|
153
|
+
const maxAge = item.source === "sec-edgar" ? 2592e6 : windowMs;
|
|
154
|
+
if (!inWindow(item.publishedAt, now, maxAge)) continue;
|
|
155
|
+
if (!matchesSymbol(item, options.symbol)) continue;
|
|
156
|
+
items.push(item);
|
|
157
|
+
}
|
|
158
|
+
else unavailable.push(result.reason instanceof Error ? result.reason.message : String(result.reason));
|
|
159
|
+
items.sort((a, b) => Date.parse(b.publishedAt) - Date.parse(a.publishedAt));
|
|
160
|
+
return {
|
|
161
|
+
items: items.slice(0, limit),
|
|
162
|
+
unavailable
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
//#endregion
|
|
166
|
+
export { aggregateNews, parseGoogleNewsRss, parseSecEdgarAtom };
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dshtrading/kit-us",
|
|
3
|
+
"description": "US market toolkit plugin for dsh-trading: US risk checklist skill provider and US news / fundamentals tools; runs as a us-trader preset row (session-scoped)",
|
|
4
|
+
"version": "0.1.0",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "lib/index.js",
|
|
7
|
+
"types": "./lib/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./lib/index.d.ts",
|
|
11
|
+
"default": "./lib/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./package.json": "./package.json"
|
|
14
|
+
},
|
|
15
|
+
"files": [
|
|
16
|
+
"lib",
|
|
17
|
+
"assets"
|
|
18
|
+
],
|
|
19
|
+
"license": "PolyForm-Noncommercial-1.0.0",
|
|
20
|
+
"dependencies": {
|
|
21
|
+
"@dshtrading/indicators": "0.1.0",
|
|
22
|
+
"@dshtrading/knowledge": "0.1.0"
|
|
23
|
+
},
|
|
24
|
+
"peerDependencies": {
|
|
25
|
+
"@deepseek-ai/cordis": ">=4.0.0",
|
|
26
|
+
"@deepseek-ai/dsh-skill": ">=0.1.2-alpha.1",
|
|
27
|
+
"@deepseek-ai/dsh-tools": ">=0.1.2-alpha.1",
|
|
28
|
+
"@deepseek-ai/schemastery": ">=3.18.0"
|
|
29
|
+
},
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"tsdown": "^0.22.0",
|
|
32
|
+
"vitest": "^3.0.0",
|
|
33
|
+
"@dshtrading/api": "0.1.0"
|
|
34
|
+
},
|
|
35
|
+
"scripts": {
|
|
36
|
+
"build": "tsdown",
|
|
37
|
+
"test": "vitest run"
|
|
38
|
+
}
|
|
39
|
+
}
|