futu-sdk
Version:
futu-api 的高性能精简易用版;基于*.proto静态编译,提供高性能的多层封装但层层开放的易用接口,获取最原始最完整的量化数据;相较于Python, nodejs更接近Web, 从而轻而易举搭建一个可视化交易站点,这是进行可控量化交易的不二选择。
91 lines (81 loc) • 3.11 kB
text/typescript
import crypto from 'crypto';
import type { WebSocket as NodeSocket } from 'ws';
import { encodeBuffer, decodeBuffer } from './utils';
import { WebRequest, ReqFn, SubFn } from './fetch';
export interface IWebRequest extends Omit<WebRequest, 'InitWebSocket'> {}
/**
* getFutuApi 内部实例化 WebSocket 连接,并基于 WebSocket 封装了的查询对象 WebRequest, 你可以使用 `webRequest` 查询各种接口,
* 也可以使用 `webSocket` 来关闭连接(`webSocket.close()`)和实现事件监听(如 onclose/onopen/onmessage等)
*
* Example:
*
* ```ts
* import { getFutuApi } from 'futu-sdk';
* // import { Trd_Common } from 'futu-proto';
*
* const { webRequest, webSocket } = getFutuApi('ws://127.0.0.1:33333', '9d261112869397f0');
* try {
* const { accList } = (await webRequest.GetAccList({ userID: 0, needGeneralSecAccount: true })) || {};
* console.log({ accList });
* } finally {
* webSocket.close();
* }
* ```
* @param wsUrl 连接本地 WebSocket 的地址,如 `ws://127.0.0.1:33333`
* @param key WebSocket 密钥,每次启动 OpenD_GUI 不设置都会随机生成,CLI 暂不清楚如何获取这个密钥
* @returns `{ webRequest: WebRequest, webSocket: WebSocket }`
*/
export const getFutuApi = (wsUrl: string, key: string) => {
const reqCallback = new Map<number, (result: any) => void>();
const subCallback = {} as Record<number, Array<(result: any) => void>>;
const webSocket = typeof window === 'undefined' ? (new (require('ws'))(wsUrl) as NodeSocket) : new WebSocket(wsUrl);
webSocket.binaryType = 'arraybuffer';
const reqFn: ReqFn = (cmd, protobuf, callback) => {
const { session, message } = encodeBuffer(cmd, protobuf);
if (webSocket.readyState !== WebSocket.OPEN) {
throw new Error('WebSocket closed');
}
webSocket.send(message);
reqCallback.set(session, callback);
return () => {
reqCallback.delete(session);
};
};
const subFn: SubFn = (cmd, callback) => {
if (!subCallback[cmd]) {
subCallback[cmd] = [];
}
subCallback[cmd].push(callback);
return () => {
subCallback[cmd] = subCallback[cmd].filter(cb => cb !== callback);
};
};
const reqFnPromise = new Promise<ReqFn>((resolve, reject) => {
webSocket.addEventListener('open', () => {
new WebRequest(Promise.resolve(reqFn), subFn)
.InitWebSocket({
websocketKey: key ? crypto.createHash('md5').update(key).digest('hex') : '',
programmingLanguage: 'JavaScript',
})
.then(() => resolve(reqFn))
.catch(reject);
});
webSocket.addEventListener('message', event => {
const { session, cmd, message } = decodeBuffer(event.data as ArrayBuffer);
if (subCallback[cmd]) {
for (const callback of subCallback[cmd]) {
callback(message);
}
} else {
reqCallback.get(session)?.(message);
}
});
webSocket.addEventListener('close', event => {
reject(event);
});
});
return {
webSocket,
webRequest: new WebRequest(reqFnPromise, subFn) as IWebRequest,
};
};