From 30302831ef3f05567bb9c0ee0fc46203cae3210f Mon Sep 17 00:00:00 2001 From: Gustavo Date: Wed, 7 Sep 2022 17:20:52 -0300 Subject: [PATCH 1/2] added code to connect with alphapoint websocket api --- .eslintrc | 2 +- examples/foxbit.ts | 6 + package.json | 5 +- src/connectors/coinext.ts | 1 + src/connectors/flowbtc.ts | 1 + src/connectors/foxbit.ts | 23 ++- src/interfaces/alphapoint.ts | 115 +++++++++++++- src/interfaces/exchange.ts | 6 +- tsconfig.json | 2 +- yarn.lock | 280 ++++++++++++++++++++++++++++++++++- 10 files changed, 426 insertions(+), 15 deletions(-) create mode 100644 examples/foxbit.ts diff --git a/.eslintrc b/.eslintrc index 4db625c..5d1ef6a 100644 --- a/.eslintrc +++ b/.eslintrc @@ -18,7 +18,7 @@ "@typescript-eslint/ban-ts-comment": "warn", "@typescript-eslint/explicit-function-return-type": "off", "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-non-null-assertion": "warn", + "@typescript-eslint/no-non-null-assertion": "off", "@typescript-eslint/promise-function-async": "off", "@typescript-eslint/interface-name-prefix": "off" } diff --git a/examples/foxbit.ts b/examples/foxbit.ts new file mode 100644 index 0000000..fcacbca --- /dev/null +++ b/examples/foxbit.ts @@ -0,0 +1,6 @@ +// @ts-ignore +import { foxbit } from "../src/index"; + +const ex = new foxbit(); + +ex.subscribeBook("BTC", "BRL"); diff --git a/package.json b/package.json index 9ec8939..b280554 100644 --- a/package.json +++ b/package.json @@ -37,6 +37,7 @@ "@types/node": "^17.0.14", "@types/node-fetch": "^2.5.12", "@types/safe-compare": "^1.1.0", + "@types/ws": "^8.5.3", "@typescript-eslint/eslint-plugin": "^5.10.2", "@typescript-eslint/parser": "^5.10.2", "eslint": "^8.8.0", @@ -48,11 +49,13 @@ "eslint-plugin-promise": "^6.0.0", "eslint-plugin-standard": "^5.0.0", "prettier": "^2.5.1", + "ts-node-dev": "^2.0.0", "typescript": "^4.5.5" }, "dependencies": { "axios": "^0.26.1", "bottleneck": "^2.19.5", - "fast-sort": "^3.1.3" + "fast-sort": "^3.1.3", + "ws": "^8.8.1" } } diff --git a/src/connectors/coinext.ts b/src/connectors/coinext.ts index 9b955eb..25b076b 100644 --- a/src/connectors/coinext.ts +++ b/src/connectors/coinext.ts @@ -9,6 +9,7 @@ export class coinext extends alphapoint { super({ id: "coinext", baseUrl: "https://api.coinext.com.br:8443/AP", + websocketUrl: "wss://api.coinext.com.br/WSGateway/", opts: args?.opts, limiter: args?.limiter, }); diff --git a/src/connectors/flowbtc.ts b/src/connectors/flowbtc.ts index 12be261..a23d0bb 100644 --- a/src/connectors/flowbtc.ts +++ b/src/connectors/flowbtc.ts @@ -6,6 +6,7 @@ export class flowbtc extends alphapoint { super({ id: "flowbtc", baseUrl: "https://api.flowbtc.com.br:8443/ap", + websocketUrl: "wss://api.flowbtc.com.br/WSGateway/", opts: args?.opts, limiter: args?.limiter, }); diff --git a/src/connectors/foxbit.ts b/src/connectors/foxbit.ts index 5e3f976..cf404e3 100644 --- a/src/connectors/foxbit.ts +++ b/src/connectors/foxbit.ts @@ -1,19 +1,32 @@ -import { - Exchange, - IExchangeImplementationConstructorArgs, -} from "../interfaces/exchange"; +import { alphapoint } from "../interfaces/alphapoint"; +import { IExchangeImplementationConstructorArgs } from "../interfaces/exchange"; import { ITicker } from "../types/common"; -export class foxbit extends Exchange { +export class foxbit extends alphapoint { constructor(args?: IExchangeImplementationConstructorArgs) { super({ id: "foxbit", baseUrl: "https://watcher.foxbit.com.br/api", + websocketUrl: "wss://api.foxbit.com.br/", opts: args?.opts, limiter: args?.limiter, }); } + normalizeAsset(asset: string | number): string | number { + if (asset == "BTC") return 1; + else if (asset == "LTC") return 2; + else if (asset == "ETH") return 4; + else if (asset == "TUSD") return 6; + else if (asset == "XRP") return 10; + else if (asset == 1) return "BTC"; + else if (asset == 2) return "LTC"; + else if (asset == 4) return "ETH"; + else if (asset == 6) return "TUSD"; + else if (asset == 10) return "XRP"; + return asset; + } + async getTicker(base: string, quote: string): Promise { const res = await this.fetch( `${this.baseUrl}/Ticker?exchange=Foxbit&Pair=${quote}X${base}`, diff --git a/src/interfaces/alphapoint.ts b/src/interfaces/alphapoint.ts index f436ac2..5c70c27 100644 --- a/src/interfaces/alphapoint.ts +++ b/src/interfaces/alphapoint.ts @@ -1,10 +1,121 @@ import { IOrderbook, IOrderbookOrder, ITicker } from "../types/common"; -import { Exchange } from "./exchange"; +import { Exchange, IExchangeBaseConstructorArgs } from "./exchange"; +import WebSocket from "ws"; +import crypto from "crypto"; + +interface IAlphapointConstructorArgs + extends IExchangeBaseConstructorArgs { + /** + * alphapoint websocket url + */ + websocketUrl: string; +} type IAlphapointOrderbookRes = number[][]; +interface IMessageFrame { + m: number; + i: number; + n: string; + o: string; +} + +type IRawMessageFrame = Omit & { o: any }; + export class alphapoint extends Exchange { public baseUrl: any; + public websocketUrl?: string; + + private ws?: WebSocket; + public wsReady?: boolean; + private wsPingInterval?: NodeJS.Timer; + private resolveMap?: Map void>; + + constructor(args: IAlphapointConstructorArgs) { + super({ ...args }); + } + + private async ensureWebsocket() { + if (!this.ws) { + await this.initWebsocket(); + } + + while (this.ws?.readyState === this.ws?.CONNECTING) { + // wait connection + } + } + + private initWebsocket(): Promise { + return new Promise((resolve) => { + this.ws = new WebSocket(this.websocketUrl!); + this.ws.on("message", (data) => { + //data contém o payload de resposta + const parsedData = JSON.parse(data.toString()) as IMessageFrame; + + if (parsedData.n === "Ping") { + this.resolveMap!.delete(parsedData.i); + } + + console.log(parsedData); + }); + this.resolveMap = new Map(); + this.ws.on("open", () => { + this.wsPingInterval = setInterval(() => { + this.sendFrameToWs({ + m: 0, + n: "Ping", + o: {}, + }); + }, 15000); + + this.wsReady = true; + resolve(this.wsReady); + }); + }); + } + + // eslint-disable-next-line @typescript-eslint/no-unused-vars + public async subscribeBook(base: string, _quote: string) { + await this.ensureWebsocket(); + this.sendFrameToWs({ + m: 2, + n: "SubscribeLevel2", + o: { OMSId: 1, InstrumentId: this.normalizeAsset(base), Depth: 50 }, + }); + } + + private buildMessageFrame(rawFrame: IRawMessageFrame): IMessageFrame { + return { + m: rawFrame.m, + i: crypto.randomInt(2, 999999), + n: rawFrame.n, + o: JSON.stringify(rawFrame.o), + }; + } + + private async sendFrameToWs(rawFrame: IRawMessageFrame) { + const promise = new Promise((resolve, reject) => { + const frame = this.buildMessageFrame(rawFrame); + + this.resolveMap!.set(frame.i, resolve); + + const rejectTimer = setTimeout(() => { + if (rawFrame.m === 0 && this.resolveMap!.delete(frame.i)) { + reject("Websocket reply timeout"); + } + }, 5000); + + this.ws?.send(JSON.stringify(frame), (err) => { + if (err) { + this.resolveMap!.delete(frame.i); + clearTimeout(rejectTimer); + reject(err); + } + }); + }); + + return promise; + } // eslint-disable-next-line @typescript-eslint/no-unused-vars normalizeAsset(asset: string | number): string | number { @@ -29,7 +140,7 @@ export class alphapoint extends Exchange { }; } - private parseOrder(order: number[]) { + private parseOrder(order: number[]): IOrderbookOrder { return { price: order[6] as number, amount: order[8] as number, diff --git a/src/interfaces/exchange.ts b/src/interfaces/exchange.ts index ae98deb..fb82f97 100644 --- a/src/interfaces/exchange.ts +++ b/src/interfaces/exchange.ts @@ -8,6 +8,7 @@ export interface IExchangeImplementationConstructorArgs { } export interface IExchangeBaseConstructorArgs { + [x: string]: any; // hack to suport any parameters /** * this exchange id */ @@ -16,6 +17,9 @@ export interface IExchangeBaseConstructorArgs { * this exchange base URL */ baseUrl: string; + /** + * custom optiions to inject in your Fetcher function + */ opts?: T; /** * bottleneck rate limit @@ -28,7 +32,7 @@ export class Exchange implements IExchangeBase { public id!: string; public baseUrl!: string; public limiter!: Bottleneck; - public opts?: any; + public opts?: T; constructor(args: IExchangeBaseConstructorArgs) { Object.assign(this, args); diff --git a/tsconfig.json b/tsconfig.json index b08eb27..1b0ef8f 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -17,6 +17,6 @@ "noImplicitAny": false }, "include": [ - "src/" + "src/", ] } \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index ca3d1c0..197c36c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2,6 +2,13 @@ # yarn lockfile v1 +"@cspotcode/source-map-support@^0.8.0": + version "0.8.1" + resolved "https://registry.yarnpkg.com/@cspotcode/source-map-support/-/source-map-support-0.8.1.tgz#00629c35a688e05a88b1cda684fb9d5e73f000a1" + integrity sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw== + dependencies: + "@jridgewell/trace-mapping" "0.3.9" + "@eslint/eslintrc@^1.2.1": version "1.2.1" resolved "https://registry.yarnpkg.com/@eslint/eslintrc/-/eslintrc-1.2.1.tgz#8b5e1c49f4077235516bc9ec7d41378c0f69b8c6" @@ -31,6 +38,24 @@ resolved "https://registry.yarnpkg.com/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz#b520529ec21d8e5945a1851dfd1c32e94e39ff45" integrity sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA== +"@jridgewell/resolve-uri@^3.0.3": + version "3.1.0" + resolved "https://registry.yarnpkg.com/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz#2203b118c157721addfe69d47b70465463066d78" + integrity sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w== + +"@jridgewell/sourcemap-codec@^1.4.10": + version "1.4.14" + resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24" + integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw== + +"@jridgewell/trace-mapping@0.3.9": + version "0.3.9" + resolved "https://registry.yarnpkg.com/@jridgewell/trace-mapping/-/trace-mapping-0.3.9.tgz#6534fd5933a53ba7cbf3a17615e273a0d1273ff9" + integrity sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ== + dependencies: + "@jridgewell/resolve-uri" "^3.0.3" + "@jridgewell/sourcemap-codec" "^1.4.10" + "@nodelib/fs.scandir@2.1.5": version "2.1.5" resolved "https://registry.yarnpkg.com/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz#7619c2eb21b25483f6d167548b4cfd5a7488c3d5" @@ -52,6 +77,26 @@ "@nodelib/fs.scandir" "2.1.5" fastq "^1.6.0" +"@tsconfig/node10@^1.0.7": + version "1.0.9" + resolved "https://registry.yarnpkg.com/@tsconfig/node10/-/node10-1.0.9.tgz#df4907fc07a886922637b15e02d4cebc4c0021b2" + integrity sha512-jNsYVVxU8v5g43Erja32laIDHXeoNvFEpX33OK4d6hljo3jDhCBDhx5dhCCTMWUojscpAagGiRkBKxpdl9fxqA== + +"@tsconfig/node12@^1.0.7": + version "1.0.11" + resolved "https://registry.yarnpkg.com/@tsconfig/node12/-/node12-1.0.11.tgz#ee3def1f27d9ed66dac6e46a295cffb0152e058d" + integrity sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag== + +"@tsconfig/node14@^1.0.0": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node14/-/node14-1.0.3.tgz#e4386316284f00b98435bf40f72f75a09dabf6c1" + integrity sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow== + +"@tsconfig/node16@^1.0.2": + version "1.0.3" + resolved "https://registry.yarnpkg.com/@tsconfig/node16/-/node16-1.0.3.tgz#472eaab5f15c1ffdd7f8628bd4c4f753995ec79e" + integrity sha512-yOlFc+7UtL/89t2ZhjPvvB/DeAr3r+Dq58IgzsFkOAvVC6NMJXmCGjbptdXdR9qsX7pKcTL+s87FtYREi2dEEQ== + "@types/debug@^4.1.7": version "4.1.7" resolved "https://registry.yarnpkg.com/@types/debug/-/debug-4.1.7.tgz#7cc0ea761509124709b8b2d1090d8f6c17aadb82" @@ -92,6 +137,23 @@ resolved "https://registry.yarnpkg.com/@types/safe-compare/-/safe-compare-1.1.0.tgz#47ed9b9ca51a3a791b431cd59b28f47fa9bf1224" integrity sha512-1ri+LJhh0gRxIa37IpGytdaW7yDEHeJniBSMD1BmitS07R1j63brcYCzry+l0WJvGdEKQNQ7DYXO2epgborWPw== +"@types/strip-bom@^3.0.0": + version "3.0.0" + resolved "https://registry.yarnpkg.com/@types/strip-bom/-/strip-bom-3.0.0.tgz#14a8ec3956c2e81edb7520790aecf21c290aebd2" + integrity sha512-xevGOReSYGM7g/kUBZzPqCrR/KYAo+F0yiPc85WFTJa0MSLtyFTVTU6cJu/aV4mid7IffDIWqo69THF2o4JiEQ== + +"@types/strip-json-comments@0.0.30": + version "0.0.30" + resolved "https://registry.yarnpkg.com/@types/strip-json-comments/-/strip-json-comments-0.0.30.tgz#9aa30c04db212a9a0649d6ae6fd50accc40748a1" + integrity sha512-7NQmHra/JILCd1QqpSzl8+mJRc8ZHz3uDm8YV1Ks9IhK0epEiTw8aIErbvH9PI+6XbqhyIQy3462nEsn7UVzjQ== + +"@types/ws@^8.5.3": + version "8.5.3" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.3.tgz#7d25a1ffbecd3c4f2d35068d0b283c037003274d" + integrity sha512-6YOoWjruKj1uLf3INHH7D3qTXwFfEsg1kf3c0uDdSBJwfa/llkwIjrAGV7j7mVgGNbzTQ3HiHKKDXl6bJPD97w== + dependencies: + "@types/node" "*" + "@typescript-eslint/eslint-plugin@^5.10.2": version "5.17.0" resolved "https://registry.yarnpkg.com/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.17.0.tgz#704eb4e75039000531255672bf1c85ee85cf1d67" @@ -177,6 +239,16 @@ acorn-jsx@^5.3.1: resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" integrity sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ== +acorn-walk@^8.1.1: + version "8.2.0" + resolved "https://registry.yarnpkg.com/acorn-walk/-/acorn-walk-8.2.0.tgz#741210f2e2426454508853a2f44d0ab83b7f69c1" + integrity sha512-k+iyHEuPgSw6SbuDpGQM+06HQUa04DZ3o+F6CSzXMvvI5KMvnaEqXe+YVe555R9nn6GPt404fos4wcgpw12SDA== + +acorn@^8.4.1: + version "8.8.0" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.8.0.tgz#88c0187620435c7f6015803f5539dae05a9dbea8" + integrity sha512-QOxyigPVrpZ2GXT+PFyZTl6TtOFc5egxHIP9IlQ+RbupQuX4RkT/Bee4/kQuC02Xkzg84JcT7oLYtDIQxp+v7w== + acorn@^8.7.0: version "8.7.0" resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.7.0.tgz#90951fde0f8f09df93549481e5fc141445b791cf" @@ -204,6 +276,19 @@ ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" +anymatch@~3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" + integrity sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg== + dependencies: + normalize-path "^3.0.0" + picomatch "^2.0.4" + +arg@^4.1.0: + version "4.1.3" + resolved "https://registry.yarnpkg.com/arg/-/arg-4.1.3.tgz#269fc7ad5b8e42cb63c896d5666017261c144089" + integrity sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA== + argparse@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" @@ -251,6 +336,11 @@ balanced-match@^1.0.0: resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.2.tgz#e83e3a7e3f300b34cb9d87f615fa0cbf357690ee" integrity sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw== +binary-extensions@^2.0.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/binary-extensions/-/binary-extensions-2.2.0.tgz#75f502eeaf9ffde42fc98829645be4ea76bd9e2d" + integrity sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA== + bottleneck@^2.19.5: version "2.19.5" resolved "https://registry.yarnpkg.com/bottleneck/-/bottleneck-2.19.5.tgz#5df0b90f59fd47656ebe63c78a98419205cadd91" @@ -264,13 +354,18 @@ brace-expansion@^1.1.7: balanced-match "^1.0.0" concat-map "0.0.1" -braces@^3.0.2: +braces@^3.0.2, braces@~3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/braces/-/braces-3.0.2.tgz#3454e1a462ee8d599e236df336cd9ea4f8afe107" integrity sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A== dependencies: fill-range "^7.0.1" +buffer-from@^1.0.0: + version "1.1.2" + resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" + integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== + call-bind@^1.0.0, call-bind@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/call-bind/-/call-bind-1.0.2.tgz#b1d4e89e688119c3c9a903ad30abb2f6a919be3c" @@ -292,6 +387,21 @@ chalk@^4.0.0: ansi-styles "^4.1.0" supports-color "^7.1.0" +chokidar@^3.5.1: + version "3.5.3" + resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" + integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== + dependencies: + anymatch "~3.1.2" + braces "~3.0.2" + glob-parent "~5.1.2" + is-binary-path "~2.1.0" + is-glob "~4.0.1" + normalize-path "~3.0.0" + readdirp "~3.6.0" + optionalDependencies: + fsevents "~2.3.2" + color-convert@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/color-convert/-/color-convert-2.0.1.tgz#72d3a68d598c9bdb3af2ad1e84f21d896abd4de3" @@ -316,6 +426,11 @@ concat-map@0.0.1: resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" integrity sha1-2Klr13/Wjfd5OnMDajug1UBdR3s= +create-require@^1.1.0: + version "1.1.1" + resolved "https://registry.yarnpkg.com/create-require/-/create-require-1.1.1.tgz#c1d7e8f1e5f6cfc9ff65f9cd352d37348756c333" + integrity sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ== + cross-spawn@^7.0.2: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" @@ -363,6 +478,11 @@ delayed-stream@~1.0.0: resolved "https://registry.yarnpkg.com/delayed-stream/-/delayed-stream-1.0.0.tgz#df3ae199acadfb7d440aaae0b29e2272b24ec619" integrity sha1-3zrhmayt+31ECqrgsp4icrJOxhk= +diff@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/diff/-/diff-4.0.2.tgz#60f3aecb89d5fae520c11aa19efc2bb982aade7d" + integrity sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A== + dir-glob@^3.0.1: version "3.0.1" resolved "https://registry.yarnpkg.com/dir-glob/-/dir-glob-3.0.1.tgz#56dbf73d992a4a93ba1584f4534063fd2e41717f" @@ -384,6 +504,13 @@ doctrine@^3.0.0: dependencies: esutils "^2.0.2" +dynamic-dedupe@^0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/dynamic-dedupe/-/dynamic-dedupe-0.3.0.tgz#06e44c223f5e4e94d78ef9db23a6515ce2f962a1" + integrity sha512-ssuANeD+z97meYOqd50e04Ze5qp4bPqo8cCkI4TRjZkzAUgIDTrXV1R8QCdINpiI+hw14+rYazvTRdQrz0/rFQ== + dependencies: + xtend "^4.0.0" + enhance-visitors@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/enhance-visitors/-/enhance-visitors-1.0.0.tgz#aa945d05da465672a1ebd38fee2ed3da8518e95a" @@ -755,6 +882,11 @@ fs.realpath@^1.0.0: resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" integrity sha1-FQStJSMVjKpA20onh8sBQRmU6k8= +fsevents@~2.3.2: + version "2.3.2" + resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a" + integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA== + function-bind@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d" @@ -782,7 +914,7 @@ get-symbol-description@^1.0.0: call-bind "^1.0.2" get-intrinsic "^1.1.1" -glob-parent@^5.1.2: +glob-parent@^5.1.2, glob-parent@~5.1.2: version "5.1.2" resolved "https://registry.yarnpkg.com/glob-parent/-/glob-parent-5.1.2.tgz#869832c58034fe68a4093c17dc15e8340d8401c4" integrity sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow== @@ -908,6 +1040,13 @@ is-bigint@^1.0.1: dependencies: has-bigints "^1.0.1" +is-binary-path@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/is-binary-path/-/is-binary-path-2.1.0.tgz#ea1f7f3b80f064236e83470f86c09c254fb45b09" + integrity sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw== + dependencies: + binary-extensions "^2.0.0" + is-boolean-object@^1.1.0: version "1.1.2" resolved "https://registry.yarnpkg.com/is-boolean-object/-/is-boolean-object-1.1.2.tgz#5c6dc200246dd9321ae4b885a114bb1f75f63719" @@ -928,6 +1067,13 @@ is-core-module@^2.8.0, is-core-module@^2.8.1: dependencies: has "^1.0.3" +is-core-module@^2.9.0: + version "2.10.0" + resolved "https://registry.yarnpkg.com/is-core-module/-/is-core-module-2.10.0.tgz#9012ede0a91c69587e647514e1d5277019e728ed" + integrity sha512-Erxj2n/LDAZ7H8WNJXd9tw38GYM3dv8rk8Zcs+jJuxYTW7sozH+SS8NtrSjVL1/vpLvWi1hxy96IzjJ3EHTJJg== + dependencies: + has "^1.0.3" + is-date-object@^1.0.1: version "1.0.5" resolved "https://registry.yarnpkg.com/is-date-object/-/is-date-object-1.0.5.tgz#0841d5536e724c25597bf6ea62e1bd38298df31f" @@ -940,7 +1086,7 @@ is-extglob@^2.1.1: resolved "https://registry.yarnpkg.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" integrity sha1-qIwCU1eR8C7TfHahueqXc8gz+MI= -is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3: +is-glob@^4.0.0, is-glob@^4.0.1, is-glob@^4.0.3, is-glob@~4.0.1: version "4.0.3" resolved "https://registry.yarnpkg.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== @@ -1067,6 +1213,11 @@ lru-cache@^6.0.0: dependencies: yallist "^4.0.0" +make-error@^1.1.1: + version "1.3.6" + resolved "https://registry.yarnpkg.com/make-error/-/make-error-1.3.6.tgz#2eb2e37ea9b67c4891f684a1394799af484cf7a2" + integrity sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw== + merge2@^1.3.0, merge2@^1.4.1: version "1.4.1" resolved "https://registry.yarnpkg.com/merge2/-/merge2-1.4.1.tgz#4368892f885e907455a6fd7dc55c0c9d404990ae" @@ -1109,6 +1260,11 @@ minimist@^1.2.0, minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.6.tgz#8637a5b759ea0d6e98702cfb3a9283323c93af44" integrity sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q== +mkdirp@^1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-1.0.4.tgz#3eb5ed62622756d79a5f0e2a221dfebad75c2f7e" + integrity sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8" @@ -1129,6 +1285,11 @@ natural-compare@^1.4.0: resolved "https://registry.yarnpkg.com/natural-compare/-/natural-compare-1.4.0.tgz#4abebfeed7541f2c27acfb29bdbbd15c8d5ba4f7" integrity sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc= +normalize-path@^3.0.0, normalize-path@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/normalize-path/-/normalize-path-3.0.0.tgz#0dcd69ff23a1c9b11fd0978316644a0388216a65" + integrity sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA== + object-inspect@^1.12.0, object-inspect@^1.9.0: version "1.12.0" resolved "https://registry.yarnpkg.com/object-inspect/-/object-inspect-1.12.0.tgz#6e2c120e868fd1fd18cb4f18c31741d0d6e776f0" @@ -1247,7 +1408,7 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== -picomatch@^2.3.1: +picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.3.1: version "2.3.1" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.1.tgz#3ba3833733646d9d3e4995946c1365a67fb07a42" integrity sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA== @@ -1286,6 +1447,13 @@ queue-microtask@^1.2.2: resolved "https://registry.yarnpkg.com/queue-microtask/-/queue-microtask-1.2.3.tgz#4929228bbc724dfac43e0efb058caf7b6cfb6243" integrity sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A== +readdirp@~3.6.0: + version "3.6.0" + resolved "https://registry.yarnpkg.com/readdirp/-/readdirp-3.6.0.tgz#74a370bd857116e245b29cc97340cd431a02a6c7" + integrity sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA== + dependencies: + picomatch "^2.2.1" + regexpp@^3.0.0, regexpp@^3.2.0: version "3.2.0" resolved "https://registry.yarnpkg.com/regexpp/-/regexpp-3.2.0.tgz#0425a2768d8f23bad70ca4b90461fa2f1213e1b2" @@ -1301,6 +1469,15 @@ resolve-from@^5.0.0: resolved "https://registry.yarnpkg.com/resolve-from/-/resolve-from-5.0.0.tgz#c35225843df8f776df21c57557bc087e9dfdfc69" integrity sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw== +resolve@^1.0.0: + version "1.22.1" + resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.1.tgz#27cb2ebb53f91abb49470a928bba7558066ac177" + integrity sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw== + dependencies: + is-core-module "^2.9.0" + path-parse "^1.0.7" + supports-preserve-symlinks-flag "^1.0.0" + resolve@^1.10.1, resolve@^1.20.0: version "1.22.0" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.0.tgz#5e0b8c67c15df57a89bdbabe603a002f21731198" @@ -1315,6 +1492,13 @@ reusify@^1.0.4: resolved "https://registry.yarnpkg.com/reusify/-/reusify-1.0.4.tgz#90da382b1e126efc02146e90845a88db12925d76" integrity sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw== +rimraf@^2.6.1: + version "2.7.1" + resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-2.7.1.tgz#35797f13a7fdadc566142c29d4f07ccad483e3ec" + integrity sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w== + dependencies: + glob "^7.1.3" + rimraf@^3.0.2: version "3.0.2" resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-3.0.2.tgz#f1a5402ba6220ad52cc1282bac1ae3aa49fd061a" @@ -1367,6 +1551,19 @@ slash@^3.0.0: resolved "https://registry.yarnpkg.com/slash/-/slash-3.0.0.tgz#6539be870c165adbd5240220dbe361f1bc4d4634" integrity sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q== +source-map-support@^0.5.12: + version "0.5.21" + resolved "https://registry.yarnpkg.com/source-map-support/-/source-map-support-0.5.21.tgz#04fe7c7f9e1ed2d662233c28cb2b35b9f63f6e4f" + integrity sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w== + dependencies: + buffer-from "^1.0.0" + source-map "^0.6.0" + +source-map@^0.6.0: + version "0.6.1" + resolved "https://registry.yarnpkg.com/source-map/-/source-map-0.6.1.tgz#74722af32e9614e9c287a8d0bbde48b5e2f1a263" + integrity sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g== + string.prototype.trimend@^1.0.4: version "1.0.4" resolved "https://registry.yarnpkg.com/string.prototype.trimend/-/string.prototype.trimend-1.0.4.tgz#e75ae90c2942c63504686c18b287b4a0b1a45f80" @@ -1395,6 +1592,11 @@ strip-bom@^3.0.0: resolved "https://registry.yarnpkg.com/strip-bom/-/strip-bom-3.0.0.tgz#2334c18e9c759f7bdd56fdef7e9ae3d588e68ed3" integrity sha1-IzTBjpx1n3vdVv3vfprj1YjmjtM= +strip-json-comments@^2.0.0: + version "2.0.1" + resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a" + integrity sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ== + strip-json-comments@^3.1.0, strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" @@ -1424,6 +1626,46 @@ to-regex-range@^5.0.1: dependencies: is-number "^7.0.0" +tree-kill@^1.2.2: + version "1.2.2" + resolved "https://registry.yarnpkg.com/tree-kill/-/tree-kill-1.2.2.tgz#4ca09a9092c88b73a7cdc5e8a01b507b0790a0cc" + integrity sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A== + +ts-node-dev@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/ts-node-dev/-/ts-node-dev-2.0.0.tgz#bdd53e17ab3b5d822ef519928dc6b4a7e0f13065" + integrity sha512-ywMrhCfH6M75yftYvrvNarLEY+SUXtUvU8/0Z6llrHQVBx12GiFk5sStF8UdfE/yfzk9IAq7O5EEbTQsxlBI8w== + dependencies: + chokidar "^3.5.1" + dynamic-dedupe "^0.3.0" + minimist "^1.2.6" + mkdirp "^1.0.4" + resolve "^1.0.0" + rimraf "^2.6.1" + source-map-support "^0.5.12" + tree-kill "^1.2.2" + ts-node "^10.4.0" + tsconfig "^7.0.0" + +ts-node@^10.4.0: + version "10.9.1" + resolved "https://registry.yarnpkg.com/ts-node/-/ts-node-10.9.1.tgz#e73de9102958af9e1f0b168a6ff320e25adcff4b" + integrity sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw== + dependencies: + "@cspotcode/source-map-support" "^0.8.0" + "@tsconfig/node10" "^1.0.7" + "@tsconfig/node12" "^1.0.7" + "@tsconfig/node14" "^1.0.0" + "@tsconfig/node16" "^1.0.2" + acorn "^8.4.1" + acorn-walk "^8.1.1" + arg "^4.1.0" + create-require "^1.1.0" + diff "^4.0.1" + make-error "^1.1.1" + v8-compile-cache-lib "^3.0.1" + yn "3.1.1" + tsconfig-paths@^3.12.0: version "3.14.1" resolved "https://registry.yarnpkg.com/tsconfig-paths/-/tsconfig-paths-3.14.1.tgz#ba0734599e8ea36c862798e920bcf163277b137a" @@ -1434,6 +1676,16 @@ tsconfig-paths@^3.12.0: minimist "^1.2.6" strip-bom "^3.0.0" +tsconfig@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/tsconfig/-/tsconfig-7.0.0.tgz#84538875a4dc216e5c4a5432b3a4dec3d54e91b7" + integrity sha512-vZXmzPrL+EmC4T/4rVlT2jNVMWCi/O4DIiSj3UHg1OE5kCKbk4mfrXc6dZksLgRM/TZlKnousKH9bbTazUWRRw== + dependencies: + "@types/strip-bom" "^3.0.0" + "@types/strip-json-comments" "0.0.30" + strip-bom "^3.0.0" + strip-json-comments "^2.0.0" + tslib@^1.8.1: version "1.14.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.14.1.tgz#cf2d38bdc34a134bcaf1091c41f6619e2f672d00" @@ -1480,6 +1732,11 @@ uri-js@^4.2.2: dependencies: punycode "^2.1.0" +v8-compile-cache-lib@^3.0.1: + version "3.0.1" + resolved "https://registry.yarnpkg.com/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz#6336e8d71965cb3d35a1bbb7868445a7c05264bf" + integrity sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg== + v8-compile-cache@^2.0.3: version "2.3.0" resolved "https://registry.yarnpkg.com/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz#2de19618c66dc247dcfb6f99338035d8245a2cee" @@ -1513,11 +1770,26 @@ wrappy@1: resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= +ws@^8.8.1: + version "8.8.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.8.1.tgz#5dbad0feb7ade8ecc99b830c1d77c913d4955ff0" + integrity sha512-bGy2JzvzkPowEJV++hF07hAD6niYSr0JzBNo/J29WsB57A2r7Wlc1UFcTR9IzrPvuNVO4B8LGqF8qcpsVOhJCA== + +xtend@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72" integrity sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A== +yn@3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/yn/-/yn-3.1.1.tgz#1e87401a09d767c1d5eab26a6e4c185182d2eb50" + integrity sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q== + yocto-queue@^0.1.0: version "0.1.0" resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-0.1.0.tgz#0294eb3dee05028d31ee1a5fa2c556a6aaf10a1b" From d3b6ac47391963bc774c830d87de813e05ffef1e Mon Sep 17 00:00:00 2001 From: Gustavo Date: Fri, 9 Sep 2022 20:00:37 -0300 Subject: [PATCH 2/2] added foxbit orderbook and all tickers --- examples/foxbit.ts | 5 +- package.json | 2 +- src/connectors/foxbit.ts | 30 +++++ src/interfaces/alphapoint.ts | 227 ++++++++++++++++++++++++++++++----- 4 files changed, 235 insertions(+), 29 deletions(-) diff --git a/examples/foxbit.ts b/examples/foxbit.ts index fcacbca..05d4388 100644 --- a/examples/foxbit.ts +++ b/examples/foxbit.ts @@ -3,4 +3,7 @@ import { foxbit } from "../src/index"; const ex = new foxbit(); -ex.subscribeBook("BTC", "BRL"); +ex.getBook("BTC", "BRL").then((book) => console.log("book from foxbit", book)); +ex.getAllTickers().then((tickers) => + console.log("tickers from foxbit", tickers), +); diff --git a/package.json b/package.json index b280554..c8e3c22 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "@coinsamba/js-exchanges-connector", "description": "Collection of JavaScript implementations of cryptocurrency exchange APIs", - "version": "1.3.1", + "version": "1.4.0", "repository": "git@github.com:coinsambacom/js-exchanges-connector.git", "author": "Gustavo ", "license": "MIT", diff --git a/src/connectors/foxbit.ts b/src/connectors/foxbit.ts index cf404e3..49986e3 100644 --- a/src/connectors/foxbit.ts +++ b/src/connectors/foxbit.ts @@ -2,6 +2,20 @@ import { alphapoint } from "../interfaces/alphapoint"; import { IExchangeImplementationConstructorArgs } from "../interfaces/exchange"; import { ITicker } from "../types/common"; +interface IFoxbitAllTickers { + symbol: string; + price: number; + priceChange24hPercent: number; + name: string; + icon: string; + rank: number; + volume: number; + low: number; + high: number; + marketcap: number; + lastUpdateTimestamp: number; +} + export class foxbit extends alphapoint { constructor(args?: IExchangeImplementationConstructorArgs) { super({ @@ -11,6 +25,8 @@ export class foxbit extends alphapoint { opts: args?.opts, limiter: args?.limiter, }); + + this.tickerUrl = "https://foxbit.com.br/_api/ticker?cache=0"; } normalizeAsset(asset: string | number): string | number { @@ -42,4 +58,18 @@ export class foxbit extends alphapoint { vol: res.vol, }; } + + async getAllTickers(): Promise { + const res = await this.fetch(this.tickerUrl); + + return res.map((ticker) => ({ + exchangeId: this.id, + base: ticker.symbol, + quote: "BRL", + last: ticker.price, + ask: ticker.price, + bid: ticker.price, + vol: ticker.volume, + })); + } } diff --git a/src/interfaces/alphapoint.ts b/src/interfaces/alphapoint.ts index 5c70c27..9b38d1f 100644 --- a/src/interfaces/alphapoint.ts +++ b/src/interfaces/alphapoint.ts @@ -2,6 +2,9 @@ import { IOrderbook, IOrderbookOrder, ITicker } from "../types/common"; import { Exchange, IExchangeBaseConstructorArgs } from "./exchange"; import WebSocket from "ws"; import crypto from "crypto"; +import * as sort from "fast-sort"; + +const WEBSOCKET_TIMEOUT_MS = 5000; interface IAlphapointConstructorArgs extends IExchangeBaseConstructorArgs { @@ -22,6 +25,28 @@ interface IMessageFrame { type IRawMessageFrame = Omit & { o: any }; +type SubscribeLevel2 = [ + number, // MDUpdateId + number, // Number of Accounts + number, // ActionDateTime in Posix format X 1000 + number, // ActionType 0 (New), 1 (Update), 2(Delete) + number, // LastTradePrice + number, // Number of Orders + number, // Price + number, // ProductPairCode + number, // Quantity + // Side 0 bid, 1 ask + number, +]; + +enum ALPHAPOINT_METHOD { + REQUEST = 0, + REPLY = 1, + SUBSCRIBE = 2, + EVENT = 3, + UNSUBSCRIBE_FROM_EVENT = 4, +} + export class alphapoint extends Exchange { public baseUrl: any; public websocketUrl?: string; @@ -30,36 +55,60 @@ export class alphapoint extends Exchange { public wsReady?: boolean; private wsPingInterval?: NodeJS.Timer; private resolveMap?: Map void>; + private wsBooks!: Map; + private wsBooksCbs!: Map void)[]>; constructor(args: IAlphapointConstructorArgs) { super({ ...args }); + this.eraseWebsocket(); } private async ensureWebsocket() { if (!this.ws) { await this.initWebsocket(); } + } - while (this.ws?.readyState === this.ws?.CONNECTING) { - // wait connection + private eraseWebsocket() { + this.ws = undefined; + this.wsReady = false; + if (this.wsPingInterval) { + clearTimeout(this.wsPingInterval); } + this.resolveMap = new Map(); + this.wsBooks = new Map(); + this.wsBooksCbs = new Map(); } private initWebsocket(): Promise { - return new Promise((resolve) => { + return new Promise((resolve, reject) => { + const rejectTimer = setTimeout(() => { + reject("websocket connection timeout"); + }, WEBSOCKET_TIMEOUT_MS); + this.ws = new WebSocket(this.websocketUrl!); this.ws.on("message", (data) => { - //data contém o payload de resposta const parsedData = JSON.parse(data.toString()) as IMessageFrame; + if (this.id === "foxbit" && parsedData.m === 0) { + parsedData.m = 1; + } + if (parsedData.n === "Ping") { this.resolveMap!.delete(parsedData.i); - } + } else if (parsedData.m === ALPHAPOINT_METHOD.REPLY) { + const prms = this.resolveMap?.get(parsedData.i); - console.log(parsedData); + if (prms) { + this.resolveMap?.delete(parsedData.i); + prms(parsedData.o ? JSON.parse(parsedData.o) : null); + } + } else if (parsedData.m === ALPHAPOINT_METHOD.EVENT) { + this.parseEvent(parsedData); + } }); this.resolveMap = new Map(); - this.ws.on("open", () => { + this.ws.once("open", () => { this.wsPingInterval = setInterval(() => { this.sendFrameToWs({ m: 0, @@ -69,18 +118,64 @@ export class alphapoint extends Exchange { }, 15000); this.wsReady = true; + + clearTimeout(rejectTimer); resolve(this.wsReady); }); + + this.ws.once("close", () => this.eraseWebsocket()); + this.ws.once("error", () => this.eraseWebsocket()); }); } + private parseEvent(frame: IMessageFrame) { + if (frame.n === "Level2UpdateEvent") { + const parsedO = JSON.parse(frame.o) as SubscribeLevel2[]; + for (const o of parsedO) { + const InstrumentId = o[7]; + const book = this.wsBooks.get(InstrumentId); + book!.parseL2(o); + const cbs = this.wsBooksCbs.get(InstrumentId) ?? []; + + cbs.forEach((cb) => cb(book!.parsedBook)); + } + } + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars - public async subscribeBook(base: string, _quote: string) { + public async subscribeBook( + base: string, + quote: string, + cb: (book: IOrderbook) => void, + ): Promise<() => Promise> { + const InstrumentId = this.normalizeAsset(base) as number; await this.ensureWebsocket(); - this.sendFrameToWs({ - m: 2, + this.sendFrameToWs({ + m: ALPHAPOINT_METHOD.SUBSCRIBE, + n: "SubscribeLevel2", + o: { OMSId: 1, InstrumentId, Depth: 500 }, + }); + + this.wsBooks.set(InstrumentId, new AlphapointOrderbook()); + + if (!this.wsBooksCbs.has(InstrumentId)) { + this.wsBooksCbs.set(InstrumentId, []); + } + + this.wsBooksCbs.get(InstrumentId)!.push(cb); + + return async () => { + this.unsubscribeBook(InstrumentId); + }; + } + + private async unsubscribeBook(InstrumentId: number) { + this.wsBooks.delete(InstrumentId); + // this.wsBooksCbs.clear(); + await this.sendFrameToWs({ + m: ALPHAPOINT_METHOD.UNSUBSCRIBE_FROM_EVENT, n: "SubscribeLevel2", - o: { OMSId: 1, InstrumentId: this.normalizeAsset(base), Depth: 50 }, + o: {}, }); } @@ -93,17 +188,22 @@ export class alphapoint extends Exchange { }; } - private async sendFrameToWs(rawFrame: IRawMessageFrame) { - const promise = new Promise((resolve, reject) => { + private async sendFrameToWs(rawFrame: IRawMessageFrame): Promise { + const promise = new Promise((resolve, reject) => { const frame = this.buildMessageFrame(rawFrame); - this.resolveMap!.set(frame.i, resolve); + if (rawFrame.m === ALPHAPOINT_METHOD.REQUEST) { + this.resolveMap!.set(frame.i, (v) => resolve(v as T)); + } const rejectTimer = setTimeout(() => { - if (rawFrame.m === 0 && this.resolveMap!.delete(frame.i)) { - reject("Websocket reply timeout"); + if ( + rawFrame.m === ALPHAPOINT_METHOD.REQUEST && + this.resolveMap!.delete(frame.i) + ) { + reject("websocket reply timeout"); } - }, 5000); + }, WEBSOCKET_TIMEOUT_MS); this.ws?.send(JSON.stringify(frame), (err) => { if (err) { @@ -149,25 +249,98 @@ export class alphapoint extends Exchange { // eslint-disable-next-line @typescript-eslint/no-unused-vars async getBook(base: string, quote: string): Promise { + await this.ensureWebsocket(); + base = this.normalizeAsset(base) as string; - const res = await this.fetch( - `${this.baseUrl}/GetL2Snapshot?OMSId=1&InstrumentId=${base}&Depth=50`, - ); + const res = await this.sendFrameToWs({ + m: ALPHAPOINT_METHOD.REQUEST, + n: "GetL2Snapshot", + o: { OMSId: 1, InstrumentId: base, Depth: 100 }, + }); const normalizedBook = { asks: [] as IOrderbookOrder[], bids: [] as IOrderbookOrder[], }; - res.forEach((order) => { - if (order[9] === 1) { - normalizedBook.asks.push(this.parseOrder(order)); - } else { - normalizedBook.bids.push(this.parseOrder(order)); - } - }); + if (Array.isArray(res)) { + res.forEach((order) => { + if (order[9] === 1) { + normalizedBook.asks.push(this.parseOrder(order)); + } else { + normalizedBook.bids.push(this.parseOrder(order)); + } + }); + } return normalizedBook; } } + +class AlphapointOrderbook { + private book: { asks: SubscribeLevel2[]; bids: SubscribeLevel2[] }; + + constructor() { + this.book = { + asks: [], + bids: [], + }; + } + + parseL2(l2: SubscribeLevel2) { + if (l2[3] === 0) { + this.insertOrder(l2); + } else if (l2[3] === 1) { + this.updateOrder(l2); + } else if (l2[3] === 2) { + this.removeOrder(l2); + } + } + + private insertOrder(order: SubscribeLevel2) { + if (order[9] === 0) { + this.book.bids.push(order); + sort.sort(this.book.bids).desc((o) => o[6]); + } else { + this.book.asks.push(order); + sort.sort(this.book.asks).asc((o) => o[6]); + } + } + + private removeOrder(order: SubscribeLevel2) { + if (order[9] === 0) { + this.book.bids = this.book.bids.filter((o) => o[0] != o[0]); + } else { + this.book.asks = this.book.asks.filter((o) => o[0] != o[0]); + } + } + + private updateOrder(order: SubscribeLevel2) { + let orderFound: SubscribeLevel2 | undefined; + if (order[9] === 0) { + orderFound = this.book.bids.find((o) => o[0] === order[0]); + } else { + orderFound = this.book.asks.find((o) => o[0] === order[0]); + } + + if (orderFound) { + orderFound[8] = order[8]; + orderFound[6] = order[6]; + } + } + + private parseSingleOrder(order: SubscribeLevel2): IOrderbookOrder { + return { + amount: order[8], + price: order[6], + }; + } + + get parsedBook() { + return { + asks: this.book.asks.map(this.parseSingleOrder), + bids: this.book.bids.map(this.parseSingleOrder), + }; + } +}