@dshtrading/connector-okx 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 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.
@@ -0,0 +1,7 @@
1
+ import { Config } from "./index.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: Config): void;
6
+ //#endregion
7
+ export { apply, inject };
@@ -0,0 +1,41 @@
1
+ import { OkxRestClient } from "./rest.js";
2
+ import { OkxMarketDataService, OkxTradeService, TRADING_CRYPTO_MARKET_DATA_KEY, TRADING_CRYPTO_TRADE_KEY, resolveCredentials } from "./index.js";
3
+ //#region src/dataplane.ts
4
+ const inject = [];
5
+ /** 解析注册表服务;老部署(base/router 未升级)返回 undefined → 调用方回退旧的直接 provide 路径。 */
6
+ function resolveMarketDataRegistry(ctx) {
7
+ const candidate = ctx.get?.("tradingMarketDataRegistry", false);
8
+ return candidate !== void 0 ? candidate : void 0;
9
+ }
10
+ /** 解析交易注册表服务(issue #40);老部署(未升级)返回 undefined → 跳过交易注册。 */
11
+ function resolveTradeRegistry(ctx) {
12
+ const candidate = ctx.get?.("tradingTradeRegistry", false);
13
+ return candidate !== void 0 ? candidate : void 0;
14
+ }
15
+ function apply(ctx, config) {
16
+ if (!config.enabled) return;
17
+ const registry = resolveMarketDataRegistry(ctx);
18
+ if (registry === void 0) {
19
+ const router = ctx.get?.("tradingMarketRouter", false);
20
+ const active = router?.activeProvider("crypto");
21
+ if (router !== void 0 && active !== "okx") return;
22
+ new OkxMarketDataService(ctx);
23
+ return;
24
+ }
25
+ const client = new OkxRestClient();
26
+ const inner = ctx.isolate(TRADING_CRYPTO_MARKET_DATA_KEY);
27
+ const service = new OkxMarketDataService(inner, {}, client);
28
+ ctx.effect(() => registry.register("crypto", "okx", service));
29
+ const tradeRegistry = resolveTradeRegistry(ctx);
30
+ if (tradeRegistry !== void 0) {
31
+ const tradeInner = ctx.isolate(TRADING_CRYPTO_TRADE_KEY);
32
+ const trade = new OkxTradeService(tradeInner, {
33
+ client,
34
+ config,
35
+ getCredentials: () => resolveCredentials(ctx, config)
36
+ });
37
+ ctx.effect(() => tradeRegistry.register("crypto", "okx", trade));
38
+ }
39
+ }
40
+ //#endregion
41
+ export { apply, inject };
package/lib/index.d.ts ADDED
@@ -0,0 +1,227 @@
1
+ import { BAR_MAP, NormalizedSize, OKX_INTERVAL_VOCABULARY, OkxCredentials, OkxFundingRate, OkxInstrument, OkxOpenInterest, OkxPlaceOrderParams, OkxRestClient, OkxRestOptions, SignedAuth, TradingServiceError, barDurationMs, buildAuthHeaders, isoTimestamp, normalizeOkxSymbol, normalizeSize, signPayload, signaturePrehash, toBar, toCanonicalOkxSymbol, toOkxSwapInstId } from "./rest.js";
2
+ import { Context, Service } from "@deepseek-ai/cordis";
3
+ import Schema from "@deepseek-ai/schemastery";
4
+ import { AccountBalance, DerivativesData, DerivativesHistory, Disposable, Interval, Kline, MarketDataService, Order, OrderRequest, OrderStatus, Orderbook, Position, Ticker, TradeFill, TradeService, TradeTick } from "@dshtrading/api";
5
+ //#region src/index.d.ts
6
+ /**
7
+ * Cordis 插件名 = preset 行 id(TEMPLATES §8):`dsh-trading-crypto-*` 市场命名空间,
8
+ * 全仓唯一,绝不使用 `base` 等官方保留 id(insert-only 铁律 #1)。
9
+ */
10
+ declare const name = "dsh-trading-crypto-connector-okx";
11
+ interface Config {
12
+ /** 互斥激活总开关(默认 false):false 时本插件不注册任何服务/工具。 */
13
+ enabled: boolean;
14
+ /** 三态环境:'demo'(默认,模拟盘 x-simulated-trading:1)| 'live'(实盘)。 */
15
+ env: 'demo' | 'live';
16
+ /** 交易安全闸门(铁律 #3):true 时下单类工具强制 dry-run。 */
17
+ dryRun: boolean;
18
+ /** 实盘总闸门(默认 false):false 时 dryRun=false 的请求被结构化拒绝。
19
+ * true 的第一默认目标是 demo(env='demo'),改 env='live' 是第二次显式解锁。 */
20
+ liveTrading: boolean;
21
+ /** 实盘凭证 ref(环境变量名,credentialRef 语义)。 */
22
+ apiKeyRef: string;
23
+ secretRef: string;
24
+ passphraseRef: string;
25
+ /** 模拟盘凭证 ref 组(demo/live key 不通用,调研 §2——按环境取不同 ref 组)。 */
26
+ demoApiKeyRef: string;
27
+ demoSecretRef: string;
28
+ demoPassphraseRef: string;
29
+ }
30
+ declare const Config: Schema<Config>;
31
+ /** 需要宿主提供的 Cordis 服务。 */
32
+ declare const inject: string[];
33
+ /** ctx 服务键(与 @dshtrading/api 的 Context 模块增强一致)。 */
34
+ declare const TRADING_CRYPTO_MARKET_DATA_KEY = "tradingCryptoMarketData";
35
+ declare const TRADING_CRYPTO_TRADE_KEY = "tradingCryptoTrade";
36
+ /**
37
+ * DSH credentials seam 的结构化最小契约(resolve 每次 {value}|undefined)。
38
+ * 本仓不引 @deepseek-ai/dsh-credentials 依赖;形状以其 CredentialProvider 为准。
39
+ */
40
+ interface CredentialResolverLike {
41
+ resolve(ref: string): Promise<{
42
+ value: string;
43
+ } | undefined>;
44
+ }
45
+ /** 凭证解析所需的最小 ctx 面(单测可直接给 { get } 假 ctx)。 */
46
+ interface CredentialsContext {
47
+ get(name: string): unknown;
48
+ }
49
+ interface ResolvedCredentialRefs {
50
+ readonly apiKeyRef: string;
51
+ readonly secretRef: string;
52
+ readonly passphraseRef: string;
53
+ }
54
+ /** env → ref 组:demo 用 demo*Ref(默认 OKX_DEMO_*),live 用 live 组(默认 OKX_*)。 */
55
+ declare function credentialRefsFor(config: Config, env?: 'demo' | 'live'): ResolvedCredentialRefs;
56
+ /**
57
+ * 三 ref 凭证解析:每次操作调用(ctx.credentials 的设计意图——换 key 无需重启插件)。
58
+ * 无 credentials seam 时回落启动环境变量(llm-deepseek 同款降级)。
59
+ * 任何一处未命中/无效 → TRADING_CREDENTIALS_MISSING,消息只带 ref 名(绝不带值)。
60
+ */
61
+ declare function resolveCredentials(ctx: CredentialsContext, config: Config): Promise<OkxCredentials>;
62
+ interface SubscribeTickerOptions {
63
+ /** 轮询间隔(ms)。切片阶段 subscribeTicker 以 REST 轮询实现,WS 在后续任务。 */
64
+ readonly intervalMs?: number;
65
+ }
66
+ declare class OkxMarketDataService extends Service implements MarketDataService {
67
+ private readonly client;
68
+ constructor(ctx: Context, options?: OkxRestOptions, client?: OkxRestClient, serviceName?: string);
69
+ getTicker(instId: string): Promise<Ticker>;
70
+ getKlines(instId: string, interval: Interval, limit?: number): Promise<Kline[]>;
71
+ listInstruments(): Promise<Array<{
72
+ symbol: string;
73
+ name?: string;
74
+ }>>;
75
+ /** OKX 专属扩展(MarketDataService 契约之外):SWAP 资金费率。 */
76
+ getFundingRate(instId: string): Promise<OkxFundingRate>;
77
+ /** 盘口快照(api 可选契约 getOrderbook,issue #39):books 20 档透传。 */
78
+ getOrderbook(symbol: string): Promise<Orderbook>;
79
+ /** 最近逐笔成交(api 可选契约 getRecentTrades,issue #39),时间升序。 */
80
+ getRecentTrades(symbol: string, limit?: number): Promise<TradeTick[]>;
81
+ /**
82
+ * 衍生品指标快照(api 可选契约 getDerivatives,issue #38):聚合 OKX 公共端点
83
+ * (funding-rate / open-interest / rubik 多空账户比 / rubik taker 买卖量)。
84
+ * 现货输入经 toOkxSwapInstId 升到对应永续(GUI 选中 BTCUSDT 也能看合约指标)。
85
+ * 任一子查询失败只降级该字段(undefined,面板按缺格隐藏);全部失败才抛
86
+ * 结构化错误(桥层转 ok:false,前端不弹横幅)。
87
+ */
88
+ getDerivatives(symbol: string): Promise<DerivativesData>;
89
+ /**
90
+ * 衍生品历史序列(api 可选契约 getDerivativesHistory,issue #54):
91
+ * funding-rate-history + rubik open-interest-history 双端点聚合并发拉取,
92
+ * 任一失败只降级该序列(字段缺省 → 对应趋势卡隐藏),全部失败才抛结构化错误。
93
+ */
94
+ getDerivativesHistory(symbol: string): Promise<DerivativesHistory>;
95
+ subscribeTicker(instId: string, cb: (ticker: Ticker) => void, options?: SubscribeTickerOptions): Disposable;
96
+ }
97
+ interface OkxTradeServiceOptions {
98
+ readonly client: OkxRestClient;
99
+ readonly config: Config;
100
+ /** 每次操作取三值凭证(内部走 resolveCredentials,未命中抛结构化错误)。 */
101
+ readonly getCredentials: () => Promise<OkxCredentials>;
102
+ }
103
+ declare class OkxTradeService extends Service implements TradeService {
104
+ private readonly client;
105
+ private readonly config;
106
+ private readonly getCredentials;
107
+ /** instId → 规格 缓存;key 前缀 demo:/live: —— demo 与实盘 ctVal 是否一致未实证(调研待验证 #5),按环境分桶。 */
108
+ private readonly instruments;
109
+ constructor(ctx: Context, options: OkxTradeServiceOptions, serviceName?: string);
110
+ private get simulated();
111
+ private auth;
112
+ /** 规格(带 TTL 缓存;demo/live 分桶)。查不到 → TRADING_UNSUPPORTED_SYMBOL。 */
113
+ private getInstrument;
114
+ /**
115
+ * 下单(api TradeService 契约)。
116
+ *
117
+ * **服务缝闸门(P0 · 铁律 #3 修订版 [S4])**:三态检查以 evaluateOrderGate 同源语义
118
+ * 下推到服务实现内第一步——绕过工具层直调本服务(dsh-tool-cordis 动态包宿主半、
119
+ * 未来任何新消费面)同样 fail-closed;工具层 evaluateOrderGate + base 审批闸门保留
120
+ * (双保险),工具层只做参数预检与富回执。
121
+ *
122
+ * - 闸门 ① reject(dryRun=false 请求实盘而 liveTrading=false)→ 结构化错误抛出
123
+ * (TRADING_LIVE_TRADING_DISABLED,api TradingError 契约);
124
+ * - 闸门 ② simulate(dryRun 缺省/true,或 config.dryRun 强制模拟)→ 本地模拟回执
125
+ * (Order.dryRun=true,不触网;工具层另有带市价参照的富回执);
126
+ * - 闸门 ③ live(dryRun=false 且 liveTrading=true)→ 真实签名下单(env=demo 加模拟盘头)。
127
+ *
128
+ * **sz 单位纪律(调研 §4,实现期最重要的换算)**:
129
+ * - api `OrderRequest.quantity` 语义恒为 base 币数;
130
+ * - SPOT:market 单显式 `tgtCcy: 'base_ccy'` —— OKX 现货市价 buy 缺省按计价币
131
+ * (USDT)金额,若不显式指定,想买 0.01 BTC 却传 0.01 会被当成 0.01 USDT,
132
+ * 这是两所词汇最大的坑;limit 单恒为 base 币数;
133
+ * - SWAP:`sz` 单位是「张」,币数 = sz × ctVal —— 服务层按 instruments 的
134
+ * ctVal/lotSz/minSz 换算并本地校验(向下取整,省一次 51000 往返)。
135
+ */
136
+ placeOrder(req: OrderRequest): Promise<Order>;
137
+ /**
138
+ * 撤单。OKX 按 instId + ordId 双键定位,symbol(instId)必填——api 契约的
139
+ * cancelOrder(id) 单参形态对 OKX 不够,扩展第二可选参数(api 包 R3 修订)。
140
+ *
141
+ * 撤单幂等化(调研 §5「实现期定」):51400(订单已成交/已撤/不存在)/51603(订单
142
+ * 不存在)视作终态成功——撤单语义是「确保不再成交」,订单已终态即达成。
143
+ */
144
+ cancelOrder(id: string, symbol?: string): Promise<void>;
145
+ /** 查单(api TradeService R3 新增成员):state → OrderStatus 映射见 ORDER_STATE_MAP。 */
146
+ getOrder(symbol: string, id: string): Promise<Order>;
147
+ /** 只读持仓(SWAP 的 pos 单位是张 → 经 ctVal 换算成币;net 模式负 pos = short)。 */
148
+ getPositions(): Promise<Position[]>;
149
+ /** 只读余额(TradeService 契约外扩展,crypto_get_balance 工具消费)。 */
150
+ getBalances(): Promise<AccountBalance[]>;
151
+ /** SWAP 的 sz/accFillSz/fillSz(张)→ base 币数(规格缓存,非 SWAP 原值)。 */
152
+ private toCoins;
153
+ /** 当前挂单(TradeService 可选契约,issue #40;只读、需凭证)。 */
154
+ listOpenOrders(symbol?: string): Promise<Order[]>;
155
+ /** 最近成交流水(TradeService 可选契约,issue #40;只读、需凭证,时间升序)。 */
156
+ listTradeFills(symbol?: string, limit?: number): Promise<TradeFill[]>;
157
+ }
158
+ /** OKX state → api OrderStatus(本切片 vocab:live/partially_filled/filled/canceled)。 */
159
+ declare function mapOrderState(state: string): OrderStatus;
160
+ /** crypto_place_order 参数契约(OKX 词汇:instId 带连字符、side/ordType 小写)。 */
161
+ interface PlaceOrderArgs {
162
+ /** 交易对符号:市场规范形(BTCUSDT / BTCUSDT-SWAP)或 OKX 原生形(BTC-USDT)皆收(docs/symbol-vocabulary.md)。 */
163
+ readonly instId: string;
164
+ /** 方向(OKX 词汇小写)。 */
165
+ readonly side: 'buy' | 'sell';
166
+ /** 订单类型(OKX ordType 词汇小写)。 */
167
+ readonly type: 'market' | 'limit';
168
+ /** **base 币数量**(SPOT 与 SWAP 同语义;SWAP 由连接器按 ctVal 换算成张)。必须 > 0。 */
169
+ readonly quantity: number;
170
+ /** LIMIT 单必填(schema 无法条件必填,execute 内校验),必须 > 0。 */
171
+ readonly price?: number;
172
+ /** 缺省视为 true:仅模拟。显式 false 即实盘意图,进入三态闸门 ②/③。 */
173
+ readonly dryRun?: boolean;
174
+ }
175
+ /**
176
+ * 三态闸门判定(顺序即铁律 #3 修订版的裁决顺序;主 agent 裁决的三态环境映射):
177
+ * - `reject` —— ① 请求实盘(dryRun!==true)而 liveTrading=false:结构化拒绝;
178
+ * - `simulate` —— ② dryRun=true(显式/缺省/被 config.dryRun 强制):本地模拟回执;
179
+ * - `live` —— ③ dryRun=false 且 liveTrading=true:真实签名下单,environment
180
+ * 决定是否带模拟盘头(demo=第一默认目标;live=实盘,base 闸门照旧 ask)。
181
+ */
182
+ type OrderGateVerdict = {
183
+ action: 'reject';
184
+ code: 'TRADING_LIVE_TRADING_DISABLED';
185
+ message: string;
186
+ } | {
187
+ action: 'simulate';
188
+ } | {
189
+ action: 'live';
190
+ environment: 'demo' | 'live';
191
+ };
192
+ declare function evaluateOrderGate(config: Config, args: PlaceOrderArgs): OrderGateVerdict;
193
+ /** DRY-RUN 回执(connector-binance 同款形状;参照行情来自 OKX 公共 ticker)。 */
194
+ interface DryRunReference {
195
+ source: 'okx-public-ticker';
196
+ price?: number;
197
+ bid?: number;
198
+ ask?: number;
199
+ timestamp?: number;
200
+ unavailable?: string;
201
+ }
202
+ declare function buildDryRunReceipt(args: PlaceOrderArgs, marketData: Pick<OkxMarketDataService, 'getTicker'>): Promise<string>;
203
+ interface PlaceOrderToolDeps {
204
+ /** 行情服务(dry-run 回执的市价参照),按接口取用,不直连 REST。 */
205
+ readonly marketData: Pick<OkxMarketDataService, 'getTicker'>;
206
+ /** 交易服务(闸门 ③ 的真实签名下单路径)。 */
207
+ readonly trade: TradeService;
208
+ /** 插件配置(dryRun 强制模拟 / liveTrading 总闸门 / env 三态)。 */
209
+ readonly config: Config;
210
+ }
211
+ /**
212
+ * crypto_place_order 工具工厂(独立导出便于单测三态闸门矩阵)。
213
+ *
214
+ * 审批不在这里做:dryRun!==true 的调用由 @dshtrading/base 的 gate 插件在
215
+ * `tools/pre-execute` waterfall 统一 ask(headless 下 ask=deny,fail-closed);
216
+ * 工具内不再重复调 ctx.approval。
217
+ */
218
+ declare function createPlaceOrderTool(deps: PlaceOrderToolDeps): import("@deepseek-ai/dsh-tools").ToolDefinition;
219
+ /** 本连接器的路由 provider slug(docs/exchange-routing.md §2.2)。 */
220
+ declare const ROUTER_PROVIDER = "okx";
221
+ /** 市场路由服务的最小消费面(api 包 MarketRouterService 同构;不定死接口)。 */
222
+ interface MarketRouterLike {
223
+ activeProvider(market: string): string | undefined;
224
+ }
225
+ declare function apply(ctx: Context, config: Config): void;
226
+ //#endregion
227
+ export { BAR_MAP, Config, CredentialResolverLike, CredentialsContext, DryRunReference, MarketRouterLike, NormalizedSize, OKX_INTERVAL_VOCABULARY, OkxCredentials, OkxFundingRate, OkxInstrument, OkxMarketDataService, OkxOpenInterest, OkxPlaceOrderParams, OkxRestClient, OkxRestOptions, OkxTradeService, OkxTradeServiceOptions, OrderGateVerdict, PlaceOrderArgs, PlaceOrderToolDeps, ROUTER_PROVIDER, ResolvedCredentialRefs, SignedAuth, SubscribeTickerOptions, TRADING_CRYPTO_MARKET_DATA_KEY, TRADING_CRYPTO_TRADE_KEY, TradingServiceError, apply, barDurationMs, buildAuthHeaders, buildDryRunReceipt, createPlaceOrderTool, credentialRefsFor, evaluateOrderGate, inject, isoTimestamp, mapOrderState, name, normalizeOkxSymbol, normalizeSize, resolveCredentials, signPayload, signaturePrehash, toBar, toCanonicalOkxSymbol, toOkxSwapInstId };