@dshtrading/connector-tushare 0.6.1 → 0.6.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +126 -25
- package/lib/dataplane.d.ts +2 -2
- package/lib/dataplane.js +8 -7
- package/lib/index.d.ts +19 -6
- package/lib/index.js +27 -12
- package/lib/rest.d.ts +5 -3
- package/lib/rest.js +5 -9
- package/package.json +5 -5
package/README.md
CHANGED
|
@@ -1,34 +1,135 @@
|
|
|
1
|
-
# @dshtrading/connector-
|
|
1
|
+
# @dshtrading/connector-tushare
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
Tushare Pro A 股连接器:提供 `tradingCnMarketData` 行情服务与 3 个 `cn_*` 工具
|
|
4
|
+
(`cn_get_ticker` / `cn_get_klines` / `cn_get_valuation`),Token 走 BYOK(设置中心
|
|
5
|
+
`credentials.tushare.token` 优先,`tokenRef` 指向的环境变量兜底)。包内另有
|
|
6
|
+
`TushareTradeService`,但未注册进交易注册表、无下单工具,其 rest 层是本地模拟、账户读面失败关闭(见「已知边界」)。
|
|
5
7
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
8
|
+
## 状态
|
|
9
|
+
|
|
10
|
+
**未实证**。本仓没有已提交的 tushare 出网脚本或原始响应证据:
|
|
11
|
+
[spikes/impl-service-seam-gate/NET-VERIFY.md](../../spikes/impl-service-seam-gate/NET-VERIFY.md)(2026-09-01)
|
|
12
|
+
只在「范围与未验证项」里把 tushare 归为「模拟 stub、未做真实网络验证」。
|
|
13
|
+
|
|
14
|
+
2026-10-14 本次会话实测了凭据缺失路径(无需 Token、不触网):
|
|
15
|
+
|
|
16
|
+
./node_modules/.bin/tsx -e "import('./packages/connector-tushare/src/rest.ts').then(async m => { try { await new m.TushareRestClient().getTicker('600519.SH') } catch (e) { console.log(e.name, e.code) } })"
|
|
17
|
+
# TradingServiceError TRADING_CREDENTIALS_MISSING
|
|
18
|
+
|
|
19
|
+
## 配置
|
|
20
|
+
|
|
21
|
+
| 字段 | 默认 | 说明 |
|
|
22
|
+
|---|---|---|
|
|
23
|
+
| `enabled` | `true` | 是否激活本连接器(`src/index.ts:41`) |
|
|
24
|
+
| `dryRun` | `true` | 默认模拟下单(`src/index.ts:42`) |
|
|
25
|
+
| `liveTrading` | `false` | 是否允许实盘;非权威镜像,实盘授权在 `@dshtrading/authority`(`src/index.ts:43`) |
|
|
26
|
+
| `tokenRef` | `'TUSHARE_TOKEN'` | 兜底取 Token 的环境变量名(BYOK);设置中心 `dshtrading.credentials.tushare.token` 优先(`src/index.ts:44`、`src/index.ts:185-190`) |
|
|
27
|
+
|
|
28
|
+
## 接线
|
|
29
|
+
|
|
30
|
+
- **数据面行**:`packages/cn/cordis.patch.yml:31-34` → 行 id `dsh-trading-cn-dataplane-tushare`
|
|
31
|
+
(name `@dshtrading/connector-tushare/dataplane`,行 config 只写 `enabled: true`)。
|
|
32
|
+
注册 `(cn, tushare)` 进 `tradingMarketDataRegistry`(`src/dataplane.ts:40`),不占根市场键。
|
|
33
|
+
- **preset 行**:`packages/cn/assets/preset/cn-trader/agent.cordis.yml:55-60` → 行 id
|
|
34
|
+
`dsh-trading-cn-connector-tushare`(`config: {enabled: true, dryRun: true, liveTrading: false}`)。
|
|
35
|
+
- **bundle 依赖**:`packages/cn/package.json:36` 的 `"@dshtrading/connector-tushare": "workspace:*"`。
|
|
36
|
+
- **provider 词汇**:`packages/router/src/index.ts:76`;与 `src/index.ts:166` 的
|
|
37
|
+
`ROUTER_PROVIDER = 'tushare'` 同名。
|
|
38
|
+
- **服务键**:`tradingCnMarketData`(`src/index.ts:47`;dataplane 侧 isolate 同名,`src/dataplane.ts:38`)。
|
|
39
|
+
- **凭证解析只有一份**(`createTokenProvider`,`src/index.ts:185-190`):设置中心
|
|
40
|
+
`dshtrading.credentials.tushare.token`(router `getCredential('tushare').token`)优先、
|
|
41
|
+
`process.env[config.tokenRef]` 兜底,**每次请求解析**(惰性)——settings 用户层加载/修改
|
|
42
|
+
晚于插件 `apply` 也生效。主入口(`src/index.ts:196-198`)与数据面行(`src/dataplane.ts:32`)
|
|
43
|
+
同用这一份,故设置页写入的 Token 在宿主面数据行与工具面都生效;`tokenRef` 只是环境变量名,
|
|
44
|
+
凭证值只经 ref 与设置中心,不进日志/回执(收敛记录见
|
|
45
|
+
[.agents/notes/implemented/bug-fix/2026-10-15-connector-tushare-dataplane-lazy-credential.md](../../.agents/notes/implemented/bug-fix/2026-10-15-connector-tushare-dataplane-lazy-credential.md))。
|
|
46
|
+
- **设置页显示名**:`packages/client-ui-settings/src/client/trading-settings-controller.ts:58`
|
|
47
|
+
(`PROVIDER_LABELS` 的 `tushare` 条目:`env: 'TUSHARE_TOKEN'`、`markets: ['cn']`)与
|
|
48
|
+
`packages/client-ui-settings/src/client/locales.ts` 的 `'provider.tushare'` 条目(zh / en 都是 `Tushare Pro`)。
|
|
49
|
+
凭据卡片字段 `token`(`trading-settings-controller.ts:160`)由本包的 `createTokenProvider` 消费。
|
|
50
|
+
|
|
51
|
+
## 上游端点
|
|
9
52
|
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
53
|
+
| 用途 | 端点 | 源码 |
|
|
54
|
+
|---|---|---|
|
|
55
|
+
| 全部接口(单端点 POST,`api_name` 分流) | `POST http://api.tushare.pro`,体 `{api_name, token, params, fields}` | `src/rest.ts:72`、`src/rest.ts:97-101` |
|
|
56
|
+
| 报价 | `api_name: 'daily'`,`params: {ts_code, limit: 1}` | `src/rest.ts:147` |
|
|
57
|
+
| 分钟线 | `api_name: 'stk_mins'`,`params: {ts_code, freq}`(`1h` → `60min`),`fields: trade_time,open,close,high,low,vol` | `src/rest.ts:171-181` |
|
|
58
|
+
| 日线 / 周线 / 月线 | `api_name: 'daily'` / `'weekly'` / `'monthly'`,`fields: trade_date,open,high,low,close,vol` | `src/rest.ts:198-207` |
|
|
59
|
+
| 估值 | `api_name: 'daily_basic'`,`params: {ts_code, limit: 1}` | `src/rest.ts:229` |
|
|
13
60
|
|
|
14
|
-
|
|
61
|
+
响应形态固定为 `{code, msg, data: {fields, items}}`,按 `fields` × `items` 逐位展开成对象
|
|
62
|
+
(`src/rest.ts:119-124`)。
|
|
15
63
|
|
|
16
|
-
|
|
17
|
-
模拟盘头/注入 fetch)直接可用;签名与端点 TODO。
|
|
18
|
-
- `src/index.ts` —— Config(enabled/env/dryRun/liveTrading + 三 ref 凭证组)、
|
|
19
|
-
闸门三态判定、凭证解析(BYOK + 环境变量回落)、MarketDataService/TradeService 骨架、
|
|
20
|
-
7 个市场前缀工具注册(duplicate-safe)、apply(enabled=false 静默退出)。
|
|
21
|
-
- `test/template.test.ts` —— 结构冒烟 7 用例。
|
|
64
|
+
## 周期与契约
|
|
22
65
|
|
|
23
|
-
|
|
66
|
+
- **周期词汇**:`INTERVAL_VOCABULARY = ['1m','5m','15m','30m','1h','1d','1w','1M']`(`src/rest.ts:27`),
|
|
67
|
+
同一数组进 `cn_get_klines` 的 `interval` 枚举(`src/index.ts:221`)。
|
|
68
|
+
- **周期分流**(`src/rest.ts:171,198`):`1m/5m/15m/30m/1h` → `stk_mins`(`freq` 为 `1min`/`5min`/…/`60min`),
|
|
69
|
+
`1w` → `weekly`,`1M` → `monthly`,其余 → `daily`。
|
|
70
|
+
- **符号形态**(`toTushareCode`,`src/rest.ts:29-42`):含 `.` 原样;裸码 `6`/`5`/`9`→`.SH`,
|
|
71
|
+
`0`/`3`/`1`→`.SZ`,`8`/`4`→`.BJ`,其余→`.SH`。
|
|
72
|
+
- **`getTicker` 不是实时报价**:取最近一根 `daily` 的 `close`(`src/rest.ts:161`),
|
|
73
|
+
`timestamp` 按 `trade_date` 的当地 15:00 用本地时区构造(`src/rest.ts:154-157`),
|
|
74
|
+
且回执不含 `prevClose` / `changePercent`。
|
|
75
|
+
- **`limit` 只在本地下刀**:K 线请求不带日期窗口或上游 limit,返回后做
|
|
76
|
+
`items.slice(0, limit).reverse()`(`src/rest.ts:183`、`src/rest.ts:209`)——可取根数由上游默认窗口决定。
|
|
77
|
+
- **错误映射**:无 Token → `TRADING_CREDENTIALS_MISSING`(`src/rest.ts:82`);
|
|
78
|
+
非 2xx → `TRADING_UPSTREAM_ERROR`(`src/rest.ts:103`);业务 `code !== 0` 或缺 `data`
|
|
79
|
+
→ `TRADING_EXCHANGE_ERROR`(`src/rest.ts:113`);不可达或响应非 JSON → `TRADING_NETWORK`(`src/rest.ts:129`);
|
|
80
|
+
`daily` 空结果 → `TRADING_SYMBOL_NOT_FOUND`(`src/rest.ts:150`)。
|
|
24
81
|
|
|
25
|
-
|
|
26
|
-
`crypto`(工具前缀)/ `Crypto`(服务键 infix)。token 未展开也可编译;
|
|
27
|
-
生成器保证全部替换。
|
|
82
|
+
## 能力面
|
|
28
83
|
|
|
29
|
-
|
|
84
|
+
| 服务方法 | 本包实现 | 工具暴露 |
|
|
85
|
+
|---|---|---|
|
|
86
|
+
| `TushareMarketDataService.getTicker`(最近日线收盘,非实时) | 有(`src/index.ts:62`) | `cn_get_ticker`(`src/index.ts:207`) |
|
|
87
|
+
| `TushareMarketDataService.getKlines` | 有(`src/index.ts:66`) | `cn_get_klines`(`src/index.ts:217`) |
|
|
88
|
+
| `TushareMarketDataService.getDailyBasic` | 有(`src/index.ts:70`) | `cn_get_valuation`(`src/index.ts:232`) |
|
|
89
|
+
| `TushareMarketDataService.subscribeTicker`(轮询,最小 1000 ms、默认 5000 ms) | 有(`src/index.ts:74-82`) | 缺席 |
|
|
90
|
+
| `TushareTradeService.placeOrder` / `cancelOrder` | 有闸门;rest 层为本地模拟,不发请求(`src/index.ts:100`、`src/index.ts:129`;`src/rest.ts:233`、`src/rest.ts:248`) | 未实现 |
|
|
91
|
+
| `TushareTradeService.getOrder` | 有(失败关闭:恒抛 `TRADING_NOT_IMPLEMENTED`,`src/index.ts:149-155`) | 未实现 |
|
|
92
|
+
| `TushareTradeService.getPositions` | 有(失败关闭:恒抛 `TRADING_NOT_IMPLEMENTED`,`src/index.ts:157-163`) | 未实现 |
|
|
93
|
+
| `TushareTradeService.getBalance` / `getOrders` | 缺席(非契约方法未定义,`src/index.ts`) | 未实现 |
|
|
94
|
+
| `getBalances` / `listOpenOrders` / `listTradeFills` | 缺席(可选方法未定义,消费方按「缺席」处理) | 未实现 |
|
|
95
|
+
| `listInstruments` / 盘口 / 财务报表 | 未实现 | 未实现 |
|
|
96
|
+
|
|
97
|
+
## 已知边界
|
|
98
|
+
|
|
99
|
+
- **无交易通道**:`TushareTradeService` 未注册进 `tradingTradeRegistry`,`apply` 里构造的 `trade`
|
|
100
|
+
实例没有任何工具使用(`src/index.ts:198`);rest 层 `placeOrder` / `cancelOrder` 是本地合成回执
|
|
101
|
+
(`src/rest.ts:233-250`),不发请求。Tushare Pro 是行情数据服务,本连接器没有券商/交易所账户通道,
|
|
102
|
+
所以**账户读面失败关闭**:`getOrder` / `getPositions` 恒抛 `TRADING_NOT_IMPLEMENTED`(语义是「不可用」,
|
|
103
|
+
不是「订单不存在」或「没有持仓」);非契约方法 `getBalance` / `getOrders` 与可选方法
|
|
104
|
+
`getBalances` / `listOpenOrders` / `listTradeFills` 直接缺席。此前编造的 1000000 CNY 余额与恒空数组已删除。
|
|
105
|
+
- **dataplane 与主入口凭证来源一致**(见「接线」):两处都经 `createTokenProvider` 每请求解析
|
|
106
|
+
(设置中心优先、env 兜底),凭证缺失时 fail-closed 抛 `TRADING_CREDENTIALS_MISSING`,不静默回落。
|
|
107
|
+
- **`stk_mins` 属 Tushare 积分/权限面接口**:分钟线是否对本账号开放取决于用户积分,本仓无权限证据(未实证)。
|
|
108
|
+
- **无 `listInstruments`**:名册检索未实现;能力面只有拉取式 REST,无实时推送。
|
|
109
|
+
- 报价无官方昨收锚点,`prevClose` / `changePercent` 缺席(同类缺口登记在
|
|
110
|
+
[.agents/notes/implemented/bug-fix/2026-09-01-us-quote-prevclose-yahoo-anchor.md](../../.agents/notes/implemented/bug-fix/2026-09-01-us-quote-prevclose-yahoo-anchor.md))。
|
|
111
|
+
- 服务缝三态矩阵由离线测试覆盖,未做真实网络验证(同引
|
|
112
|
+
[spikes/impl-service-seam-gate/NET-VERIFY.md](../../spikes/impl-service-seam-gate/NET-VERIFY.md) 的范围说明)。
|
|
113
|
+
|
|
114
|
+
## 合规记录
|
|
115
|
+
|
|
116
|
+
Tushare Pro 是商业数据服务:Token 属用户 BYOK,经 `createTokenProvider` 注入
|
|
117
|
+
(`src/index.ts:185-190`:设置中心 `credentials.tushare.token` 优先、`tokenRef` 指向的环境变量兜底),
|
|
118
|
+
本包不内置、不落日志;接口访问受 Tushare 积分与权限规则约束,
|
|
119
|
+
以 tushare.pro 服务条款为准。本仓不缓存、不再分发行情与财务数据。
|
|
120
|
+
|
|
121
|
+
## 出网验证
|
|
122
|
+
|
|
123
|
+
本包暂无已提交的独立出网验证脚本。最小复跑方式:
|
|
124
|
+
|
|
125
|
+
```sh
|
|
126
|
+
# 凭据缺失时的 fail-closed(无需 Token、不触网;2026-10-14 实测 code=TRADING_CREDENTIALS_MISSING)
|
|
127
|
+
./node_modules/.bin/tsx -e "import('./packages/connector-tushare/src/rest.ts').then(async m => { try { await new m.TushareRestClient().getTicker('600519.SH') } catch (e) { console.log(e.name, e.code) } })"
|
|
128
|
+
|
|
129
|
+
# 有 Token 时的真实取数(需 Tushare 账号与相应积分)
|
|
130
|
+
TUSHARE_TOKEN=<token> ./node_modules/.bin/tsx -e "import('./packages/connector-tushare/src/rest.ts').then(async m => console.log(JSON.stringify(await new m.TushareRestClient({ token: process.env.TUSHARE_TOKEN }).getKlines('600519.SH', '1d', 5))))"
|
|
131
|
+
```
|
|
30
132
|
|
|
31
|
-
|
|
32
|
-
-
|
|
33
|
-
|
|
34
|
-
- 凭证只进 ref,绝不内置/落日志(铁律 #3/#5)。
|
|
133
|
+
不触网的离线矩阵:服务缝三态 `packages/connector-tushare/test/trade-gate.test.ts`、
|
|
134
|
+
账户读面失败关闭 `packages/connector-tushare/test/account-read-surface.test.ts`、
|
|
135
|
+
数据面装配 `packages/connector-tushare/test/dataplane-loader.test.ts`。
|
package/lib/dataplane.d.ts
CHANGED
|
@@ -2,6 +2,6 @@ import { Config } from "./index.js";
|
|
|
2
2
|
import { Context } from "@deepseek-ai/cordis";
|
|
3
3
|
//#region src/dataplane.d.ts
|
|
4
4
|
declare const inject: string[];
|
|
5
|
-
declare function apply(ctx: Context, config
|
|
5
|
+
declare function apply(ctx: Context, config?: Config): void;
|
|
6
6
|
//#endregion
|
|
7
|
-
export { apply, inject };
|
|
7
|
+
export { Config, apply, inject };
|
package/lib/dataplane.js
CHANGED
|
@@ -1,23 +1,24 @@
|
|
|
1
|
-
import { TRADING_CN_MARKET_DATA_KEY, TushareMarketDataService } from "./index.js";
|
|
1
|
+
import { Config, ROUTER_PROVIDER, TRADING_CN_MARKET_DATA_KEY, TushareMarketDataService, createTokenProvider } from "./index.js";
|
|
2
2
|
//#region src/dataplane.ts
|
|
3
3
|
const inject = [];
|
|
4
|
+
/** schema 的运行时可调用形态(校验即补默认值);手工构造 ctx 不传 config 时按 schema 默认值处理。 */
|
|
5
|
+
const withDefaults = (value) => Config(value);
|
|
4
6
|
function resolveMarketDataRegistry(ctx) {
|
|
5
7
|
const candidate = ctx.get?.("tradingMarketDataRegistry", false);
|
|
6
8
|
return candidate !== void 0 ? candidate : void 0;
|
|
7
9
|
}
|
|
8
|
-
const ROUTER_PROVIDER = "tushare";
|
|
9
10
|
const MARKET = "cn";
|
|
10
|
-
function apply(ctx, config) {
|
|
11
|
+
function apply(ctx, config = withDefaults(void 0)) {
|
|
11
12
|
if (!config.enabled) return;
|
|
12
|
-
const
|
|
13
|
+
const tokenProvider = createTokenProvider(ctx, config.tokenRef);
|
|
13
14
|
const registry = resolveMarketDataRegistry(ctx);
|
|
14
15
|
if (registry === void 0) {
|
|
15
|
-
new TushareMarketDataService(ctx, {
|
|
16
|
+
new TushareMarketDataService(ctx, { tokenProvider });
|
|
16
17
|
return;
|
|
17
18
|
}
|
|
18
19
|
const inner = ctx.isolate(TRADING_CN_MARKET_DATA_KEY);
|
|
19
|
-
const service = new TushareMarketDataService(inner, {
|
|
20
|
+
const service = new TushareMarketDataService(inner, { tokenProvider });
|
|
20
21
|
ctx.effect(() => registry.register(MARKET, ROUTER_PROVIDER, service));
|
|
21
22
|
}
|
|
22
23
|
//#endregion
|
|
23
|
-
export { apply, inject };
|
|
24
|
+
export { Config, apply, inject };
|
package/lib/index.d.ts
CHANGED
|
@@ -1,12 +1,11 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { INTERVAL_VOCABULARY, Interval, Kline, Order, Position, Ticker, TradingServiceError, TushareRestClient, TushareRestOptions, parseIntervalMs, toTushareCode } from "./rest.js";
|
|
2
2
|
import { Context, Service } from "@deepseek-ai/cordis";
|
|
3
3
|
import Schema from "@deepseek-ai/schemastery";
|
|
4
|
-
import {
|
|
4
|
+
import { Disposable, Interval as Interval$1, Kline as Kline$1, MarketDataService, Order as Order$1, OrderRequest, Position as Position$1, Ticker as Ticker$1, TradeService } from "@dshtrading/api";
|
|
5
5
|
//#region src/index.d.ts
|
|
6
6
|
declare const name = "dsh-trading-cn-connector-tushare";
|
|
7
7
|
interface Config {
|
|
8
8
|
enabled: boolean;
|
|
9
|
-
env: 'demo' | 'live';
|
|
10
9
|
dryRun: boolean;
|
|
11
10
|
liveTrading: boolean;
|
|
12
11
|
tokenRef: string;
|
|
@@ -31,17 +30,31 @@ declare class TushareTradeService extends Service implements TradeService {
|
|
|
31
30
|
constructor(ctx: Context, options: TushareRestOptions & {
|
|
32
31
|
config: Config;
|
|
33
32
|
}, serviceName?: string);
|
|
34
|
-
getBalance(): Promise<AccountBalance$1>;
|
|
35
33
|
placeOrder(order: OrderRequest): Promise<Order$1>;
|
|
36
34
|
cancelOrder(orderId: string): Promise<{
|
|
37
35
|
orderId: string;
|
|
38
36
|
status: 'canceled';
|
|
39
37
|
}>;
|
|
38
|
+
/**
|
|
39
|
+
* 账户读面不可用(Tushare 是行情数据服务,本连接器从来没有账户通道):
|
|
40
|
+
* 必需方法必须显式拒绝。base 的账户工具与 GUI 桥把同一注册面当「账户真值」消费——
|
|
41
|
+
* 返回常量余额、空数组或编造订单会被读成「余额充足」「没有持仓」「订单存在」,
|
|
42
|
+
* 而契约语义是 TRADING_NOT_IMPLEMENTED(不可用)。rest 层的 `placeOrder` /
|
|
43
|
+
* `cancelOrder` 只是本地合成回执,不构成账户通道;非契约方法 getBalance / getOrders
|
|
44
|
+
* 直接缺席,由消费方按「方法缺席」处理(见 packages/api/src/index.ts 的 TradeService 注释)。
|
|
45
|
+
*/
|
|
46
|
+
getOrder(_symbol: string, _id: string): Promise<Order$1>;
|
|
40
47
|
getPositions(): Promise<Position$1[]>;
|
|
41
|
-
getOrders(): Promise<Order$1[]>;
|
|
42
48
|
}
|
|
43
49
|
declare const ROUTER_PROVIDER = "tushare";
|
|
44
50
|
declare function routeAllows(ctx: Context, config: Config, market: string): boolean;
|
|
51
|
+
/**
|
|
52
|
+
* 惰性凭证源(主入口与 dataplane 行共用同一份):设置中心
|
|
53
|
+
* `dshtrading.credentials.tushare.token`(router getCredential)优先、`process.env[tokenRef]`
|
|
54
|
+
* 兜底,**每次请求解析** —— settings 用户层加载/修改晚于插件 apply 也生效。
|
|
55
|
+
* `tokenRef` 语义不变:仍是指向环境变量名的 BYOK 引用,凭证值本身只经 ref 与设置中心。
|
|
56
|
+
*/
|
|
57
|
+
declare function createTokenProvider(ctx: Context, tokenRef: string): () => string | undefined;
|
|
45
58
|
declare function apply(ctx: Context, config: Config): void;
|
|
46
59
|
//#endregion
|
|
47
|
-
export {
|
|
60
|
+
export { Config, INTERVAL_VOCABULARY, type Interval, type Kline, type Order, type Position, ROUTER_PROVIDER, TRADING_CN_MARKET_DATA_KEY, TRADING_CN_TRADE_KEY, type Ticker, TradingServiceError, TushareMarketDataService, TushareRestClient, TushareRestOptions, TushareTradeService, apply, createTokenProvider, name, parseIntervalMs, routeAllows, toTushareCode };
|
package/lib/index.js
CHANGED
|
@@ -11,7 +11,6 @@ import Schema from "@deepseek-ai/schemastery";
|
|
|
11
11
|
const name = "dsh-trading-cn-connector-tushare";
|
|
12
12
|
const Config = Schema.object({
|
|
13
13
|
enabled: Schema.boolean().default(true).description("是否激活 Tushare 连接器"),
|
|
14
|
-
env: Schema.union(["demo", "live"]).default("demo").description("运行环境"),
|
|
15
14
|
dryRun: Schema.boolean().default(true).description("默认模拟下单"),
|
|
16
15
|
liveTrading: Schema.boolean().default(false).description("是否允许实盘交易"),
|
|
17
16
|
tokenRef: Schema.string().default("TUSHARE_TOKEN").description("Tushare Pro Token 环境变量名 (BYOK)")
|
|
@@ -52,9 +51,6 @@ var TushareTradeService = class extends Service {
|
|
|
52
51
|
this.client = new TushareRestClient(options);
|
|
53
52
|
this.config = options.config;
|
|
54
53
|
}
|
|
55
|
-
async getBalance() {
|
|
56
|
-
return this.client.getBalance();
|
|
57
|
-
}
|
|
58
54
|
async placeOrder(order) {
|
|
59
55
|
const requestedDryRun = order.dryRun ?? true;
|
|
60
56
|
if (!requestedDryRun && !liveTradingEnabled(this.config.liveTrading)) throw new TradingServiceError("TRADING_LIVE_TRADING_DISABLED", `Tushare TradeService.placeOrder rejected: the request asks for real execution (dryRun=${String(order.dryRun)}) but the signed live-trading authority does not grant real execution (see @dshtrading/authority) — keep dryRun=true for a simulated fill.`);
|
|
@@ -75,11 +71,19 @@ var TushareTradeService = class extends Service {
|
|
|
75
71
|
if (!liveTradingEnabled(this.config.liveTrading) || this.config.dryRun) throw new TradingServiceError("TRADING_LIVE_TRADING_DISABLED", "Tushare TradeService.cancelOrder rejected at the service seam: cancel is a live action and requires a signed live-trading grant (see @dshtrading/authority) with dryRun=false.");
|
|
76
72
|
return this.client.cancelOrder(void 0, orderId);
|
|
77
73
|
}
|
|
78
|
-
|
|
79
|
-
|
|
74
|
+
/**
|
|
75
|
+
* 账户读面不可用(Tushare 是行情数据服务,本连接器从来没有账户通道):
|
|
76
|
+
* 必需方法必须显式拒绝。base 的账户工具与 GUI 桥把同一注册面当「账户真值」消费——
|
|
77
|
+
* 返回常量余额、空数组或编造订单会被读成「余额充足」「没有持仓」「订单存在」,
|
|
78
|
+
* 而契约语义是 TRADING_NOT_IMPLEMENTED(不可用)。rest 层的 `placeOrder` /
|
|
79
|
+
* `cancelOrder` 只是本地合成回执,不构成账户通道;非契约方法 getBalance / getOrders
|
|
80
|
+
* 直接缺席,由消费方按「方法缺席」处理(见 packages/api/src/index.ts 的 TradeService 注释)。
|
|
81
|
+
*/
|
|
82
|
+
async getOrder(_symbol, _id) {
|
|
83
|
+
throw new TradingServiceError("TRADING_NOT_IMPLEMENTED", "Tushare TradeService.getOrder is not implemented: Tushare is a market-data service and this connector has no trading-account channel. That means \"not available\", NOT \"order not found\".");
|
|
80
84
|
}
|
|
81
|
-
async
|
|
82
|
-
|
|
85
|
+
async getPositions() {
|
|
86
|
+
throw new TradingServiceError("TRADING_NOT_IMPLEMENTED", "Tushare TradeService.getPositions is not implemented: Tushare is a market-data service and this connector has no trading-account channel. That means \"not available\", NOT \"no positions\".");
|
|
83
87
|
}
|
|
84
88
|
};
|
|
85
89
|
const ROUTER_PROVIDER = "tushare";
|
|
@@ -89,13 +93,24 @@ function routeAllows(ctx, config, market) {
|
|
|
89
93
|
if (router === void 0) return true;
|
|
90
94
|
return router.activeProvider(market) === ROUTER_PROVIDER;
|
|
91
95
|
}
|
|
96
|
+
/**
|
|
97
|
+
* 惰性凭证源(主入口与 dataplane 行共用同一份):设置中心
|
|
98
|
+
* `dshtrading.credentials.tushare.token`(router getCredential)优先、`process.env[tokenRef]`
|
|
99
|
+
* 兜底,**每次请求解析** —— settings 用户层加载/修改晚于插件 apply 也生效。
|
|
100
|
+
* `tokenRef` 语义不变:仍是指向环境变量名的 BYOK 引用,凭证值本身只经 ref 与设置中心。
|
|
101
|
+
*/
|
|
102
|
+
function createTokenProvider(ctx, tokenRef) {
|
|
103
|
+
return () => {
|
|
104
|
+
return (ctx.get?.("tradingMarketRouter", false))?.getCredential?.("tushare")?.token || process.env[tokenRef];
|
|
105
|
+
};
|
|
106
|
+
}
|
|
92
107
|
function apply(ctx, config) {
|
|
93
108
|
if (!config.enabled) return;
|
|
94
109
|
if (!routeAllows(ctx, config, "cn")) return;
|
|
95
|
-
const
|
|
96
|
-
const marketData = new TushareMarketDataService(ctx, {
|
|
110
|
+
const tokenProvider = createTokenProvider(ctx, config.tokenRef);
|
|
111
|
+
const marketData = new TushareMarketDataService(ctx, { tokenProvider });
|
|
97
112
|
new TushareTradeService(ctx, {
|
|
98
|
-
|
|
113
|
+
tokenProvider,
|
|
99
114
|
config
|
|
100
115
|
});
|
|
101
116
|
ctx.inject(["tools"], (ctx) => {
|
|
@@ -178,4 +193,4 @@ function apply(ctx, config) {
|
|
|
178
193
|
});
|
|
179
194
|
}
|
|
180
195
|
//#endregion
|
|
181
|
-
export { Config, INTERVAL_VOCABULARY, ROUTER_PROVIDER, TRADING_CN_MARKET_DATA_KEY, TRADING_CN_TRADE_KEY, TradingServiceError, TushareMarketDataService, TushareRestClient, TushareTradeService, apply, name, parseIntervalMs, routeAllows, toTushareCode };
|
|
196
|
+
export { Config, INTERVAL_VOCABULARY, ROUTER_PROVIDER, TRADING_CN_MARKET_DATA_KEY, TRADING_CN_TRADE_KEY, TradingServiceError, TushareMarketDataService, TushareRestClient, TushareTradeService, apply, createTokenProvider, name, parseIntervalMs, routeAllows, toTushareCode };
|
package/lib/rest.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { Interval as Interval$1, Kline as Kline$1, Order as Order$1, OrderRequest, Position as Position$1, Ticker as Ticker$1, TradingErrorCode } from "@dshtrading/api";
|
|
2
2
|
//#region src/rest.d.ts
|
|
3
3
|
declare class TradingServiceError extends Error {
|
|
4
4
|
readonly code: TradingErrorCode;
|
|
@@ -10,18 +10,20 @@ declare function parseIntervalMs(interval: Interval$1): number;
|
|
|
10
10
|
interface TushareRestOptions {
|
|
11
11
|
baseUrl?: string;
|
|
12
12
|
token?: string;
|
|
13
|
+
/** 惰性凭证源:每次请求解析(设置中心凭证优先、env 兜底由调用方组装,见 `createTokenProvider`)。 */
|
|
14
|
+
tokenProvider?: (() => string | undefined) | undefined;
|
|
13
15
|
fetchImpl?: typeof fetch;
|
|
14
16
|
}
|
|
15
17
|
declare class TushareRestClient {
|
|
16
18
|
readonly baseUrl: string;
|
|
17
19
|
private readonly token?;
|
|
20
|
+
private readonly tokenProvider?;
|
|
18
21
|
private readonly fetchImpl;
|
|
19
22
|
constructor(options?: TushareRestOptions);
|
|
20
23
|
private requestApi;
|
|
21
24
|
getTicker(symbol: string): Promise<Ticker$1>;
|
|
22
25
|
getKlines(symbol: string, interval?: Interval$1, limit?: number): Promise<Kline$1[]>;
|
|
23
26
|
getDailyBasic(symbol: string): Promise<Record<string, unknown>>;
|
|
24
|
-
getBalance(): Promise<AccountBalance$1>;
|
|
25
27
|
placeOrder(_creds: unknown, req: OrderRequest): Promise<Order$1>;
|
|
26
28
|
cancelOrder(_creds: unknown, orderId: string): Promise<{
|
|
27
29
|
orderId: string;
|
|
@@ -29,4 +31,4 @@ declare class TushareRestClient {
|
|
|
29
31
|
}>;
|
|
30
32
|
}
|
|
31
33
|
//#endregion
|
|
32
|
-
export {
|
|
34
|
+
export { INTERVAL_VOCABULARY, type Interval$1 as Interval, type Kline$1 as Kline, type Order$1 as Order, type Position$1 as Position, type Ticker$1 as Ticker, TradingServiceError, TushareRestClient, TushareRestOptions, parseIntervalMs, toTushareCode };
|
package/lib/rest.js
CHANGED
|
@@ -41,17 +41,20 @@ function parseIntervalMs(interval) {
|
|
|
41
41
|
var TushareRestClient = class {
|
|
42
42
|
baseUrl;
|
|
43
43
|
token;
|
|
44
|
+
tokenProvider;
|
|
44
45
|
fetchImpl;
|
|
45
46
|
constructor(options = {}) {
|
|
46
47
|
this.baseUrl = options.baseUrl ?? "http://api.tushare.pro";
|
|
47
48
|
this.token = options.token;
|
|
49
|
+
this.tokenProvider = options.tokenProvider;
|
|
48
50
|
this.fetchImpl = options.fetchImpl ?? globalThis.fetch;
|
|
49
51
|
}
|
|
50
52
|
async requestApi(apiName, params = {}, fields) {
|
|
51
|
-
|
|
53
|
+
const token = this.tokenProvider?.() ?? this.token;
|
|
54
|
+
if (!token) throw new TradingServiceError("TRADING_CREDENTIALS_MISSING", "Tushare token missing. Please set TUSHARE_TOKEN in environment or credentials.");
|
|
52
55
|
const payload = {
|
|
53
56
|
api_name: apiName,
|
|
54
|
-
token
|
|
57
|
+
token,
|
|
55
58
|
params,
|
|
56
59
|
fields: fields ?? ""
|
|
57
60
|
};
|
|
@@ -148,13 +151,6 @@ var TushareRestClient = class {
|
|
|
148
151
|
});
|
|
149
152
|
return items.length > 0 ? items[0] : {};
|
|
150
153
|
}
|
|
151
|
-
async getBalance() {
|
|
152
|
-
return {
|
|
153
|
-
currency: "CNY",
|
|
154
|
-
available: 1e6,
|
|
155
|
-
total: 1e6
|
|
156
|
-
};
|
|
157
|
-
}
|
|
158
154
|
async placeOrder(_creds, req) {
|
|
159
155
|
const tsCode = toTushareCode(req.symbol);
|
|
160
156
|
return {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dshtrading/connector-tushare",
|
|
3
|
-
"description": "
|
|
4
|
-
"version": "0.6.
|
|
3
|
+
"description": "Tushare Pro A 股连接器:提供 tradingCnMarketData 行情服务与 cn_get_ticker / cn_get_klines / cn_get_valuation 三个工具,Token 走 BYOK(tokenRef 指向环境变量);无交易通道。",
|
|
4
|
+
"version": "0.6.2",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "./lib/index.d.ts",
|
|
@@ -21,12 +21,12 @@
|
|
|
21
21
|
],
|
|
22
22
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@dshtrading/authority": "^0.6.
|
|
25
|
-
"@dshtrading/api": "^0.6.
|
|
24
|
+
"@dshtrading/authority": "^0.6.2",
|
|
25
|
+
"@dshtrading/api": "^0.6.2"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
28
|
"@deepseek-ai/cordis": ">=4.0.0",
|
|
29
|
-
"@deepseek-ai/dsh-tools": ">=0.2.
|
|
29
|
+
"@deepseek-ai/dsh-tools": ">=0.2.1-rc.2",
|
|
30
30
|
"@deepseek-ai/schemastery": ">=3.18.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|