@yuants/vendor-okx 0.17.0 → 0.17.1
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/dist/ohlc.js +75 -2
- package/dist/ohlc.js.map +1 -1
- package/dist/websocket.js +80 -0
- package/dist/websocket.js.map +1 -0
- package/lib/ohlc.js +73 -0
- package/lib/ohlc.js.map +1 -1
- package/lib/websocket.d.ts +14 -0
- package/lib/websocket.d.ts.map +1 -0
- package/lib/websocket.js +83 -0
- package/lib/websocket.js.map +1 -0
- package/package.json +5 -3
- package/temp/image-tag +1 -1
- package/temp/package-deps.json +6 -5
package/dist/ohlc.js
CHANGED
@@ -12,9 +12,11 @@ var __asyncGenerator = (this && this.__asyncGenerator) || function (thisArg, _ar
|
|
12
12
|
};
|
13
13
|
import { createSeriesProvider } from '@yuants/data-series';
|
14
14
|
import { Terminal } from '@yuants/protocol';
|
15
|
-
import { convertDurationToOffset, decodePath, formatTime } from '@yuants/utils';
|
16
|
-
import { firstValueFrom, timer } from 'rxjs';
|
15
|
+
import { convertDurationToOffset, decodePath, encodePath, formatTime } from '@yuants/utils';
|
16
|
+
import { firstValueFrom, Observable, timer } from 'rxjs';
|
17
17
|
import { client } from './api';
|
18
|
+
import { okxBusinessWsClient } from './websocket';
|
19
|
+
import { writeToSQL } from '@yuants/sql';
|
18
20
|
// 时间粒度,默认值1m
|
19
21
|
// 如 [1m/3m/5m/15m/30m/1H/2H/4H]
|
20
22
|
// 香港时间开盘价k线:[6H/12H/1D/1W/1M]
|
@@ -34,6 +36,21 @@ const DURATION_TO_OKX_BAR_TYPE = {
|
|
34
36
|
P1W: '1W',
|
35
37
|
P1M: '1M',
|
36
38
|
};
|
39
|
+
const DURATION_TO_OKX_CANDLE_TYPE = {
|
40
|
+
PT1M: 'mark-price-candle1m',
|
41
|
+
PT3M: 'mark-price-candle3m',
|
42
|
+
PT5M: 'mark-price-candle5m',
|
43
|
+
PT15M: 'mark-price-candle15m',
|
44
|
+
PT30M: 'mark-price-candle30m',
|
45
|
+
PT1H: 'mark-price-candle1H',
|
46
|
+
PT2H: 'mark-price-candle2H',
|
47
|
+
PT4H: 'mark-price-candle4H',
|
48
|
+
PT6H: 'mark-price-candle6H',
|
49
|
+
PT12H: 'mark-price-candle12H',
|
50
|
+
P1D: 'mark-price-candle1D',
|
51
|
+
P1W: 'mark-price-candle1W',
|
52
|
+
P1M: 'mark-price-candle1M',
|
53
|
+
};
|
37
54
|
createSeriesProvider(Terminal.fromNodeEnv(), {
|
38
55
|
tableName: 'ohlc',
|
39
56
|
series_id_prefix_parts: ['OKX'],
|
@@ -95,4 +112,60 @@ createSeriesProvider(Terminal.fromNodeEnv(), {
|
|
95
112
|
});
|
96
113
|
},
|
97
114
|
});
|
115
|
+
Terminal.fromNodeEnv().channel.publishChannel('ohlc', { pattern: `^OKX/` }, (series_id) => {
|
116
|
+
// const [] = decodePath(args)
|
117
|
+
const [datasource_id, product_id, duration] = decodePath(series_id);
|
118
|
+
const [, instId] = decodePath(product_id);
|
119
|
+
const offset = convertDurationToOffset(duration);
|
120
|
+
if (!datasource_id) {
|
121
|
+
throw 'datasource_id is required';
|
122
|
+
}
|
123
|
+
if (!product_id) {
|
124
|
+
throw 'product_id is required';
|
125
|
+
}
|
126
|
+
if (!offset) {
|
127
|
+
throw 'duration is invalid';
|
128
|
+
}
|
129
|
+
const candleType = DURATION_TO_OKX_CANDLE_TYPE[duration];
|
130
|
+
console.info(formatTime(Date.now()), `subscribe`, series_id);
|
131
|
+
return new Observable((subscriber) => {
|
132
|
+
console.info(formatTime(Date.now()), `subscribe`, candleType, instId);
|
133
|
+
okxBusinessWsClient.subscribe(candleType, instId, async (data) => {
|
134
|
+
const created_at = Number(data[0]);
|
135
|
+
const closed_at = created_at + offset;
|
136
|
+
const open = data[1];
|
137
|
+
const high = data[2];
|
138
|
+
const low = data[3];
|
139
|
+
const close = data[4];
|
140
|
+
if (isNaN(closed_at)) {
|
141
|
+
return;
|
142
|
+
}
|
143
|
+
console.info(formatTime(Date.now()), `insertData`, data);
|
144
|
+
const cancelData = {
|
145
|
+
closed_at: formatTime(closed_at),
|
146
|
+
created_at: formatTime(created_at),
|
147
|
+
open,
|
148
|
+
high,
|
149
|
+
low,
|
150
|
+
close,
|
151
|
+
series_id,
|
152
|
+
datasource_id,
|
153
|
+
duration,
|
154
|
+
product_id,
|
155
|
+
volume: '0',
|
156
|
+
open_interest: '0',
|
157
|
+
};
|
158
|
+
subscriber.next(cancelData);
|
159
|
+
});
|
160
|
+
return () => {
|
161
|
+
okxBusinessWsClient.unsubscribe(candleType, instId);
|
162
|
+
};
|
163
|
+
}).pipe(writeToSQL({
|
164
|
+
tableName: 'ohlc',
|
165
|
+
conflictKeys: ['created_at', 'series_id'],
|
166
|
+
writeInterval: 1000,
|
167
|
+
terminal: Terminal.fromNodeEnv(),
|
168
|
+
keyFn: (x) => encodePath(x.created_at, x.series_id),
|
169
|
+
}));
|
170
|
+
});
|
98
171
|
//# sourceMappingURL=ohlc.js.map
|
package/dist/ohlc.js.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"ohlc.js","sourceRoot":"","sources":["../src/ohlc.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,uBAAuB,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;
|
1
|
+
{"version":3,"file":"ohlc.js","sourceRoot":"","sources":["../src/ohlc.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,OAAO,EAAE,oBAAoB,EAAE,MAAM,qBAAqB,CAAC;AAC3D,OAAO,EAAE,QAAQ,EAAE,MAAM,kBAAkB,CAAC;AAC5C,OAAO,EAAE,uBAAuB,EAAE,UAAU,EAAE,UAAU,EAAE,UAAU,EAAE,MAAM,eAAe,CAAC;AAC5F,OAAO,EAAE,cAAc,EAAE,UAAU,EAAM,KAAK,EAAE,MAAM,MAAM,CAAC;AAC7D,OAAO,EAAE,MAAM,EAAE,MAAM,OAAO,CAAC;AAC/B,OAAO,EAAE,mBAAmB,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EAA2C,UAAU,EAAE,MAAM,aAAa,CAAC;AAElF,aAAa;AACb,gCAAgC;AAChC,8BAA8B;AAC9B,8CAA8C;AAE9C,MAAM,wBAAwB,GAA2B;IACvD,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,IAAI;IACV,KAAK,EAAE,KAAK;IACZ,KAAK,EAAE,KAAK;IAEZ,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,IAAI;IACV,IAAI,EAAE,IAAI;IACV,KAAK,EAAE,KAAK;IAEZ,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;IACT,GAAG,EAAE,IAAI;CACV,CAAC;AAEF,MAAM,2BAA2B,GAA2B;IAC1D,IAAI,EAAE,qBAAqB;IAC3B,IAAI,EAAE,qBAAqB;IAC3B,IAAI,EAAE,qBAAqB;IAC3B,KAAK,EAAE,sBAAsB;IAC7B,KAAK,EAAE,sBAAsB;IAE7B,IAAI,EAAE,qBAAqB;IAC3B,IAAI,EAAE,qBAAqB;IAC3B,IAAI,EAAE,qBAAqB;IAC3B,IAAI,EAAE,qBAAqB;IAC3B,KAAK,EAAE,sBAAsB;IAE7B,GAAG,EAAE,qBAAqB;IAC1B,GAAG,EAAE,qBAAqB;IAC1B,GAAG,EAAE,qBAAqB;CAC3B,CAAC;AAEF,oBAAoB,CAAQ,QAAQ,CAAC,WAAW,EAAE,EAAE;IAClD,SAAS,EAAE,MAAM;IACjB,sBAAsB,EAAE,CAAC,KAAK,CAAC;IAC/B,QAAQ,EAAE,IAAI;IACd,cAAc,EAAE,EAAE,UAAU,EAAE,CAAC,EAAE;IACjC,OAAO,EAAE,UAAiB,EAAE,SAAS,EAAE,QAAQ,EAAE;;YAC/C,MAAM,CAAC,aAAa,EAAE,UAAU,EAAE,QAAQ,CAAC,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;YACpE,MAAM,MAAM,GAAG,uBAAuB,CAAC,QAAQ,CAAC,CAAC;YACjD,IAAI,CAAC,aAAa,EAAE;gBAClB,MAAM,2BAA2B,CAAC;aACnC;YACD,IAAI,CAAC,UAAU,EAAE;gBACf,MAAM,wBAAwB,CAAC;aAChC;YACD,IAAI,CAAC,MAAM,EAAE;gBACX,MAAM,qBAAqB,CAAC;aAC7B;YACD,MAAM,CAAC,QAAQ,EAAE,MAAM,CAAC,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;YAClD,IAAI,CAAC,MAAM,EAAE;gBACX,MAAM,uBAAuB,UAAU,EAAE,CAAC;aAC3C;YAED,MAAM,GAAG,GAAG,wBAAwB,CAAC,QAAQ,CAAC,CAAC;YAC/C,IAAI,CAAC,GAAG,EAAE;gBACR,MAAM,yBAAyB,QAAQ,EAAE,CAAC;aAC3C;YAED,IAAI,gBAAgB,GAAG,QAAQ,CAAC;YAEhC,OAAO,IAAI,EAAE;gBACX,yBAAyB;gBACzB,MAAM,GAAG,GAAG,cAAM,MAAM,CAAC,iBAAiB,CAAC;oBACzC,MAAM;oBACN,GAAG;oBACH,KAAK,EAAE,GAAG,gBAAgB,EAAE;oBAC5B,KAAK,EAAE,KAAK;iBACb,CAAC,CAAA,CAAC;gBACH,IAAI,GAAG,CAAC,IAAI,KAAK,GAAG,EAAE;oBACpB,MAAM,eAAe,GAAG,CAAC,IAAI,IAAI,GAAG,CAAC,GAAG,EAAE,CAAC;iBAC5C;gBACD,IAAI,GAAG,CAAC,IAAI,CAAC,MAAM,KAAK,CAAC;oBAAE,MAAM;gBACjC,gBAAgB,GAAG,CAAC,GAAG,CAAC,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;gBACrD,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,CAAC,GAAG,CACvB,CAAC,CAAC,EAAS,EAAE,CAAC,CAAC;oBACb,SAAS;oBACT,aAAa;oBACb,UAAU;oBACV,QAAQ;oBACR,UAAU,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;oBAC7B,SAAS,EAAE,UAAU,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,CAAC;oBACrC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;oBACV,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;oBACV,GAAG,EAAE,CAAC,CAAC,CAAC,CAAC;oBACT,KAAK,EAAE,CAAC,CAAC,CAAC,CAAC;oBACX,MAAM,EAAE,CAAC,CAAC,CAAC,CAAC;oBACZ,aAAa,EAAE,GAAG;iBACnB,CAAC,CACH,CAAC;gBACF,oBAAM,IAAI,CAAA,CAAC;gBACX,cAAM,cAAc,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,CAAA,CAAC;aACnC;QACH,CAAC;KAAA;CACF,CAAC,CAAC;AAEH,QAAQ,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,cAAc,CAAC,MAAM,EAAE,EAAE,OAAO,EAAE,OAAO,EAAE,EAAE,CAAC,SAAS,EAAE,EAAE;IACxF,8BAA8B;IAC9B,MAAM,CAAC,aAAa,EAAE,UAAU,EAAE,QAAQ,CAAC,GAAG,UAAU,CAAC,SAAS,CAAC,CAAC;IACpE,MAAM,CAAC,EAAE,MAAM,CAAC,GAAG,UAAU,CAAC,UAAU,CAAC,CAAC;IAC1C,MAAM,MAAM,GAAG,uBAAuB,CAAC,QAAQ,CAAC,CAAC;IACjD,IAAI,CAAC,aAAa,EAAE;QAClB,MAAM,2BAA2B,CAAC;KACnC;IACD,IAAI,CAAC,UAAU,EAAE;QACf,MAAM,wBAAwB,CAAC;KAChC;IACD,IAAI,CAAC,MAAM,EAAE;QACX,MAAM,qBAAqB,CAAC;KAC7B;IACD,MAAM,UAAU,GAAG,2BAA2B,CAAC,QAAQ,CAAC,CAAC;IACzD,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,WAAW,EAAE,SAAS,CAAC,CAAC;IAC7D,OAAO,IAAI,UAAU,CAAQ,CAAC,UAAU,EAAE,EAAE;QAC1C,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,CAAC,CAAC;QACtE,mBAAmB,CAAC,SAAS,CAAC,UAAU,EAAE,MAAM,EAAE,KAAK,EAAE,IAAc,EAAE,EAAE;YACzE,MAAM,UAAU,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC;YACnC,MAAM,SAAS,GAAG,UAAU,GAAG,MAAM,CAAC;YACtC,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YACrB,MAAM,IAAI,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YACrB,MAAM,GAAG,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YACpB,MAAM,KAAK,GAAG,IAAI,CAAC,CAAC,CAAC,CAAC;YACtB,IAAI,KAAK,CAAC,SAAS,CAAC,EAAE;gBACpB,OAAO;aACR;YACD,OAAO,CAAC,IAAI,CAAC,UAAU,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,EAAE,YAAY,EAAE,IAAI,CAAC,CAAC;YACzD,MAAM,UAAU,GAAU;gBACxB,SAAS,EAAE,UAAU,CAAC,SAAS,CAAC;gBAChC,UAAU,EAAE,UAAU,CAAC,UAAU,CAAC;gBAClC,IAAI;gBACJ,IAAI;gBACJ,GAAG;gBACH,KAAK;gBACL,SAAS;gBACT,aAAa;gBACb,QAAQ;gBACR,UAAU;gBACV,MAAM,EAAE,GAAG;gBACX,aAAa,EAAE,GAAG;aACnB,CAAC;YACF,UAAU,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;QAC9B,CAAC,CAAC,CAAC;QACH,OAAO,GAAG,EAAE;YACV,mBAAmB,CAAC,WAAW,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC;QACtD,CAAC,CAAC;IACJ,CAAC,CAAC,CAAC,IAAI,CACL,UAAU,CAAC;QACT,SAAS,EAAE,MAAM;QACjB,YAAY,EAAE,CAAC,YAAY,EAAE,WAAW,CAAC;QACzC,aAAa,EAAE,IAAI;QACnB,QAAQ,EAAE,QAAQ,CAAC,WAAW,EAAE;QAChC,KAAK,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,UAAU,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,SAAS,CAAC;KACpD,CAAC,CACH,CAAC;AACJ,CAAC,CAAC,CAAC","sourcesContent":["import { IOHLC } from '@yuants/data-ohlc';\nimport { createSeriesProvider } from '@yuants/data-series';\nimport { Terminal } from '@yuants/protocol';\nimport { convertDurationToOffset, decodePath, encodePath, formatTime } from '@yuants/utils';\nimport { firstValueFrom, Observable, of, timer } from 'rxjs';\nimport { client } from './api';\nimport { okxBusinessWsClient } from './websocket';\nimport { buildInsertManyIntoTableSQL, requestSQL, writeToSQL } from '@yuants/sql';\n\n// 时间粒度,默认值1m\n// 如 [1m/3m/5m/15m/30m/1H/2H/4H]\n// 香港时间开盘价k线:[6H/12H/1D/1W/1M]\n// UTC时间开盘价k线:[6Hutc/12Hutc/1Dutc/1Wutc/1Mutc]\n\nconst DURATION_TO_OKX_BAR_TYPE: Record<string, string> = {\n PT1M: '1m',\n PT3M: '3m',\n PT5M: '5m',\n PT15M: '15m',\n PT30M: '30m',\n\n PT1H: '1H',\n PT2H: '2H',\n PT4H: '4H',\n PT6H: '6H',\n PT12H: '12H',\n\n P1D: '1D',\n P1W: '1W',\n P1M: '1M',\n};\n\nconst DURATION_TO_OKX_CANDLE_TYPE: Record<string, string> = {\n PT1M: 'mark-price-candle1m',\n PT3M: 'mark-price-candle3m',\n PT5M: 'mark-price-candle5m',\n PT15M: 'mark-price-candle15m',\n PT30M: 'mark-price-candle30m',\n\n PT1H: 'mark-price-candle1H',\n PT2H: 'mark-price-candle2H',\n PT4H: 'mark-price-candle4H',\n PT6H: 'mark-price-candle6H',\n PT12H: 'mark-price-candle12H',\n\n P1D: 'mark-price-candle1D',\n P1W: 'mark-price-candle1W',\n P1M: 'mark-price-candle1M',\n};\n\ncreateSeriesProvider<IOHLC>(Terminal.fromNodeEnv(), {\n tableName: 'ohlc',\n series_id_prefix_parts: ['OKX'],\n reversed: true,\n serviceOptions: { concurrent: 1 },\n queryFn: async function* ({ series_id, ended_at }) {\n const [datasource_id, product_id, duration] = decodePath(series_id);\n const offset = convertDurationToOffset(duration);\n if (!datasource_id) {\n throw 'datasource_id is required';\n }\n if (!product_id) {\n throw 'product_id is required';\n }\n if (!offset) {\n throw 'duration is invalid';\n }\n const [instType, instId] = decodePath(product_id);\n if (!instId) {\n throw `invalid product_id: ${product_id}`;\n }\n\n const bar = DURATION_TO_OKX_BAR_TYPE[duration];\n if (!bar) {\n throw `unsupported duration: ${duration}`;\n }\n\n let currentStartTime = ended_at;\n\n while (true) {\n // 向前翻页,时间降序,不含 after 时间点\n const res = await client.getHistoryCandles({\n instId,\n bar,\n after: `${currentStartTime}`,\n limit: '100',\n });\n if (res.code !== '0') {\n throw `API failed: ${res.code} ${res.msg}`;\n }\n if (res.data.length === 0) break;\n currentStartTime = +res.data[res.data.length - 1][0];\n const data = res.data.map(\n (x): IOHLC => ({\n series_id,\n datasource_id,\n product_id,\n duration,\n created_at: formatTime(+x[0]),\n closed_at: formatTime(+x[0] + offset),\n open: x[1],\n high: x[2],\n low: x[3],\n close: x[4],\n volume: x[5],\n open_interest: '0',\n }),\n );\n yield data;\n await firstValueFrom(timer(1000));\n }\n },\n});\n\nTerminal.fromNodeEnv().channel.publishChannel('ohlc', { pattern: `^OKX/` }, (series_id) => {\n // const [] = decodePath(args)\n const [datasource_id, product_id, duration] = decodePath(series_id);\n const [, instId] = decodePath(product_id);\n const offset = convertDurationToOffset(duration);\n if (!datasource_id) {\n throw 'datasource_id is required';\n }\n if (!product_id) {\n throw 'product_id is required';\n }\n if (!offset) {\n throw 'duration is invalid';\n }\n const candleType = DURATION_TO_OKX_CANDLE_TYPE[duration];\n console.info(formatTime(Date.now()), `subscribe`, series_id);\n return new Observable<IOHLC>((subscriber) => {\n console.info(formatTime(Date.now()), `subscribe`, candleType, instId);\n okxBusinessWsClient.subscribe(candleType, instId, async (data: string[]) => {\n const created_at = Number(data[0]);\n const closed_at = created_at + offset;\n const open = data[1];\n const high = data[2];\n const low = data[3];\n const close = data[4];\n if (isNaN(closed_at)) {\n return;\n }\n console.info(formatTime(Date.now()), `insertData`, data);\n const cancelData: IOHLC = {\n closed_at: formatTime(closed_at),\n created_at: formatTime(created_at),\n open,\n high,\n low,\n close,\n series_id,\n datasource_id,\n duration,\n product_id,\n volume: '0',\n open_interest: '0',\n };\n subscriber.next(cancelData);\n });\n return () => {\n okxBusinessWsClient.unsubscribe(candleType, instId);\n };\n }).pipe(\n writeToSQL({\n tableName: 'ohlc',\n conflictKeys: ['created_at', 'series_id'],\n writeInterval: 1000,\n terminal: Terminal.fromNodeEnv(),\n keyFn: (x) => encodePath(x.created_at, x.series_id),\n }),\n );\n});\n"]}
|
@@ -0,0 +1,80 @@
|
|
1
|
+
import { encodePath, formatTime } from '@yuants/utils';
|
2
|
+
// import WebSocket from 'ws';
|
3
|
+
class OKXWsClient {
|
4
|
+
constructor(url) {
|
5
|
+
this.connected = false;
|
6
|
+
// this.instId = instId;
|
7
|
+
this.ws = new WebSocket(url);
|
8
|
+
this.pendingSub = [];
|
9
|
+
// this.ws.
|
10
|
+
this.ws.addEventListener('open', () => {
|
11
|
+
this.connected = true;
|
12
|
+
console.info(formatTime(Date.now()), '✅ WS connected');
|
13
|
+
while (this.pendingSub.length > 0) {
|
14
|
+
const msg = this.pendingSub.shift();
|
15
|
+
if (msg) {
|
16
|
+
this.ws.send(msg);
|
17
|
+
console.info(formatTime(Date.now()), `📩 Sent subscribe for ${msg}`);
|
18
|
+
}
|
19
|
+
}
|
20
|
+
});
|
21
|
+
this.ws.addEventListener('message', (raw) => this.handleMessage(raw));
|
22
|
+
this.subscriptions = new Set();
|
23
|
+
this.handlers = {}; // key: channel, value: callback
|
24
|
+
}
|
25
|
+
// 处理消息
|
26
|
+
handleMessage(raw) {
|
27
|
+
var _a, _b;
|
28
|
+
const msg = JSON.parse(raw.data);
|
29
|
+
if ((_a = msg.arg) === null || _a === void 0 ? void 0 : _a.channel) {
|
30
|
+
const channelId = encodePath(msg.arg.channel, msg.arg.instId);
|
31
|
+
const data = (_b = msg.data) === null || _b === void 0 ? void 0 : _b[0];
|
32
|
+
if (data && this.handlers[channelId]) {
|
33
|
+
this.handlers[channelId](data, msg.arg);
|
34
|
+
}
|
35
|
+
}
|
36
|
+
else if (msg.event) {
|
37
|
+
console.info('Event:', msg);
|
38
|
+
}
|
39
|
+
}
|
40
|
+
// 调用订阅
|
41
|
+
subscribe(channel, instId, handler) {
|
42
|
+
const channelId = encodePath(channel, instId);
|
43
|
+
if (this.subscriptions.has(channelId)) {
|
44
|
+
console.info(formatTime(Date.now()), `⚠️ Already subscribed: ${channelId}`);
|
45
|
+
return;
|
46
|
+
}
|
47
|
+
const subMsg = {
|
48
|
+
op: 'subscribe',
|
49
|
+
args: [{ channel, instId }],
|
50
|
+
};
|
51
|
+
if (this.connected) {
|
52
|
+
this.ws.send(JSON.stringify(subMsg));
|
53
|
+
console.info(formatTime(Date.now()), `📩 Sent subscribe for ${channelId}`);
|
54
|
+
}
|
55
|
+
else {
|
56
|
+
this.pendingSub.push(JSON.stringify(subMsg));
|
57
|
+
console.info(formatTime(Date.now()), `📩 add subscribe for ${channelId} to pending list`);
|
58
|
+
}
|
59
|
+
this.subscriptions.add(channelId);
|
60
|
+
if (handler) {
|
61
|
+
this.handlers[channelId] = handler;
|
62
|
+
}
|
63
|
+
}
|
64
|
+
// 取消订阅
|
65
|
+
unsubscribe(channel, instId) {
|
66
|
+
const channelId = encodePath(channel, instId);
|
67
|
+
if (!this.subscriptions.has(channelId))
|
68
|
+
return;
|
69
|
+
const unSubMsg = {
|
70
|
+
op: 'unsubscribe',
|
71
|
+
args: [{ channel, instId }],
|
72
|
+
};
|
73
|
+
this.ws.send(JSON.stringify(unSubMsg));
|
74
|
+
this.subscriptions.delete(channelId);
|
75
|
+
delete this.handlers[channelId];
|
76
|
+
console.info(`📩 Sent unsubscribe for ${channelId}`);
|
77
|
+
}
|
78
|
+
}
|
79
|
+
export const okxBusinessWsClient = new OKXWsClient('wss://wspap.okx.com:8443/ws/v5/business');
|
80
|
+
//# sourceMappingURL=websocket.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"websocket.js","sourceRoot":"","sources":["../src/websocket.ts"],"names":[],"mappings":"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","sourcesContent":["import { encodePath, formatTime } from '@yuants/utils';\n// import WebSocket from 'ws';\n\nclass OKXWsClient {\n ws: WebSocket;\n connected: boolean = false;\n subscriptions: Set<string>;\n handlers: Record<string, Function>;\n pendingSub: string[];\n constructor(url: string) {\n // this.instId = instId;\n this.ws = new WebSocket(url);\n this.pendingSub = [];\n // this.ws.\n this.ws.addEventListener('open', () => {\n this.connected = true;\n console.info(formatTime(Date.now()), '✅ WS connected');\n while (this.pendingSub.length > 0) {\n const msg = this.pendingSub.shift();\n if (msg) {\n this.ws.send(msg);\n console.info(formatTime(Date.now()), `📩 Sent subscribe for ${msg}`);\n }\n }\n });\n this.ws.addEventListener('message', (raw) => this.handleMessage(raw));\n this.subscriptions = new Set();\n this.handlers = {}; // key: channel, value: callback\n }\n\n // 处理消息\n handleMessage(raw: any) {\n const msg = JSON.parse(raw.data);\n if (msg.arg?.channel) {\n const channelId = encodePath(msg.arg.channel, msg.arg.instId);\n const data = msg.data?.[0];\n if (data && this.handlers[channelId]) {\n this.handlers[channelId](data, msg.arg);\n }\n } else if (msg.event) {\n console.info('Event:', msg);\n }\n }\n\n // 调用订阅\n subscribe(channel: string, instId?: string, handler?: Function) {\n const channelId = encodePath(channel, instId);\n if (this.subscriptions.has(channelId)) {\n console.info(formatTime(Date.now()), `⚠️ Already subscribed: ${channelId}`);\n return;\n }\n\n const subMsg = {\n op: 'subscribe',\n args: [{ channel, instId }],\n };\n\n if (this.connected) {\n this.ws.send(JSON.stringify(subMsg));\n console.info(formatTime(Date.now()), `📩 Sent subscribe for ${channelId}`);\n } else {\n this.pendingSub.push(JSON.stringify(subMsg));\n console.info(formatTime(Date.now()), `📩 add subscribe for ${channelId} to pending list`);\n }\n this.subscriptions.add(channelId);\n if (handler) {\n this.handlers[channelId] = handler;\n }\n }\n\n // 取消订阅\n unsubscribe(channel: string, instId?: string) {\n const channelId = encodePath(channel, instId);\n if (!this.subscriptions.has(channelId)) return;\n\n const unSubMsg = {\n op: 'unsubscribe',\n args: [{ channel, instId }],\n };\n\n this.ws.send(JSON.stringify(unSubMsg));\n this.subscriptions.delete(channelId);\n delete this.handlers[channelId];\n console.info(`📩 Sent unsubscribe for ${channelId}`);\n }\n}\n\nexport const okxBusinessWsClient = new OKXWsClient('wss://wspap.okx.com:8443/ws/v5/business');\n"]}
|
package/lib/ohlc.js
CHANGED
@@ -17,6 +17,8 @@ const protocol_1 = require("@yuants/protocol");
|
|
17
17
|
const utils_1 = require("@yuants/utils");
|
18
18
|
const rxjs_1 = require("rxjs");
|
19
19
|
const api_1 = require("./api");
|
20
|
+
const websocket_1 = require("./websocket");
|
21
|
+
const sql_1 = require("@yuants/sql");
|
20
22
|
// 时间粒度,默认值1m
|
21
23
|
// 如 [1m/3m/5m/15m/30m/1H/2H/4H]
|
22
24
|
// 香港时间开盘价k线:[6H/12H/1D/1W/1M]
|
@@ -36,6 +38,21 @@ const DURATION_TO_OKX_BAR_TYPE = {
|
|
36
38
|
P1W: '1W',
|
37
39
|
P1M: '1M',
|
38
40
|
};
|
41
|
+
const DURATION_TO_OKX_CANDLE_TYPE = {
|
42
|
+
PT1M: 'mark-price-candle1m',
|
43
|
+
PT3M: 'mark-price-candle3m',
|
44
|
+
PT5M: 'mark-price-candle5m',
|
45
|
+
PT15M: 'mark-price-candle15m',
|
46
|
+
PT30M: 'mark-price-candle30m',
|
47
|
+
PT1H: 'mark-price-candle1H',
|
48
|
+
PT2H: 'mark-price-candle2H',
|
49
|
+
PT4H: 'mark-price-candle4H',
|
50
|
+
PT6H: 'mark-price-candle6H',
|
51
|
+
PT12H: 'mark-price-candle12H',
|
52
|
+
P1D: 'mark-price-candle1D',
|
53
|
+
P1W: 'mark-price-candle1W',
|
54
|
+
P1M: 'mark-price-candle1M',
|
55
|
+
};
|
39
56
|
(0, data_series_1.createSeriesProvider)(protocol_1.Terminal.fromNodeEnv(), {
|
40
57
|
tableName: 'ohlc',
|
41
58
|
series_id_prefix_parts: ['OKX'],
|
@@ -97,4 +114,60 @@ const DURATION_TO_OKX_BAR_TYPE = {
|
|
97
114
|
});
|
98
115
|
},
|
99
116
|
});
|
117
|
+
protocol_1.Terminal.fromNodeEnv().channel.publishChannel('ohlc', { pattern: `^OKX/` }, (series_id) => {
|
118
|
+
// const [] = decodePath(args)
|
119
|
+
const [datasource_id, product_id, duration] = (0, utils_1.decodePath)(series_id);
|
120
|
+
const [, instId] = (0, utils_1.decodePath)(product_id);
|
121
|
+
const offset = (0, utils_1.convertDurationToOffset)(duration);
|
122
|
+
if (!datasource_id) {
|
123
|
+
throw 'datasource_id is required';
|
124
|
+
}
|
125
|
+
if (!product_id) {
|
126
|
+
throw 'product_id is required';
|
127
|
+
}
|
128
|
+
if (!offset) {
|
129
|
+
throw 'duration is invalid';
|
130
|
+
}
|
131
|
+
const candleType = DURATION_TO_OKX_CANDLE_TYPE[duration];
|
132
|
+
console.info((0, utils_1.formatTime)(Date.now()), `subscribe`, series_id);
|
133
|
+
return new rxjs_1.Observable((subscriber) => {
|
134
|
+
console.info((0, utils_1.formatTime)(Date.now()), `subscribe`, candleType, instId);
|
135
|
+
websocket_1.okxBusinessWsClient.subscribe(candleType, instId, async (data) => {
|
136
|
+
const created_at = Number(data[0]);
|
137
|
+
const closed_at = created_at + offset;
|
138
|
+
const open = data[1];
|
139
|
+
const high = data[2];
|
140
|
+
const low = data[3];
|
141
|
+
const close = data[4];
|
142
|
+
if (isNaN(closed_at)) {
|
143
|
+
return;
|
144
|
+
}
|
145
|
+
console.info((0, utils_1.formatTime)(Date.now()), `insertData`, data);
|
146
|
+
const cancelData = {
|
147
|
+
closed_at: (0, utils_1.formatTime)(closed_at),
|
148
|
+
created_at: (0, utils_1.formatTime)(created_at),
|
149
|
+
open,
|
150
|
+
high,
|
151
|
+
low,
|
152
|
+
close,
|
153
|
+
series_id,
|
154
|
+
datasource_id,
|
155
|
+
duration,
|
156
|
+
product_id,
|
157
|
+
volume: '0',
|
158
|
+
open_interest: '0',
|
159
|
+
};
|
160
|
+
subscriber.next(cancelData);
|
161
|
+
});
|
162
|
+
return () => {
|
163
|
+
websocket_1.okxBusinessWsClient.unsubscribe(candleType, instId);
|
164
|
+
};
|
165
|
+
}).pipe((0, sql_1.writeToSQL)({
|
166
|
+
tableName: 'ohlc',
|
167
|
+
conflictKeys: ['created_at', 'series_id'],
|
168
|
+
writeInterval: 1000,
|
169
|
+
terminal: protocol_1.Terminal.fromNodeEnv(),
|
170
|
+
keyFn: (x) => (0, utils_1.encodePath)(x.created_at, x.series_id),
|
171
|
+
}));
|
172
|
+
});
|
100
173
|
//# sourceMappingURL=ohlc.js.map
|
package/lib/ohlc.js.map
CHANGED
@@ -1 +1 @@
|
|
1
|
-
{"version":3,"file":"ohlc.js","sourceRoot":"","sources":["../src/ohlc.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AACA,qDAA2D;AAC3D,+CAA4C;AAC5C,
|
1
|
+
{"version":3,"file":"ohlc.js","sourceRoot":"","sources":["../src/ohlc.ts"],"names":[],"mappings":"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","sourcesContent":["import { IOHLC } from '@yuants/data-ohlc';\nimport { createSeriesProvider } from '@yuants/data-series';\nimport { Terminal } from '@yuants/protocol';\nimport { convertDurationToOffset, decodePath, encodePath, formatTime } from '@yuants/utils';\nimport { firstValueFrom, Observable, of, timer } from 'rxjs';\nimport { client } from './api';\nimport { okxBusinessWsClient } from './websocket';\nimport { buildInsertManyIntoTableSQL, requestSQL, writeToSQL } from '@yuants/sql';\n\n// 时间粒度,默认值1m\n// 如 [1m/3m/5m/15m/30m/1H/2H/4H]\n// 香港时间开盘价k线:[6H/12H/1D/1W/1M]\n// UTC时间开盘价k线:[6Hutc/12Hutc/1Dutc/1Wutc/1Mutc]\n\nconst DURATION_TO_OKX_BAR_TYPE: Record<string, string> = {\n PT1M: '1m',\n PT3M: '3m',\n PT5M: '5m',\n PT15M: '15m',\n PT30M: '30m',\n\n PT1H: '1H',\n PT2H: '2H',\n PT4H: '4H',\n PT6H: '6H',\n PT12H: '12H',\n\n P1D: '1D',\n P1W: '1W',\n P1M: '1M',\n};\n\nconst DURATION_TO_OKX_CANDLE_TYPE: Record<string, string> = {\n PT1M: 'mark-price-candle1m',\n PT3M: 'mark-price-candle3m',\n PT5M: 'mark-price-candle5m',\n PT15M: 'mark-price-candle15m',\n PT30M: 'mark-price-candle30m',\n\n PT1H: 'mark-price-candle1H',\n PT2H: 'mark-price-candle2H',\n PT4H: 'mark-price-candle4H',\n PT6H: 'mark-price-candle6H',\n PT12H: 'mark-price-candle12H',\n\n P1D: 'mark-price-candle1D',\n P1W: 'mark-price-candle1W',\n P1M: 'mark-price-candle1M',\n};\n\ncreateSeriesProvider<IOHLC>(Terminal.fromNodeEnv(), {\n tableName: 'ohlc',\n series_id_prefix_parts: ['OKX'],\n reversed: true,\n serviceOptions: { concurrent: 1 },\n queryFn: async function* ({ series_id, ended_at }) {\n const [datasource_id, product_id, duration] = decodePath(series_id);\n const offset = convertDurationToOffset(duration);\n if (!datasource_id) {\n throw 'datasource_id is required';\n }\n if (!product_id) {\n throw 'product_id is required';\n }\n if (!offset) {\n throw 'duration is invalid';\n }\n const [instType, instId] = decodePath(product_id);\n if (!instId) {\n throw `invalid product_id: ${product_id}`;\n }\n\n const bar = DURATION_TO_OKX_BAR_TYPE[duration];\n if (!bar) {\n throw `unsupported duration: ${duration}`;\n }\n\n let currentStartTime = ended_at;\n\n while (true) {\n // 向前翻页,时间降序,不含 after 时间点\n const res = await client.getHistoryCandles({\n instId,\n bar,\n after: `${currentStartTime}`,\n limit: '100',\n });\n if (res.code !== '0') {\n throw `API failed: ${res.code} ${res.msg}`;\n }\n if (res.data.length === 0) break;\n currentStartTime = +res.data[res.data.length - 1][0];\n const data = res.data.map(\n (x): IOHLC => ({\n series_id,\n datasource_id,\n product_id,\n duration,\n created_at: formatTime(+x[0]),\n closed_at: formatTime(+x[0] + offset),\n open: x[1],\n high: x[2],\n low: x[3],\n close: x[4],\n volume: x[5],\n open_interest: '0',\n }),\n );\n yield data;\n await firstValueFrom(timer(1000));\n }\n },\n});\n\nTerminal.fromNodeEnv().channel.publishChannel('ohlc', { pattern: `^OKX/` }, (series_id) => {\n // const [] = decodePath(args)\n const [datasource_id, product_id, duration] = decodePath(series_id);\n const [, instId] = decodePath(product_id);\n const offset = convertDurationToOffset(duration);\n if (!datasource_id) {\n throw 'datasource_id is required';\n }\n if (!product_id) {\n throw 'product_id is required';\n }\n if (!offset) {\n throw 'duration is invalid';\n }\n const candleType = DURATION_TO_OKX_CANDLE_TYPE[duration];\n console.info(formatTime(Date.now()), `subscribe`, series_id);\n return new Observable<IOHLC>((subscriber) => {\n console.info(formatTime(Date.now()), `subscribe`, candleType, instId);\n okxBusinessWsClient.subscribe(candleType, instId, async (data: string[]) => {\n const created_at = Number(data[0]);\n const closed_at = created_at + offset;\n const open = data[1];\n const high = data[2];\n const low = data[3];\n const close = data[4];\n if (isNaN(closed_at)) {\n return;\n }\n console.info(formatTime(Date.now()), `insertData`, data);\n const cancelData: IOHLC = {\n closed_at: formatTime(closed_at),\n created_at: formatTime(created_at),\n open,\n high,\n low,\n close,\n series_id,\n datasource_id,\n duration,\n product_id,\n volume: '0',\n open_interest: '0',\n };\n subscriber.next(cancelData);\n });\n return () => {\n okxBusinessWsClient.unsubscribe(candleType, instId);\n };\n }).pipe(\n writeToSQL({\n tableName: 'ohlc',\n conflictKeys: ['created_at', 'series_id'],\n writeInterval: 1000,\n terminal: Terminal.fromNodeEnv(),\n keyFn: (x) => encodePath(x.created_at, x.series_id),\n }),\n );\n});\n"]}
|
@@ -0,0 +1,14 @@
|
|
1
|
+
declare class OKXWsClient {
|
2
|
+
ws: WebSocket;
|
3
|
+
connected: boolean;
|
4
|
+
subscriptions: Set<string>;
|
5
|
+
handlers: Record<string, Function>;
|
6
|
+
pendingSub: string[];
|
7
|
+
constructor(url: string);
|
8
|
+
handleMessage(raw: any): void;
|
9
|
+
subscribe(channel: string, instId?: string, handler?: Function): void;
|
10
|
+
unsubscribe(channel: string, instId?: string): void;
|
11
|
+
}
|
12
|
+
export declare const okxBusinessWsClient: OKXWsClient;
|
13
|
+
export {};
|
14
|
+
//# sourceMappingURL=websocket.d.ts.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"websocket.d.ts","sourceRoot":"","sources":["../src/websocket.ts"],"names":[],"mappings":"AAGA,cAAM,WAAW;IACf,EAAE,EAAE,SAAS,CAAC;IACd,SAAS,EAAE,OAAO,CAAS;IAC3B,aAAa,EAAE,GAAG,CAAC,MAAM,CAAC,CAAC;IAC3B,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,QAAQ,CAAC,CAAC;IACnC,UAAU,EAAE,MAAM,EAAE,CAAC;gBACT,GAAG,EAAE,MAAM;IAsBvB,aAAa,CAAC,GAAG,EAAE,GAAG;IActB,SAAS,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ;IA0B9D,WAAW,CAAC,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC,EAAE,MAAM;CAc7C;AAED,eAAO,MAAM,mBAAmB,aAA6D,CAAC"}
|
package/lib/websocket.js
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
exports.okxBusinessWsClient = void 0;
|
4
|
+
const utils_1 = require("@yuants/utils");
|
5
|
+
// import WebSocket from 'ws';
|
6
|
+
class OKXWsClient {
|
7
|
+
constructor(url) {
|
8
|
+
this.connected = false;
|
9
|
+
// this.instId = instId;
|
10
|
+
this.ws = new WebSocket(url);
|
11
|
+
this.pendingSub = [];
|
12
|
+
// this.ws.
|
13
|
+
this.ws.addEventListener('open', () => {
|
14
|
+
this.connected = true;
|
15
|
+
console.info((0, utils_1.formatTime)(Date.now()), '✅ WS connected');
|
16
|
+
while (this.pendingSub.length > 0) {
|
17
|
+
const msg = this.pendingSub.shift();
|
18
|
+
if (msg) {
|
19
|
+
this.ws.send(msg);
|
20
|
+
console.info((0, utils_1.formatTime)(Date.now()), `📩 Sent subscribe for ${msg}`);
|
21
|
+
}
|
22
|
+
}
|
23
|
+
});
|
24
|
+
this.ws.addEventListener('message', (raw) => this.handleMessage(raw));
|
25
|
+
this.subscriptions = new Set();
|
26
|
+
this.handlers = {}; // key: channel, value: callback
|
27
|
+
}
|
28
|
+
// 处理消息
|
29
|
+
handleMessage(raw) {
|
30
|
+
var _a, _b;
|
31
|
+
const msg = JSON.parse(raw.data);
|
32
|
+
if ((_a = msg.arg) === null || _a === void 0 ? void 0 : _a.channel) {
|
33
|
+
const channelId = (0, utils_1.encodePath)(msg.arg.channel, msg.arg.instId);
|
34
|
+
const data = (_b = msg.data) === null || _b === void 0 ? void 0 : _b[0];
|
35
|
+
if (data && this.handlers[channelId]) {
|
36
|
+
this.handlers[channelId](data, msg.arg);
|
37
|
+
}
|
38
|
+
}
|
39
|
+
else if (msg.event) {
|
40
|
+
console.info('Event:', msg);
|
41
|
+
}
|
42
|
+
}
|
43
|
+
// 调用订阅
|
44
|
+
subscribe(channel, instId, handler) {
|
45
|
+
const channelId = (0, utils_1.encodePath)(channel, instId);
|
46
|
+
if (this.subscriptions.has(channelId)) {
|
47
|
+
console.info((0, utils_1.formatTime)(Date.now()), `⚠️ Already subscribed: ${channelId}`);
|
48
|
+
return;
|
49
|
+
}
|
50
|
+
const subMsg = {
|
51
|
+
op: 'subscribe',
|
52
|
+
args: [{ channel, instId }],
|
53
|
+
};
|
54
|
+
if (this.connected) {
|
55
|
+
this.ws.send(JSON.stringify(subMsg));
|
56
|
+
console.info((0, utils_1.formatTime)(Date.now()), `📩 Sent subscribe for ${channelId}`);
|
57
|
+
}
|
58
|
+
else {
|
59
|
+
this.pendingSub.push(JSON.stringify(subMsg));
|
60
|
+
console.info((0, utils_1.formatTime)(Date.now()), `📩 add subscribe for ${channelId} to pending list`);
|
61
|
+
}
|
62
|
+
this.subscriptions.add(channelId);
|
63
|
+
if (handler) {
|
64
|
+
this.handlers[channelId] = handler;
|
65
|
+
}
|
66
|
+
}
|
67
|
+
// 取消订阅
|
68
|
+
unsubscribe(channel, instId) {
|
69
|
+
const channelId = (0, utils_1.encodePath)(channel, instId);
|
70
|
+
if (!this.subscriptions.has(channelId))
|
71
|
+
return;
|
72
|
+
const unSubMsg = {
|
73
|
+
op: 'unsubscribe',
|
74
|
+
args: [{ channel, instId }],
|
75
|
+
};
|
76
|
+
this.ws.send(JSON.stringify(unSubMsg));
|
77
|
+
this.subscriptions.delete(channelId);
|
78
|
+
delete this.handlers[channelId];
|
79
|
+
console.info(`📩 Sent unsubscribe for ${channelId}`);
|
80
|
+
}
|
81
|
+
}
|
82
|
+
exports.okxBusinessWsClient = new OKXWsClient('wss://wspap.okx.com:8443/ws/v5/business');
|
83
|
+
//# sourceMappingURL=websocket.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"websocket.js","sourceRoot":"","sources":["../src/websocket.ts"],"names":[],"mappings":"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","sourcesContent":["import { encodePath, formatTime } from '@yuants/utils';\n// import WebSocket from 'ws';\n\nclass OKXWsClient {\n ws: WebSocket;\n connected: boolean = false;\n subscriptions: Set<string>;\n handlers: Record<string, Function>;\n pendingSub: string[];\n constructor(url: string) {\n // this.instId = instId;\n this.ws = new WebSocket(url);\n this.pendingSub = [];\n // this.ws.\n this.ws.addEventListener('open', () => {\n this.connected = true;\n console.info(formatTime(Date.now()), '✅ WS connected');\n while (this.pendingSub.length > 0) {\n const msg = this.pendingSub.shift();\n if (msg) {\n this.ws.send(msg);\n console.info(formatTime(Date.now()), `📩 Sent subscribe for ${msg}`);\n }\n }\n });\n this.ws.addEventListener('message', (raw) => this.handleMessage(raw));\n this.subscriptions = new Set();\n this.handlers = {}; // key: channel, value: callback\n }\n\n // 处理消息\n handleMessage(raw: any) {\n const msg = JSON.parse(raw.data);\n if (msg.arg?.channel) {\n const channelId = encodePath(msg.arg.channel, msg.arg.instId);\n const data = msg.data?.[0];\n if (data && this.handlers[channelId]) {\n this.handlers[channelId](data, msg.arg);\n }\n } else if (msg.event) {\n console.info('Event:', msg);\n }\n }\n\n // 调用订阅\n subscribe(channel: string, instId?: string, handler?: Function) {\n const channelId = encodePath(channel, instId);\n if (this.subscriptions.has(channelId)) {\n console.info(formatTime(Date.now()), `⚠️ Already subscribed: ${channelId}`);\n return;\n }\n\n const subMsg = {\n op: 'subscribe',\n args: [{ channel, instId }],\n };\n\n if (this.connected) {\n this.ws.send(JSON.stringify(subMsg));\n console.info(formatTime(Date.now()), `📩 Sent subscribe for ${channelId}`);\n } else {\n this.pendingSub.push(JSON.stringify(subMsg));\n console.info(formatTime(Date.now()), `📩 add subscribe for ${channelId} to pending list`);\n }\n this.subscriptions.add(channelId);\n if (handler) {\n this.handlers[channelId] = handler;\n }\n }\n\n // 取消订阅\n unsubscribe(channel: string, instId?: string) {\n const channelId = encodePath(channel, instId);\n if (!this.subscriptions.has(channelId)) return;\n\n const unSubMsg = {\n op: 'unsubscribe',\n args: [{ channel, instId }],\n };\n\n this.ws.send(JSON.stringify(unSubMsg));\n this.subscriptions.delete(channelId);\n delete this.handlers[channelId];\n console.info(`📩 Sent unsubscribe for ${channelId}`);\n }\n}\n\nexport const okxBusinessWsClient = new OKXWsClient('wss://wspap.okx.com:8443/ws/v5/business');\n"]}
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "@yuants/vendor-okx",
|
3
|
-
"version": "0.17.
|
3
|
+
"version": "0.17.1",
|
4
4
|
"bin": "lib/cli.js",
|
5
5
|
"files": [
|
6
6
|
"dist",
|
@@ -21,7 +21,8 @@
|
|
21
21
|
"@yuants/data-quote": "0.2.8",
|
22
22
|
"@yuants/secret": "0.1.8",
|
23
23
|
"rxjs": "~7.5.6",
|
24
|
-
"crypto-js": "^4.2.0"
|
24
|
+
"crypto-js": "^4.2.0",
|
25
|
+
"ws": "~8.8.1"
|
25
26
|
},
|
26
27
|
"devDependencies": {
|
27
28
|
"@microsoft/api-extractor": "~7.30.0",
|
@@ -33,7 +34,8 @@
|
|
33
34
|
"@yuants/extension": "0.2.20",
|
34
35
|
"@yuants/tool-kit": "0.1.10",
|
35
36
|
"typescript": "~4.7.4",
|
36
|
-
"ts-node": "~10.9.2"
|
37
|
+
"ts-node": "~10.9.2",
|
38
|
+
"@types/ws": "~8.5.3"
|
37
39
|
},
|
38
40
|
"publishConfig": {
|
39
41
|
"registry": "https://registry.npmjs.org",
|
package/temp/image-tag
CHANGED
@@ -1 +1 @@
|
|
1
|
-
sha-
|
1
|
+
sha-8e0b6ef6a5e54de9f49b72355c2845da5cb055a5
|
package/temp/package-deps.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
|
-
"apps/vendor-okx/CHANGELOG.json": "
|
3
|
-
"apps/vendor-okx/CHANGELOG.md": "
|
2
|
+
"apps/vendor-okx/CHANGELOG.json": "065eafb1c1ec13c2d594c989bc205c94a874e9ce",
|
3
|
+
"apps/vendor-okx/CHANGELOG.md": "1d22611d9bf572cbd8f0f7f901dcb555b4a14192",
|
4
4
|
"apps/vendor-okx/README.md": "ac3d1f6c4c8d73066664699b0bb1b01db02e67a2",
|
5
5
|
"apps/vendor-okx/api-extractor.json": "62f4fd324425b9a235f0c117975967aab09ced0c",
|
6
6
|
"apps/vendor-okx/build/Dockerfile": "e625b9593e496c343297c9ce133d6cc4be9617a7",
|
@@ -8,7 +8,7 @@
|
|
8
8
|
"apps/vendor-okx/config/rig.json": "f6c7b5537dc77a3170ba9f008bae3b6c3ee11956",
|
9
9
|
"apps/vendor-okx/config/typescript.json": "854907e8a821f2050f6533368db160c649c25348",
|
10
10
|
"apps/vendor-okx/etc/vendor-okx.api.md": "92c54ccd547d2ad81ca6088158ca193e85c208df",
|
11
|
-
"apps/vendor-okx/package.json": "
|
11
|
+
"apps/vendor-okx/package.json": "38623ab0fceb6799e6363732a5669cb9dd647648",
|
12
12
|
"apps/vendor-okx/src/account.ts": "ee25dbdc249b056856d64d3a67e654d8aa2dc53b",
|
13
13
|
"apps/vendor-okx/src/api.ts": "0433a359ec1fb84c866c1875b630cbb861696533",
|
14
14
|
"apps/vendor-okx/src/cli.ts": "9bf6b5559a6c6f33da20e74cc6c5d702c60ec891",
|
@@ -18,12 +18,13 @@
|
|
18
18
|
"apps/vendor-okx/src/interest_rate.ts": "da173624ffed4cb083d797328174fe717809769c",
|
19
19
|
"apps/vendor-okx/src/legacy_index.ts": "19f9e4af4b6b8ae846e0800b1bd52d5eeaa8a2ce",
|
20
20
|
"apps/vendor-okx/src/logger.ts": "d25d427e74f46819a601e0cb17d5358b22c8db7b",
|
21
|
-
"apps/vendor-okx/src/ohlc.ts": "
|
21
|
+
"apps/vendor-okx/src/ohlc.ts": "ffbe2feadd43befb925984fdc704d00527717e92",
|
22
22
|
"apps/vendor-okx/src/order.ts": "09509ae5af4b212cc1c99c55c436b3b0ce8bda33",
|
23
23
|
"apps/vendor-okx/src/product.ts": "a2e7feb86bc01fe3b1af6dcb9a9ebfba6be91434",
|
24
24
|
"apps/vendor-okx/src/quote.ts": "b8dd019e6cceb50e486a9d41e54385bda4e21ab0",
|
25
|
+
"apps/vendor-okx/src/websocket.ts": "7a22838b75247cdc3da90c8601100b6e8b34a350",
|
25
26
|
"apps/vendor-okx/tsconfig.json": "81da8f78196974b5d15da0edb6b2d9f48641063c",
|
26
|
-
"apps/vendor-okx/.rush/temp/shrinkwrap-deps.json": "
|
27
|
+
"apps/vendor-okx/.rush/temp/shrinkwrap-deps.json": "87df045a10e829fd62d50013da30fd929442d54c",
|
27
28
|
"libraries/protocol/temp/package-deps.json": "d326f24867d9ae3fef16ff84ba537eabb7c8adf0",
|
28
29
|
"libraries/transfer/temp/package-deps.json": "cb2786055e5e79cd7d9d0f5787e300ca1c0bf002",
|
29
30
|
"libraries/data-account/temp/package-deps.json": "7fcf0ee1dd95c391d26c35e9c380a0fe3636c004",
|