@dshtrading/connector-longbridge 0.6.2 → 0.7.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/README.md +27 -4
- package/lib/index.d.ts +8 -4
- package/lib/index.js +12 -3
- package/lib/rest.d.ts +17 -0
- package/lib/rest.js +25 -17
- package/package.json +6 -6
package/README.md
CHANGED
|
@@ -85,15 +85,38 @@
|
|
|
85
85
|
|---|---|---|
|
|
86
86
|
| 实时报价 | `GET /v1/quote/realtime?symbol=00700.HK` | `src/rest.ts:184` |
|
|
87
87
|
| K 线 | `GET /v1/quote/candlesticks?symbol=&period=&count=` | `src/rest.ts:221` |
|
|
88
|
-
| 账户资产 | `GET /v1/asset/account` | `src/rest.ts:255` |
|
|
89
|
-
| 持仓 | `GET /v1/
|
|
90
|
-
| 下单 | `POST /v1/trade/order`(`symbol` / `order_type`(MO\|LO) / `side`(Buy\|Sell) / `submitted_quantity` / `submitted_price` / `time_in_force: 'Day'` / `remark?`) | `src/rest.ts:317-
|
|
91
|
-
| 撤单 | `DELETE /v1/trade/order?order_id=` | `src/rest.ts:352
|
|
88
|
+
| 账户资产 | `GET /v1/asset/account`(上游 `currency` / `total_cash` / `net_assets` → 契约的 `asset` / `free` / `locked`) | `src/rest.ts:255` |
|
|
89
|
+
| 持仓 | **不发请求**:文档化端点 `GET /v1/asset/stock` 的应答缺 api 要求的 `side` / `timestamp`,见下节 | `src/rest.ts:291-304` |
|
|
90
|
+
| 下单 | `POST /v1/trade/order`(`symbol` / `order_type`(MO\|LO) / `side`(Buy\|Sell) / `submitted_quantity` / `submitted_price` / `time_in_force: 'Day'` / `remark?`) | `src/rest.ts:317-327` |
|
|
91
|
+
| 撤单 | `DELETE /v1/trade/order?order_id=` | `src/rest.ts:350-352` |
|
|
92
92
|
|
|
93
93
|
请求头(`src/rest.ts:136-161`):`Accept` / `Content-Type` 恒发;`authorization: Bearer <token>`
|
|
94
94
|
在有 access token 时发;`x-api-key` + `x-timestamp`(`Date.now()`)在有 app key 时发;
|
|
95
95
|
`x-api-signature` **只在 appKey、accessToken 与 appSecret 三者齐备**时发(第 148-160 行)。
|
|
96
96
|
|
|
97
|
+
## 持仓(getPositions):显式不可用,不返回任何行
|
|
98
|
+
|
|
99
|
+
`getPositions()` 现在**不发上游请求**,直接抛 `TRADING_NOT_IMPLEMENTED`:api 契约要求的两样真数据在上游不存在,编不出来。
|
|
100
|
+
|
|
101
|
+
- `@dshtrading/api` 的 `Position` 必填 `side: 'long' | 'short'` 与 `timestamp: number`
|
|
102
|
+
(`packages/api/src/index.ts:499-520`;`size` 的注释明写「正数;方向由 side 表达」)。
|
|
103
|
+
- 长桥文档化的持仓端点是 `GET /v1/asset/stock`,应答 `data.list[].stock_info[]` 只有
|
|
104
|
+
`symbol` / `symbol_name` / `currency` / `quantity` / `market` / `available_quantity` /
|
|
105
|
+
`cost_price` / `init_quantity`(open.longbridge.com「Stock Positions」)。官方 Node SDK
|
|
106
|
+
`longport@4.3.7` 的 `StockPosition`(`index.d.ts:2377-2399`)字段集完全一致:**没有方向、也没有持仓时间**。
|
|
107
|
+
源码原先读的 `GET /v1/trade/stock/position` 与 `data.channels[].positions[].unrealized_pnl` 是
|
|
108
|
+
protobuf SDK 的消息形状,不是这套 HTTP 文档的形状(24 个 trade 文档页里没有该路径,`unrealized_pnl` 也不存在)。
|
|
109
|
+
- `quantity` 是持股数,不是带符号仓位:`quantity > 0 ? 'long' : 'short'` 只要上游给的是绝对值,
|
|
110
|
+
就会把所有空头标成多头——那是把「数量空白 + 恒显示空头」换成一个更难发现的方向错误。
|
|
111
|
+
`timestamp` 同理没有来源,用 `Date.now()` 冒充持仓时间也是编数据。
|
|
112
|
+
|
|
113
|
+
所以这里选「显式不可用」(与 connector-futu / connector-bybit / connector-ccxt 的持仓读数同裁决),
|
|
114
|
+
而不是返回空数组:空数组的语义是「没有持仓」,和「读不到」是两句不同的话。
|
|
115
|
+
|
|
116
|
+
解除条件(任一):① api 侧把 `side` / `timestamp` 降为可选;② api 侧新增「持仓快照」类型
|
|
117
|
+
(`size` + 可选 `side` / `timestamp`);③ 上游给出方向与持仓时间,届时按 `size = |quantity|` 映射
|
|
118
|
+
上游字段,仍不在本地推方向。
|
|
119
|
+
|
|
97
120
|
## 周期与契约
|
|
98
121
|
|
|
99
122
|
- **周期词汇 8 个**:`1m/5m/15m/30m/1h/1d/1w/1M`(`src/rest.ts:24`),到上游的映射是
|
package/lib/index.d.ts
CHANGED
|
@@ -47,10 +47,14 @@ declare class LongbridgeTradeService extends Service implements TradeService {
|
|
|
47
47
|
}, serviceName?: string);
|
|
48
48
|
getBalance(): Promise<AccountBalance$1>;
|
|
49
49
|
placeOrder(order: OrderRequest): Promise<Order$1>;
|
|
50
|
-
cancelOrder(orderId: string): Promise<
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
50
|
+
cancelOrder(orderId: string, _symbol?: string): Promise<void>;
|
|
51
|
+
/**
|
|
52
|
+
* 单笔订单状态查询(api 契约必选方法,R3 okx 切片 2026-08-29 引入)。本连接器没有单笔
|
|
53
|
+
* 订单状态端点:必须显式拒绝,绝不编造订单回执——编造回执会被读成「订单存在/不存在」,
|
|
54
|
+
* 而契约语义是 TRADING_NOT_IMPLEMENTED(不可用)。与 connector-ccxt / connector-bybit /
|
|
55
|
+
* connector-tushare 的同名方法同形。
|
|
56
|
+
*/
|
|
57
|
+
getOrder(_symbol: string, _id: string): Promise<Order$1>;
|
|
54
58
|
getPositions(): Promise<Position$1[]>;
|
|
55
59
|
getOrders(): Promise<Order$1[]>;
|
|
56
60
|
}
|
package/lib/index.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { INTERVAL_VOCABULARY, LongbridgeRestClient, TradingServiceError, assertSupportedInterval, generateLongbridgeSignature, parseIntervalMs, toLongbridgePeriod, toLongbridgeSymbol
|
|
1
|
+
import { INTERVAL_VOCABULARY, LongbridgeRestClient, TradingServiceError, assertSupportedInterval, generateLongbridgeSignature, parseIntervalMs, toLongbridgePeriod, toLongbridgeSymbol } from "./rest.js";
|
|
2
2
|
import { liveTradingEnabled } from "@dshtrading/authority";
|
|
3
3
|
import { OrderIntentLedger, deriveVenueClientKey, orderFromReceipt, receiptFromOrder } from "@dshtrading/authority/order-intents";
|
|
4
4
|
import { Service } from "@deepseek-ai/cordis";
|
|
@@ -111,10 +111,19 @@ var LongbridgeTradeService = class extends Service {
|
|
|
111
111
|
});
|
|
112
112
|
return placed;
|
|
113
113
|
}
|
|
114
|
-
async cancelOrder(orderId) {
|
|
114
|
+
async cancelOrder(orderId, _symbol) {
|
|
115
115
|
if (!liveTradingEnabled(this.config.liveTrading) || this.config.dryRun) throw new TradingServiceError("TRADING_LIVE_TRADING_DISABLED", "Longbridge 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.");
|
|
116
116
|
return this.client.cancelOrder(void 0, orderId);
|
|
117
117
|
}
|
|
118
|
+
/**
|
|
119
|
+
* 单笔订单状态查询(api 契约必选方法,R3 okx 切片 2026-08-29 引入)。本连接器没有单笔
|
|
120
|
+
* 订单状态端点:必须显式拒绝,绝不编造订单回执——编造回执会被读成「订单存在/不存在」,
|
|
121
|
+
* 而契约语义是 TRADING_NOT_IMPLEMENTED(不可用)。与 connector-ccxt / connector-bybit /
|
|
122
|
+
* connector-tushare 的同名方法同形。
|
|
123
|
+
*/
|
|
124
|
+
async getOrder(_symbol, _id) {
|
|
125
|
+
throw new TradingServiceError("TRADING_NOT_IMPLEMENTED", "Longbridge TradeService.getOrder is not implemented: this connector has no single-order status lookup. That means \"not available\", NOT \"order not found\".");
|
|
126
|
+
}
|
|
118
127
|
async getPositions() {
|
|
119
128
|
return this.client.getPositions();
|
|
120
129
|
}
|
|
@@ -338,4 +347,4 @@ function apply(ctx, config) {
|
|
|
338
347
|
});
|
|
339
348
|
}
|
|
340
349
|
//#endregion
|
|
341
|
-
export { Config, INTERVAL_VOCABULARY, LONGBRIDGE_REMARK_RULE, LongbridgeMarketDataService, LongbridgeRestClient, LongbridgeTradeService, ROUTER_PROVIDER, TRADING_HK_MARKET_DATA_KEY, TRADING_HK_TRADE_KEY, TradingServiceError, apply, assertSupportedInterval, generateLongbridgeSignature, name, parseIntervalMs, resolveCredentials, routeAllows, toLongbridgePeriod, toLongbridgeSymbol
|
|
350
|
+
export { Config, INTERVAL_VOCABULARY, LONGBRIDGE_REMARK_RULE, LongbridgeMarketDataService, LongbridgeRestClient, LongbridgeTradeService, ROUTER_PROVIDER, TRADING_HK_MARKET_DATA_KEY, TRADING_HK_TRADE_KEY, TradingServiceError, apply, assertSupportedInterval, generateLongbridgeSignature, name, parseIntervalMs, resolveCredentials, routeAllows, toLongbridgePeriod, toLongbridgeSymbol };
|
package/lib/rest.d.ts
CHANGED
|
@@ -42,6 +42,23 @@ declare class LongbridgeRestClient {
|
|
|
42
42
|
getTicker(symbol: string): Promise<Ticker$1>;
|
|
43
43
|
getKlines(symbol: string, interval?: Interval$1, limit?: number): Promise<Kline$1[]>;
|
|
44
44
|
getBalance(): Promise<AccountBalance$1>;
|
|
45
|
+
/**
|
|
46
|
+
* 持仓快照。**当前显式拒绝,不产出任何行**——上游没有 api 契约要求的那两样真数据:
|
|
47
|
+
*
|
|
48
|
+
* - api 的 `Position` 必填 `side: 'long' | 'short'` 与 `timestamp: number`
|
|
49
|
+
* (packages/api/src/index.ts:499-520,size 的注释明写「正数;方向由 side 表达」)。
|
|
50
|
+
* - 长桥文档化的持仓端点是 `GET /v1/asset/stock`,应答 `data.list[].stock_info[]`
|
|
51
|
+
* 只有 symbol / symbol_name / currency / quantity / market / available_quantity /
|
|
52
|
+
* cost_price / init_quantity;官方 SDK 的 `StockPosition`(longport 4.3.7
|
|
53
|
+
* index.d.ts:2377-2399)字段集完全一致,同样没有方向、也没有持仓时间。
|
|
54
|
+
* - `quantity` 是持股数,不是带符号的仓位:`quantity > 0 ? 'long' : 'short'` 这种填充
|
|
55
|
+
* 只要上游给的是绝对值,就把所有空头标成多头——那是把「数量空白 + 恒显示空头」换成一个
|
|
56
|
+
* 更难发现的方向错误。`timestamp` 同理没有来源,拿 Date.now() 冒充持仓时间也是编数据。
|
|
57
|
+
*
|
|
58
|
+
* 裁决与 connector-futu / connector-bybit / connector-ccxt 的持仓读数同款:不可用就
|
|
59
|
+
* TRADING_NOT_IMPLEMENTED,绝不返回空数组(空数组的语义是「没有持仓」,与「读不到」是两句
|
|
60
|
+
* 不同的话)。解除条件见 README「持仓(getPositions)」一节。
|
|
61
|
+
*/
|
|
45
62
|
getPositions(): Promise<Position$1[]>;
|
|
46
63
|
placeOrder(_creds: unknown, req: OrderRequest, remark?: string): Promise<Order$1>;
|
|
47
64
|
cancelOrder(_creds: unknown, orderId: string): Promise<{
|
package/lib/rest.js
CHANGED
|
@@ -164,28 +164,36 @@ var LongbridgeRestClient = class {
|
|
|
164
164
|
async getBalance() {
|
|
165
165
|
if (!this.appKey || !this.accessToken) throw new TradingServiceError("TRADING_AUTH_FAILED", "Longbridge: appKey and accessToken are required for getBalance");
|
|
166
166
|
const res = await this.requestJson("/v1/asset/account");
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
const
|
|
170
|
-
const
|
|
167
|
+
const first = res.data?.list?.[0];
|
|
168
|
+
if (res.code !== 0 || first === void 0) throw new TradingServiceError("TRADING_EXCHANGE_ERROR", "Longbridge getBalance failed: " + (res.message ?? "code " + res.code));
|
|
169
|
+
const cash = parseFloat(first.total_cash || "0");
|
|
170
|
+
const netAssets = parseFloat(first.net_assets || first.total_cash || "0");
|
|
171
171
|
return {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
172
|
+
asset: first.currency ?? "HKD",
|
|
173
|
+
free: cash,
|
|
174
|
+
locked: Math.max(netAssets - cash, 0)
|
|
175
175
|
};
|
|
176
176
|
}
|
|
177
|
+
/**
|
|
178
|
+
* 持仓快照。**当前显式拒绝,不产出任何行**——上游没有 api 契约要求的那两样真数据:
|
|
179
|
+
*
|
|
180
|
+
* - api 的 `Position` 必填 `side: 'long' | 'short'` 与 `timestamp: number`
|
|
181
|
+
* (packages/api/src/index.ts:499-520,size 的注释明写「正数;方向由 side 表达」)。
|
|
182
|
+
* - 长桥文档化的持仓端点是 `GET /v1/asset/stock`,应答 `data.list[].stock_info[]`
|
|
183
|
+
* 只有 symbol / symbol_name / currency / quantity / market / available_quantity /
|
|
184
|
+
* cost_price / init_quantity;官方 SDK 的 `StockPosition`(longport 4.3.7
|
|
185
|
+
* index.d.ts:2377-2399)字段集完全一致,同样没有方向、也没有持仓时间。
|
|
186
|
+
* - `quantity` 是持股数,不是带符号的仓位:`quantity > 0 ? 'long' : 'short'` 这种填充
|
|
187
|
+
* 只要上游给的是绝对值,就把所有空头标成多头——那是把「数量空白 + 恒显示空头」换成一个
|
|
188
|
+
* 更难发现的方向错误。`timestamp` 同理没有来源,拿 Date.now() 冒充持仓时间也是编数据。
|
|
189
|
+
*
|
|
190
|
+
* 裁决与 connector-futu / connector-bybit / connector-ccxt 的持仓读数同款:不可用就
|
|
191
|
+
* TRADING_NOT_IMPLEMENTED,绝不返回空数组(空数组的语义是「没有持仓」,与「读不到」是两句
|
|
192
|
+
* 不同的话)。解除条件见 README「持仓(getPositions)」一节。
|
|
193
|
+
*/
|
|
177
194
|
async getPositions() {
|
|
178
195
|
if (!this.appKey || !this.accessToken) throw new TradingServiceError("TRADING_AUTH_FAILED", "Longbridge: appKey and accessToken are required for getPositions");
|
|
179
|
-
|
|
180
|
-
if (res.code !== 0 || !Array.isArray(res.data?.channels)) return [];
|
|
181
|
-
const positions = [];
|
|
182
|
-
for (const channel of res.data.channels) if (Array.isArray(channel.positions)) for (const p of channel.positions) positions.push({
|
|
183
|
-
symbol: toLongbridgeSymbol(p.symbol).canonical,
|
|
184
|
-
quantity: parseFloat(p.quantity || "0"),
|
|
185
|
-
entryPrice: parseFloat(p.cost_price || "0"),
|
|
186
|
-
unrealizedPnl: parseFloat(p.unrealized_pnl || "0")
|
|
187
|
-
});
|
|
188
|
-
return positions;
|
|
196
|
+
throw new TradingServiceError("TRADING_NOT_IMPLEMENTED", "Longbridge TradeService.getPositions is not available: @dshtrading/api Position requires side (long|short) and timestamp, and the Longbridge stock-position snapshot provides neither. The documented endpoint GET /v1/asset/stock answers data.list[].stock_info[] with only symbol, symbol_name, currency, quantity, market, available_quantity, cost_price and init_quantity (the official SDK StockPosition carries the same field set). quantity is a holding count rather than a signed size, so direction cannot be derived, and there is no open time to report. That means \"not available\", NOT \"no positions\".");
|
|
189
197
|
}
|
|
190
198
|
async placeOrder(_creds, req, remark) {
|
|
191
199
|
if (!this.appKey || !this.accessToken) throw new TradingServiceError("TRADING_AUTH_FAILED", "Longbridge: appKey and accessToken are required for placeOrder");
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dshtrading/connector-longbridge",
|
|
3
3
|
"description": "长桥(Longbridge)港股连接器:直连券商 OpenAPI(openapi.longportapp.com,HMAC-SHA256 签名 + BYOK 三凭证 ref),实现 MarketDataService/TradeService 与 5 个 hk_* 工具;dry-run 缺省、liveTrading 只是非权威镜像(放行归 @dshtrading/authority 人工签署)、凭证只进 ref。",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.7.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"types": "./lib/index.d.ts",
|
|
@@ -21,13 +21,13 @@
|
|
|
21
21
|
],
|
|
22
22
|
"license": "PolyForm-Noncommercial-1.0.0",
|
|
23
23
|
"dependencies": {
|
|
24
|
-
"@dshtrading/
|
|
25
|
-
"@dshtrading/
|
|
24
|
+
"@dshtrading/api": "^0.7.0",
|
|
25
|
+
"@dshtrading/authority": "^0.7.0"
|
|
26
26
|
},
|
|
27
27
|
"peerDependencies": {
|
|
28
|
-
"@deepseek-ai/cordis": ">=4.
|
|
29
|
-
"@deepseek-ai/dsh-tools": ">=0.2.
|
|
30
|
-
"@deepseek-ai/schemastery": ">=3.
|
|
28
|
+
"@deepseek-ai/cordis": ">=4.1.0",
|
|
29
|
+
"@deepseek-ai/dsh-tools": ">=0.2.2-rc.1",
|
|
30
|
+
"@deepseek-ai/schemastery": ">=3.19.0"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"tsdown": "^0.22.0",
|