@dshtrading/connector-jin10 0.3.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 ADDED
@@ -0,0 +1,75 @@
1
+ Copyright (c) 2026 zhu1090093659 (dsh-trading)
2
+
3
+ # PolyForm Noncommercial License 1.0.0
4
+
5
+ <https://polyformproject.org/licenses/noncommercial/1.0.0>
6
+
7
+ ## Acceptance
8
+
9
+ In order to get any license under these terms, you must agree to them as both strict obligations and conditions to all your licenses.
10
+
11
+ ## Copyright License
12
+
13
+ The licensor grants you a copyright license for the software to do everything you might do with the software that would otherwise infringe the licensor's copyright in it for any permitted purpose. However, you may only distribute the software according to [Distribution License](#distribution-license) and make changes or new works based on the software according to [Changes and New Works License](#changes-and-new-works-license).
14
+
15
+ ## Distribution License
16
+
17
+ The licensor grants you an additional copyright license to distribute copies of the software. Your license to distribute covers distributing the software with changes and new works permitted by [Changes and New Works License](#changes-and-new-works-license).
18
+
19
+ ## Notices
20
+
21
+ You must ensure that anyone who gets a copy of any part of the software from you also gets a copy of these terms or the URL for them above, as well as copies of any plain-text lines beginning with `Required Notice:` that the licensor provided with the software. For example:
22
+
23
+ > Required Notice: Copyright Yoyodyne, Inc. (http://example.com)
24
+
25
+ ## Changes and New Works License
26
+
27
+ The licensor grants you an additional copyright license to make changes and new works based on the software for any permitted purpose.
28
+
29
+ ## Patent License
30
+
31
+ The licensor grants you a patent license for the software that covers patent claims the licensor can license, or becomes able to license, that you would infringe by using the software.
32
+
33
+ ## Noncommercial Purposes
34
+
35
+ Any noncommercial purpose is a permitted purpose.
36
+
37
+ ## Personal Uses
38
+
39
+ Personal use for research, experiment, and testing for the benefit of public knowledge, personal study, private entertainment, hobby projects, amateur pursuits, or religious observance, without any anticipated commercial application, is use for a permitted purpose.
40
+
41
+ ## Noncommercial Organizations
42
+
43
+ Use by any charitable organization, educational institution, public research organization, public safety or health organization, environmental protection organization, or government institution is use for a permitted purpose regardless of the source of funding or obligations resulting from the funding.
44
+
45
+ ## Fair Use
46
+
47
+ You may have "fair use" rights for the software under the law. These terms do not limit them.
48
+
49
+ ## No Other Rights
50
+
51
+ These terms do not allow you to sublicense or transfer any of your licenses to anyone else, or prevent the licensor from granting licenses to anyone else. These terms do not imply any other licenses.
52
+
53
+ ## Patent Defense
54
+
55
+ If you make any written claim that the software infringes or contributes to infringement of any patent, your patent license for the software granted under these terms ends immediately. If your company makes such a claim, your patent license ends immediately for work on behalf of your company.
56
+
57
+ ## Violations
58
+
59
+ The first time you are notified in writing that you have violated any of these terms, or done anything with the software not covered by your licenses, your licenses can nonetheless continue if you come into full compliance with these terms, and take practical steps to correct past violations, within 32 days of receiving notice. Otherwise, all your licenses end immediately.
60
+
61
+ ## No Liability
62
+
63
+ ***As far as the law allows, the software comes as is, without any warranty or condition, and the licensor will not be liable to you for any damages arising out of these terms or the use or nature of the software, under any kind of legal claim.***
64
+
65
+ ## Definitions
66
+
67
+ The **licensor** is the individual or entity offering these terms, and the **software** is the software the licensor makes available under these terms.
68
+
69
+ **You** refers to the individual or entity agreeing to these terms.
70
+
71
+ **Your company** is any legal entity, sole proprietorship, or other kind of organization that you work for, plus all organizations that have control over, are under the control of, or are under common control with that organization. **Control** means ownership of substantially all the assets of an entity, or the power to direct its management and policies by vote, contract, or otherwise. Control can be direct or indirect.
72
+
73
+ **Your licenses** are all the licenses granted to you for the software under these terms.
74
+
75
+ **Use** means anything you do with the software requiring one of your licenses.
package/README.md ADDED
@@ -0,0 +1,76 @@
1
+ # @dshtrading/connector-jin10
2
+
3
+ 金十数据(Jin10)MCP 连接器:把 `https://mcp.jin10.com/mcp` 的**市场快讯、财经资讯、财经日历与全球品种行情**封装成本仓的 agent 工具面。
4
+
5
+ 与交易所连接器的区别:本包**没有行情/交易服务面**(不 provide `trading<Market>MarketData`,不进市场路由),只注册只读工具——金十内容是跨市场的(宏观/大宗/外汇/A 股/地缘),故为**市场无关共享行**,归 `@dshtrading/base` 所有(README 设计铁律 #1)。
6
+
7
+ ## 工具面
8
+
9
+ | 工具 | 上游 MCP 工具 | 说明 |
10
+ | :--- | :--- | :--- |
11
+ | `flash_list` | `list_flash` | 最新快讯流,`cursor` 翻页 |
12
+ | `flash_search` | `search_flash` | 关键词搜快讯(一次最多 150 条,上游不支持翻页) |
13
+ | `news_list` | `list_news` | 最新财经资讯列表,`cursor` 翻页 |
14
+ | `news_search` | `search_news` | 关键词搜资讯,`cursor` 翻页 |
15
+ | `news_get` | `get_news` | 单篇资讯元数据(标题/时间/链接/导语) |
16
+ | `econ_calendar` | `list_calendar` | 本周财经日历(时间/星级/前值/预期/公布/影响) |
17
+ | `global_instruments` | `quote://codes` 资源 | 品种代码名册(现货/能源/外汇/指数),可关键词过滤 |
18
+ | `global_quote` | `get_quote` | 单品种实时报价 |
19
+ | `global_klines` | `get_kline` | 分钟级 K 线(`time` 起点后 24h 窗内,`count` ≤ 100) |
20
+
21
+ 工具名不带 provider 名:换快讯源不改工具面(与 `symbol_search`、`knowledge_search` 同族的跨市场命名)。
22
+
23
+ ## 配置(Config)
24
+
25
+ ```yaml
26
+ - id: dsh-trading-connector-jin10
27
+ name: '@dshtrading/connector-jin10'
28
+ config:
29
+ enabled: true # 缺省 true
30
+ endpoint: https://mcp.jin10.com/mcp # 缺省即此
31
+ tokenRef: JIN10_MCP_TOKEN # 环境变量名(BYOK)
32
+ timeoutMs: 15000
33
+ ```
34
+
35
+ 凭证解析(BYOK,不内置密钥):设置中心 `dshtrading.credentials.jin10.token` 优先,`process.env[tokenRef]` 兜底;**每次请求惰性解析**,所以 settings 用户层在插件 apply 之后加载/修改同样生效。缺凭证时调用报 `TRADING_CREDENTIALS_MISSING`,消息里带配置路径。
36
+
37
+ ## 上游契约(2026-09-13 实测,证据见 `spikes/impl-jin10-mcp/EVIDENCE/`)
38
+
39
+ - 传输:Streamable HTTP,JSON-RPC 2.0;响应是 `text/event-stream` 里的单行 `data: {...}`(也吃直出 JSON)。协议版本 `2025-11-25`,`initialize → notifications/initialized → tools/list / resources/list / resources/read → tools/call`。
40
+ - 鉴权:`Authorization: Bearer <token>`;上游当前不返回 `mcp-session-id`(有则该头原样回带)。
41
+ - 结果读取:`result.structuredContent` 优先;`result.content[].text` 只在 structuredContent 缺席时作机器可读兜底。
42
+ - 分页:请求 `cursor`、响应 `data.next_cursor` + `data.has_more`(不传未声明参数,例如不要用 `offset`)。
43
+ - 错误:`isError=true`、`status !== 200`、`data === null` 一律抛错(不冒充空数据);限流文案「今日该工具调用次数已达上限」映射 `TRADING_RATE_LIMITED`,未知品种映射 `TRADING_UNSUPPORTED_SYMBOL`。
44
+ - 限流:每用户每工具每北京时间自然日 1500 次。
45
+
46
+ ## global 市场数据面(行情接市场路由,2026-09-13)
47
+
48
+ 同包还有一个数据面插件行,把金十行情接进本仓的市场路由:
49
+
50
+ ```yaml
51
+ - id: dsh-trading-global-dataplane-jin10
52
+ name: '@dshtrading/connector-jin10/dataplane'
53
+ ```
54
+
55
+ - 市场 id `global`(provider slug `jin10`):provide `tradingGlobalMarketData` + 向 `tradingMarketDataRegistry` 注册 `(global, jin10)`;GUI 行情面板/自选/图表与 `router` 的 `DEFAULT_MARKETS.global` 由此生效。
56
+ - 实现(`market-data.ts`):`get_quote` → Ticker(昨收由 `close - ups_price` 反推)、`get_kline` → Kline、`quote://codes` → `listInstruments`(97 品种,1h 缓存)、`subscribeTicker` 5s 轮询。
57
+ - **周期与深度**:上游只有分钟 K 线,单次上限 100 根且语义是「从 time 往后取」,本层最多拼 300 分钟窗口(3 次上游调用)→ 支持 `1m/3m/5m/15m/30m/1h`,`1d` 及以上显式报错(不换源、不用单日分钟冒充历史)。闭市窗口返回空序列(上游 `klines: []`,合法结果)。
58
+ - 凭证与主行同源(`credentials.jin10.token` / `JIN10_MCP_TOKEN`,逐请求惰性解析);未配置时面板/桥报凭证缺失,不伪装成空行情。
59
+
60
+ ## 数据边界(铁律 #5 数据零再分发)
61
+
62
+ - 快讯只下发**标题(从 `【标题】正文` 提取)/时间/链接**,`content` 正文丢弃;
63
+ - 资讯下发标题/时间/链接/`id`,详情额外给上游 `introduction`(导语),`content` 正文丢弃;
64
+ - 时间解析失败一律丢弃该条,绝不回退「现在」;
65
+ - 不缓存、不落盘、不批量抓取。
66
+
67
+ ## 验证
68
+
69
+ ```sh
70
+ pnpm --filter @dshtrading/connector-jin10 test # 67 例:传输/握手/错误映射/解析/工具渲染/插件接线/快讯面/数据面
71
+ JIN10_TOKEN=sk-... node spikes/impl-jin10-mcp/verify.mjs # 出网验证(构建产物 + 真实 MCP)
72
+ ```
73
+
74
+ `verify.mjs` 覆盖资源 → 快讯两页翻页 → 快讯搜索 → 资讯列表/搜索/详情 → 财经日历 → 报价 → K 线,以及缺凭证/非法 code 两条错误语义;证据 JSON 落 `spikes/impl-jin10-mcp/EVIDENCE/`。
75
+
76
+ 已知限制:闭市(周末)上游对全部品种回 `status: 200` + `klines: []`(工具与 `global` 市场数据面照实回空序列,属真实语义而非故障);2026-09-13(周日)实测全品种为空,分钟 K 线的拼窗口与本地聚合路径待交易日复测。`global` 市场只上 `1m/5m/15m` 周期(上游单窗口 100 根、本层最多拼 300 分钟),深度上限见上文数据面节。
@@ -0,0 +1,7 @@
1
+ import { Config } from "./plugin.js";
2
+ import { Context } from "@deepseek-ai/cordis";
3
+ //#region src/dataplane.d.ts
4
+ declare const inject: string[];
5
+ declare function apply(ctx: Context, config?: Partial<Config>): void;
6
+ //#endregion
7
+ export { Config, apply, inject };
@@ -0,0 +1,23 @@
1
+ import { GLOBAL_MARKET, JIN10_PROVIDER, TRADING_GLOBAL_MARKET_DATA_KEY, createJin10GlobalMarketDataService } from "./market-data.js";
2
+ import "./mcp.js";
3
+ import { Config, createJin10Service } from "./plugin.js";
4
+ //#region src/dataplane.ts
5
+ const inject = [];
6
+ function resolveMarketDataRegistry(ctx) {
7
+ const candidate = ctx.get?.("tradingMarketDataRegistry", false);
8
+ return candidate !== void 0 ? candidate : void 0;
9
+ }
10
+ function apply(ctx, config) {
11
+ if (config?.enabled === false) return;
12
+ const service = createJin10GlobalMarketDataService(createJin10Service(ctx, {
13
+ endpoint: config?.endpoint ?? "https://mcp.jin10.com/mcp",
14
+ tokenRef: config?.tokenRef ?? "JIN10_MCP_TOKEN",
15
+ timeoutMs: config?.timeoutMs ?? 15e3
16
+ }));
17
+ ctx.reflect.provide(TRADING_GLOBAL_MARKET_DATA_KEY, service);
18
+ const registry = resolveMarketDataRegistry(ctx);
19
+ if (registry === void 0) return;
20
+ ctx.effect(() => registry.register(GLOBAL_MARKET, JIN10_PROVIDER, service));
21
+ }
22
+ //#endregion
23
+ export { Config, apply, inject };
@@ -0,0 +1,18 @@
1
+ import { TradingErrorCode } from "@dshtrading/api";
2
+ //#region src/errors.d.ts
3
+ /** 本连接器的凭证提示(错误消息里必须给出可执行的配置路径 —— BYOK 铁律)。 */
4
+ declare const TOKEN_HINT = "配置金十 MCP Token:设置中心 dshtrading.credentials.jin10.token,或环境变量 JIN10_MCP_TOKEN。";
5
+ /** 错误词汇对齐 @dshtrading/api(本包不做跨包 Error 类基座,与 connector-eastmoney 同款)。 */
6
+ declare class Jin10Error extends Error {
7
+ readonly code: TradingErrorCode;
8
+ constructor(code: TradingErrorCode, message: string, cause?: unknown);
9
+ }
10
+ /**
11
+ * 上游业务错误消息 → 错误码。
12
+ * 限流文案必须单独识别(决定「今天别再试」的语义);「不支持该品种」= 代码不在
13
+ * quote://codes 名册内(本出口实测文案,2026-09-13),映射到 UNSUPPORTED_SYMBOL
14
+ * 让工具层语义与市场工具族一致。
15
+ */
16
+ declare function codeForUpstreamMessage(message: string): TradingErrorCode;
17
+ //#endregion
18
+ export { Jin10Error, TOKEN_HINT, codeForUpstreamMessage };
package/lib/errors.js ADDED
@@ -0,0 +1,26 @@
1
+ //#region src/errors.ts
2
+ /** 本连接器的凭证提示(错误消息里必须给出可执行的配置路径 —— BYOK 铁律)。 */
3
+ const TOKEN_HINT = "配置金十 MCP Token:设置中心 dshtrading.credentials.jin10.token,或环境变量 JIN10_MCP_TOKEN。";
4
+ /** 错误词汇对齐 @dshtrading/api(本包不做跨包 Error 类基座,与 connector-eastmoney 同款)。 */
5
+ var Jin10Error = class extends Error {
6
+ code;
7
+ constructor(code, message, cause) {
8
+ super(message);
9
+ this.name = "Jin10Error";
10
+ this.code = code;
11
+ if (cause !== void 0) this.cause = cause;
12
+ }
13
+ };
14
+ /**
15
+ * 上游业务错误消息 → 错误码。
16
+ * 限流文案必须单独识别(决定「今天别再试」的语义);「不支持该品种」= 代码不在
17
+ * quote://codes 名册内(本出口实测文案,2026-09-13),映射到 UNSUPPORTED_SYMBOL
18
+ * 让工具层语义与市场工具族一致。
19
+ */
20
+ function codeForUpstreamMessage(message) {
21
+ if (/上限|限流|频率|rate.?limit|too many/i.test(message)) return "TRADING_RATE_LIMITED";
22
+ if (/不支持该品种|unsupported (instrument|code|symbol)/i.test(message)) return "TRADING_UNSUPPORTED_SYMBOL";
23
+ return "TRADING_EXCHANGE_ERROR";
24
+ }
25
+ //#endregion
26
+ export { Jin10Error, TOKEN_HINT, codeForUpstreamMessage };
@@ -0,0 +1,8 @@
1
+ import { Jin10Service } from "./service.js";
2
+ import { FlashFeedService } from "@dshtrading/api";
3
+ //#region src/flash-service.d.ts
4
+ declare const TRADING_FLASH_FEED_KEY = "tradingFlashFeed";
5
+ /** 快讯源实现(桥与工具面共用同一 Jin10Service)。 */
6
+ declare function createJin10FlashFeed(feed: Jin10Service): FlashFeedService;
7
+ //#endregion
8
+ export { TRADING_FLASH_FEED_KEY, createJin10FlashFeed };
@@ -0,0 +1,30 @@
1
+ import { isJin10HeatLevel } from "./web-flash.js";
2
+ //#region src/flash-service.ts
3
+ const TRADING_FLASH_FEED_KEY = "tradingFlashFeed";
4
+ /** 快讯源实现(桥与工具面共用同一 Jin10Service)。 */
5
+ function createJin10FlashFeed(feed) {
6
+ return {
7
+ /**
8
+ * 最新快讯流(cursor 翻页;条目只含元数据,正文零再分发)。
9
+ * `hot` 含有效热度等级时走网页版服务端过滤(火/热/沸/爆);否则走 MCP 全量流。
10
+ */
11
+ async listFlash(options = {}) {
12
+ const hot = (options.hot ?? []).filter(isJin10HeatLevel);
13
+ const page = hot.length > 0 ? await feed.listFlashByHeat({
14
+ hot,
15
+ ...options.cursor !== void 0 ? { cursor: options.cursor } : {}
16
+ }) : await feed.listFlash(options);
17
+ return {
18
+ items: page.items,
19
+ hasMore: page.hasMore,
20
+ ...page.nextCursor !== void 0 ? { nextCursor: page.nextCursor } : {}
21
+ };
22
+ },
23
+ /** 关键词搜快讯(上游一次性返回、不支持翻页)。 */
24
+ async searchFlash(keyword, limit) {
25
+ return feed.searchFlash(keyword, limit);
26
+ }
27
+ };
28
+ }
29
+ //#endregion
30
+ export { TRADING_FLASH_FEED_KEY, createJin10FlashFeed };
package/lib/index.d.ts ADDED
@@ -0,0 +1,10 @@
1
+ import { DEFAULT_ENDPOINT, DEFAULT_PROTOCOL_VERSION, DEFAULT_TIMEOUT_MS, Jin10McpClient, Jin10McpOptions, McpContentPart, McpResourceDescriptor, McpToolDescriptor, McpToolResult, parseMcpBody } from "./mcp.js";
2
+ import { Jin10ArticleDetail, Jin10ArticleSummary, Jin10CalendarEntry, Jin10Instrument, Jin10Kline, Jin10NewsItem, Jin10NewsPage, Jin10Quote, flashTitle, parseArticle, parseCalendar, parseFlashItems, parseFlashPage, parseJin10Time, parseKlines, parseNewsItems, parseNewsPage, parseQuote, parseQuoteCodes } from "./parse.js";
3
+ import { JIN10_HEAT_LEVELS, JIN10_WEB_APP_ID, JIN10_WEB_FLASH_CHANNELS, JIN10_WEB_FLASH_ENDPOINT, JIN10_WEB_PAGE_SIZE, JIN10_WEB_X_VERSION, Jin10FlashItem, Jin10HeatLevel, Jin10HotFlashOptions, fetchJin10HotFlash, isJin10HeatLevel, parseWebFlashItems } from "./web-flash.js";
4
+ import { FLASH_PAGE_SIZE, INSTRUMENT_CACHE_TTL_MS, Jin10PageOptions, Jin10Service, SEARCH_FLASH_MAX } from "./service.js";
5
+ import { Config, apply, inject, name } from "./plugin.js";
6
+ import { Jin10Error, TOKEN_HINT, codeForUpstreamMessage } from "./errors.js";
7
+ import { TRADING_FLASH_FEED_KEY, createJin10FlashFeed } from "./flash-service.js";
8
+ import { GLOBAL_MARKET, JIN10_PROVIDER, MAX_MINUTE_WINDOW, MINUTE_BARS_PER_CALL, TICKER_POLL_MS, TRADING_GLOBAL_MARKET_DATA_KEY, aggregateKlines, createJin10GlobalMarketDataService, resolveIntervalMinutes, tickerFromQuote } from "./market-data.js";
9
+ import { createJin10Tools } from "./tools.js";
10
+ export { Config, DEFAULT_ENDPOINT, DEFAULT_PROTOCOL_VERSION, DEFAULT_TIMEOUT_MS, FLASH_PAGE_SIZE, GLOBAL_MARKET, INSTRUMENT_CACHE_TTL_MS, JIN10_HEAT_LEVELS, JIN10_PROVIDER, JIN10_WEB_APP_ID, JIN10_WEB_FLASH_CHANNELS, JIN10_WEB_FLASH_ENDPOINT, JIN10_WEB_PAGE_SIZE, JIN10_WEB_X_VERSION, Jin10ArticleDetail, Jin10ArticleSummary, Jin10CalendarEntry, Jin10Error, Jin10FlashItem, Jin10HeatLevel, Jin10HotFlashOptions, Jin10Instrument, Jin10Kline, Jin10McpClient, Jin10McpOptions, Jin10NewsItem, Jin10NewsPage, Jin10PageOptions, Jin10Quote, Jin10Service, MAX_MINUTE_WINDOW, MINUTE_BARS_PER_CALL, McpContentPart, McpResourceDescriptor, McpToolDescriptor, McpToolResult, SEARCH_FLASH_MAX, TICKER_POLL_MS, TOKEN_HINT, TRADING_FLASH_FEED_KEY, TRADING_GLOBAL_MARKET_DATA_KEY, aggregateKlines, apply, codeForUpstreamMessage, createJin10FlashFeed, createJin10GlobalMarketDataService, createJin10Tools, fetchJin10HotFlash, flashTitle, inject, isJin10HeatLevel, name, parseArticle, parseCalendar, parseFlashItems, parseFlashPage, parseJin10Time, parseKlines, parseMcpBody, parseNewsItems, parseNewsPage, parseQuote, parseQuoteCodes, parseWebFlashItems, resolveIntervalMinutes, tickerFromQuote };
package/lib/index.js ADDED
@@ -0,0 +1,10 @@
1
+ import { Jin10Error, TOKEN_HINT, codeForUpstreamMessage } from "./errors.js";
2
+ import { flashTitle, parseArticle, parseCalendar, parseFlashItems, parseFlashPage, parseJin10Time, parseKlines, parseNewsItems, parseNewsPage, parseQuote, parseQuoteCodes } from "./parse.js";
3
+ import { JIN10_HEAT_LEVELS, JIN10_WEB_APP_ID, JIN10_WEB_FLASH_CHANNELS, JIN10_WEB_FLASH_ENDPOINT, JIN10_WEB_PAGE_SIZE, JIN10_WEB_X_VERSION, fetchJin10HotFlash, isJin10HeatLevel, parseWebFlashItems } from "./web-flash.js";
4
+ import { TRADING_FLASH_FEED_KEY, createJin10FlashFeed } from "./flash-service.js";
5
+ import { GLOBAL_MARKET, JIN10_PROVIDER, MAX_MINUTE_WINDOW, MINUTE_BARS_PER_CALL, TICKER_POLL_MS, TRADING_GLOBAL_MARKET_DATA_KEY, aggregateKlines, createJin10GlobalMarketDataService, resolveIntervalMinutes, tickerFromQuote } from "./market-data.js";
6
+ import { DEFAULT_ENDPOINT, DEFAULT_PROTOCOL_VERSION, DEFAULT_TIMEOUT_MS, Jin10McpClient, parseMcpBody } from "./mcp.js";
7
+ import { FLASH_PAGE_SIZE, INSTRUMENT_CACHE_TTL_MS, Jin10Service, SEARCH_FLASH_MAX } from "./service.js";
8
+ import { createJin10Tools } from "./tools.js";
9
+ import { Config, apply, inject, name } from "./plugin.js";
10
+ export { Config, DEFAULT_ENDPOINT, DEFAULT_PROTOCOL_VERSION, DEFAULT_TIMEOUT_MS, FLASH_PAGE_SIZE, GLOBAL_MARKET, INSTRUMENT_CACHE_TTL_MS, JIN10_HEAT_LEVELS, JIN10_PROVIDER, JIN10_WEB_APP_ID, JIN10_WEB_FLASH_CHANNELS, JIN10_WEB_FLASH_ENDPOINT, JIN10_WEB_PAGE_SIZE, JIN10_WEB_X_VERSION, Jin10Error, Jin10McpClient, Jin10Service, MAX_MINUTE_WINDOW, MINUTE_BARS_PER_CALL, SEARCH_FLASH_MAX, TICKER_POLL_MS, TOKEN_HINT, TRADING_FLASH_FEED_KEY, TRADING_GLOBAL_MARKET_DATA_KEY, aggregateKlines, apply, codeForUpstreamMessage, createJin10FlashFeed, createJin10GlobalMarketDataService, createJin10Tools, fetchJin10HotFlash, flashTitle, inject, isJin10HeatLevel, name, parseArticle, parseCalendar, parseFlashItems, parseFlashPage, parseJin10Time, parseKlines, parseMcpBody, parseNewsItems, parseNewsPage, parseQuote, parseQuoteCodes, parseWebFlashItems, resolveIntervalMinutes, tickerFromQuote };
@@ -0,0 +1,27 @@
1
+ import { Jin10Quote } from "./parse.js";
2
+ import { Jin10Service } from "./service.js";
3
+ import { Kline, MarketDataService, Ticker } from "@dshtrading/api";
4
+ //#region src/market-data.d.ts
5
+ /** 市场 id:全局品种(大宗/外汇/海外指数),与 crypto/us/cn/hk/futures 同级。 */
6
+ declare const GLOBAL_MARKET = "global";
7
+ /** 路由 provider slug(router PROVIDER_VOCABULARY 同步登记)。 */
8
+ declare const JIN10_PROVIDER = "jin10";
9
+ /** Context 服务键(与其余市场 trading<Market>MarketData 命名一致)。 */
10
+ declare const TRADING_GLOBAL_MARKET_DATA_KEY = "tradingGlobalMarketData";
11
+ /** 上游单次 get_kline 上限(count 1..100,从 time 往后取)。 */
12
+ declare const MINUTE_BARS_PER_CALL = 100;
13
+ /** 本仓最多拼的分钟窗口(3 次上游调用);再深的历史上游没有,不做假数据。 */
14
+ declare const MAX_MINUTE_WINDOW = 300;
15
+ /** Ticker 轮询周期(上游无推送通道)。 */
16
+ declare const TICKER_POLL_MS = 5000;
17
+ declare function resolveIntervalMinutes(interval: string): number | undefined;
18
+ /** 按分钟数聚合(桶起点 = 本地时间轴对齐;量能求和,缺量按 0)。 */
19
+ declare function aggregateKlines(bars: readonly Kline[], minutes: number): Kline[];
20
+ /** get_quote → Ticker(缺失最新价 = 上游形状异常,如实报错,不编价格)。 */
21
+ declare function tickerFromQuote(quote: Jin10Quote): Ticker;
22
+ /** global 市场数据面实现(纯对象;与快讯面同款,避免 Service 构造签名的类型漂移)。 */
23
+ declare function createJin10GlobalMarketDataService(feed: Jin10Service, options?: {
24
+ pollMs?: number;
25
+ }): MarketDataService;
26
+ //#endregion
27
+ export { GLOBAL_MARKET, JIN10_PROVIDER, MAX_MINUTE_WINDOW, MINUTE_BARS_PER_CALL, TICKER_POLL_MS, TRADING_GLOBAL_MARKET_DATA_KEY, aggregateKlines, createJin10GlobalMarketDataService, resolveIntervalMinutes, tickerFromQuote };
@@ -0,0 +1,160 @@
1
+ import { Jin10Error } from "./errors.js";
2
+ //#region src/market-data.ts
3
+ /** 市场 id:全局品种(大宗/外汇/海外指数),与 crypto/us/cn/hk/futures 同级。 */
4
+ const GLOBAL_MARKET = "global";
5
+ /** 路由 provider slug(router PROVIDER_VOCABULARY 同步登记)。 */
6
+ const JIN10_PROVIDER = "jin10";
7
+ /** Context 服务键(与其余市场 trading<Market>MarketData 命名一致)。 */
8
+ const TRADING_GLOBAL_MARKET_DATA_KEY = "tradingGlobalMarketData";
9
+ /** 上游单次 get_kline 上限(count 1..100,从 time 往后取)。 */
10
+ const MINUTE_BARS_PER_CALL = 100;
11
+ /** 本仓最多拼的分钟窗口(3 次上游调用);再深的历史上游没有,不做假数据。 */
12
+ const MAX_MINUTE_WINDOW = 300;
13
+ /** Ticker 轮询周期(上游无推送通道)。 */
14
+ const TICKER_POLL_MS = 5e3;
15
+ /** 支持的周期 → 每根分钟数(api Interval 的子集:分钟级来源能诚实支撑的部分)。 */
16
+ const INTERVAL_MINUTES = {
17
+ "1m": 1,
18
+ "3m": 3,
19
+ "5m": 5,
20
+ "15m": 15,
21
+ "30m": 30,
22
+ "1h": 60
23
+ };
24
+ function resolveIntervalMinutes(interval) {
25
+ return INTERVAL_MINUTES[interval];
26
+ }
27
+ /** 按分钟数聚合(桶起点 = 本地时间轴对齐;量能求和,缺量按 0)。 */
28
+ function aggregateKlines(bars, minutes) {
29
+ if (minutes <= 1) return bars.map((bar) => ({ ...bar }));
30
+ const bucketMs = minutes * 6e4;
31
+ const out = [];
32
+ for (const bar of bars) {
33
+ const start = Math.floor(bar.openTime / bucketMs) * bucketMs;
34
+ const last = out[out.length - 1];
35
+ if (last !== void 0 && last.openTime === start) {
36
+ out[out.length - 1] = {
37
+ openTime: last.openTime,
38
+ closeTime: bar.closeTime,
39
+ open: last.open,
40
+ high: Math.max(last.high, bar.high),
41
+ low: Math.min(last.low, bar.low),
42
+ close: bar.close,
43
+ volume: last.volume + bar.volume
44
+ };
45
+ continue;
46
+ }
47
+ out.push({
48
+ ...bar,
49
+ openTime: start,
50
+ closeTime: start + bucketMs - 1
51
+ });
52
+ }
53
+ return out;
54
+ }
55
+ /** get_quote → Ticker(缺失最新价 = 上游形状异常,如实报错,不编价格)。 */
56
+ function tickerFromQuote(quote) {
57
+ const price = quote.close;
58
+ if (price === void 0 || !Number.isFinite(price)) throw new Jin10Error("TRADING_EXCHANGE_ERROR", "jin10 get_quote: " + quote.code + " has no latest price (market closed or symbol unavailable)");
59
+ const change = quote.change;
60
+ const prevClose = change === void 0 ? void 0 : Number((price - change).toFixed(6));
61
+ const parsedTime = quote.time === void 0 ? NaN : Date.parse(quote.time);
62
+ return {
63
+ symbol: quote.code.toUpperCase(),
64
+ ...quote.name !== void 0 ? { name: quote.name } : {},
65
+ price,
66
+ ...prevClose !== void 0 ? { prevClose } : {},
67
+ ...quote.changePercent !== void 0 ? { changePercent: quote.changePercent } : {},
68
+ ...quote.volume !== void 0 ? { volume: quote.volume } : {},
69
+ timestamp: Number.isFinite(parsedTime) ? parsedTime : Date.now()
70
+ };
71
+ }
72
+ /**
73
+ * 单根分钟 K → 契约 Kline(openTime/closeTime 毫秒,量缺省 0)。
74
+ * 上游字段可缺省 → 缺 OHLC 的残根返回 undefined(丢弃,不编价格)。
75
+ */
76
+ function minuteBarToKline(bar) {
77
+ const { open, high, low, close } = bar;
78
+ if (open === void 0 || high === void 0 || low === void 0 || close === void 0) return void 0;
79
+ const openTime = bar.time * 1e3;
80
+ return {
81
+ openTime,
82
+ closeTime: openTime + 6e4 - 1,
83
+ open,
84
+ high,
85
+ low,
86
+ close,
87
+ volume: bar.volume ?? 0
88
+ };
89
+ }
90
+ /** global 市场数据面实现(纯对象;与快讯面同款,避免 Service 构造签名的类型漂移)。 */
91
+ function createJin10GlobalMarketDataService(feed, options = {}) {
92
+ const pollMs = options.pollMs ?? 5e3;
93
+ const requireCode = (symbol) => {
94
+ const trimmed = symbol.trim().toUpperCase();
95
+ if (trimmed === "") throw new Jin10Error("TRADING_UNKNOWN", "jin10 global: symbol must not be empty");
96
+ return trimmed;
97
+ };
98
+ const loadTicker = async (symbol) => tickerFromQuote(await feed.getQuote(requireCode(symbol)));
99
+ /** 拼分钟窗口(从 time 往后取,最多 3 次上游调用;按时间戳去重后升序)。 */
100
+ const loadMinuteWindow = async (symbol, minutes) => {
101
+ const start = Math.floor(Date.now() / 1e3) - minutes * 60;
102
+ const calls = Math.max(1, Math.min(Math.ceil(minutes / 100), Math.ceil(3)));
103
+ const seen = /* @__PURE__ */ new Set();
104
+ const collected = [];
105
+ for (let index = 0; index < calls; index += 1) {
106
+ const from = start + index * 100 * 60;
107
+ const raw = await feed.getKlines(symbol, {
108
+ time: from,
109
+ count: 100
110
+ });
111
+ for (const bar of raw) {
112
+ if (seen.has(bar.time)) continue;
113
+ const kline = minuteBarToKline(bar);
114
+ if (kline === void 0) continue;
115
+ seen.add(bar.time);
116
+ collected.push(kline);
117
+ }
118
+ }
119
+ return collected.sort((a, b) => a.openTime - b.openTime);
120
+ };
121
+ return {
122
+ async getTicker(symbol) {
123
+ return loadTicker(symbol);
124
+ },
125
+ async getKlines(symbol, interval, limit = 120) {
126
+ const minutes = resolveIntervalMinutes(interval);
127
+ if (minutes === void 0) throw new Jin10Error("TRADING_UNKNOWN", "jin10 global: unsupported interval \"" + interval + "\" (upstream serves minute bars only: 1m/3m/5m/15m/30m/1h)");
128
+ const wanted = Math.max(1, Math.min(Math.floor(limit), Math.floor(300 / minutes)));
129
+ return aggregateKlines(await loadMinuteWindow(requireCode(symbol), wanted * minutes), minutes).slice(-wanted);
130
+ },
131
+ subscribeTicker(symbol, listener) {
132
+ let stopped = false;
133
+ const tick = async () => {
134
+ if (stopped) return;
135
+ try {
136
+ const ticker = await loadTicker(symbol);
137
+ if (!stopped) listener(ticker);
138
+ } catch (error) {
139
+ if (!stopped) console.warn("[dsh-trading] jin10 ticker poll failed for " + symbol + ":", error);
140
+ }
141
+ };
142
+ tick();
143
+ const timer = setInterval(() => {
144
+ tick();
145
+ }, pollMs);
146
+ return { dispose() {
147
+ stopped = true;
148
+ clearInterval(timer);
149
+ } };
150
+ },
151
+ async listInstruments() {
152
+ return (await feed.listInstruments()).map((item) => ({
153
+ symbol: item.code.toUpperCase(),
154
+ name: item.name
155
+ }));
156
+ }
157
+ };
158
+ }
159
+ //#endregion
160
+ export { GLOBAL_MARKET, JIN10_PROVIDER, MAX_MINUTE_WINDOW, MINUTE_BARS_PER_CALL, TICKER_POLL_MS, TRADING_GLOBAL_MARKET_DATA_KEY, aggregateKlines, createJin10GlobalMarketDataService, resolveIntervalMinutes, tickerFromQuote };
package/lib/mcp.d.ts ADDED
@@ -0,0 +1,81 @@
1
+ //#region src/mcp.d.ts
2
+ declare const DEFAULT_ENDPOINT = "https://mcp.jin10.com/mcp";
3
+ declare const DEFAULT_PROTOCOL_VERSION = "2025-11-25";
4
+ declare const DEFAULT_TIMEOUT_MS = 15000;
5
+ interface McpToolDescriptor {
6
+ name: string;
7
+ description?: string;
8
+ inputSchema?: unknown;
9
+ outputSchema?: unknown;
10
+ }
11
+ interface McpResourceDescriptor {
12
+ uri: string;
13
+ name?: string;
14
+ mimeType?: string;
15
+ description?: string;
16
+ }
17
+ /** 可读文本段(structuredContent 缺席时的兜底来源)。 */
18
+ interface McpContentPart {
19
+ type?: string;
20
+ text?: string;
21
+ }
22
+ /** `tools/call` 的原始结果(structuredContent 优先,content 只作文本兜底)。 */
23
+ interface McpToolResult {
24
+ structuredContent?: unknown;
25
+ content?: readonly McpContentPart[];
26
+ isError?: boolean;
27
+ }
28
+ interface Jin10McpOptions {
29
+ endpoint?: string;
30
+ /** 惰性凭证源:每次请求解析(settings 热切换/异步加载晚于插件 apply 亦生效)。 */
31
+ token: () => string | undefined;
32
+ /** 单次请求超时(ms),缺省 15s。 */
33
+ timeoutMs?: number;
34
+ /** 依赖注入的 fetch(测试用 mock;缺省 globalThis.fetch)。 */
35
+ fetchImpl?: typeof globalThis.fetch;
36
+ /** 协议版本(缺省 2025-11-25)。 */
37
+ protocolVersion?: string;
38
+ clientName?: string;
39
+ clientVersion?: string;
40
+ }
41
+ interface JsonRpcMessage {
42
+ jsonrpc?: string;
43
+ id?: unknown;
44
+ result?: unknown;
45
+ error?: {
46
+ code?: unknown;
47
+ message?: unknown;
48
+ data?: unknown;
49
+ };
50
+ }
51
+ /** 把响应体解析成 JSON-RPC 消息(直出 JSON 或 SSE 单行 data 均可)。 */
52
+ declare function parseMcpBody(text: string): JsonRpcMessage | undefined;
53
+ declare class Jin10McpClient {
54
+ private readonly endpoint;
55
+ private readonly token;
56
+ private readonly timeoutMs;
57
+ private readonly fetchImpl;
58
+ private readonly protocolVersion;
59
+ private readonly clientName;
60
+ private readonly clientVersion;
61
+ private nextId;
62
+ private sessionId;
63
+ private handshake;
64
+ constructor(options: Jin10McpOptions);
65
+ listTools(): Promise<McpToolDescriptor[]>;
66
+ listResources(): Promise<McpResourceDescriptor[]>;
67
+ /** 读资源正文(quote://codes 等;返回全部 text 段拼接)。 */
68
+ readResource(uri: string): Promise<string>;
69
+ /** 原始 tools/call(不做业务解包;协议/HTTP 错误在此抛出)。 */
70
+ callTool(tool: string, args?: Record<string, unknown>): Promise<McpToolResult>;
71
+ /**
72
+ * 业务取数:structuredContent 优先,text 兜底;`isError`/`status !== 200`/`data === null`
73
+ * 都按上游业务错误抛出(含限流文案识别)。返回上游 `data` 字段。
74
+ */
75
+ callToolData(tool: string, args?: Record<string, unknown>): Promise<unknown>;
76
+ private ensureHandshake;
77
+ private performHandshake;
78
+ private rpc;
79
+ }
80
+ //#endregion
81
+ export { DEFAULT_ENDPOINT, DEFAULT_PROTOCOL_VERSION, DEFAULT_TIMEOUT_MS, Jin10McpClient, Jin10McpOptions, McpContentPart, McpResourceDescriptor, McpToolDescriptor, McpToolResult, parseMcpBody };