From 82c7b3f1bbb44806ac9e0af381dc7f11d13df69b Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Tue, 17 May 2022 21:28:29 -0300 Subject: [PATCH 001/105] Add okx initial files --- src/aluna.spec.ts | 4 +- src/exchanges/okx/Okx.spec.ts | 28 ++ src/exchanges/okx/Okx.ts | 38 ++ src/exchanges/okx/OkxAuthed.spec.ts | 38 ++ src/exchanges/okx/OkxAuthed.ts | 46 ++ src/exchanges/okx/OkxHttp.spec.ts | 476 ++++++++++++++++++ src/exchanges/okx/OkxHttp.ts | 192 +++++++ src/exchanges/okx/README.md | 47 ++ .../okx/enums/OkxMarketStatusEnum.ts | 4 + src/exchanges/okx/enums/OkxOrderSideEnum.ts | 4 + src/exchanges/okx/enums/OkxOrderStatusEnum.ts | 4 + src/exchanges/okx/enums/OkxOrderTypeEnum.ts | 6 + .../adapters/okxOrderSideAdapter.spec.ts | 86 ++++ .../okx/enums/adapters/okxOrderSideAdapter.ts | 33 ++ .../adapters/okxOrderStatusAdapter.spec.ts | 93 ++++ .../enums/adapters/okxOrderStatusAdapter.ts | 59 +++ .../adapters/okxOrderTypeAdapter.spec.ts | 104 ++++ .../okx/enums/adapters/okxOrderTypeAdapter.ts | 37 ++ .../okx/errors/handleOkxRequestError.spec.ts | 169 +++++++ .../okx/errors/handleOkxRequestError.ts | 84 ++++ src/exchanges/okx/index.ts | 9 + src/exchanges/okx/modules/authed/balance.ts | 19 + .../okx/modules/authed/balance/list.spec.ts | 22 + .../okx/modules/authed/balance/list.ts | 35 ++ .../modules/authed/balance/listRaw.spec.ts | 56 +++ .../okx/modules/authed/balance/listRaw.ts | 46 ++ .../okx/modules/authed/balance/parse.spec.ts | 41 ++ .../okx/modules/authed/balance/parse.ts | 21 + .../modules/authed/balance/parseMany.spec.ts | 45 ++ .../okx/modules/authed/balance/parseMany.ts | 36 ++ src/exchanges/okx/modules/authed/key.ts | 17 + .../modules/authed/key/fetchDetails.spec.ts | 81 +++ .../okx/modules/authed/key/fetchDetails.ts | 48 ++ .../modules/authed/key/parseDetails.spec.ts | 68 +++ .../okx/modules/authed/key/parseDetails.ts | 37 ++ .../authed/key/parsePermissions.spec.ts | 42 ++ .../modules/authed/key/parsePermissions.ts | 33 ++ src/exchanges/okx/modules/authed/order.ts | 29 ++ .../okx/modules/authed/order/cancel.spec.ts | 119 +++++ .../okx/modules/authed/order/cancel.ts | 75 +++ .../okx/modules/authed/order/edit.spec.ts | 96 ++++ .../okx/modules/authed/order/edit.ts | 65 +++ .../okx/modules/authed/order/get.spec.ts | 68 +++ src/exchanges/okx/modules/authed/order/get.ts | 33 ++ .../okx/modules/authed/order/getRaw.spec.ts | 61 +++ .../okx/modules/authed/order/getRaw.ts | 49 ++ .../okx/modules/authed/order/list.spec.ts | 22 + .../okx/modules/authed/order/list.ts | 35 ++ .../okx/modules/authed/order/listRaw.spec.ts | 54 ++ .../okx/modules/authed/order/listRaw.ts | 46 ++ .../okx/modules/authed/order/parse.spec.ts | 34 ++ .../okx/modules/authed/order/parse.ts | 53 ++ .../modules/authed/order/parseMany.spec.ts | 48 ++ .../okx/modules/authed/order/parseMany.ts | 34 ++ .../okx/modules/authed/order/place.spec.ts | 348 +++++++++++++ .../okx/modules/authed/order/place.ts | 127 +++++ src/exchanges/okx/modules/public/market.ts | 20 + .../okx/modules/public/market/list.spec.ts | 22 + .../okx/modules/public/market/list.ts | 34 ++ .../okx/modules/public/market/listRaw.spec.ts | 48 ++ .../okx/modules/public/market/listRaw.ts | 40 ++ .../okx/modules/public/market/parse.spec.ts | 38 ++ .../okx/modules/public/market/parse.ts | 22 + .../modules/public/market/parseMany.spec.ts | 41 ++ .../okx/modules/public/market/parseMany.ts | 38 ++ src/exchanges/okx/modules/public/symbol.ts | 20 + .../okx/modules/public/symbol/list.spec.ts | 22 + .../okx/modules/public/symbol/list.ts | 35 ++ .../okx/modules/public/symbol/listRaw.spec.ts | 48 ++ .../okx/modules/public/symbol/listRaw.ts | 40 ++ .../okx/modules/public/symbol/parse.spec.ts | 87 ++++ .../okx/modules/public/symbol/parse.ts | 42 ++ .../modules/public/symbol/parseMany.spec.ts | 38 ++ .../okx/modules/public/symbol/parseMany.ts | 36 ++ src/exchanges/okx/okxSpecs.spec.ts | 38 ++ src/exchanges/okx/okxSpecs.ts | 186 +++++++ .../okx/schemas/IOkxBalanceSchema.ts | 7 + src/exchanges/okx/schemas/IOkxKeySchema.ts | 8 + src/exchanges/okx/schemas/IOkxMarketSchema.ts | 12 + src/exchanges/okx/schemas/IOkxOrderSchema.ts | 11 + src/exchanges/okx/schemas/IOkxSymbolSchema.ts | 6 + .../okx/test/fixtures/okxBalances.ts | 23 + src/exchanges/okx/test/fixtures/okxKey.ts | 9 + src/exchanges/okx/test/fixtures/okxMarket.ts | 27 + src/exchanges/okx/test/fixtures/okxOrders.ts | 18 + src/exchanges/okx/test/fixtures/okxSymbols.ts | 20 + src/lib/exchanges.ts | 3 + 87 files changed, 4641 insertions(+), 2 deletions(-) create mode 100644 src/exchanges/okx/Okx.spec.ts create mode 100644 src/exchanges/okx/Okx.ts create mode 100644 src/exchanges/okx/OkxAuthed.spec.ts create mode 100644 src/exchanges/okx/OkxAuthed.ts create mode 100644 src/exchanges/okx/OkxHttp.spec.ts create mode 100644 src/exchanges/okx/OkxHttp.ts create mode 100644 src/exchanges/okx/README.md create mode 100644 src/exchanges/okx/enums/OkxMarketStatusEnum.ts create mode 100644 src/exchanges/okx/enums/OkxOrderSideEnum.ts create mode 100644 src/exchanges/okx/enums/OkxOrderStatusEnum.ts create mode 100644 src/exchanges/okx/enums/OkxOrderTypeEnum.ts create mode 100644 src/exchanges/okx/enums/adapters/okxOrderSideAdapter.spec.ts create mode 100644 src/exchanges/okx/enums/adapters/okxOrderSideAdapter.ts create mode 100644 src/exchanges/okx/enums/adapters/okxOrderStatusAdapter.spec.ts create mode 100644 src/exchanges/okx/enums/adapters/okxOrderStatusAdapter.ts create mode 100644 src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.spec.ts create mode 100644 src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.ts create mode 100644 src/exchanges/okx/errors/handleOkxRequestError.spec.ts create mode 100644 src/exchanges/okx/errors/handleOkxRequestError.ts create mode 100644 src/exchanges/okx/index.ts create mode 100644 src/exchanges/okx/modules/authed/balance.ts create mode 100644 src/exchanges/okx/modules/authed/balance/list.spec.ts create mode 100644 src/exchanges/okx/modules/authed/balance/list.ts create mode 100644 src/exchanges/okx/modules/authed/balance/listRaw.spec.ts create mode 100644 src/exchanges/okx/modules/authed/balance/listRaw.ts create mode 100644 src/exchanges/okx/modules/authed/balance/parse.spec.ts create mode 100644 src/exchanges/okx/modules/authed/balance/parse.ts create mode 100644 src/exchanges/okx/modules/authed/balance/parseMany.spec.ts create mode 100644 src/exchanges/okx/modules/authed/balance/parseMany.ts create mode 100644 src/exchanges/okx/modules/authed/key.ts create mode 100644 src/exchanges/okx/modules/authed/key/fetchDetails.spec.ts create mode 100644 src/exchanges/okx/modules/authed/key/fetchDetails.ts create mode 100644 src/exchanges/okx/modules/authed/key/parseDetails.spec.ts create mode 100644 src/exchanges/okx/modules/authed/key/parseDetails.ts create mode 100644 src/exchanges/okx/modules/authed/key/parsePermissions.spec.ts create mode 100644 src/exchanges/okx/modules/authed/key/parsePermissions.ts create mode 100644 src/exchanges/okx/modules/authed/order.ts create mode 100644 src/exchanges/okx/modules/authed/order/cancel.spec.ts create mode 100644 src/exchanges/okx/modules/authed/order/cancel.ts create mode 100644 src/exchanges/okx/modules/authed/order/edit.spec.ts create mode 100644 src/exchanges/okx/modules/authed/order/edit.ts create mode 100644 src/exchanges/okx/modules/authed/order/get.spec.ts create mode 100644 src/exchanges/okx/modules/authed/order/get.ts create mode 100644 src/exchanges/okx/modules/authed/order/getRaw.spec.ts create mode 100644 src/exchanges/okx/modules/authed/order/getRaw.ts create mode 100644 src/exchanges/okx/modules/authed/order/list.spec.ts create mode 100644 src/exchanges/okx/modules/authed/order/list.ts create mode 100644 src/exchanges/okx/modules/authed/order/listRaw.spec.ts create mode 100644 src/exchanges/okx/modules/authed/order/listRaw.ts create mode 100644 src/exchanges/okx/modules/authed/order/parse.spec.ts create mode 100644 src/exchanges/okx/modules/authed/order/parse.ts create mode 100644 src/exchanges/okx/modules/authed/order/parseMany.spec.ts create mode 100644 src/exchanges/okx/modules/authed/order/parseMany.ts create mode 100644 src/exchanges/okx/modules/authed/order/place.spec.ts create mode 100644 src/exchanges/okx/modules/authed/order/place.ts create mode 100644 src/exchanges/okx/modules/public/market.ts create mode 100644 src/exchanges/okx/modules/public/market/list.spec.ts create mode 100644 src/exchanges/okx/modules/public/market/list.ts create mode 100644 src/exchanges/okx/modules/public/market/listRaw.spec.ts create mode 100644 src/exchanges/okx/modules/public/market/listRaw.ts create mode 100644 src/exchanges/okx/modules/public/market/parse.spec.ts create mode 100644 src/exchanges/okx/modules/public/market/parse.ts create mode 100644 src/exchanges/okx/modules/public/market/parseMany.spec.ts create mode 100644 src/exchanges/okx/modules/public/market/parseMany.ts create mode 100644 src/exchanges/okx/modules/public/symbol.ts create mode 100644 src/exchanges/okx/modules/public/symbol/list.spec.ts create mode 100644 src/exchanges/okx/modules/public/symbol/list.ts create mode 100644 src/exchanges/okx/modules/public/symbol/listRaw.spec.ts create mode 100644 src/exchanges/okx/modules/public/symbol/listRaw.ts create mode 100644 src/exchanges/okx/modules/public/symbol/parse.spec.ts create mode 100644 src/exchanges/okx/modules/public/symbol/parse.ts create mode 100644 src/exchanges/okx/modules/public/symbol/parseMany.spec.ts create mode 100644 src/exchanges/okx/modules/public/symbol/parseMany.ts create mode 100644 src/exchanges/okx/okxSpecs.spec.ts create mode 100644 src/exchanges/okx/okxSpecs.ts create mode 100644 src/exchanges/okx/schemas/IOkxBalanceSchema.ts create mode 100644 src/exchanges/okx/schemas/IOkxKeySchema.ts create mode 100644 src/exchanges/okx/schemas/IOkxMarketSchema.ts create mode 100644 src/exchanges/okx/schemas/IOkxOrderSchema.ts create mode 100644 src/exchanges/okx/schemas/IOkxSymbolSchema.ts create mode 100644 src/exchanges/okx/test/fixtures/okxBalances.ts create mode 100644 src/exchanges/okx/test/fixtures/okxKey.ts create mode 100644 src/exchanges/okx/test/fixtures/okxMarket.ts create mode 100644 src/exchanges/okx/test/fixtures/okxOrders.ts create mode 100644 src/exchanges/okx/test/fixtures/okxSymbols.ts diff --git a/src/aluna.spec.ts b/src/aluna.spec.ts index 7ed55d1a..953f9b3d 100644 --- a/src/aluna.spec.ts +++ b/src/aluna.spec.ts @@ -20,7 +20,7 @@ describe(__filename, () => { const exchangeIds = Object.keys(exchanges) - expect(exchangeIds.length).to.eq(7) + expect(exchangeIds.length).to.eq(8) each(exchangeIds, (exchangeId) => { @@ -51,7 +51,7 @@ describe(__filename, () => { const exchangeIds = Object.keys(exchanges) - expect(exchangeIds.length).to.eq(7) + expect(exchangeIds.length).to.eq(8) each(exchangeIds, (exchangeId) => { diff --git a/src/exchanges/okx/Okx.spec.ts b/src/exchanges/okx/Okx.spec.ts new file mode 100644 index 00000000..c56caf1c --- /dev/null +++ b/src/exchanges/okx/Okx.spec.ts @@ -0,0 +1,28 @@ +import { expect } from 'chai' + +import { IAlunaSettingsSchema } from '../../lib/schemas/IAlunaSettingsSchema' +import { Okx } from './Okx' + + + +describe(__filename, () => { + + it('should contain public modules', async () => { + + const settings: IAlunaSettingsSchema = { + referralCode: '123', + } + + const okx = new Okx({ settings }) + + expect(okx.id).to.eq('okx') + + expect(okx.symbol).to.be.ok + expect(okx.market).to.be.ok + + expect(okx.specs).to.be.ok + expect(okx.settings).to.deep.eq(okx.settings) + + }) + +}) diff --git a/src/exchanges/okx/Okx.ts b/src/exchanges/okx/Okx.ts new file mode 100644 index 00000000..341201fa --- /dev/null +++ b/src/exchanges/okx/Okx.ts @@ -0,0 +1,38 @@ +import { IAlunaExchangePublic } from '../../lib/core/IAlunaExchange' +import { IAlunaMarketModule } from '../../lib/modules/public/IAlunaMarketModule' +import { IAlunaSymbolModule } from '../../lib/modules/public/IAlunaSymbolModule' +import { IAlunaExchangeSchema } from '../../lib/schemas/IAlunaExchangeSchema' +import { IAlunaSettingsSchema } from '../../lib/schemas/IAlunaSettingsSchema' +import { buildOkxSpecs } from './okxSpecs' +import { market } from './modules/public/market' +import { symbol } from './modules/public/symbol' + + + +export class Okx implements IAlunaExchangePublic { + + public id: string + public specs: IAlunaExchangeSchema + public settings: IAlunaSettingsSchema + + public symbol: IAlunaSymbolModule + public market: IAlunaMarketModule + + + + constructor(params: { + settings?: IAlunaSettingsSchema + }) { + + const { settings = {} } = params + + this.settings = settings + this.specs = buildOkxSpecs({ settings }) + this.id = this.specs.id + + this.market = market(this) + this.symbol = symbol(this) + + } + +} diff --git a/src/exchanges/okx/OkxAuthed.spec.ts b/src/exchanges/okx/OkxAuthed.spec.ts new file mode 100644 index 00000000..d9dd3d1e --- /dev/null +++ b/src/exchanges/okx/OkxAuthed.spec.ts @@ -0,0 +1,38 @@ +import { expect } from 'chai' + +import { IAlunaCredentialsSchema } from '../../lib/schemas/IAlunaCredentialsSchema' +import { IAlunaSettingsSchema } from '../../lib/schemas/IAlunaSettingsSchema' +import { OkxAuthed } from './OkxAuthed' + + + +describe(__filename, () => { + + it('should contain public & authed modules', async () => { + + const credentials: IAlunaCredentialsSchema = { + key: 'some-key', + secret: 'some-secret', + } + + const settings: IAlunaSettingsSchema = { + referralCode: '123', + } + + const okx = new OkxAuthed({ settings, credentials }) + + expect(okx.id).to.eq('okx') + + expect(okx.symbol).to.be.ok + expect(okx.market).to.be.ok + + expect(okx.key).to.be.ok + expect(okx.balance).to.be.ok + expect(okx.order).to.be.ok + + expect(okx.specs).to.be.ok + expect(okx.settings).to.deep.eq(okx.settings) + + }) + +}) diff --git a/src/exchanges/okx/OkxAuthed.ts b/src/exchanges/okx/OkxAuthed.ts new file mode 100644 index 00000000..c2c30ddb --- /dev/null +++ b/src/exchanges/okx/OkxAuthed.ts @@ -0,0 +1,46 @@ +import { IAlunaExchangeAuthed } from '../../lib/core/IAlunaExchange' +import { IAlunaBalanceModule } from '../../lib/modules/authed/IAlunaBalanceModule' +import { IAlunaKeyModule } from '../../lib/modules/authed/IAlunaKeyModule' +import { IAlunaOrderWriteModule } from '../../lib/modules/authed/IAlunaOrderModule' +import { IAlunaCredentialsSchema } from '../../lib/schemas/IAlunaCredentialsSchema' +import { IAlunaSettingsSchema } from '../../lib/schemas/IAlunaSettingsSchema' +import { balance } from './modules/authed/balance' +import { key } from './modules/authed/key' +import { order } from './modules/authed/order' +import { Okx } from './Okx' + + + +export class OkxAuthed extends Okx implements IAlunaExchangeAuthed { + + public credentials: IAlunaCredentialsSchema + + public key: IAlunaKeyModule + public order: IAlunaOrderWriteModule + public balance: IAlunaBalanceModule + + + + constructor(params: { + settings?: IAlunaSettingsSchema + credentials: IAlunaCredentialsSchema + }) { + + const { + settings, + credentials, + } = params + + super({ settings }) + + this.credentials = credentials + + this.key = key(this) + this.balance = balance(this) + this.order = order(this) + + return this + + } + +} diff --git a/src/exchanges/okx/OkxHttp.spec.ts b/src/exchanges/okx/OkxHttp.spec.ts new file mode 100644 index 00000000..ec39b883 --- /dev/null +++ b/src/exchanges/okx/OkxHttp.spec.ts @@ -0,0 +1,476 @@ +import { expect } from 'chai' +import { Agent } from 'https' +import { random } from 'lodash' +import { ImportMock } from 'ts-mock-imports' + +import { testCache } from '../../../test/macros/testCache' +import { mockAxiosRequest } from '../../../test/mocks/axios/request' +import { AlunaHttpVerbEnum } from '../../lib/enums/AlunaHtttpVerbEnum' +import { AlunaProtocolsEnum } from '../../lib/enums/AlunaProxyAgentEnum' +import { IAlunaCredentialsSchema } from '../../lib/schemas/IAlunaCredentialsSchema' +import { + IAlunaProxySchema, + IAlunaSettingsSchema, +} from '../../lib/schemas/IAlunaSettingsSchema' +import { mockAssembleRequestConfig } from '../../utils/axios/assembleRequestConfig.mock' +import { mockAlunaCache } from '../../utils/cache/AlunaCache.mock' +import { executeAndCatch } from '../../utils/executeAndCatch' +import * as handleOkxRequestErrorMod from './errors/handleOkxRequestError' +import * as OkxHttpMod from './OkxHttp' + + + +describe.skip(__filename, () => { + + const { OkxHttp } = OkxHttpMod + + const url = 'https://okx.com/api/path' + const response = 'response' + const body = { + data: 'some-data', + } + const credentials: IAlunaCredentialsSchema = { + key: 'key', + secret: 'key', + passphrase: 'key', + } + const signedHeader = { + 'Api-Key': 'apikey', + } + const proxySettings: IAlunaProxySchema = { + host: 'host', + port: 8080, + agent: new Agent(), + protocol: AlunaProtocolsEnum.HTTPS, + } + const settings: IAlunaSettingsSchema = { + proxySettings, + } + + + + const mockDeps = ( + params: { + mockGenerateAuthHeader?: boolean + cacheParams?: { + get?: any + has?: boolean + set?: boolean + } + } = {}, + ) => { + + const { assembleRequestConfig } = mockAssembleRequestConfig() + + const { request } = mockAxiosRequest() + + const { + mockGenerateAuthHeader = true, + cacheParams = { + get: {}, + has: false, + set: true, + }, + } = params + + const generateAuthHeader = ImportMock.mockFunction( + OkxHttpMod, + 'generateAuthHeader', + signedHeader, + ) + + const handleOkxRequestError = ImportMock.mockFunction( + handleOkxRequestErrorMod, + 'handleOkxRequestError', + ) + + if (!mockGenerateAuthHeader) { + + generateAuthHeader.restore() + + } + + const { + cache, + hashCacheKey, + } = mockAlunaCache(cacheParams) + + return { + cache, + request, + hashCacheKey, + generateAuthHeader, + assembleRequestConfig, + handleOkxRequestError, + } + + } + + it('should execute public request just fine', async () => { + + // preparing data + const verb = AlunaHttpVerbEnum.POST + + + // mocking + const { + cache, + request, + hashCacheKey, + generateAuthHeader, + assembleRequestConfig, + } = mockDeps() + + const okxHttp = new OkxHttp({}) + + request.returns(Promise.resolve({ data: response })) + + + // executing + const responseData = await okxHttp.publicRequest({ + verb, + url, + body, + }) + + + // validating + expect(responseData).to.be.eq(response) + + expect(okxHttp.requestWeight.public).to.be.eq(1) + expect(okxHttp.requestWeight.authed).to.be.eq(0) + + expect(request.callCount).to.be.eq(1) + expect(request.firstCall.args[0]).to.deep.eq({ + url, + method: verb, + data: body, + }) + + expect(hashCacheKey.callCount).to.be.eq(1) + + expect(cache.has.callCount).to.be.eq(1) + expect(cache.set.callCount).to.be.eq(1) + expect(cache.get.callCount).to.be.eq(0) + + expect(assembleRequestConfig.callCount).to.be.eq(1) + expect(assembleRequestConfig.firstCall.args[0]).to.deep.eq({ + url, + method: verb, + data: body, + proxySettings: undefined, + }) + + expect(generateAuthHeader.callCount).to.be.eq(0) + + }) + + it('should execute authed request just fine', async () => { + + // preparing data + const okxHttp = new OkxHttp({}) + + + // mocking + const { + cache, + request, + hashCacheKey, + generateAuthHeader, + assembleRequestConfig, + } = mockDeps() + + request.returns(Promise.resolve({ data: response })) + + + // executing + const responseData = await okxHttp.authedRequest({ + verb: AlunaHttpVerbEnum.POST, + url, + body, + credentials, + }) + + + // validating + expect(responseData).to.be.eq(response) + + expect(okxHttp.requestWeight.public).to.be.eq(0) + expect(okxHttp.requestWeight.authed).to.be.eq(1) + + expect(request.callCount).to.be.eq(1) + expect(request.firstCall.args[0]).to.deep.eq({ + url, + method: AlunaHttpVerbEnum.POST, + data: body, + headers: signedHeader, + }) + + expect(assembleRequestConfig.callCount).to.be.eq(1) + + expect(generateAuthHeader.callCount).to.be.eq(1) + expect(generateAuthHeader.firstCall.args[0]).to.deep.eq({ + verb: AlunaHttpVerbEnum.POST, + path: new URL(url).pathname, + credentials, + body, + url, + }) + + expect(hashCacheKey.callCount).to.be.eq(0) + + expect(cache.has.callCount).to.be.eq(0) + expect(cache.get.callCount).to.be.eq(0) + expect(cache.set.callCount).to.be.eq(0) + + }) + + it('should properly increment request count on public requests', async () => { + + // preparing data + const okxHttp = new OkxHttp({}) + + const weight = random() + const pubRequestCount = random() + const authRequestCount = random() + + okxHttp.requestWeight.public = pubRequestCount + okxHttp.requestWeight.authed = authRequestCount + + + // mocking + const { request } = mockDeps() + + request.returns(Promise.resolve({ data: response })) + + + // executing + await okxHttp.publicRequest({ + url, + body, + weight, + }) + + + // validating + expect(okxHttp.requestWeight.public).to.be.eq(pubRequestCount + weight) + expect(okxHttp.requestWeight.authed).to.be.eq(authRequestCount) + + expect(request.callCount).to.be.eq(1) + + }) + + it('should properly increment request count on authed requests', async () => { + + // preparing data + const okxHttp = new OkxHttp({}) + + const weight = random() + const pubRequestCount = random() + const authRequestCount = random() + + okxHttp.requestWeight.public = pubRequestCount + okxHttp.requestWeight.authed = authRequestCount + + + // mocking + const { request } = mockDeps() + + request.returns(Promise.resolve({ data: response })) + + + // executing + await okxHttp.authedRequest({ + url, + body, + weight, + credentials, + }) + + + // validating + expect(okxHttp.requestWeight.public).to.be.eq(pubRequestCount) + expect(okxHttp.requestWeight.authed).to.be.eq(authRequestCount + weight) + + expect(request.callCount).to.be.eq(1) + + }) + + it('should properly handle request error on public requests', async () => { + + // preparing data + const okxHttp = new OkxHttp({}) + + const throwedError = new Error('unknown error') + + + // mocking + const { + request, + handleOkxRequestError, + } = mockDeps() + + request.returns(Promise.reject(throwedError)) + + + // executing + const publicRes = await executeAndCatch(() => okxHttp.publicRequest({ + url, + body, + })) + + + // validating + expect(publicRes.result).not.to.be.ok + + expect(request.callCount).to.be.eq(1) + + expect(handleOkxRequestError.callCount).to.be.eq(1) + + }) + + it('should properly handle request error on authed requests', async () => { + + // preparing data + const okxHttp = new OkxHttp({}) + + const throwedError = new Error('unknown error') + + + // mocking + const { + request, + handleOkxRequestError, + } = mockDeps() + + request.returns(Promise.reject(throwedError)) + + + // executing + const autheRes = await executeAndCatch(() => okxHttp.authedRequest({ + url, + body, + credentials, + })) + + + // validating + expect(autheRes.result).not.to.be.ok + + expect(request.callCount).to.be.eq(1) + + expect(handleOkxRequestError.callCount).to.be.eq(1) + + }) + + it('should properly use proxy settings on public requests', async () => { + + // preparing data + const okxHttp = new OkxHttp({}) + + + // mocking + const { + request, + assembleRequestConfig, + } = mockDeps() + + request.returns(Promise.resolve({ data: response })) + + + // executing + await okxHttp.publicRequest({ + url, + body, + settings, + }) + + + // validating + expect(request.callCount).to.be.eq(1) + + expect(assembleRequestConfig.callCount).to.be.eq(1) + expect(assembleRequestConfig.firstCall.args[0]).to.deep.eq({ + url, + method: AlunaHttpVerbEnum.GET, + data: body, + proxySettings: settings.proxySettings, + }) + + }) + + it('should properly use proxy settings on authed requests', async () => { + + // preparing data + const okxHttp = new OkxHttp({}) + + + // mocking + const { + request, + assembleRequestConfig, + } = mockDeps() + + request.returns(Promise.resolve({ data: response })) + + + // executing + await okxHttp.authedRequest({ + url, + body, + settings, + credentials, + }) + + + // validating + expect(request.callCount).to.be.eq(1) + + expect(assembleRequestConfig.callCount).to.be.eq(1) + expect(assembleRequestConfig.firstCall.args[0]).to.deep.eq({ + url, + method: AlunaHttpVerbEnum.POST, + data: body, + headers: signedHeader, + proxySettings: settings.proxySettings, + }) + + }) + + it('should generate signed auth header just fine', async () => { + + // preparing data + const path = 'path' + const verb = 'verb' as AlunaHttpVerbEnum + + const currentDate = Date.now() + + // mocking + const dateMock = ImportMock.mockFunction( + Date, + 'now', + currentDate, + ) + + // executing + const signedHash = OkxHttpMod.generateAuthHeader({ + credentials, + path, + verb, + body, + url, + }) + + // validating + expect(dateMock.callCount).to.be.eq(1) + expect(signedHash['Api-Timestamp']).to.be.eq(currentDate) + + }) + + + /** + * Executes macro test. + * */ + testCache({ HttpClass: OkxHttp }) + +}) diff --git a/src/exchanges/okx/OkxHttp.ts b/src/exchanges/okx/OkxHttp.ts new file mode 100644 index 00000000..1a190dfe --- /dev/null +++ b/src/exchanges/okx/OkxHttp.ts @@ -0,0 +1,192 @@ +import axios from 'axios' + +import { + IAlunaHttp, + IAlunaHttpAuthedParams, + IAlunaHttpPublicParams, + IAlunaHttpRequestCount, +} from '../../lib/core/IAlunaHttp' +import { AlunaHttpVerbEnum } from '../../lib/enums/AlunaHtttpVerbEnum' +import { IAlunaCredentialsSchema } from '../../lib/schemas/IAlunaCredentialsSchema' +import { IAlunaSettingsSchema } from '../../lib/schemas/IAlunaSettingsSchema' +import { assembleRequestConfig } from '../../utils/axios/assembleRequestConfig' +import { AlunaCache } from '../../utils/cache/AlunaCache' +import { handleOkxRequestError } from './errors/handleOkxRequestError' + + + +export const OKX_HTTP_CACHE_KEY_PREFIX = 'OkxHttp.publicRequest' + + + +export class OkxHttp implements IAlunaHttp { + + public settings: IAlunaSettingsSchema + public requestWeight: IAlunaHttpRequestCount + + + + constructor(settings: IAlunaSettingsSchema) { + + this.requestWeight = { + authed: 0, + public: 0, + } + + this.settings = settings + + } + + + + public async publicRequest ( + params: IAlunaHttpPublicParams, + ): Promise { + + const { + url, + body, + verb = AlunaHttpVerbEnum.GET, + weight = 1, + } = params + + const settings = (params.settings || this.settings) + + const { + disableCache = false, + cacheTtlInSeconds = 60, + } = settings + + const cacheKey = AlunaCache.hashCacheKey({ + args: params, + prefix: OKX_HTTP_CACHE_KEY_PREFIX, + }) + + if (!disableCache && AlunaCache.cache.has(cacheKey)) { + return AlunaCache.cache.get(cacheKey) as T + } + + const { proxySettings } = settings + + const { requestConfig } = assembleRequestConfig({ + url, + method: verb, + data: body, + proxySettings, + }) + + this.requestWeight.public += weight + + try { + + const { data } = await axios.create().request(requestConfig) + + if (!disableCache) { + AlunaCache.cache.set(cacheKey, data, cacheTtlInSeconds) + } + + return data + + } catch (error) { + + throw handleOkxRequestError({ error }) + + } + + } + + + + public async authedRequest ( + params: IAlunaHttpAuthedParams, + ): Promise { + + const { + url, + body, + verb = AlunaHttpVerbEnum.POST, + credentials, + weight = 1, + } = params + + const settings = (params.settings || this.settings) + + const signedHash = generateAuthHeader({ + verb, + path: new URL(url).pathname, + credentials, + body, + url, + }) + + const { proxySettings } = settings + + const { requestConfig } = assembleRequestConfig({ + url, + method: verb, + data: body, + headers: signedHash, // TODO: Review headers injection + proxySettings, + }) + + this.requestWeight.authed += weight + + try { + + const { data } = await axios.create().request(requestConfig) + + return data + + } catch (error) { + + throw handleOkxRequestError({ error }) + + } + + } + +} + + + +// TODO: Review interface properties +interface ISignedHashParams { + verb: AlunaHttpVerbEnum + path: string + credentials: IAlunaCredentialsSchema + url: string + body?: any +} + +// TODO: Review interface properties +export interface IOkxSignedHeaders { + 'Api-Timestamp': number +} + + + +export const generateAuthHeader = ( + _params: ISignedHashParams, +): IOkxSignedHeaders => { + + // TODO: Implement method (and rename `_params` to `params`) + + // const { + // credentials, + // verb, + // body, + // url, + // } = params + + // const { + // key, + // secret, + // } = credentials + + const timestamp = Date.now() + + return { + 'Api-Timestamp': timestamp, + } + +} diff --git a/src/exchanges/okx/README.md b/src/exchanges/okx/README.md new file mode 100644 index 00000000..67c27047 --- /dev/null +++ b/src/exchanges/okx/README.md @@ -0,0 +1,47 @@ +# Sample + + - API vX: + - https://sample.com/api + +## Usage + +```ts +import { + aluna, + IAlunaCredentialsSchema, + IAlunaSettingsSchema, +} from 'alunajs' + + + +const settings: IAlunaSettingsSchema = { + // ... +} + +const credentials: IAlunaCredentialsSchema = { + key: 'xxx',, + secret: 'yyy', +} + +const sample = aluna('sample', { settings }) + +sample.symbol.list() +``` + +## Features + +| Functionality | Supported | +| -- | :-: | +| `offersOrderEditing` | ✅ | +| `offersPositionId` | ❌ | + +## Notes + +### API Rates + - ... + +### Orders + - ... + +### Positions + - ... diff --git a/src/exchanges/okx/enums/OkxMarketStatusEnum.ts b/src/exchanges/okx/enums/OkxMarketStatusEnum.ts new file mode 100644 index 00000000..2493da2b --- /dev/null +++ b/src/exchanges/okx/enums/OkxMarketStatusEnum.ts @@ -0,0 +1,4 @@ +export enum OkxMarketStatusEnum { + ONLINE = 'ONLINE', + OFFLINE = 'OFFLINE', +} diff --git a/src/exchanges/okx/enums/OkxOrderSideEnum.ts b/src/exchanges/okx/enums/OkxOrderSideEnum.ts new file mode 100644 index 00000000..e872efad --- /dev/null +++ b/src/exchanges/okx/enums/OkxOrderSideEnum.ts @@ -0,0 +1,4 @@ +export enum OkxOrderSideEnum { + BUY = 'BUY', + SELL = 'SELL' +} diff --git a/src/exchanges/okx/enums/OkxOrderStatusEnum.ts b/src/exchanges/okx/enums/OkxOrderStatusEnum.ts new file mode 100644 index 00000000..21e2fb5a --- /dev/null +++ b/src/exchanges/okx/enums/OkxOrderStatusEnum.ts @@ -0,0 +1,4 @@ +export enum OkxOrderStatusEnum { + OPEN = 'OPEN', + CLOSED = 'CLOSED' +} diff --git a/src/exchanges/okx/enums/OkxOrderTypeEnum.ts b/src/exchanges/okx/enums/OkxOrderTypeEnum.ts new file mode 100644 index 00000000..e2ed7d40 --- /dev/null +++ b/src/exchanges/okx/enums/OkxOrderTypeEnum.ts @@ -0,0 +1,6 @@ +export enum OkxOrderTypeEnum { + LIMIT = 'LIMIT', + MARKET = 'MARKET', + CEILING_LIMIT = 'CEILING_LIMIT', + CEILING_MARKET = 'CEILING_MARKET' +} diff --git a/src/exchanges/okx/enums/adapters/okxOrderSideAdapter.spec.ts b/src/exchanges/okx/enums/adapters/okxOrderSideAdapter.spec.ts new file mode 100644 index 00000000..eefdb008 --- /dev/null +++ b/src/exchanges/okx/enums/adapters/okxOrderSideAdapter.spec.ts @@ -0,0 +1,86 @@ +import { expect } from 'chai' + +import { AlunaError } from '../../../../lib/core/AlunaError' +import { AlunaOrderSideEnum } from '../../../../lib/enums/AlunaOrderSideEnum' +import { OkxOrderSideEnum } from '../OkxOrderSideEnum' +import { + translateOrderSideToAluna, + translateOrderSideToOkx, +} from './okxOrderSideAdapter' + + + +describe(__filename, () => { + + const notSupported = 'not-supported' + + + + it('should properly translate Okx order sides to Aluna order sides', () => { + + expect(translateOrderSideToAluna({ + from: OkxOrderSideEnum.BUY, + })).to.be.eq(AlunaOrderSideEnum.BUY) + + expect(translateOrderSideToAluna({ + from: OkxOrderSideEnum.SELL, + })).to.be.eq(AlunaOrderSideEnum.SELL) + + let result + let error + + try { + + result = translateOrderSideToAluna({ + from: notSupported as OkxOrderSideEnum, + }) + + } catch (err) { + + error = err + + } + + expect(result).not.to.be.ok + + expect(error instanceof AlunaError).to.be.ok + expect(error.message) + .to.be.eq(`Order side not supported: ${notSupported}`) + + }) + + + + it('should properly translate Aluna order sides to Okx order sides', () => { + + expect(translateOrderSideToOkx({ + from: AlunaOrderSideEnum.BUY, + })).to.be.eq(OkxOrderSideEnum.BUY) + + expect(translateOrderSideToOkx({ + from: AlunaOrderSideEnum.SELL, + })).to.be.eq(OkxOrderSideEnum.SELL) + + let result + let error + + try { + + result = translateOrderSideToOkx({ + from: notSupported as AlunaOrderSideEnum, + }) + + } catch (err) { + + error = err + + } + + expect(result).not.to.be.ok + expect(error instanceof AlunaError).to.be.ok + expect(error.message) + .to.be.eq(`Order side not supported: ${notSupported}`) + + }) + +}) diff --git a/src/exchanges/okx/enums/adapters/okxOrderSideAdapter.ts b/src/exchanges/okx/enums/adapters/okxOrderSideAdapter.ts new file mode 100644 index 00000000..cb22eb6f --- /dev/null +++ b/src/exchanges/okx/enums/adapters/okxOrderSideAdapter.ts @@ -0,0 +1,33 @@ +import { buildAdapter } from '../../../../lib/enums/adapters/buildAdapter' +import { AlunaOrderSideEnum } from '../../../../lib/enums/AlunaOrderSideEnum' +import { OkxOrderSideEnum } from '../OkxOrderSideEnum' + + + +const errorMessagePrefix = 'Order side' + + + +export const translateOrderSideToAluna = buildAdapter< + OkxOrderSideEnum, + AlunaOrderSideEnum +>({ + errorMessagePrefix, + mappings: { + [OkxOrderSideEnum.BUY]: AlunaOrderSideEnum.BUY, + [OkxOrderSideEnum.SELL]: AlunaOrderSideEnum.SELL, + }, +}) + + + +export const translateOrderSideToOkx = buildAdapter< + AlunaOrderSideEnum, + OkxOrderSideEnum +>({ + errorMessagePrefix, + mappings: { + [AlunaOrderSideEnum.BUY]: OkxOrderSideEnum.BUY, + [AlunaOrderSideEnum.SELL]: OkxOrderSideEnum.SELL, + }, +}) diff --git a/src/exchanges/okx/enums/adapters/okxOrderStatusAdapter.spec.ts b/src/exchanges/okx/enums/adapters/okxOrderStatusAdapter.spec.ts new file mode 100644 index 00000000..5c4feb54 --- /dev/null +++ b/src/exchanges/okx/enums/adapters/okxOrderStatusAdapter.spec.ts @@ -0,0 +1,93 @@ +import { expect } from 'chai' + +import { AlunaError } from '../../../../lib/core/AlunaError' +import { AlunaOrderStatusEnum } from '../../../../lib/enums/AlunaOrderStatusEnum' +import { OkxOrderStatusEnum } from '../OkxOrderStatusEnum' +import { + translateOrderStatusToAluna, + translateOrderStatusToOkx, +} from './okxOrderStatusAdapter' + + + +describe(__filename, () => { + + const notSupported = 'not-supported' + + + it('should translate Okx order status to Aluna order status', () => { + + const quantity = '5' + const zeroedfillQty = '0' + const partiallyFillQty = '3' + const totalFillQty = '5' + + expect(translateOrderStatusToAluna({ + fillQuantity: zeroedfillQty, + quantity, + from: OkxOrderStatusEnum.CLOSED, + })).to.be.eq(AlunaOrderStatusEnum.CANCELED) + + expect(translateOrderStatusToAluna({ + fillQuantity: partiallyFillQty, + quantity, + from: OkxOrderStatusEnum.CLOSED, + })).to.be.eq(AlunaOrderStatusEnum.PARTIALLY_FILLED) + + expect(translateOrderStatusToAluna({ + fillQuantity: totalFillQty, + quantity, + from: OkxOrderStatusEnum.CLOSED, + })).to.be.eq(AlunaOrderStatusEnum.FILLED) + + expect(translateOrderStatusToAluna({ + fillQuantity: totalFillQty, + quantity, + from: OkxOrderStatusEnum.OPEN, + })).to.be.eq(AlunaOrderStatusEnum.OPEN) + + }) + + + + it('should translate Aluna order status to Okx order status', () => { + + expect(translateOrderStatusToOkx({ + from: AlunaOrderStatusEnum.OPEN, + })).to.be.eq(OkxOrderStatusEnum.OPEN) + + expect(translateOrderStatusToOkx({ + from: AlunaOrderStatusEnum.PARTIALLY_FILLED, + })).to.be.eq(OkxOrderStatusEnum.OPEN) + + expect(translateOrderStatusToOkx({ + from: AlunaOrderStatusEnum.FILLED, + })).to.be.eq(OkxOrderStatusEnum.CLOSED) + + expect(translateOrderStatusToOkx({ + from: AlunaOrderStatusEnum.CANCELED, + })).to.be.eq(OkxOrderStatusEnum.CLOSED) + + let result + let error + + try { + + result = translateOrderStatusToOkx({ + from: notSupported as AlunaOrderStatusEnum, + }) + + } catch (err) { + + error = err + + } + + expect(result).not.to.be.ok + expect(error instanceof AlunaError).to.be.ok + expect(error.message) + .to.be.eq(`Order status not supported: ${notSupported}`) + + }) + +}) diff --git a/src/exchanges/okx/enums/adapters/okxOrderStatusAdapter.ts b/src/exchanges/okx/enums/adapters/okxOrderStatusAdapter.ts new file mode 100644 index 00000000..ef1b10dc --- /dev/null +++ b/src/exchanges/okx/enums/adapters/okxOrderStatusAdapter.ts @@ -0,0 +1,59 @@ +import { buildAdapter } from '../../../../lib/enums/adapters/buildAdapter' +import { AlunaOrderStatusEnum } from '../../../../lib/enums/AlunaOrderStatusEnum' +import { OkxOrderStatusEnum } from '../OkxOrderStatusEnum' + + + +const errorMessagePrefix = 'Order status' + +export const translateOrderStatusToAluna = ( + params: { + fillQuantity: string + quantity: string + from: OkxOrderStatusEnum + }, +): AlunaOrderStatusEnum => { + + const { fillQuantity, quantity, from } = params + + const isOpen = from === OkxOrderStatusEnum.OPEN + + if (isOpen) { + + return AlunaOrderStatusEnum.OPEN + + } + + const parsedFillQty = parseFloat(fillQuantity) + const parsedQty = parseFloat(quantity) + + if (parsedQty === parsedFillQty) { + + return AlunaOrderStatusEnum.FILLED + + } + + if (parsedFillQty > 0) { + + return AlunaOrderStatusEnum.PARTIALLY_FILLED + + } + + return AlunaOrderStatusEnum.CANCELED + +} + + + +export const translateOrderStatusToOkx = buildAdapter< + AlunaOrderStatusEnum, + OkxOrderStatusEnum +>({ + errorMessagePrefix, + mappings: { + [AlunaOrderStatusEnum.OPEN]: OkxOrderStatusEnum.OPEN, + [AlunaOrderStatusEnum.PARTIALLY_FILLED]: OkxOrderStatusEnum.OPEN, + [AlunaOrderStatusEnum.FILLED]: OkxOrderStatusEnum.CLOSED, + [AlunaOrderStatusEnum.CANCELED]: OkxOrderStatusEnum.CLOSED, + }, +}) diff --git a/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.spec.ts b/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.spec.ts new file mode 100644 index 00000000..d46349b5 --- /dev/null +++ b/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.spec.ts @@ -0,0 +1,104 @@ +import { expect } from 'chai' + +import { AlunaError } from '../../../../lib/core/AlunaError' +import { AlunaOrderTypesEnum } from '../../../../lib/enums/AlunaOrderTypesEnum' +import { OkxOrderTypeEnum } from '../OkxOrderTypeEnum' +import { + translateOrderTypeToAluna, + translateOrderTypeToOkx, +} from './okxOrderTypeAdapter' + + + +describe(__filename, () => { + + const notSupported = 'not-supported' + + + + it('should properly translate Okx order types to Aluna order types', () => { + + expect(translateOrderTypeToAluna({ + from: OkxOrderTypeEnum.LIMIT, + })).to.be.eq(AlunaOrderTypesEnum.LIMIT) + + expect(translateOrderTypeToAluna({ + from: OkxOrderTypeEnum.MARKET, + })).to.be.eq(AlunaOrderTypesEnum.MARKET) + + expect(translateOrderTypeToAluna({ + from: OkxOrderTypeEnum.CEILING_LIMIT, + })).to.be.eq(AlunaOrderTypesEnum.LIMIT_ORDER_BOOK) + + expect(translateOrderTypeToAluna({ + from: OkxOrderTypeEnum.CEILING_MARKET, + })).to.be.eq(AlunaOrderTypesEnum.TAKE_PROFIT_MARKET) + + let result + let error + + try { + + result = translateOrderTypeToAluna({ + from: notSupported as OkxOrderTypeEnum, + }) + + } catch (err) { + + error = err + + } + + expect(result).not.to.be.ok + + expect(error instanceof AlunaError).to.be.ok + expect(error.message) + .to.be.eq(`Order type not supported: ${notSupported}`) + + + }) + + + + it('should properly translate Aluna order types to Okx order types', () => { + + expect(translateOrderTypeToOkx({ + from: AlunaOrderTypesEnum.LIMIT, + })).to.be.eq(OkxOrderTypeEnum.LIMIT) + + expect(translateOrderTypeToOkx({ + from: AlunaOrderTypesEnum.MARKET, + })).to.be.eq(OkxOrderTypeEnum.MARKET) + + expect(translateOrderTypeToOkx({ + from: AlunaOrderTypesEnum.LIMIT_ORDER_BOOK, + })).to.be.eq(OkxOrderTypeEnum.CEILING_LIMIT) + + expect(translateOrderTypeToOkx({ + from: AlunaOrderTypesEnum.TAKE_PROFIT_MARKET, + })).to.be.eq(OkxOrderTypeEnum.CEILING_MARKET) + + let result + let error + + try { + + translateOrderTypeToOkx({ + from: notSupported as AlunaOrderTypesEnum, + }) + + } catch (err) { + + error = err + + } + + + expect(result).not.to.be.ok + expect(error instanceof AlunaError).to.be.ok + expect(error.message) + .to.be.eq(`Order type not supported: ${notSupported}`) + + }) + +}) diff --git a/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.ts b/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.ts new file mode 100644 index 00000000..73b0217f --- /dev/null +++ b/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.ts @@ -0,0 +1,37 @@ +import { buildAdapter } from '../../../../lib/enums/adapters/buildAdapter' +import { AlunaOrderTypesEnum } from '../../../../lib/enums/AlunaOrderTypesEnum' +import { OkxOrderTypeEnum } from '../OkxOrderTypeEnum' + + + +const errorMessagePrefix = 'Order type' + + + +export const translateOrderTypeToAluna = buildAdapter< + OkxOrderTypeEnum, + AlunaOrderTypesEnum +>({ + errorMessagePrefix, + mappings: { + [OkxOrderTypeEnum.LIMIT]: AlunaOrderTypesEnum.LIMIT, + [OkxOrderTypeEnum.MARKET]: AlunaOrderTypesEnum.MARKET, + [OkxOrderTypeEnum.CEILING_LIMIT]: AlunaOrderTypesEnum.LIMIT_ORDER_BOOK, + [OkxOrderTypeEnum.CEILING_MARKET]: AlunaOrderTypesEnum.TAKE_PROFIT_MARKET, + }, +}) + + + +export const translateOrderTypeToOkx = buildAdapter< + AlunaOrderTypesEnum, + OkxOrderTypeEnum +>({ + errorMessagePrefix, + mappings: { + [AlunaOrderTypesEnum.LIMIT]: OkxOrderTypeEnum.LIMIT, + [AlunaOrderTypesEnum.MARKET]: OkxOrderTypeEnum.MARKET, + [AlunaOrderTypesEnum.LIMIT_ORDER_BOOK]: OkxOrderTypeEnum.CEILING_LIMIT, + [AlunaOrderTypesEnum.TAKE_PROFIT_MARKET]: OkxOrderTypeEnum.CEILING_MARKET, + }, +}) diff --git a/src/exchanges/okx/errors/handleOkxRequestError.spec.ts b/src/exchanges/okx/errors/handleOkxRequestError.spec.ts new file mode 100644 index 00000000..9d41885a --- /dev/null +++ b/src/exchanges/okx/errors/handleOkxRequestError.spec.ts @@ -0,0 +1,169 @@ +import { AxiosError } from 'axios' +import { expect } from 'chai' +import { ImportMock } from 'ts-mock-imports' + +import { AlunaHttpErrorCodes } from '../../../lib/errors/AlunaHttpErrorCodes' +import { AlunaKeyErrorCodes } from '../../../lib/errors/AlunaKeyErrorCodes' +import * as handleOkxMod from './handleOkxRequestError' + + + +describe(__filename, () => { + + const { + isOkxKeyInvalid, + handleOkxRequestError, + } = handleOkxMod + + const requestMessage = 'Error while executing request.' + + const mockDeps = ( + params: { + isInvalid: boolean + } = { + isInvalid: false, + }, + ) => { + + const { + isInvalid, + } = params + + const isOkxKeyInvalidMock = ImportMock.mockFunction( + handleOkxMod, + 'isOkxKeyInvalid', + isInvalid, + ) + + return { + isOkxKeyInvalidMock, + } + + } + + it('should return Okx key invalid error when applicable', async () => { + + const { isOkxKeyInvalidMock } = mockDeps({ isInvalid: true }) + + const dummyError = 'Key is invalid' + + const axiosError1 = { + isAxiosError: true, + response: { + status: 400, + data: { + exchangeErroMsg: dummyError, + }, + }, + } as AxiosError + + const alunaError = handleOkxRequestError({ error: axiosError1 }) + + expect(isOkxKeyInvalidMock.callCount).to.be.eq(1) + + expect(alunaError).to.deep.eq({ + code: AlunaKeyErrorCodes.INVALID, + message: dummyError, + httpStatusCode: axiosError1.response?.status, + metadata: axiosError1.response?.data, + }) + + expect(isOkxKeyInvalidMock.callCount).to.be.eq(1) + expect(isOkxKeyInvalidMock.args[0][0]).to.be.eq(dummyError) + + }) + + it('should ensure Okx request error is being handle', async () => { + + const dummyError = 'dummy-error' + + const axiosError1 = { + isAxiosError: true, + response: { + status: 400, + data: { + exchangeErroMsg: dummyError, + }, + }, + } as AxiosError + + let alunaError = handleOkxRequestError({ error: axiosError1 }) + + expect(alunaError).to.deep.eq({ + code: AlunaHttpErrorCodes.REQUEST_ERROR, + message: dummyError, + httpStatusCode: axiosError1.response?.status, + metadata: axiosError1.response?.data, + }) + + + const axiosError2 = { + isAxiosError: true, + response: { + status: 500, + data: { + }, + }, + } as AxiosError + + alunaError = handleOkxRequestError({ error: axiosError2 }) + + expect(alunaError).to.deep.eq({ + code: AlunaHttpErrorCodes.REQUEST_ERROR, + message: requestMessage, + httpStatusCode: axiosError2.response?.status, + metadata: axiosError2.response?.data, + }) + + + const axiosError3 = { + isAxiosError: true, + } as AxiosError + + alunaError = handleOkxRequestError({ error: axiosError3 }) + + expect(alunaError).to.deep.eq({ + code: AlunaHttpErrorCodes.REQUEST_ERROR, + message: requestMessage, + httpStatusCode: 500, + metadata: axiosError3, + }) + + const error = { + message: dummyError, + } as Error + + alunaError = handleOkxRequestError({ error }) + + expect(alunaError).to.deep.eq({ + code: AlunaHttpErrorCodes.REQUEST_ERROR, + message: error.message, + httpStatusCode: 500, + metadata: error, + }) + + + const unknown = {} as any + + alunaError = handleOkxRequestError({ error: unknown }) + + expect(alunaError).to.deep.eq({ + code: AlunaHttpErrorCodes.REQUEST_ERROR, + message: requestMessage, + httpStatusCode: 500, + metadata: unknown, + }) + + }) + + it( + 'should ensure Okx invalid api patterns work as expected', + async () => { + + const message = 'api-invalid' + expect(isOkxKeyInvalid(message)).to.be.ok + + }, + ) + +}) diff --git a/src/exchanges/okx/errors/handleOkxRequestError.ts b/src/exchanges/okx/errors/handleOkxRequestError.ts new file mode 100644 index 00000000..7d6ec8a2 --- /dev/null +++ b/src/exchanges/okx/errors/handleOkxRequestError.ts @@ -0,0 +1,84 @@ +import { AxiosError } from 'axios' +import { some } from 'lodash' + +import { AlunaError } from '../../../lib/core/AlunaError' +import { AlunaHttpErrorCodes } from '../../../lib/errors/AlunaHttpErrorCodes' +import { AlunaKeyErrorCodes } from '../../../lib/errors/AlunaKeyErrorCodes' + + + +export const okxInvalidKeyPatterns: Array = [ + // TODO: Review exchange invalid api key error patterns + /api-invalid/mi, +] + + + +export const okxDownErrorPatterns: Array = [ + // Add okx exchange down errors +] + + +export const isOkxKeyInvalid = (errorMessage: string) => { + + return some(okxInvalidKeyPatterns, (pattern) => { + + return pattern.test(errorMessage) + + }) + +} + + +export interface IHandleOkxRequestErrorsParams { + error: AxiosError | Error +} + + + +export const handleOkxRequestError = ( + params: IHandleOkxRequestErrorsParams, +): AlunaError => { + + const { error } = params + + let metadata: any = error + + let code = AlunaHttpErrorCodes.REQUEST_ERROR + let message = 'Error while executing request.' + let httpStatusCode = 500 + + if ((error as AxiosError).isAxiosError) { + + const { response } = error as AxiosError + + // TODO: Review property `exchangeErroMsg` on request response + message = response?.data?.exchangeErroMsg || message + + httpStatusCode = response?.status || httpStatusCode + + metadata = response?.data || metadata + + } else { + + message = error.message || message + + } + + + if (isOkxKeyInvalid(message)) { + + code = AlunaKeyErrorCodes.INVALID + + } + + const alunaError = new AlunaError({ + metadata, + message, + httpStatusCode, + code, + }) + + return alunaError + +} diff --git a/src/exchanges/okx/index.ts b/src/exchanges/okx/index.ts new file mode 100644 index 00000000..c7622f29 --- /dev/null +++ b/src/exchanges/okx/index.ts @@ -0,0 +1,9 @@ +import { Okx } from './Okx' +import { OkxAuthed } from './OkxAuthed' + + + +export const okx = { + PublicClass: Okx, + AuthedClass: OkxAuthed, +} diff --git a/src/exchanges/okx/modules/authed/balance.ts b/src/exchanges/okx/modules/authed/balance.ts new file mode 100644 index 00000000..3c8fa492 --- /dev/null +++ b/src/exchanges/okx/modules/authed/balance.ts @@ -0,0 +1,19 @@ +import { IAlunaExchangeAuthed } from '../../../../lib/core/IAlunaExchange' +import { IAlunaBalanceModule } from '../../../../lib/modules/authed/IAlunaBalanceModule' +import { list } from './balance/list' +import { listRaw } from './balance/listRaw' +import { parse } from './balance/parse' +import { parseMany } from './balance/parseMany' + + + +export function balance(exchange: IAlunaExchangeAuthed): IAlunaBalanceModule { + + return { + listRaw: listRaw(exchange), + list: list(exchange), + parseMany: parseMany(exchange), + parse: parse(exchange), + } + +} diff --git a/src/exchanges/okx/modules/authed/balance/list.spec.ts b/src/exchanges/okx/modules/authed/balance/list.spec.ts new file mode 100644 index 00000000..589b37b2 --- /dev/null +++ b/src/exchanges/okx/modules/authed/balance/list.spec.ts @@ -0,0 +1,22 @@ +import { PARSED_BALANCES } from '../../../../../../test/fixtures/parsedBalances' +import { testList } from '../../../../../../test/macros/testList' +import { OkxAuthed } from '../../../OkxAuthed' +import { OKX_RAW_BALANCES } from '../../../test/fixtures/okxBalances' +import * as listRawMod from './listRaw' +import * as parseManyMod from './parseMany' + + + +describe(__filename, () => { + + testList({ + AuthedClass: OkxAuthed, + exchangeId: 'okx', + methodModuleName: 'balance', + listModule: listRawMod, + parseManyModule: parseManyMod, + rawList: { rawBalances: OKX_RAW_BALANCES }, + parsedList: { balances: PARSED_BALANCES }, + }) + +}) diff --git a/src/exchanges/okx/modules/authed/balance/list.ts b/src/exchanges/okx/modules/authed/balance/list.ts new file mode 100644 index 00000000..46cbf3a4 --- /dev/null +++ b/src/exchanges/okx/modules/authed/balance/list.ts @@ -0,0 +1,35 @@ +import { debug } from 'debug' + +import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { + IAlunaBalanceListParams, + IAlunaBalanceListReturns, +} from '../../../../../lib/modules/authed/IAlunaBalanceModule' +import { OkxHttp } from '../../../OkxHttp' + + + +const log = debug('alunajs:okx/balance/list') + + + +export const list = (exchange: IAlunaExchangeAuthed) => async ( + params: IAlunaBalanceListParams = {}, +): Promise => { + + log('listing balances', params) + + const { http = new OkxHttp(exchange.settings) } = params + + const { rawBalances } = await exchange.balance.listRaw({ http }) + + const { balances } = exchange.balance.parseMany({ rawBalances }) + + const { requestWeight } = http + + return { + balances, + requestWeight, + } + +} diff --git a/src/exchanges/okx/modules/authed/balance/listRaw.spec.ts b/src/exchanges/okx/modules/authed/balance/listRaw.spec.ts new file mode 100644 index 00000000..ce36df0c --- /dev/null +++ b/src/exchanges/okx/modules/authed/balance/listRaw.spec.ts @@ -0,0 +1,56 @@ +import { expect } from 'chai' + +import { mockHttp } from '../../../../../../test/mocks/exchange/Http' +import { AlunaHttpVerbEnum } from '../../../../../lib/enums/AlunaHtttpVerbEnum' +import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' +import { OkxAuthed } from '../../../OkxAuthed' +import { OkxHttp } from '../../../OkxHttp' +import { getOkxEndpoints } from '../../../okxSpecs' +import { OKX_RAW_BALANCES } from '../../../test/fixtures/okxBalances' + + + +describe(__filename, () => { + + const credentials: IAlunaCredentialsSchema = { + key: 'key', + secret: 'secret', + } + + it('should list Okx raw balances just fine', async () => { + + // preparing data + const mockedBalances = OKX_RAW_BALANCES + + + // mocking + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + authedRequest.returns(Promise.resolve(mockedBalances)) + + + // executing + const exchange = new OkxAuthed({ credentials }) + + const { rawBalances } = await exchange.balance.listRaw() + + + // validating + expect(rawBalances).to.deep.eq(mockedBalances) + + expect(authedRequest.callCount).to.be.eq(1) + + expect(authedRequest.firstCall.args[0]).to.deep.eq({ + verb: AlunaHttpVerbEnum.GET, + url: getOkxEndpoints(exchange.settings).balance.list, + credentials, + }) + + expect(publicRequest.callCount).to.be.eq(0) + + }) + +}) diff --git a/src/exchanges/okx/modules/authed/balance/listRaw.ts b/src/exchanges/okx/modules/authed/balance/listRaw.ts new file mode 100644 index 00000000..ddd57da5 --- /dev/null +++ b/src/exchanges/okx/modules/authed/balance/listRaw.ts @@ -0,0 +1,46 @@ +import { debug } from 'debug' + +import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { AlunaHttpVerbEnum } from '../../../../../lib/enums/AlunaHtttpVerbEnum' +import { + IAlunaBalanceListParams, + IAlunaBalanceListRawReturns, +} from '../../../../../lib/modules/authed/IAlunaBalanceModule' +import { OkxHttp } from '../../../OkxHttp' +import { getOkxEndpoints } from '../../../okxSpecs' +import { IOkxBalanceSchema } from '../../../schemas/IOkxBalanceSchema' + + + +const log = debug('alunajs:okx/balance/listRaw') + + + +export const listRaw = (exchange: IAlunaExchangeAuthed) => async ( + params: IAlunaBalanceListParams = {}, +): Promise> => { + + log('listing raw balances', params) + + const { + settings, + credentials, + } = exchange + + const { http = new OkxHttp(settings) } = params + + // TODO: Implement balance 'listRaw' + const rawBalances = await http.authedRequest({ + verb: AlunaHttpVerbEnum.GET, + url: getOkxEndpoints(settings).balance.list, + credentials, + }) + + const { requestWeight } = http + + return { + rawBalances, + requestWeight, + } + +} diff --git a/src/exchanges/okx/modules/authed/balance/parse.spec.ts b/src/exchanges/okx/modules/authed/balance/parse.spec.ts new file mode 100644 index 00000000..2661ed90 --- /dev/null +++ b/src/exchanges/okx/modules/authed/balance/parse.spec.ts @@ -0,0 +1,41 @@ +import { expect } from 'chai' + +import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' +import { mockTranslateSymbolId } from '../../../../../utils/mappings/translateSymbolId.mock' +import { OkxAuthed } from '../../../OkxAuthed' +import { OKX_RAW_BALANCES } from '../../../test/fixtures/okxBalances' + + + +describe.skip(__filename, () => { + + const credentials: IAlunaCredentialsSchema = { + key: 'key', + secret: 'secret', + } + + it('should parse a Okx raw balance just fine', async () => { + + // preparing data + const rawBalance = OKX_RAW_BALANCES[0] + + + // mocking + const { translateSymbolId } = mockTranslateSymbolId() + translateSymbolId.returns(rawBalance.currencySymbol) + + // executing + const exchange = new OkxAuthed({ credentials }) + + const { balance } = exchange.balance.parse({ rawBalance }) + + + // validating + expect(balance).to.exist + + // TODO: add expectations for everything + // expect(balance).to.deep.eq(...) + + }) + +}) diff --git a/src/exchanges/okx/modules/authed/balance/parse.ts b/src/exchanges/okx/modules/authed/balance/parse.ts new file mode 100644 index 00000000..57eb7092 --- /dev/null +++ b/src/exchanges/okx/modules/authed/balance/parse.ts @@ -0,0 +1,21 @@ +import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { + IAlunaBalanceParseParams, + IAlunaBalanceParseReturns, +} from '../../../../../lib/modules/authed/IAlunaBalanceModule' +import { IAlunaBalanceSchema } from '../../../../../lib/schemas/IAlunaBalanceSchema' +import { IOkxBalanceSchema } from '../../../schemas/IOkxBalanceSchema' + + + +export const parse = (exchange: IAlunaExchangeAuthed) => ( + params: IAlunaBalanceParseParams, +): IAlunaBalanceParseReturns => { + + + // TODO: Implement balance parse + const balance: IAlunaBalanceSchema = params as any + + return { balance } + +} diff --git a/src/exchanges/okx/modules/authed/balance/parseMany.spec.ts b/src/exchanges/okx/modules/authed/balance/parseMany.spec.ts new file mode 100644 index 00000000..618dfed9 --- /dev/null +++ b/src/exchanges/okx/modules/authed/balance/parseMany.spec.ts @@ -0,0 +1,45 @@ +import { expect } from 'chai' +import { each } from 'lodash' + +import { PARSED_BALANCES } from '../../../../../../test/fixtures/parsedBalances' +import { mockParse } from '../../../../../../test/mocks/exchange/modules/mockParse' +import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' +import { OkxAuthed } from '../../../OkxAuthed' +import { OKX_RAW_BALANCES } from '../../../test/fixtures/okxBalances' +import * as parseMod from './parse' + + + +describe(__filename, () => { + + const credentials: IAlunaCredentialsSchema = { + key: 'key', + secret: 'secret', + } + + it('should parse many Okx raw balances just fine', async () => { + + // preparing data + const rawBalances = OKX_RAW_BALANCES + + + // mocking + const { parse } = mockParse({ module: parseMod }) + + each(PARSED_BALANCES, (balance, i) => { + parse.onCall(i).returns({ balance }) + }) + + + // executing + const exchange = new OkxAuthed({ credentials }) + + const { balances } = exchange.balance.parseMany({ rawBalances }) + + + // validating + expect(balances).to.deep.eq(PARSED_BALANCES) + + }) + +}) diff --git a/src/exchanges/okx/modules/authed/balance/parseMany.ts b/src/exchanges/okx/modules/authed/balance/parseMany.ts new file mode 100644 index 00000000..4e5e8544 --- /dev/null +++ b/src/exchanges/okx/modules/authed/balance/parseMany.ts @@ -0,0 +1,36 @@ +import { debug } from 'debug' +import { map } from 'lodash' + +import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { + IAlunaBalanceParseManyParams, + IAlunaBalanceParseManyReturns, +} from '../../../../../lib/modules/authed/IAlunaBalanceModule' +import { IOkxBalanceSchema } from '../../../schemas/IOkxBalanceSchema' + + + +const log = debug('alunajs:okx/balance/parseMany') + + + +export const parseMany = (exchange: IAlunaExchangeAuthed) => ( + params: IAlunaBalanceParseManyParams, +): IAlunaBalanceParseManyReturns => { + + const { rawBalances } = params + + // TODO: Review map implementation + const parsedBalances = map(rawBalances, (rawBalance) => { + + const { balance } = exchange.balance.parse({ rawBalance }) + + return balance + + }) + + log(`parsed ${parsedBalances.length} balances`) + + return { balances: parsedBalances } + +} diff --git a/src/exchanges/okx/modules/authed/key.ts b/src/exchanges/okx/modules/authed/key.ts new file mode 100644 index 00000000..1af0fcef --- /dev/null +++ b/src/exchanges/okx/modules/authed/key.ts @@ -0,0 +1,17 @@ +import { IAlunaExchangeAuthed } from '../../../../lib/core/IAlunaExchange' +import { IAlunaKeyModule } from '../../../../lib/modules/authed/IAlunaKeyModule' +import { fetchDetails } from './key/fetchDetails' +import { parseDetails } from './key/parseDetails' +import { parsePermissions } from './key/parsePermissions' + + + +export function key(exchange: IAlunaExchangeAuthed): IAlunaKeyModule { + + return { + fetchDetails: fetchDetails(exchange), + parseDetails: parseDetails(exchange), + parsePermissions: parsePermissions(exchange), + } + +} diff --git a/src/exchanges/okx/modules/authed/key/fetchDetails.spec.ts b/src/exchanges/okx/modules/authed/key/fetchDetails.spec.ts new file mode 100644 index 00000000..1d857063 --- /dev/null +++ b/src/exchanges/okx/modules/authed/key/fetchDetails.spec.ts @@ -0,0 +1,81 @@ +import { expect } from 'chai' + +import { mockHttp } from '../../../../../../test/mocks/exchange/Http' +import { mockParseDetails } from '../../../../../../test/mocks/exchange/modules/key/mockParseDetails' +import { AlunaHttpVerbEnum } from '../../../../../lib/enums/AlunaHtttpVerbEnum' +import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' +import { IAlunaKeySchema } from '../../../../../lib/schemas/IAlunaKeySchema' +import { OkxAuthed } from '../../../OkxAuthed' +import { OkxHttp } from '../../../OkxHttp' +import { getOkxEndpoints } from '../../../okxSpecs' +import { OKX_KEY_PERMISSIONS } from '../../../test/fixtures/okxKey' +import * as parseDetailsMod from './parseDetails' + + + +describe(__filename, () => { + + it('should fetch Okx key details just fine', async () => { + + // preparing data + const credentials: IAlunaCredentialsSchema = { + key: 'key', + secret: 'secret', + } + + const accountId = 'accountId' + + const parsedKey: IAlunaKeySchema = { + accountId, + permissions: OKX_KEY_PERMISSIONS, + meta: {}, + } + + // mocking + const http = new OkxHttp({}) + + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + authedRequest.returns(Promise.resolve(OKX_KEY_PERMISSIONS)) + + const { parseDetails } = mockParseDetails({ + module: parseDetailsMod, + }) + + parseDetails.returns({ key: parsedKey }) + + + // executing + const exchange = new OkxAuthed({ settings: {}, credentials }) + + const { + key, + requestWeight, + } = await exchange.key.fetchDetails() + + + // validating + expect(key).to.be.eq(key) + + expect(requestWeight).to.deep.eq(http.requestWeight) + + expect(authedRequest.callCount).to.be.eq(1) + expect(authedRequest.firstCall.args[0]).to.deep.eq({ + verb: AlunaHttpVerbEnum.GET, + url: getOkxEndpoints(exchange.settings).key.fetchDetails, + credentials, + }) + + + expect(publicRequest.callCount).to.be.eq(0) + + expect(parseDetails.firstCall.args[0]).to.deep.eq({ + rawKey: OKX_KEY_PERMISSIONS, + }) + + }) + +}) diff --git a/src/exchanges/okx/modules/authed/key/fetchDetails.ts b/src/exchanges/okx/modules/authed/key/fetchDetails.ts new file mode 100644 index 00000000..68976351 --- /dev/null +++ b/src/exchanges/okx/modules/authed/key/fetchDetails.ts @@ -0,0 +1,48 @@ +import { debug } from 'debug' + +import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { AlunaHttpVerbEnum } from '../../../../../lib/enums/AlunaHtttpVerbEnum' +import { + IAlunaKeyFetchDetailsParams, + IAlunaKeyFetchDetailsReturns, +} from '../../../../../lib/modules/authed/IAlunaKeyModule' +import { OkxHttp } from '../../../OkxHttp' +import { getOkxEndpoints } from '../../../okxSpecs' +import { IOkxKeySchema } from '../../../schemas/IOkxKeySchema' + + + +const log = debug('alunajs:okx/key/fetchDetails') + + + +export const fetchDetails = (exchange: IAlunaExchangeAuthed) => async ( + params: IAlunaKeyFetchDetailsParams = {}, +): Promise => { + + log('fetching Okx key permissions') + + const { + settings, + credentials, + } = exchange + + const { http = new OkxHttp(settings) } = params + + // TODO: Implement proper request + const rawKey = await http.authedRequest({ + verb: AlunaHttpVerbEnum.GET, + url: getOkxEndpoints(settings).key.fetchDetails, + credentials, + }) + + const { key } = exchange.key.parseDetails({ rawKey }) + + const { requestWeight } = http + + return { + key, + requestWeight, + } + +} diff --git a/src/exchanges/okx/modules/authed/key/parseDetails.spec.ts b/src/exchanges/okx/modules/authed/key/parseDetails.spec.ts new file mode 100644 index 00000000..da9cbdce --- /dev/null +++ b/src/exchanges/okx/modules/authed/key/parseDetails.spec.ts @@ -0,0 +1,68 @@ +import { expect } from 'chai' +import { omit } from 'lodash' + +import { mockParsePermissions } from '../../../../../../test/mocks/exchange/modules/key/mockParsePermissions' +import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' +import { IAlunaKeyPermissionSchema } from '../../../../../lib/schemas/IAlunaKeySchema' +import { OkxAuthed } from '../../../OkxAuthed' +import { IOkxKeySchema } from '../../../schemas/IOkxKeySchema' +import * as mockParsePermissionsMod from './parsePermissions' + + + +describe(__filename, () => { + + it('should parse Okx key details just fine', async () => { + + // preparing data + const credentials: IAlunaCredentialsSchema = { + key: 'key', + secret: 'secret', + passphrase: 'passphrase', + } + + const accountId = 'accountId' + + const rawKey: IOkxKeySchema = { + read: false, + trade: false, + withdraw: false, + accountId, + } + + const rawKeyWithoutAccId = omit(rawKey, 'accountId') + + const permissions: IAlunaKeyPermissionSchema = { + ...rawKeyWithoutAccId, + } + + + // mocking + const { parsePermissions } = mockParsePermissions({ + module: mockParsePermissionsMod, + }) + + parsePermissions.returns({ permissions }) + + + // executing + const exchange = new OkxAuthed({ credentials }) + + const { key } = exchange.key.parseDetails({ rawKey }) + + + // validating + expect(key).to.deep.eq({ + accountId, + permissions, + meta: rawKey, + }) + + expect(parsePermissions.callCount).to.be.eq(1) + expect(parsePermissions.firstCall.args[0]).to.deep.eq({ + rawKey, + }) + + }) + +}) diff --git a/src/exchanges/okx/modules/authed/key/parseDetails.ts b/src/exchanges/okx/modules/authed/key/parseDetails.ts new file mode 100644 index 00000000..1491307c --- /dev/null +++ b/src/exchanges/okx/modules/authed/key/parseDetails.ts @@ -0,0 +1,37 @@ +import { debug } from 'debug' + +import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { + IAlunaKeyParseDetailsParams, + IAlunaKeyParseDetailsReturns, +} from '../../../../../lib/modules/authed/IAlunaKeyModule' +import { IAlunaKeySchema } from '../../../../../lib/schemas/IAlunaKeySchema' +import { IOkxKeySchema } from '../../../schemas/IOkxKeySchema' + + + +const log = debug('alunajs:okx/key/parseDetails') + + + +export const parseDetails = (exchange: IAlunaExchangeAuthed) => ( + params: IAlunaKeyParseDetailsParams, +): IAlunaKeyParseDetailsReturns => { + + log('parsing Okx key details') + + const { rawKey } = params + + const { accountId } = rawKey + + const { permissions } = exchange.key.parsePermissions({ rawKey }) + + const key: IAlunaKeySchema = { + accountId, + permissions, + meta: rawKey, + } + + return { key } + +} diff --git a/src/exchanges/okx/modules/authed/key/parsePermissions.spec.ts b/src/exchanges/okx/modules/authed/key/parsePermissions.spec.ts new file mode 100644 index 00000000..a9229ec8 --- /dev/null +++ b/src/exchanges/okx/modules/authed/key/parsePermissions.spec.ts @@ -0,0 +1,42 @@ +import { expect } from 'chai' +import { omit } from 'lodash' + +import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' +import { OkxAuthed } from '../../../OkxAuthed' +import { IOkxKeySchema } from '../../../schemas/IOkxKeySchema' + + + +describe(__filename, () => { + + it('should parse Okx key permissions just fine', async () => { + + // preparing data + const credentials: IAlunaCredentialsSchema = { + key: 'key', + secret: 'secret', + passphrase: 'passphrase', + } + + const accountId = 'accountId' + + const rawKey: IOkxKeySchema = { + accountId, + read: false, + trade: false, + withdraw: false, + } + + + // executing + const exchange = new OkxAuthed({ credentials }) + + const { permissions } = exchange.key.parsePermissions({ rawKey }) + + + // validating + expect(permissions).to.deep.eq(omit(rawKey, 'accountId')) + + }) + +}) diff --git a/src/exchanges/okx/modules/authed/key/parsePermissions.ts b/src/exchanges/okx/modules/authed/key/parsePermissions.ts new file mode 100644 index 00000000..325d7724 --- /dev/null +++ b/src/exchanges/okx/modules/authed/key/parsePermissions.ts @@ -0,0 +1,33 @@ +import { debug } from 'debug' + +import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { + IAlunaKeyParsePermissionsParams, + IAlunaKeyParsePermissionsReturns, +} from '../../../../../lib/modules/authed/IAlunaKeyModule' +import { IAlunaKeyPermissionSchema } from '../../../../../lib/schemas/IAlunaKeySchema' +import { IOkxKeySchema } from '../../../schemas/IOkxKeySchema' + + + +const log = debug('alunajs:okx/key/parsePermissions') + + + +export const parsePermissions = (exchange: IAlunaExchangeAuthed) => ( + params: IAlunaKeyParsePermissionsParams, +): IAlunaKeyParsePermissionsReturns => { + + log('parsing Okx key permissions', params) + + const { rawKey } = params + + const permissions: IAlunaKeyPermissionSchema = { + read: rawKey.read, + trade: rawKey.trade, + withdraw: rawKey.withdraw, + } + + return { permissions } + +} diff --git a/src/exchanges/okx/modules/authed/order.ts b/src/exchanges/okx/modules/authed/order.ts new file mode 100644 index 00000000..505ec501 --- /dev/null +++ b/src/exchanges/okx/modules/authed/order.ts @@ -0,0 +1,29 @@ +import { IAlunaExchangeAuthed } from '../../../../lib/core/IAlunaExchange' +import { IAlunaOrderWriteModule } from '../../../../lib/modules/authed/IAlunaOrderModule' +import { cancel } from './order/cancel' +import { edit } from './order/edit' +import { get } from './order/get' +import { getRaw } from './order/getRaw' +import { list } from './order/list' +import { listRaw } from './order/listRaw' +import { parse } from './order/parse' +import { parseMany } from './order/parseMany' +import { place } from './order/place' + + + +export function order(exchange: IAlunaExchangeAuthed): IAlunaOrderWriteModule { + + return { + cancel: cancel(exchange), + edit: edit(exchange), + get: get(exchange), + getRaw: getRaw(exchange), + list: list(exchange), + listRaw: listRaw(exchange), + parse: parse(exchange), + parseMany: parseMany(exchange), + place: place(exchange), + } + +} diff --git a/src/exchanges/okx/modules/authed/order/cancel.spec.ts b/src/exchanges/okx/modules/authed/order/cancel.spec.ts new file mode 100644 index 00000000..b0b43bc3 --- /dev/null +++ b/src/exchanges/okx/modules/authed/order/cancel.spec.ts @@ -0,0 +1,119 @@ +import { expect } from 'chai' + +import { PARSED_ORDERS } from '../../../../../../test/fixtures/parsedOrders' +import { mockHttp } from '../../../../../../test/mocks/exchange/Http' +import { mockParse } from '../../../../../../test/mocks/exchange/modules/mockParse' +import { AlunaError } from '../../../../../lib/core/AlunaError' +import { AlunaHttpVerbEnum } from '../../../../../lib/enums/AlunaHtttpVerbEnum' +import { AlunaOrderErrorCodes } from '../../../../../lib/errors/AlunaOrderErrorCodes' +import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' +import { executeAndCatch } from '../../../../../utils/executeAndCatch' +import { OkxAuthed } from '../../../OkxAuthed' +import { OkxHttp } from '../../../OkxHttp' +import { getOkxEndpoints } from '../../../okxSpecs' +import { OKX_RAW_ORDERS } from '../../../test/fixtures/okxOrders' +import * as parseMod from './parse' + + + +describe(__filename, () => { + + const credentials: IAlunaCredentialsSchema = { + key: 'key', + secret: 'secret', + } + + it('should cancel a Okx order just fine', async () => { + + // preparing data + const mockedRawOrder = OKX_RAW_ORDERS[0] + const mockedParsedOrder = PARSED_ORDERS[0] + + const { id } = mockedRawOrder + + + // mocking + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + const { parse } = mockParse({ module: parseMod }) + + parse.returns({ order: mockedParsedOrder }) + + authedRequest.returns(Promise.resolve(mockedRawOrder)) + + + // executing + const exchange = new OkxAuthed({ credentials }) + + const { order } = await exchange.order.cancel({ + id, + symbolPair: '', + }) + + + // validating + expect(order).to.deep.eq(mockedParsedOrder) + + expect(authedRequest.callCount).to.be.eq(1) + + expect(authedRequest.firstCall.args[0]).to.deep.eq({ + verb: AlunaHttpVerbEnum.DELETE, + credentials, + url: getOkxEndpoints(exchange.settings).order.cancel(id), + }) + + expect(publicRequest.callCount).to.be.eq(0) + + }) + + it('should throw an error when canceling a Okx order', async () => { + + // preparing data + const id = 'id' + + // mocking + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + const error = new AlunaError({ + code: AlunaOrderErrorCodes.CANCEL_FAILED, + message: 'Something went wrong, order not canceled', + httpStatusCode: 401, + metadata: {}, + }) + + authedRequest.returns(Promise.reject(error)) + + + // executing + const exchange = new OkxAuthed({ credentials }) + + const { error: responseError } = await executeAndCatch( + () => exchange.order.cancel({ + id, + symbolPair: 'symbolPair', + }), + ) + + + // validating + expect(responseError).to.deep.eq(error) + + expect(authedRequest.callCount).to.be.eq(1) + + expect(authedRequest.firstCall.args[0]).to.deep.eq({ + verb: AlunaHttpVerbEnum.DELETE, + credentials, + url: getOkxEndpoints(exchange.settings).order.cancel(id), + }) + + expect(publicRequest.callCount).to.be.eq(0) + + }) + +}) diff --git a/src/exchanges/okx/modules/authed/order/cancel.ts b/src/exchanges/okx/modules/authed/order/cancel.ts new file mode 100644 index 00000000..2a7285ac --- /dev/null +++ b/src/exchanges/okx/modules/authed/order/cancel.ts @@ -0,0 +1,75 @@ +import { debug } from 'debug' + +import { AlunaError } from '../../../../../lib/core/AlunaError' +import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { AlunaHttpVerbEnum } from '../../../../../lib/enums/AlunaHtttpVerbEnum' +import { AlunaOrderErrorCodes } from '../../../../../lib/errors/AlunaOrderErrorCodes' +import { + IAlunaOrderCancelParams, + IAlunaOrderCancelReturns, +} from '../../../../../lib/modules/authed/IAlunaOrderModule' +import { OkxHttp } from '../../../OkxHttp' +import { getOkxEndpoints } from '../../../okxSpecs' +import { IOkxOrderSchema } from '../../../schemas/IOkxOrderSchema' + + + +const log = debug('alunajs:okx/order/cancel') + + + +export const cancel = (exchange: IAlunaExchangeAuthed) => async ( + params: IAlunaOrderCancelParams, +): Promise => { + + log('canceling order', params) + + const { + settings, + credentials, + } = exchange + + const { + id, + http = new OkxHttp(settings), + } = params + + try { + + // TODO: Implement proper request + const rawOrder = await http.authedRequest({ + verb: AlunaHttpVerbEnum.DELETE, + url: getOkxEndpoints(settings).order.get(id), + credentials, + }) + + const { order } = exchange.order.parse({ rawOrder }) + + const { requestWeight } = http + + return { + order, + requestWeight, + } + + } catch (err) { + + const { + metadata, + httpStatusCode, + } = err + + const error = new AlunaError({ + message: 'Something went wrong, order not canceled', + httpStatusCode, + code: AlunaOrderErrorCodes.CANCEL_FAILED, + metadata, + }) + + log(error) + + throw error + + } + +} diff --git a/src/exchanges/okx/modules/authed/order/edit.spec.ts b/src/exchanges/okx/modules/authed/order/edit.spec.ts new file mode 100644 index 00000000..b1bcbb56 --- /dev/null +++ b/src/exchanges/okx/modules/authed/order/edit.spec.ts @@ -0,0 +1,96 @@ +import { expect } from 'chai' + +import { PARSED_ORDERS } from '../../../../../../test/fixtures/parsedOrders' +import { mockHttp } from '../../../../../../test/mocks/exchange/Http' +import { mockOrderCancel } from '../../../../../../test/mocks/exchange/modules/order/mockOrderCancel' +import { mockOrderPlace } from '../../../../../../test/mocks/exchange/modules/order/mockOrderPlace' +import { AlunaAccountEnum } from '../../../../../lib/enums/AlunaAccountEnum' +import { AlunaOrderSideEnum } from '../../../../../lib/enums/AlunaOrderSideEnum' +import { AlunaOrderTypesEnum } from '../../../../../lib/enums/AlunaOrderTypesEnum' +import { IAlunaOrderEditParams } from '../../../../../lib/modules/authed/IAlunaOrderModule' +import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' +import { mockValidateParams } from '../../../../../utils/validation/validateParams.mock' +import { OkxAuthed } from '../../../OkxAuthed' +import { OkxHttp } from '../../../OkxHttp' +import { OKX_RAW_ORDERS } from '../../../test/fixtures/okxOrders' +import * as cancelMod from './cancel' +import * as placeMod from './place' + + + +describe(__filename, () => { + + const credentials: IAlunaCredentialsSchema = { + key: 'key', + secret: 'secret', + } + + it('should edit a Okx order just fine', async () => { + + // preparing data + const http = new OkxHttp({}) + + const mockedRawOrder = OKX_RAW_ORDERS[0] + const mockedParsedOrder = PARSED_ORDERS[0] + + const { id } = mockedRawOrder + + + // mocking + mockHttp({ classPrototype: OkxHttp.prototype }) + + const { cancel } = mockOrderCancel({ module: cancelMod }) + + const { place } = mockOrderPlace({ module: placeMod }) + + place.returns({ order: mockedParsedOrder }) + + mockValidateParams() + + // executing + const exchange = new OkxAuthed({ credentials }) + + const params: IAlunaOrderEditParams = { + id, + symbolPair: '', + account: AlunaAccountEnum.SPOT, + amount: 0.01, + side: AlunaOrderSideEnum.BUY, + type: AlunaOrderTypesEnum.LIMIT, + rate: 0, + } + + const { + order, + requestWeight, + } = await exchange.order.edit(params) + + + // validating + expect(order).to.deep.eq(mockedParsedOrder) + + expect(requestWeight).to.deep.eq(http.requestWeight) + + expect(cancel.callCount).to.be.eq(1) + + expect(cancel.firstCall.args[0]).to.deep.eq({ + http, + id, + symbolPair: params.symbolPair, + }) + + expect(place.callCount).to.be.eq(1) + + expect(place.firstCall.args[0]).to.deep.eq({ + http, + rate: params.rate, + side: params.side, + type: params.type, + amount: params.amount, + account: params.account, + symbolPair: params.symbolPair, + }) + + }) + +}) diff --git a/src/exchanges/okx/modules/authed/order/edit.ts b/src/exchanges/okx/modules/authed/order/edit.ts new file mode 100644 index 00000000..31c8132f --- /dev/null +++ b/src/exchanges/okx/modules/authed/order/edit.ts @@ -0,0 +1,65 @@ +import { debug } from 'debug' + +import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { + IAlunaOrderEditParams, + IAlunaOrderEditReturns, +} from '../../../../../lib/modules/authed/IAlunaOrderModule' +import { editOrderParamsSchema } from '../../../../../utils/validation/schemas/editOrderParamsSchema' +import { validateParams } from '../../../../../utils/validation/validateParams' +import { OkxHttp } from '../../../OkxHttp' + + + +const log = debug('alunajs:okx/order/edit') + + + +export const edit = (exchange: IAlunaExchangeAuthed) => async ( + params: IAlunaOrderEditParams, +): Promise => { + + log('editing order', params) + + validateParams({ + params, + schema: editOrderParamsSchema, + }) + + log('editing order for Okx') + + const { + id, + rate, + side, + type, + amount, + account, + symbolPair, + http = new OkxHttp(exchange.settings), + } = params + + await exchange.order.cancel({ + id, + symbolPair, + http, + }) + + const { order: newOrder } = await exchange.order.place({ + rate, + side, + type, + amount, + account, + symbolPair, + http, + }) + + const { requestWeight } = http + + return { + order: newOrder, + requestWeight, + } + +} diff --git a/src/exchanges/okx/modules/authed/order/get.spec.ts b/src/exchanges/okx/modules/authed/order/get.spec.ts new file mode 100644 index 00000000..720efc1d --- /dev/null +++ b/src/exchanges/okx/modules/authed/order/get.spec.ts @@ -0,0 +1,68 @@ +import { expect } from 'chai' + +import { PARSED_ORDERS } from '../../../../../../test/fixtures/parsedOrders' +import { mockGetRaw } from '../../../../../../test/mocks/exchange/modules/mockGetRaw' +import { mockParse } from '../../../../../../test/mocks/exchange/modules/mockParse' +import { IAlunaOrderGetParams } from '../../../../../lib/modules/authed/IAlunaOrderModule' +import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' +import { OkxAuthed } from '../../../OkxAuthed' +import { OKX_RAW_ORDERS } from '../../../test/fixtures/okxOrders' +import * as getRawMod from './getRaw' +import * as parseMod from './parse' + + + +describe(__filename, () => { + + const credentials: IAlunaCredentialsSchema = { + key: 'key', + secret: 'secret', + } + + it('should get a Okx order just fine', async () => { + + // preparing data + const mockedRawOrder = OKX_RAW_ORDERS[0] + const mockedParsedOrder = PARSED_ORDERS[0] + + const { id } = mockedRawOrder + + const params: IAlunaOrderGetParams = { + id, + symbolPair: '', + } + + + // mocking + const { getRaw } = mockGetRaw({ module: getRawMod }) + + getRaw.returns(Promise.resolve({ rawOrder: mockedRawOrder })) + + const { parse } = mockParse({ module: parseMod }) + + parse.returns({ order: mockedParsedOrder }) + + + // executing + const exchange = new OkxAuthed({ credentials }) + + const { order } = await exchange.order.get({ + id, + symbolPair: '', + }) + + + // validating + expect(order).to.deep.eq(mockedParsedOrder) + + expect(getRaw.callCount).to.be.eq(1) + expect(getRaw.firstCall.args[0]).to.deep.eq(params) + + expect(parse.callCount).to.be.eq(1) + expect(parse.firstCall.args[0]).to.deep.eq({ + rawOrder: mockedRawOrder, + }) + + }) + +}) diff --git a/src/exchanges/okx/modules/authed/order/get.ts b/src/exchanges/okx/modules/authed/order/get.ts new file mode 100644 index 00000000..f7246461 --- /dev/null +++ b/src/exchanges/okx/modules/authed/order/get.ts @@ -0,0 +1,33 @@ +import { debug } from 'debug' + +import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { + IAlunaOrderGetParams, + IAlunaOrderGetReturns, +} from '../../../../../lib/modules/authed/IAlunaOrderModule' + + + +const log = debug('alunajs:okx/order/get') + + + +export const get = (exchange: IAlunaExchangeAuthed) => async ( + params: IAlunaOrderGetParams, +): Promise => { + + log('getting order', params) + + const { + rawOrder, + requestWeight, + } = await exchange.order.getRaw(params) + + const { order } = exchange.order.parse({ rawOrder }) + + return { + order, + requestWeight, + } + +} diff --git a/src/exchanges/okx/modules/authed/order/getRaw.spec.ts b/src/exchanges/okx/modules/authed/order/getRaw.spec.ts new file mode 100644 index 00000000..9ae252ab --- /dev/null +++ b/src/exchanges/okx/modules/authed/order/getRaw.spec.ts @@ -0,0 +1,61 @@ +import { expect } from 'chai' + +import { mockHttp } from '../../../../../../test/mocks/exchange/Http' +import { AlunaHttpVerbEnum } from '../../../../../lib/enums/AlunaHtttpVerbEnum' +import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' +import { OkxAuthed } from '../../../OkxAuthed' +import { OkxHttp } from '../../../OkxHttp' +import { getOkxEndpoints } from '../../../okxSpecs' +import { OKX_RAW_ORDERS } from '../../../test/fixtures/okxOrders' + + + +describe(__filename, () => { + + const credentials: IAlunaCredentialsSchema = { + key: 'key', + secret: 'secret', + } + + it('should get a Okx raw order just fine', async () => { + + // preparing data + const mockedRawOrder = OKX_RAW_ORDERS[0] + + const { id } = mockedRawOrder + + + // mocking + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + authedRequest.returns(Promise.resolve(mockedRawOrder)) + + + // executing + const exchange = new OkxAuthed({ credentials }) + + const { rawOrder } = await exchange.order.getRaw({ + id, + symbolPair: '', + }) + + + // validating + expect(rawOrder).to.deep.eq(mockedRawOrder) + + expect(authedRequest.callCount).to.be.eq(1) + + expect(authedRequest.firstCall.args[0]).to.deep.eq({ + verb: AlunaHttpVerbEnum.GET, + credentials, + url: getOkxEndpoints(exchange.settings).order.get(id), + }) + + expect(publicRequest.callCount).to.be.eq(0) + + }) + +}) diff --git a/src/exchanges/okx/modules/authed/order/getRaw.ts b/src/exchanges/okx/modules/authed/order/getRaw.ts new file mode 100644 index 00000000..1542471e --- /dev/null +++ b/src/exchanges/okx/modules/authed/order/getRaw.ts @@ -0,0 +1,49 @@ +import { debug } from 'debug' + +import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { AlunaHttpVerbEnum } from '../../../../../lib/enums/AlunaHtttpVerbEnum' +import { + IAlunaOrderGetParams, + IAlunaOrderGetRawReturns, +} from '../../../../../lib/modules/authed/IAlunaOrderModule' +import { OkxHttp } from '../../../OkxHttp' +import { getOkxEndpoints } from '../../../okxSpecs' +import { IOkxOrderSchema } from '../../../schemas/IOkxOrderSchema' + + + +const log = debug('alunajs:okx/order/getRaw') + + + +export const getRaw = (exchange: IAlunaExchangeAuthed) => async ( + params: IAlunaOrderGetParams, +): Promise> => { + + log('getting raw order', params) + + const { + settings, + credentials, + } = exchange + + const { + id, + http = new OkxHttp(settings), + } = params + + // TODO: Implement proper request + const rawOrder = await http.authedRequest({ + credentials, + verb: AlunaHttpVerbEnum.GET, + url: getOkxEndpoints(settings).order.get(id), + }) + + const { requestWeight } = http + + return { + rawOrder, + requestWeight, + } + +} diff --git a/src/exchanges/okx/modules/authed/order/list.spec.ts b/src/exchanges/okx/modules/authed/order/list.spec.ts new file mode 100644 index 00000000..8b0961c0 --- /dev/null +++ b/src/exchanges/okx/modules/authed/order/list.spec.ts @@ -0,0 +1,22 @@ +import { PARSED_ORDERS } from '../../../../../../test/fixtures/parsedOrders' +import { testList } from '../../../../../../test/macros/testList' +import { OkxAuthed } from '../../../OkxAuthed' +import { OKX_RAW_ORDERS } from '../../../test/fixtures/okxOrders' +import * as listRawMod from './listRaw' +import * as parseManyMod from './parseMany' + + + +describe(__filename, () => { + + testList({ + AuthedClass: OkxAuthed, + exchangeId: 'okx', + methodModuleName: 'order', + listModule: listRawMod, + parseManyModule: parseManyMod, + rawList: { rawOrders: OKX_RAW_ORDERS }, + parsedList: { orders: PARSED_ORDERS }, + }) + +}) diff --git a/src/exchanges/okx/modules/authed/order/list.ts b/src/exchanges/okx/modules/authed/order/list.ts new file mode 100644 index 00000000..4ebacec5 --- /dev/null +++ b/src/exchanges/okx/modules/authed/order/list.ts @@ -0,0 +1,35 @@ +import { debug } from 'debug' + +import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { + IAlunaOrderListParams, + IAlunaOrderListReturns, +} from '../../../../../lib/modules/authed/IAlunaOrderModule' +import { OkxHttp } from '../../../OkxHttp' + + + +const log = debug('alunajs:okx/order/list') + + + +export const list = (exchange: IAlunaExchangeAuthed) => async ( + params: IAlunaOrderListParams = {}, +): Promise => { + + log('listing orders', params) + + const { http = new OkxHttp(exchange.settings) } = params + + const { rawOrders } = await exchange.order.listRaw({ http }) + + const { orders } = exchange.order.parseMany({ rawOrders }) + + const { requestWeight } = http + + return { + orders, + requestWeight, + } + +} diff --git a/src/exchanges/okx/modules/authed/order/listRaw.spec.ts b/src/exchanges/okx/modules/authed/order/listRaw.spec.ts new file mode 100644 index 00000000..07ae629d --- /dev/null +++ b/src/exchanges/okx/modules/authed/order/listRaw.spec.ts @@ -0,0 +1,54 @@ +import { expect } from 'chai' + +import { mockHttp } from '../../../../../../test/mocks/exchange/Http' +import { AlunaHttpVerbEnum } from '../../../../../lib/enums/AlunaHtttpVerbEnum' +import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' +import { OkxAuthed } from '../../../OkxAuthed' +import { OkxHttp } from '../../../OkxHttp' +import { getOkxEndpoints } from '../../../okxSpecs' +import { OKX_RAW_ORDERS } from '../../../test/fixtures/okxOrders' + + + +describe(__filename, () => { + + const credentials: IAlunaCredentialsSchema = { + key: 'key', + secret: 'secret', + } + + it('should list Okx raw orders just fine', async () => { + + // preparing data + const mockedRawOrders = OKX_RAW_ORDERS + + // mocking + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + authedRequest.returns(Promise.resolve(mockedRawOrders)) + + // executing + const exchange = new OkxAuthed({ credentials }) + + const { rawOrders } = await exchange.order.listRaw() + + + // validating + expect(rawOrders).to.deep.eq(mockedRawOrders) + + expect(authedRequest.callCount).to.be.eq(1) + + expect(authedRequest.firstCall.args[0]).to.deep.eq({ + verb: AlunaHttpVerbEnum.GET, + credentials, + url: getOkxEndpoints(exchange.settings).order.list, + }) + + expect(publicRequest.callCount).to.be.eq(0) + + }) + +}) diff --git a/src/exchanges/okx/modules/authed/order/listRaw.ts b/src/exchanges/okx/modules/authed/order/listRaw.ts new file mode 100644 index 00000000..08be216d --- /dev/null +++ b/src/exchanges/okx/modules/authed/order/listRaw.ts @@ -0,0 +1,46 @@ +import { debug } from 'debug' + +import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { AlunaHttpVerbEnum } from '../../../../../lib/enums/AlunaHtttpVerbEnum' +import { + IAlunaOrderListParams, + IAlunaOrderListRawReturns, +} from '../../../../../lib/modules/authed/IAlunaOrderModule' +import { OkxHttp } from '../../../OkxHttp' +import { getOkxEndpoints } from '../../../okxSpecs' +import { IOkxOrderSchema } from '../../../schemas/IOkxOrderSchema' + + + +const log = debug('alunajs:okx/order/listRaw') + + + +export const listRaw = (exchange: IAlunaExchangeAuthed) => async ( + params: IAlunaOrderListParams = {}, +): Promise> => { + + log('fetching Okx open orders', params) + + const { + settings, + credentials, + } = exchange + + const { http = new OkxHttp(settings) } = params + + // TODO: Implement proper request + const rawOrders = await http.authedRequest({ + verb: AlunaHttpVerbEnum.GET, + url: getOkxEndpoints(settings).order.list, + credentials, + }) + + const { requestWeight } = http + + return { + rawOrders, + requestWeight, + } + +} diff --git a/src/exchanges/okx/modules/authed/order/parse.spec.ts b/src/exchanges/okx/modules/authed/order/parse.spec.ts new file mode 100644 index 00000000..ed84a187 --- /dev/null +++ b/src/exchanges/okx/modules/authed/order/parse.spec.ts @@ -0,0 +1,34 @@ +import { expect } from 'chai' + +import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' +import { OkxAuthed } from '../../../OkxAuthed' +import { OKX_RAW_ORDERS } from '../../../test/fixtures/okxOrders' + + + +describe.skip(__filename, () => { + + const credentials: IAlunaCredentialsSchema = { + key: 'key', + secret: 'secret', + } + + it('should parse a Okx raw order just fine', async () => { + + // preparing data + const rawOrder = OKX_RAW_ORDERS[0] + + const exchange = new OkxAuthed({ credentials }) + + const { order } = exchange.order.parse({ rawOrder }) + + + // validating + expect(order).to.exist + + // TODO: add expectations for everything + // expect(order).to.deep.eq(...) + + }) + +}) diff --git a/src/exchanges/okx/modules/authed/order/parse.ts b/src/exchanges/okx/modules/authed/order/parse.ts new file mode 100644 index 00000000..80644406 --- /dev/null +++ b/src/exchanges/okx/modules/authed/order/parse.ts @@ -0,0 +1,53 @@ +import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { + IAlunaOrderParseParams, + IAlunaOrderParseReturns, +} from '../../../../../lib/modules/authed/IAlunaOrderModule' +import { IAlunaOrderSchema } from '../../../../../lib/schemas/IAlunaOrderSchema' +import { translateSymbolId } from '../../../../../utils/mappings/translateSymbolId' +import { IOkxOrderSchema } from '../../../schemas/IOkxOrderSchema' + + + +export const parse = (exchange: IAlunaExchangeAuthed) => ( + params: IAlunaOrderParseParams, +): IAlunaOrderParseReturns => { + + + const { rawOrder } = params + + const { symbol } = rawOrder + + let [ + baseSymbolId, + quoteSymbolId, + ] = symbol.split('/') + + baseSymbolId = translateSymbolId({ + exchangeSymbolId: baseSymbolId, + symbolMappings: exchange.settings.symbolMappings, + }) + + quoteSymbolId = translateSymbolId({ + exchangeSymbolId: quoteSymbolId, + symbolMappings: exchange.settings.symbolMappings, + }) + + // TODO: Implement proper parser + const order: IAlunaOrderSchema = { + id: rawOrder.id, + symbolPair: rawOrder.id, + exchangeId: exchange.specs.id, + baseSymbolId, + quoteSymbolId, + // total: rawOrder.total, + // amount: rawOrder.amount, + // account: AlunaAccountEnum.MARGIN, + // status: rawOrder.status, + // side: rawOrder.side, + meta: rawOrder, + } as any // TODO: Remove casting to any + + return { order } + +} diff --git a/src/exchanges/okx/modules/authed/order/parseMany.spec.ts b/src/exchanges/okx/modules/authed/order/parseMany.spec.ts new file mode 100644 index 00000000..097f3e6f --- /dev/null +++ b/src/exchanges/okx/modules/authed/order/parseMany.spec.ts @@ -0,0 +1,48 @@ +import { expect } from 'chai' +import { each } from 'lodash' + +import { PARSED_ORDERS } from '../../../../../../test/fixtures/parsedOrders' +import { mockParse } from '../../../../../../test/mocks/exchange/modules/mockParse' +import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' +import { OkxAuthed } from '../../../OkxAuthed' +import { OKX_RAW_ORDERS } from '../../../test/fixtures/okxOrders' +import * as parseMod from './parse' + + + +describe(__filename, () => { + + const credentials: IAlunaCredentialsSchema = { + key: 'key', + secret: 'secret', + } + + it('should parse many Okx raw orders just fine', async () => { + + // preparing data + const parsedOrders = PARSED_ORDERS + const rawOrders = OKX_RAW_ORDERS + + + // mocking + const { parse } = mockParse({ module: parseMod }) + + each(parsedOrders, (order, index) => { + parse.onCall(index).returns({ order }) + }) + + + // executing + const exchange = new OkxAuthed({ credentials }) + + const { orders } = exchange.order.parseMany({ rawOrders }) + + + // validating + expect(orders).to.deep.eq(parsedOrders) + + expect(parse.callCount).to.be.eq(rawOrders.length) + + }) + +}) diff --git a/src/exchanges/okx/modules/authed/order/parseMany.ts b/src/exchanges/okx/modules/authed/order/parseMany.ts new file mode 100644 index 00000000..d2980305 --- /dev/null +++ b/src/exchanges/okx/modules/authed/order/parseMany.ts @@ -0,0 +1,34 @@ +import { debug } from 'debug' + +import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { + IAlunaOrderParseManyParams, + IAlunaOrderParseManyReturns, +} from '../../../../../lib/modules/authed/IAlunaOrderModule' +import { IOkxOrderSchema } from '../../../schemas/IOkxOrderSchema' + + + +const log = debug('alunajs:okx/order/parseMany') + + + +export const parseMany = (exchange: IAlunaExchangeAuthed) => ( + params: IAlunaOrderParseManyParams, +): IAlunaOrderParseManyReturns => { + + const { rawOrders } = params + + const parsedOrders = rawOrders.map((rawOrder) => { + + const { order } = exchange.order.parse({ rawOrder }) + + return order + + }) + + log(`parsed ${parsedOrders.length} orders`) + + return { orders: parsedOrders } + +} diff --git a/src/exchanges/okx/modules/authed/order/place.spec.ts b/src/exchanges/okx/modules/authed/order/place.spec.ts new file mode 100644 index 00000000..0b6f7321 --- /dev/null +++ b/src/exchanges/okx/modules/authed/order/place.spec.ts @@ -0,0 +1,348 @@ +import { expect } from 'chai' + +import { PARSED_ORDERS } from '../../../../../../test/fixtures/parsedOrders' +import { mockHttp } from '../../../../../../test/mocks/exchange/Http' +import { mockParse } from '../../../../../../test/mocks/exchange/modules/mockParse' +import { AlunaError } from '../../../../../lib/core/AlunaError' +import { AlunaAccountEnum } from '../../../../../lib/enums/AlunaAccountEnum' +import { AlunaOrderSideEnum } from '../../../../../lib/enums/AlunaOrderSideEnum' +import { AlunaOrderTypesEnum } from '../../../../../lib/enums/AlunaOrderTypesEnum' +import { AlunaBalanceErrorCodes } from '../../../../../lib/errors/AlunaBalanceErrorCodes' +import { AlunaOrderErrorCodes } from '../../../../../lib/errors/AlunaOrderErrorCodes' +import { IAlunaOrderPlaceParams } from '../../../../../lib/modules/authed/IAlunaOrderModule' +import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' +import { executeAndCatch } from '../../../../../utils/executeAndCatch' +import { mockEnsureOrderIsSupported } from '../../../../../utils/orders/ensureOrderIsSupported.mock' +import { mockValidateParams } from '../../../../../utils/validation/validateParams.mock' +import { translateOrderSideToOkx } from '../../../enums/adapters/okxOrderSideAdapter' +import { translateOrderTypeToOkx } from '../../../enums/adapters/okxOrderTypeAdapter' +import { OkxAuthed } from '../../../OkxAuthed' +import { OkxHttp } from '../../../OkxHttp' +import { getOkxEndpoints } from '../../../okxSpecs' +import { OKX_RAW_ORDERS } from '../../../test/fixtures/okxOrders' +import * as parseMod from './parse' + + + +describe(__filename, () => { + + const credentials: IAlunaCredentialsSchema = { + key: 'key', + secret: 'secret', + } + + it('should place a Okx limit order just fine', async () => { + + // preparing data + const mockedRawOrder = OKX_RAW_ORDERS[0] + const mockedParsedOrder = PARSED_ORDERS[0] + + const side = AlunaOrderSideEnum.BUY + const type = AlunaOrderTypesEnum.LIMIT + + const translatedOrderSide = translateOrderSideToOkx({ from: side }) + const translatedOrderType = translateOrderTypeToOkx({ from: type }) + + const body = { + direction: translatedOrderSide, + marketSymbol: '', + type: translatedOrderType, + quantity: 0.01, + rate: 0, + // limit: 0, + // timeInForce: OkxOrderTimeInForceEnum.GOOD_TIL_CANCELLED, + } + + // mocking + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + const { parse } = mockParse({ module: parseMod }) + + parse.returns({ order: mockedParsedOrder }) + + authedRequest.returns(Promise.resolve(mockedRawOrder)) + + mockValidateParams() + + const { ensureOrderIsSupported } = mockEnsureOrderIsSupported() + + + // executing + const exchange = new OkxAuthed({ credentials }) + + const params: IAlunaOrderPlaceParams = { + symbolPair: '', + account: AlunaAccountEnum.SPOT, + amount: 0.01, + side, + type, + rate: 0, + } + + const { order } = await exchange.order.place(params) + + + // validating + expect(order).to.deep.eq(mockedParsedOrder) + + expect(authedRequest.callCount).to.be.eq(1) + + expect(authedRequest.firstCall.args[0]).to.deep.eq({ + body, + credentials, + url: getOkxEndpoints(exchange.settings).order.place, + }) + + expect(publicRequest.callCount).to.be.eq(0) + + expect(ensureOrderIsSupported.callCount).to.be.eq(1) + + }) + + it('should place a Okx market order just fine', async () => { + + // preparing data + + const mockedRawOrder = OKX_RAW_ORDERS[0] + const mockedParsedOrder = PARSED_ORDERS[0] + + const side = AlunaOrderSideEnum.BUY + const type = AlunaOrderTypesEnum.MARKET + + const translatedOrderSide = translateOrderSideToOkx({ from: side }) + const translatedOrderType = translateOrderTypeToOkx({ from: type }) + + const body = { + direction: translatedOrderSide, + marketSymbol: '', + type: translatedOrderType, + quantity: 0.01, + rate: 0, + // timeInForce: OkxOrderTimeInForceEnum.FILL_OR_KILL, + } + + // mocking + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + const { parse } = mockParse({ module: parseMod }) + + parse.returns({ order: mockedParsedOrder }) + + authedRequest.returns(Promise.resolve(mockedRawOrder)) + + mockValidateParams() + + mockEnsureOrderIsSupported() + + + // executing + const exchange = new OkxAuthed({ credentials }) + + const { order } = await exchange.order.place({ + symbolPair: '', + account: AlunaAccountEnum.SPOT, + amount: 0.01, + side, + type, + rate: 0, + }) + + + // validating + expect(order).to.deep.eq(mockedParsedOrder) + + expect(authedRequest.callCount).to.be.eq(1) + + expect(authedRequest.firstCall.args[0]).to.deep.eq({ + body, + credentials, + url: getOkxEndpoints(exchange.settings).order.place, + }) + + expect(publicRequest.callCount).to.be.eq(0) + + }) + + it( + 'should throw error for insufficient funds when placing new okx order', + async () => { + + // preparing data + // const mockedRawOrder = OKX_RAW_ORDERS[0] + + const side = AlunaOrderSideEnum.BUY + const type = AlunaOrderTypesEnum.MARKET + + const expectedMessage = 'Account has insufficient balance ' + .concat('for requested action.') + const expectedCode = AlunaBalanceErrorCodes.INSUFFICIENT_BALANCE + + const alunaError = new AlunaError({ + message: 'dummy-error', + code: AlunaOrderErrorCodes.PLACE_FAILED, + httpStatusCode: 401, + metadata: { + code: 'INSUFFICIENT_FUNDS', + }, + }) + + // mocking + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + authedRequest.returns(Promise.reject(alunaError)) + + mockValidateParams() + + mockEnsureOrderIsSupported() + + + // executing + const exchange = new OkxAuthed({ credentials }) + + const { error } = await executeAndCatch(() => exchange.order.place({ + symbolPair: '', + account: AlunaAccountEnum.SPOT, + amount: 0.01, + side, + type, + rate: 0, + })) + + + // validating + + expect(error instanceof AlunaError).to.be.ok + expect(error?.code).to.be.eq(expectedCode) + expect(error?.message).to.be.eq(expectedMessage) + + expect(authedRequest.callCount).to.be.eq(1) + + expect(publicRequest.callCount).to.be.eq(0) + + }, + ) + + it( + 'should throw an error placing for minimum size placing new okx order', + async () => { + + // preparing data + // const mockedRawOrder = OKX_RAW_ORDERS[0] + + const side = AlunaOrderSideEnum.BUY + const type = AlunaOrderTypesEnum.MARKET + + const expectedMessage = 'The trade was smaller than the min ' + .concat('trade size quantity for the market') + const expectedCode = AlunaOrderErrorCodes.PLACE_FAILED + + const alunaError = new AlunaError({ + message: 'dummy-error', + code: AlunaOrderErrorCodes.PLACE_FAILED, + httpStatusCode: 401, + metadata: { + code: 'MIN_TRADE_REQUIREMENT_NOT_MET', + }, + }) + + // mocking + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + authedRequest.returns(Promise.reject(alunaError)) + + mockValidateParams() + + mockEnsureOrderIsSupported() + + + // executing + const exchange = new OkxAuthed({ credentials }) + + const { error } = await executeAndCatch(() => exchange.order.place({ + symbolPair: '', + account: AlunaAccountEnum.SPOT, + amount: 0.01, + side, + type, + rate: 0, + })) + + // validating + expect(error instanceof AlunaError).to.be.ok + expect(error?.code).to.be.eq(expectedCode) + expect(error?.message).to.be.eq(expectedMessage) + + expect(authedRequest.callCount).to.be.eq(1) + + expect(publicRequest.callCount).to.be.eq(0) + + }, + ) + + it('should throw an error placing new okx order', async () => { + + // preparing data + // const mockedRawOrder = OKX_RAW_ORDERS[0] + + const side = AlunaOrderSideEnum.BUY + const type = AlunaOrderTypesEnum.MARKET + + const expectedMessage = 'dummy-error' + const expectedCode = AlunaOrderErrorCodes.PLACE_FAILED + + const alunaError = new AlunaError({ + message: 'dummy-error', + code: AlunaOrderErrorCodes.PLACE_FAILED, + httpStatusCode: 401, + metadata: {}, + }) + + // mocking + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + authedRequest.returns(Promise.reject(alunaError)) + + mockValidateParams() + + mockEnsureOrderIsSupported() + + + // executing + const exchange = new OkxAuthed({ credentials }) + + const { error } = await executeAndCatch(() => exchange.order.place({ + symbolPair: '', + account: AlunaAccountEnum.SPOT, + amount: 0.01, + side, + type, + rate: Number(0), + })) + + // validating + expect(error instanceof AlunaError).to.be.ok + expect(error?.code).to.be.eq(expectedCode) + expect(error?.message).to.be.eq(expectedMessage) + + expect(authedRequest.callCount).to.be.eq(1) + + expect(publicRequest.callCount).to.be.eq(0) + + }) + +}) diff --git a/src/exchanges/okx/modules/authed/order/place.ts b/src/exchanges/okx/modules/authed/order/place.ts new file mode 100644 index 00000000..6556517a --- /dev/null +++ b/src/exchanges/okx/modules/authed/order/place.ts @@ -0,0 +1,127 @@ +import { debug } from 'debug' + +import { AlunaError } from '../../../../../lib/core/AlunaError' +import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { AlunaBalanceErrorCodes } from '../../../../../lib/errors/AlunaBalanceErrorCodes' +import { AlunaOrderErrorCodes } from '../../../../../lib/errors/AlunaOrderErrorCodes' +import { + IAlunaOrderPlaceParams, + IAlunaOrderPlaceReturns, +} from '../../../../../lib/modules/authed/IAlunaOrderModule' +import { ensureOrderIsSupported } from '../../../../../utils/orders/ensureOrderIsSupported' +import { placeOrderParamsSchema } from '../../../../../utils/validation/schemas/placeOrderParamsSchema' +import { validateParams } from '../../../../../utils/validation/validateParams' +import { translateOrderSideToOkx } from '../../../enums/adapters/okxOrderSideAdapter' +import { translateOrderTypeToOkx } from '../../../enums/adapters/okxOrderTypeAdapter' +import { OkxHttp } from '../../../OkxHttp' +import { getOkxEndpoints } from '../../../okxSpecs' +import { IOkxOrderSchema } from '../../../schemas/IOkxOrderSchema' + + + +const log = debug('alunajs:okx/order/place') + + + +export const place = (exchange: IAlunaExchangeAuthed) => async ( + params: IAlunaOrderPlaceParams, +): Promise => { + + log('placing order', params) + + const { + specs, + settings, + credentials, + } = exchange + + validateParams({ + params, + schema: placeOrderParamsSchema, + }) + + ensureOrderIsSupported({ + exchangeSpecs: specs, + orderPlaceParams: params, + }) + + const { + amount, + rate, + symbolPair, + side, + type, + http = new OkxHttp(settings), + } = params + + const translatedOrderType = translateOrderTypeToOkx({ + from: type, + }) + + // TODO: Validate all body properties + const body = { + direction: translateOrderSideToOkx({ from: side }), + marketSymbol: symbolPair, + type: translatedOrderType, + quantity: Number(amount), + rate, + } + + log('placing new order for Okx') + + let placedOrder: IOkxOrderSchema + + try { + + // TODO: Implement proper request + const orderResponse = await http.authedRequest({ + url: getOkxEndpoints(settings).order.place, + body, + credentials, + }) + + placedOrder = orderResponse + + } catch (err) { + + let { + code, + message, + } = err + + const { metadata } = err + + // TODO: Review error handlings + if (metadata.code === 'INSUFFICIENT_FUNDS') { + + code = AlunaBalanceErrorCodes.INSUFFICIENT_BALANCE + + message = 'Account has insufficient balance for requested action.' + + } else if (metadata.code === 'MIN_TRADE_REQUIREMENT_NOT_MET') { + + code = AlunaOrderErrorCodes.PLACE_FAILED + + message = 'The trade was smaller than the min trade size quantity for ' + .concat('the market') + + } + + throw new AlunaError({ + ...err, + code, + message, + }) + + } + + const { order } = exchange.order.parse({ rawOrder: placedOrder }) + + const { requestWeight } = http + + return { + order, + requestWeight, + } + +} diff --git a/src/exchanges/okx/modules/public/market.ts b/src/exchanges/okx/modules/public/market.ts new file mode 100644 index 00000000..71b70a46 --- /dev/null +++ b/src/exchanges/okx/modules/public/market.ts @@ -0,0 +1,20 @@ +import { IAlunaExchangePublic } from '../../../../lib/core/IAlunaExchange' +import { IAlunaMarketModule } from '../../../../lib/modules/public/IAlunaMarketModule' +import { list } from './market/list' +import { listRaw } from './market/listRaw' +import { parse } from './market/parse' +import { parseMany } from './market/parseMany' + + + +export function market(exchange: IAlunaExchangePublic): IAlunaMarketModule { + + return { + list: list(exchange), + listRaw: listRaw(exchange), + + parse: parse(exchange), + parseMany: parseMany(exchange), + } + +} diff --git a/src/exchanges/okx/modules/public/market/list.spec.ts b/src/exchanges/okx/modules/public/market/list.spec.ts new file mode 100644 index 00000000..5d0a6733 --- /dev/null +++ b/src/exchanges/okx/modules/public/market/list.spec.ts @@ -0,0 +1,22 @@ +import { PARSED_MARKETS } from '../../../../../../test/fixtures/parsedMarkets' +import { testList } from '../../../../../../test/macros/testList' +import { OkxAuthed } from '../../../OkxAuthed' +import { OKX_RAW_MARKETS } from '../../../test/fixtures/okxMarket' +import * as listRawMod from './listRaw' +import * as parseManyMod from './parseMany' + + + +describe(__filename, () => { + + testList({ + AuthedClass: OkxAuthed, + exchangeId: 'okx', + methodModuleName: 'market', + listModule: listRawMod, + parseManyModule: parseManyMod, + rawList: { rawMarkets: OKX_RAW_MARKETS }, + parsedList: { markets: PARSED_MARKETS }, + }) + +}) diff --git a/src/exchanges/okx/modules/public/market/list.ts b/src/exchanges/okx/modules/public/market/list.ts new file mode 100644 index 00000000..a10ebfd9 --- /dev/null +++ b/src/exchanges/okx/modules/public/market/list.ts @@ -0,0 +1,34 @@ +import debug from 'debug' + +import { IAlunaExchangePublic } from '../../../../../lib/core/IAlunaExchange' +import { + IAlunaMarketListParams, + IAlunaMarketListReturns, +} from '../../../../../lib/modules/public/IAlunaMarketModule' +import { OkxHttp } from '../../../OkxHttp' + + + +const log = debug('alunajs:okx/market/list') + + +export const list = (exchange: IAlunaExchangePublic) => async ( + params: IAlunaMarketListParams = {}, +): Promise => { + + log('listing Okx markets') + + const { http = new OkxHttp(exchange.settings) } = params + + const { requestWeight } = http + + const { rawMarkets } = await exchange.market.listRaw({ http }) + + const { markets } = exchange.market.parseMany({ rawMarkets }) + + return { + markets, + requestWeight, + } + +} diff --git a/src/exchanges/okx/modules/public/market/listRaw.spec.ts b/src/exchanges/okx/modules/public/market/listRaw.spec.ts new file mode 100644 index 00000000..d06085ce --- /dev/null +++ b/src/exchanges/okx/modules/public/market/listRaw.spec.ts @@ -0,0 +1,48 @@ +import { expect } from 'chai' + +import { mockHttp } from '../../../../../../test/mocks/exchange/Http' +import { Okx } from '../../../Okx' +import { OkxHttp } from '../../../OkxHttp' +import { getOkxEndpoints } from '../../../okxSpecs' +import { OKX_RAW_MARKETS } from '../../../test/fixtures/okxMarket' + + + +describe(__filename, () => { + + it('should list Okx raw markets just fine', async () => { + + // mocking + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + publicRequest.returns(Promise.resolve(OKX_RAW_MARKETS)) + + + // executing + const exchange = new Okx({}) + + const { + rawMarkets, + requestWeight, + } = await exchange.market.listRaw() + + + // validating + expect(rawMarkets).to.deep.eq(OKX_RAW_MARKETS) + + expect(requestWeight).to.be.ok + + expect(publicRequest.callCount).to.be.eq(1) + + expect(publicRequest.firstCall.args[0]).to.deep.eq({ + url: getOkxEndpoints(exchange.settings).market.list, + }) + + expect(authedRequest.callCount).to.be.eq(0) + + }) + +}) diff --git a/src/exchanges/okx/modules/public/market/listRaw.ts b/src/exchanges/okx/modules/public/market/listRaw.ts new file mode 100644 index 00000000..78b6481b --- /dev/null +++ b/src/exchanges/okx/modules/public/market/listRaw.ts @@ -0,0 +1,40 @@ +import debug from 'debug' + +import { IAlunaExchangePublic } from '../../../../../lib/core/IAlunaExchange' +import { + IAlunaMarketListParams, + IAlunaMarketListRawReturns, +} from '../../../../../lib/modules/public/IAlunaMarketModule' +import { OkxHttp } from '../../../OkxHttp' +import { getOkxEndpoints } from '../../../okxSpecs' +import { IOkxMarketSchema } from '../../../schemas/IOkxMarketSchema' + + + +const log = debug('alunajs:okx/market/listRaw') + + + +export const listRaw = (exchange: IAlunaExchangePublic) => async ( + params: IAlunaMarketListParams = {}, +): Promise> => { + + const { settings } = exchange + + const { http = new OkxHttp(settings) } = params + + log('fetching Okx markets') + + // TODO: Implement proper request + const rawMarkets = await http.publicRequest({ + url: getOkxEndpoints(settings).market.list, + }) + + const { requestWeight } = http + + return { + rawMarkets, + requestWeight, + } + +} diff --git a/src/exchanges/okx/modules/public/market/parse.spec.ts b/src/exchanges/okx/modules/public/market/parse.spec.ts new file mode 100644 index 00000000..8e2be8dc --- /dev/null +++ b/src/exchanges/okx/modules/public/market/parse.spec.ts @@ -0,0 +1,38 @@ +import { expect } from 'chai' + +import { mockTranslateSymbolId } from '../../../../../utils/mappings/translateSymbolId.mock' +import { Okx } from '../../../Okx' +import { OKX_RAW_MARKETS } from '../../../test/fixtures/okxMarket' + + + +describe.skip(__filename, () => { + + it('should parse a Okx raw market just fine', async () => { + + // preparing data + const translatedSymbolId = 'BTC' + const rawMarket = OKX_RAW_MARKETS[0] + + + // mocking + const { translateSymbolId } = mockTranslateSymbolId() + + translateSymbolId.returns(translatedSymbolId) + + + // executing + const exchange = new Okx({}) + + const { market } = exchange.market.parse({ + rawMarket, + }) + + + // validating + // TODO: add proper validations + expect(market).to.exist + + }) + +}) diff --git a/src/exchanges/okx/modules/public/market/parse.ts b/src/exchanges/okx/modules/public/market/parse.ts new file mode 100644 index 00000000..3e2d5d43 --- /dev/null +++ b/src/exchanges/okx/modules/public/market/parse.ts @@ -0,0 +1,22 @@ +import { IAlunaExchangePublic } from '../../../../../lib/core/IAlunaExchange' +import { + IAlunaMarketParseParams, + IAlunaMarketParseReturns, +} from '../../../../../lib/modules/public/IAlunaMarketModule' +import { IAlunaMarketSchema } from '../../../../../lib/schemas/IAlunaMarketSchema' +import { IOkxMarketSchema } from '../../../schemas/IOkxMarketSchema' + + + +export const parse = (exchange: IAlunaExchangePublic) => ( + params: IAlunaMarketParseParams, +): IAlunaMarketParseReturns => { + + const { rawMarket } = params + + // TODO: Implement proper parser + const market: IAlunaMarketSchema = rawMarket as any + + return { market } + +} diff --git a/src/exchanges/okx/modules/public/market/parseMany.spec.ts b/src/exchanges/okx/modules/public/market/parseMany.spec.ts new file mode 100644 index 00000000..5349a5a1 --- /dev/null +++ b/src/exchanges/okx/modules/public/market/parseMany.spec.ts @@ -0,0 +1,41 @@ +import { expect } from 'chai' +import { each } from 'lodash' + +import { PARSED_MARKETS } from '../../../../../../test/fixtures/parsedMarkets' +import { mockParse } from '../../../../../../test/mocks/exchange/modules/mockParse' +import { Okx } from '../../../Okx' +import { OKX_RAW_MARKETS } from '../../../test/fixtures/okxMarket' +import * as parseMod from './parse' + + + +describe(__filename, () => { + + it('should parse many Okx raw markets just fine', async () => { + + // preparing data + const rawMarkets = OKX_RAW_MARKETS + + // mocking + const { parse } = mockParse({ module: parseMod }) + + each(PARSED_MARKETS, (market, index) => { + parse.onCall(index).returns({ market }) + }) + + + // executing + const exchange = new Okx({}) + + const { markets } = exchange.market.parseMany({ + rawMarkets, + }) + + + // validating + expect(parse.callCount).to.be.eq(PARSED_MARKETS.length) + expect(markets).to.deep.eq(PARSED_MARKETS) + + }) + +}) diff --git a/src/exchanges/okx/modules/public/market/parseMany.ts b/src/exchanges/okx/modules/public/market/parseMany.ts new file mode 100644 index 00000000..67ede325 --- /dev/null +++ b/src/exchanges/okx/modules/public/market/parseMany.ts @@ -0,0 +1,38 @@ +import debug from 'debug' +import { map } from 'lodash' + +import { IAlunaExchangePublic } from '../../../../../lib/core/IAlunaExchange' +import { + IAlunaMarketParseManyParams, + IAlunaMarketParseManyReturns, +} from '../../../../../lib/modules/public/IAlunaMarketModule' +import { IOkxMarketSchema } from '../../../schemas/IOkxMarketSchema' + + + +const log = debug('alunajs:okx/market/parseMany') + + + +export const parseMany = (exchange: IAlunaExchangePublic) => ( + params: IAlunaMarketParseManyParams, +): IAlunaMarketParseManyReturns => { + + const { rawMarkets } = params + + // TODO: Review implementation + const markets = map(rawMarkets, (rawMarket) => { + + const { market } = exchange.market.parse({ + rawMarket, + }) + + return market + + }) + + log(`parsed ${markets.length} markets for Okx`) + + return { markets } + +} diff --git a/src/exchanges/okx/modules/public/symbol.ts b/src/exchanges/okx/modules/public/symbol.ts new file mode 100644 index 00000000..3965b21b --- /dev/null +++ b/src/exchanges/okx/modules/public/symbol.ts @@ -0,0 +1,20 @@ +import { IAlunaExchangePublic } from '../../../../lib/core/IAlunaExchange' +import { IAlunaSymbolModule } from '../../../../lib/modules/public/IAlunaSymbolModule' +import { list } from './symbol/list' +import { listRaw } from './symbol/listRaw' +import { parse } from './symbol/parse' +import { parseMany } from './symbol/parseMany' + + + +export function symbol(exchange: IAlunaExchangePublic): IAlunaSymbolModule { + + return { + list: list(exchange), + listRaw: listRaw(exchange), + + parse: parse(exchange), + parseMany: parseMany(exchange), + } + +} diff --git a/src/exchanges/okx/modules/public/symbol/list.spec.ts b/src/exchanges/okx/modules/public/symbol/list.spec.ts new file mode 100644 index 00000000..3ac359e8 --- /dev/null +++ b/src/exchanges/okx/modules/public/symbol/list.spec.ts @@ -0,0 +1,22 @@ +import { PARSED_SYMBOLS } from '../../../../../../test/fixtures/parsedSymbols' +import { testList } from '../../../../../../test/macros/testList' +import { OkxAuthed } from '../../../OkxAuthed' +import { OKX_RAW_SYMBOLS } from '../../../test/fixtures/okxSymbols' +import * as listRawMod from './listRaw' +import * as parseManyMod from './parseMany' + + + +describe(__filename, () => { + + testList({ + AuthedClass: OkxAuthed, + exchangeId: 'okx', + methodModuleName: 'symbol', + listModule: listRawMod, + parseManyModule: parseManyMod, + rawList: { rawSymbols: OKX_RAW_SYMBOLS }, + parsedList: { symbols: PARSED_SYMBOLS }, + }) + +}) diff --git a/src/exchanges/okx/modules/public/symbol/list.ts b/src/exchanges/okx/modules/public/symbol/list.ts new file mode 100644 index 00000000..44516be1 --- /dev/null +++ b/src/exchanges/okx/modules/public/symbol/list.ts @@ -0,0 +1,35 @@ +import debug from 'debug' + +import { IAlunaExchangePublic } from '../../../../../lib/core/IAlunaExchange' +import { + IAlunaSymbolListParams, + IAlunaSymbolListReturns, +} from '../../../../../lib/modules/public/IAlunaSymbolModule' +import { OkxHttp } from '../../../OkxHttp' + + + +const log = debug('alunajs:okx/symbol/list') + + + +export const list = (exchange: IAlunaExchangePublic) => async ( + params: IAlunaSymbolListParams = {}, +): Promise => { + + log('listing Okx symbols') + + const { http = new OkxHttp(exchange.settings) } = params + + const { requestWeight } = http + + const { rawSymbols } = await exchange.symbol.listRaw({ http }) + + const { symbols } = exchange.symbol.parseMany({ rawSymbols }) + + return { + symbols, + requestWeight, + } + +} diff --git a/src/exchanges/okx/modules/public/symbol/listRaw.spec.ts b/src/exchanges/okx/modules/public/symbol/listRaw.spec.ts new file mode 100644 index 00000000..b0f2b479 --- /dev/null +++ b/src/exchanges/okx/modules/public/symbol/listRaw.spec.ts @@ -0,0 +1,48 @@ +import { expect } from 'chai' + +import { mockHttp } from '../../../../../../test/mocks/exchange/Http' +import { Okx } from '../../../Okx' +import { OkxHttp } from '../../../OkxHttp' +import { getOkxEndpoints } from '../../../okxSpecs' +import { OKX_RAW_SYMBOLS } from '../../../test/fixtures/okxSymbols' + + + +describe(__filename, () => { + + it('should list Okx raw symbols just fine', async () => { + + // mocking + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + publicRequest.returns(Promise.resolve(OKX_RAW_SYMBOLS)) + + + // executing + const exchange = new Okx({}) + + const { + rawSymbols, + requestWeight, + } = await exchange.symbol.listRaw() + + + // validating + expect(rawSymbols).to.deep.eq(OKX_RAW_SYMBOLS) + + expect(requestWeight).to.be.ok + + expect(publicRequest.callCount).to.be.eq(1) + + expect(publicRequest.firstCall.args[0]).to.deep.eq({ + url: getOkxEndpoints(exchange.settings).symbol.list, + }) + + expect(authedRequest.callCount).to.be.eq(0) + + }) + +}) diff --git a/src/exchanges/okx/modules/public/symbol/listRaw.ts b/src/exchanges/okx/modules/public/symbol/listRaw.ts new file mode 100644 index 00000000..83029723 --- /dev/null +++ b/src/exchanges/okx/modules/public/symbol/listRaw.ts @@ -0,0 +1,40 @@ +import debug from 'debug' + +import { IAlunaExchangePublic } from '../../../../../lib/core/IAlunaExchange' +import { + IAlunaSymbolListParams, + IAlunaSymbolListRawReturns, +} from '../../../../../lib/modules/public/IAlunaSymbolModule' +import { OkxHttp } from '../../../OkxHttp' +import { getOkxEndpoints } from '../../../okxSpecs' +import { IOkxSymbolSchema } from '../../../schemas/IOkxSymbolSchema' + + + +const log = debug('alunajs:okx/symbol/listRaw') + + + +export const listRaw = (exchange: IAlunaExchangePublic) => async ( + params: IAlunaSymbolListParams = {}, +): Promise> => { + + log('fetching Okx raw symbols') + + const { settings } = exchange + + const { http = new OkxHttp(settings) } = params + + // TODO: Implement proper request + const rawSymbols = await http.publicRequest({ + url: getOkxEndpoints(settings).symbol.list, + }) + + const { requestWeight } = http + + return { + requestWeight, + rawSymbols, + } + +} diff --git a/src/exchanges/okx/modules/public/symbol/parse.spec.ts b/src/exchanges/okx/modules/public/symbol/parse.spec.ts new file mode 100644 index 00000000..0b074175 --- /dev/null +++ b/src/exchanges/okx/modules/public/symbol/parse.spec.ts @@ -0,0 +1,87 @@ +import { expect } from 'chai' + +import { IAlunaSettingsSchema } from '../../../../../lib/schemas/IAlunaSettingsSchema' +import { mockTranslateSymbolId } from '../../../../../utils/mappings/translateSymbolId.mock' +import { Okx } from '../../../Okx' +import { okxBaseSpecs } from '../../../okxSpecs' +import { OKX_RAW_SYMBOLS } from '../../../test/fixtures/okxSymbols' + + + +describe.skip(__filename, () => { + + it('should parse a Okx symbol just fine (w/ alias)', async () => { + + // preparing data + const rawSymbol = OKX_RAW_SYMBOLS[0] // first fixture + + const translatedSymbolId = 'XBT' + + + // mocking + const { translateSymbolId } = mockTranslateSymbolId() + + translateSymbolId.returns(translatedSymbolId) + + + // executing + const settings: IAlunaSettingsSchema = { + symbolMappings: {}, + } + const exchange = new Okx({ settings }) + + const { symbol } = exchange.symbol.parse({ rawSymbol }) + + + // validating + expect(symbol.exchangeId).to.be.eq(okxBaseSpecs.id) + expect(symbol.id).to.be.eq(translatedSymbolId) + expect(symbol.name).to.be.eq(rawSymbol.name) + expect(symbol.alias).to.be.eq(rawSymbol.symbol) // should be equal + expect(symbol.meta).to.be.eq(rawSymbol) + + expect(translateSymbolId.callCount).to.be.eq(1) + expect(translateSymbolId.firstCall.args[0]).to.deep.eq({ + exchangeSymbolId: rawSymbol.symbol, + symbolMappings: settings.symbolMappings, + }) + + }) + + + + it('should parse a Okx symbol just fine (w/o alias)', async () => { + + // preparing data + const rawSymbol = OKX_RAW_SYMBOLS[1] // second fixture + + + // mocking + const { translateSymbolId } = mockTranslateSymbolId() + + translateSymbolId.returns(rawSymbol.symbol) + + + // executing + const exchange = new Okx({}) + + const { symbol } = exchange.symbol.parse({ rawSymbol }) + + + // validating + expect(symbol.exchangeId).to.be.eq(okxBaseSpecs.id) + expect(symbol.id).to.be.eq(rawSymbol.symbol) + expect(symbol.name).to.be.eq(rawSymbol.name) + expect(symbol.alias).to.be.eq(undefined) // different = undefined + expect(symbol.meta).to.be.eq(rawSymbol) + + expect(translateSymbolId.callCount).to.be.eq(1) + + expect(translateSymbolId.firstCall.args[0]).to.deep.eq({ + exchangeSymbolId: rawSymbol.symbol, + symbolMappings: undefined, + }) + + }) + +}) diff --git a/src/exchanges/okx/modules/public/symbol/parse.ts b/src/exchanges/okx/modules/public/symbol/parse.ts new file mode 100644 index 00000000..e78dbf70 --- /dev/null +++ b/src/exchanges/okx/modules/public/symbol/parse.ts @@ -0,0 +1,42 @@ +import { IAlunaExchangePublic } from '../../../../../lib/core/IAlunaExchange' +import { + IAlunaSymbolParseParams, + IAlunaSymbolParseReturns, +} from '../../../../../lib/modules/public/IAlunaSymbolModule' +import { IAlunaSymbolSchema } from '../../../../../lib/schemas/IAlunaSymbolSchema' +import { translateSymbolId } from '../../../../../utils/mappings/translateSymbolId' +import { okxBaseSpecs } from '../../../okxSpecs' +import { IOkxSymbolSchema } from '../../../schemas/IOkxSymbolSchema' + + + +export const parse = (exchange: IAlunaExchangePublic) => ( + params: IAlunaSymbolParseParams, +): IAlunaSymbolParseReturns => { + + const { rawSymbol } = params + + const { + name, + symbol, + } = rawSymbol + + const id = translateSymbolId({ + exchangeSymbolId: symbol, + symbolMappings: exchange.settings.symbolMappings, + }) + + const alias = (id !== symbol ? symbol : undefined) + + // TODO: Review symbol assembling + const parsedSymbol: IAlunaSymbolSchema = { + id, + name, + alias, + exchangeId: okxBaseSpecs.id, + meta: rawSymbol, + } + + return { symbol: parsedSymbol } + +} diff --git a/src/exchanges/okx/modules/public/symbol/parseMany.spec.ts b/src/exchanges/okx/modules/public/symbol/parseMany.spec.ts new file mode 100644 index 00000000..b6958b81 --- /dev/null +++ b/src/exchanges/okx/modules/public/symbol/parseMany.spec.ts @@ -0,0 +1,38 @@ +import { expect } from 'chai' +import { each } from 'lodash' + +import { PARSED_SYMBOLS } from '../../../../../../test/fixtures/parsedSymbols' +import { mockParse } from '../../../../../../test/mocks/exchange/modules/mockParse' +import { Okx } from '../../../Okx' +import { OKX_RAW_SYMBOLS } from '../../../test/fixtures/okxSymbols' +import * as parseMod from './parse' + + + +describe(__filename, () => { + + it('should parse many Okx symbols just fine', async () => { + + // mocking + const { parse } = mockParse({ module: parseMod }) + + each(PARSED_SYMBOLS, (symbol, index) => { + parse.onCall(index).returns({ symbol }) + }) + + + // executing + const exchange = new Okx({}) + + const { symbols } = exchange.symbol.parseMany({ + rawSymbols: OKX_RAW_SYMBOLS, + }) + + + // validating + expect(parse.callCount).to.be.eq(OKX_RAW_SYMBOLS.length) + expect(symbols.length).to.be.eq(OKX_RAW_SYMBOLS.length) + + }) + +}) diff --git a/src/exchanges/okx/modules/public/symbol/parseMany.ts b/src/exchanges/okx/modules/public/symbol/parseMany.ts new file mode 100644 index 00000000..f3769e39 --- /dev/null +++ b/src/exchanges/okx/modules/public/symbol/parseMany.ts @@ -0,0 +1,36 @@ +import debug from 'debug' +import { map } from 'lodash' + +import { IAlunaExchangePublic } from '../../../../../lib/core/IAlunaExchange' +import { + IAlunaSymbolParseManyParams, + IAlunaSymbolParseManyReturns, +} from '../../../../../lib/modules/public/IAlunaSymbolModule' +import { IOkxSymbolSchema } from '../../../schemas/IOkxSymbolSchema' + + + +const log = debug('alunajs:okx/symbol/parseMany') + + + +export const parseMany = (exchange: IAlunaExchangePublic) => ( + params: IAlunaSymbolParseManyParams, +): IAlunaSymbolParseManyReturns => { + + const { rawSymbols } = params + + // TODO: Review implementation + const symbols = map(rawSymbols, (rawSymbol) => { + + const { symbol } = exchange.symbol.parse({ rawSymbol }) + + return symbol + + }) + + log(`parsed ${symbols.length} symbols for Okx`) + + return { symbols } + +} diff --git a/src/exchanges/okx/okxSpecs.spec.ts b/src/exchanges/okx/okxSpecs.spec.ts new file mode 100644 index 00000000..c4f51b35 --- /dev/null +++ b/src/exchanges/okx/okxSpecs.spec.ts @@ -0,0 +1,38 @@ +import { expect } from 'chai' + +import { executeAndCatch } from '../../utils/executeAndCatch' +import { getOkxEndpoints } from './okxSpecs' + + + +describe(__filename, () => { + + it('should get production endpoints', async () => { + + // executing + const { + error, + result, + } = await executeAndCatch(() => getOkxEndpoints({ useTestNet: false })) + + // validating + expect(error).not.to.be.ok + expect(result).to.be.ok + + }) + + it('should get testnet endpoints', async () => { + + // executing + const { + error, + result, + } = await executeAndCatch(() => getOkxEndpoints({ useTestNet: true })) + + // validating + expect(error).not.to.be.ok + expect(result).to.be.ok + + }) + +}) diff --git a/src/exchanges/okx/okxSpecs.ts b/src/exchanges/okx/okxSpecs.ts new file mode 100644 index 00000000..e219522e --- /dev/null +++ b/src/exchanges/okx/okxSpecs.ts @@ -0,0 +1,186 @@ +import { cloneDeep } from 'lodash' + +import { AlunaAccountEnum } from '../../lib/enums/AlunaAccountEnum' +import { AlunaFeaturesModeEnum } from '../../lib/enums/AlunaFeaturesModeEnum' +import { AlunaOrderTypesEnum } from '../../lib/enums/AlunaOrderTypesEnum' +import { + IAlunaExchangeOrderSpecsSchema, + IAlunaExchangeSchema, +} from '../../lib/schemas/IAlunaExchangeSchema' +import { IAlunaSettingsSchema } from '../../lib/schemas/IAlunaSettingsSchema' + + + +// TODO: set proper urls +export const OKX_PRODUCTION_URL = 'https://api.okx.com/v3' +export const OKX_TESTNET_URL = 'https://testnet.api.okx.com/v3' + + + +export const okxExchangeOrderTypes: IAlunaExchangeOrderSpecsSchema[] = [ + { + type: AlunaOrderTypesEnum.LIMIT, + supported: true, + implemented: true, + mode: AlunaFeaturesModeEnum.WRITE, + options: { + rate: 1, + amount: 1, + }, + }, + { + type: AlunaOrderTypesEnum.MARKET, + supported: true, + implemented: true, + mode: AlunaFeaturesModeEnum.WRITE, + options: { + rate: 1, + amount: 1, + }, + }, + { + type: AlunaOrderTypesEnum.STOP_LIMIT, + supported: true, + implemented: true, + mode: AlunaFeaturesModeEnum.READ, + options: { + rate: 1, + amount: 1, + limitRate: 1, + }, + }, + { + type: AlunaOrderTypesEnum.TRAILING_STOP, + supported: true, + implemented: true, + mode: AlunaFeaturesModeEnum.READ, + options: { + rate: 1, + amount: 1, + limitRate: 1, + }, + }, +] + + + +export const okxBaseSpecs: IAlunaExchangeSchema = { + id: 'okx', + name: 'Okx', + // TODO: Review 'signupUrl' + signupUrl: 'https://okx.com/account/register', + // TODO: Review 'connectApiUrl' + connectApiUrl: 'https://okx.com/manage?view=api', + // TODO: Review exchange rates limits + rateLimitingPerMinute: { + perApiKey: 0, + perIp: 0, + }, + // TODO: Review supported features + features: { + offersOrderEditing: false, + offersPositionId: false, + }, + modes: { + balance: AlunaFeaturesModeEnum.READ, + order: AlunaFeaturesModeEnum.WRITE, + }, + accounts: [ + // TODO: Review supported/implemented accounts + { + type: AlunaAccountEnum.SPOT, + supported: true, + implemented: true, + orderTypes: okxExchangeOrderTypes, + }, + { + type: AlunaAccountEnum.MARGIN, + supported: false, + implemented: false, + orderTypes: [], + }, + { + type: AlunaAccountEnum.DERIVATIVES, + supported: false, + implemented: false, + orderTypes: [], + }, + { + type: AlunaAccountEnum.LENDING, + supported: false, + implemented: false, + orderTypes: [], + }, + ], + settings: {}, +} + + + +export const buildOkxSpecs = (params: { + settings: IAlunaSettingsSchema +}) => { + + const { settings } = params + const { referralCode } = settings + + const specs = cloneDeep(okxBaseSpecs) + + if (referralCode) { + specs.signupUrl = `${specs.signupUrl}?referralCode=${referralCode}` + } + + specs.settings = settings + + return specs + +} + + +export const getOkxEndpoints = ( + settings: IAlunaSettingsSchema, +) => { + + let baseUrl = OKX_PRODUCTION_URL + + if (settings.useTestNet) { + baseUrl = OKX_TESTNET_URL + /* + throw new AlunaError({ + code: ExchangeErrorCodes.EXCHANGE_DONT_PROVIDE_TESTNET, + message: 'Okx don't have a testnet.', + }) + */ + } + + return { + symbol: { + get: `${baseUrl}/`, + list: `${baseUrl}/`, + }, + market: { + get: `${baseUrl}/`, + list: `${baseUrl}/`, + }, + key: { + fetchDetails: `${baseUrl}/`, + }, + balance: { + list: `${baseUrl}/`, + }, + order: { + get: (id: string) => `${baseUrl}//${id}`, + list: `${baseUrl}/`, + place: `${baseUrl}/`, + cancel: (id: string) => `${baseUrl}//${id}`, + edit: `${baseUrl}/`, + }, + position: { + list: `${baseUrl}/`, + get: `${baseUrl}/`, + close: `${baseUrl}/`, + getLeverage: `${baseUrl}/`, + setLeverage: `${baseUrl}/`, + }, + } +} diff --git a/src/exchanges/okx/schemas/IOkxBalanceSchema.ts b/src/exchanges/okx/schemas/IOkxBalanceSchema.ts new file mode 100644 index 00000000..723a3cac --- /dev/null +++ b/src/exchanges/okx/schemas/IOkxBalanceSchema.ts @@ -0,0 +1,7 @@ +// TODO: Describe balance interface for Okx exchange +export interface IOkxBalanceSchema { + total: string + available: string + currencySymbol: string + // ... +} diff --git a/src/exchanges/okx/schemas/IOkxKeySchema.ts b/src/exchanges/okx/schemas/IOkxKeySchema.ts new file mode 100644 index 00000000..10e55572 --- /dev/null +++ b/src/exchanges/okx/schemas/IOkxKeySchema.ts @@ -0,0 +1,8 @@ +// TODO: Describe key interface for Okx exchange +export interface IOkxKeySchema { + read: boolean + trade: boolean + withdraw: boolean + accountId?: string + // ... +} diff --git a/src/exchanges/okx/schemas/IOkxMarketSchema.ts b/src/exchanges/okx/schemas/IOkxMarketSchema.ts new file mode 100644 index 00000000..d2e6f29e --- /dev/null +++ b/src/exchanges/okx/schemas/IOkxMarketSchema.ts @@ -0,0 +1,12 @@ +import { OkxMarketStatusEnum } from '../enums/OkxMarketStatusEnum' + + + +// TODO: Describe market interface for Okx exchange +export interface IOkxMarketSchema { + symbol: string + volume: string + quoteVolume: string + status: OkxMarketStatusEnum + // ... +} diff --git a/src/exchanges/okx/schemas/IOkxOrderSchema.ts b/src/exchanges/okx/schemas/IOkxOrderSchema.ts new file mode 100644 index 00000000..a303b656 --- /dev/null +++ b/src/exchanges/okx/schemas/IOkxOrderSchema.ts @@ -0,0 +1,11 @@ +import { OkxOrderTypeEnum } from '../enums/OkxOrderTypeEnum' + + + +// TODO: Describe order interface for Okx exchange +export interface IOkxOrderSchema { + id: string + symbol: string + type: OkxOrderTypeEnum + // ... +} diff --git a/src/exchanges/okx/schemas/IOkxSymbolSchema.ts b/src/exchanges/okx/schemas/IOkxSymbolSchema.ts new file mode 100644 index 00000000..a18d5558 --- /dev/null +++ b/src/exchanges/okx/schemas/IOkxSymbolSchema.ts @@ -0,0 +1,6 @@ +// TODO: Describe symbol interface for Okx exchange +export interface IOkxSymbolSchema { + symbol: string + name: string + // ... +} diff --git a/src/exchanges/okx/test/fixtures/okxBalances.ts b/src/exchanges/okx/test/fixtures/okxBalances.ts new file mode 100644 index 00000000..3e0cce25 --- /dev/null +++ b/src/exchanges/okx/test/fixtures/okxBalances.ts @@ -0,0 +1,23 @@ +import { IOkxBalanceSchema } from '../../schemas/IOkxBalanceSchema' + + + +// TODO: Review fixtures +export const OKX_RAW_BALANCES: IOkxBalanceSchema[] = [ + { + total: '1500.00000000', + available: '1500.00000000', + currencySymbol: 'BTC', + }, + { + total: '32.00000000', + available: '32.00000000', + currencySymbol: 'BTC', + }, + { + total: '11.00000000', + available: '11.00000000', + currencySymbol: 'BTC', + }, +] + diff --git a/src/exchanges/okx/test/fixtures/okxKey.ts b/src/exchanges/okx/test/fixtures/okxKey.ts new file mode 100644 index 00000000..67c7bd02 --- /dev/null +++ b/src/exchanges/okx/test/fixtures/okxKey.ts @@ -0,0 +1,9 @@ +import { IOkxKeySchema } from '../../schemas/IOkxKeySchema' + + + +export const OKX_KEY_PERMISSIONS: IOkxKeySchema = { + read: true, + trade: true, + withdraw: true, +} diff --git a/src/exchanges/okx/test/fixtures/okxMarket.ts b/src/exchanges/okx/test/fixtures/okxMarket.ts new file mode 100644 index 00000000..baf6421d --- /dev/null +++ b/src/exchanges/okx/test/fixtures/okxMarket.ts @@ -0,0 +1,27 @@ +import { OkxMarketStatusEnum } from '../../enums/OkxMarketStatusEnum' +import { IOkxMarketSchema } from '../../schemas/IOkxMarketSchema' + + + +// TODO: Review fixtures +export const OKX_RAW_MARKETS: IOkxMarketSchema[] = [ + { + symbol: 'BTC-EUR', + volume: '9.64127008', + quoteVolume: '311825.04145095', + status: OkxMarketStatusEnum.ONLINE, + }, + { + symbol: 'BTC-USD', + volume: '126.19108264', + quoteVolume: '4696409.21649740', + status: OkxMarketStatusEnum.ONLINE, + }, + { + symbol: 'BTC-USDT', + volume: '86.32449650', + quoteVolume: '3191538.22788546', + status: OkxMarketStatusEnum.OFFLINE, + }, +] + diff --git a/src/exchanges/okx/test/fixtures/okxOrders.ts b/src/exchanges/okx/test/fixtures/okxOrders.ts new file mode 100644 index 00000000..304be3bf --- /dev/null +++ b/src/exchanges/okx/test/fixtures/okxOrders.ts @@ -0,0 +1,18 @@ +import { OkxOrderTypeEnum } from '../../enums/OkxOrderTypeEnum' +import { IOkxOrderSchema } from '../../schemas/IOkxOrderSchema' + + + +// TODO: Review fixtures +export const OKX_RAW_ORDERS: IOkxOrderSchema[] = [ + { + id: '8bc1e59c-77fa-4554-bd11-966e360e4eb7', + symbol: 'BTC/USD', + type: OkxOrderTypeEnum.LIMIT, + }, + { + id: '8bc1e59c-77fa-4554-bd11-966e360e4eb8', + symbol: 'ETH/USD', + type: OkxOrderTypeEnum.MARKET, + }, +] diff --git a/src/exchanges/okx/test/fixtures/okxSymbols.ts b/src/exchanges/okx/test/fixtures/okxSymbols.ts new file mode 100644 index 00000000..b2f76519 --- /dev/null +++ b/src/exchanges/okx/test/fixtures/okxSymbols.ts @@ -0,0 +1,20 @@ +import { IOkxSymbolSchema } from '../../schemas/IOkxSymbolSchema' + + + +// TODO: Review fixtures +export const OKX_RAW_SYMBOLS: IOkxSymbolSchema[] = [ + { + symbol: 'BTC', + name: 'Bitcoin', + }, + { + symbol: 'LTC', + name: 'Litecoin', + }, + { + symbol: 'ETH', + name: 'Ethereum', + }, +] + diff --git a/src/lib/exchanges.ts b/src/lib/exchanges.ts index e19d8435..09444830 100644 --- a/src/lib/exchanges.ts +++ b/src/lib/exchanges.ts @@ -1,3 +1,5 @@ +import { okx } from '../exchanges/okx' +import { okxBaseSpecs } from '../exchanges/okx/okxSpecs' import { binance } from '../exchanges/binance' import { binanceBaseSpecs } from '../exchanges/binance/binanceSpecs' import { bitfinex } from '../exchanges/bitfinex' @@ -16,6 +18,7 @@ import { valrBaseSpecs } from '../exchanges/valr/valrSpecs' export const exchanges = { + [okxBaseSpecs.id]: okx, [poloniexBaseSpecs.id]: poloniex, [binanceBaseSpecs.id]: binance, [bitmexBaseSpecs.id]: bitmex, From 2b4780979e2b8281e7947f58fff10a6e9128be84 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Wed, 18 May 2022 22:01:17 -0300 Subject: [PATCH 002/105] Add Okx Symbol schema and fixtures --- src/exchanges/okx/schemas/IOkxSymbolSchema.ts | 29 +++++- src/exchanges/okx/test/fixtures/okxSymbols.ts | 93 +++++++++++++++++-- test/macros/testCache.ts | 10 +- 3 files changed, 120 insertions(+), 12 deletions(-) diff --git a/src/exchanges/okx/schemas/IOkxSymbolSchema.ts b/src/exchanges/okx/schemas/IOkxSymbolSchema.ts index a18d5558..e4f3072c 100644 --- a/src/exchanges/okx/schemas/IOkxSymbolSchema.ts +++ b/src/exchanges/okx/schemas/IOkxSymbolSchema.ts @@ -1,6 +1,27 @@ -// TODO: Describe symbol interface for Okx exchange +import { OkxSymbolStatusEnum } from '../enums/OkxSymbolStatusEnum' + + + export interface IOkxSymbolSchema { - symbol: string - name: string - // ... + instType: string + instId: string + uly: string + category: string + baseCcy: string + quoteCcy: string + settleCcy: string + ctVal: string + ctMult: string + ctValCcy: string + optType: string + stk: string + listTime: string + expTime: string + lever: string + tickSz: string + lotSz: string + minSz: string + ctType: string + alias: string + state: OkxSymbolStatusEnum } diff --git a/src/exchanges/okx/test/fixtures/okxSymbols.ts b/src/exchanges/okx/test/fixtures/okxSymbols.ts index b2f76519..6d83b861 100644 --- a/src/exchanges/okx/test/fixtures/okxSymbols.ts +++ b/src/exchanges/okx/test/fixtures/okxSymbols.ts @@ -1,3 +1,4 @@ +import { OkxSymbolStatusEnum } from '../../enums/OkxSymbolStatusEnum' import { IOkxSymbolSchema } from '../../schemas/IOkxSymbolSchema' @@ -5,16 +6,96 @@ import { IOkxSymbolSchema } from '../../schemas/IOkxSymbolSchema' // TODO: Review fixtures export const OKX_RAW_SYMBOLS: IOkxSymbolSchema[] = [ { - symbol: 'BTC', - name: 'Bitcoin', + alias: '', + baseCcy: 'BTC', + category: '1', + ctMult: '', + ctType: '', + ctVal: '', + ctValCcy: '', + expTime: '', + instId: 'BTC-USDT', + instType: 'SPOT', + lever: '10', + listTime: '1548133413000', + lotSz: '0.00000001', + minSz: '0.00001', + optType: '', + quoteCcy: 'USDT', + settleCcy: '', + state: OkxSymbolStatusEnum.LIVE, + stk: '', + tickSz: '0.1', + uly: '', }, { - symbol: 'LTC', - name: 'Litecoin', + alias: '', + baseCcy: 'ETH', + category: '1', + ctMult: '', + ctType: '', + ctVal: '', + ctValCcy: '', + expTime: '', + instId: 'ETH-USDT', + instType: 'SPOT', + lever: '10', + listTime: '1548133413000', + lotSz: '0.000001', + minSz: '0.001', + optType: '', + quoteCcy: 'USDT', + settleCcy: '', + state: OkxSymbolStatusEnum.LIVE, + stk: '', + tickSz: '0.01', + uly: '', }, { - symbol: 'ETH', - name: 'Ethereum', + alias: '', + baseCcy: 'USDT', + category: '2', + ctMult: '', + ctType: '', + ctVal: '', + ctValCcy: '', + expTime: '', + instId: 'USDT-DOGE', + instType: 'SPOT', + lever: '5', + listTime: '1548133413000', + lotSz: '0.000001', + minSz: '10', + optType: '', + quoteCcy: 'DOGE', + settleCcy: '', + state: OkxSymbolStatusEnum.PREOPEN, + stk: '', + tickSz: '0.000001', + uly: '', + }, + { + alias: '', + baseCcy: 'USDT', + category: '2', + ctMult: '', + ctType: '', + ctVal: '', + ctValCcy: '', + expTime: '', + instId: 'USDT-BNB', + instType: 'SPOT', + lever: '5', + listTime: '1548133413000', + lotSz: '0.000001', + minSz: '10', + optType: '', + quoteCcy: 'BNB', + settleCcy: '', + state: OkxSymbolStatusEnum.SUSPEND, + stk: '', + tickSz: '0.000001', + uly: '', }, ] diff --git a/test/macros/testCache.ts b/test/macros/testCache.ts index 0aa873ad..371f4b00 100644 --- a/test/macros/testCache.ts +++ b/test/macros/testCache.ts @@ -9,13 +9,17 @@ import { mockAxiosRequest } from '../mocks/axios/request' export interface ITestCacheParams { HttpClass: any // class constructor + useObjectAsResponse?: boolean } export const testCache = async (params: ITestCacheParams) => { - const { HttpClass } = params + const { + HttpClass, + useObjectAsResponse = false, + } = params const requestParams: IAlunaHttpPublicParams = { url: 'http://someurl', @@ -23,7 +27,9 @@ export const testCache = async (params: ITestCacheParams) => { verb: AlunaHttpVerbEnum.GET, } - const response = 'mocked response' + const response = useObjectAsResponse + ? { data: 'mocked response' } + : 'mocked response' From ffaa3c9f9e3afbae3161919a59c771b576a1be35 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Wed, 18 May 2022 22:01:50 -0300 Subject: [PATCH 003/105] Add Okx Market schema and fixtures --- src/exchanges/okx/schemas/IOkxMarketSchema.ts | 26 +++++--- src/exchanges/okx/test/fixtures/okxMarket.ts | 62 ++++++++++++++----- 2 files changed, 64 insertions(+), 24 deletions(-) diff --git a/src/exchanges/okx/schemas/IOkxMarketSchema.ts b/src/exchanges/okx/schemas/IOkxMarketSchema.ts index d2e6f29e..5445e036 100644 --- a/src/exchanges/okx/schemas/IOkxMarketSchema.ts +++ b/src/exchanges/okx/schemas/IOkxMarketSchema.ts @@ -1,12 +1,18 @@ -import { OkxMarketStatusEnum } from '../enums/OkxMarketStatusEnum' - - - -// TODO: Describe market interface for Okx exchange export interface IOkxMarketSchema { - symbol: string - volume: string - quoteVolume: string - status: OkxMarketStatusEnum - // ... + instType: string + instId: string + last: string + lastSz: string + askPx: string + askSz: string + bidPx: string + bidSz: string + open24h: string + high24h: string + low24h: string + volCcy24h: string + vol24h: string + ts: string + sodUtc0: string + sodUtc8: string } diff --git a/src/exchanges/okx/test/fixtures/okxMarket.ts b/src/exchanges/okx/test/fixtures/okxMarket.ts index baf6421d..24ce3015 100644 --- a/src/exchanges/okx/test/fixtures/okxMarket.ts +++ b/src/exchanges/okx/test/fixtures/okxMarket.ts @@ -1,27 +1,61 @@ -import { OkxMarketStatusEnum } from '../../enums/OkxMarketStatusEnum' import { IOkxMarketSchema } from '../../schemas/IOkxMarketSchema' -// TODO: Review fixtures export const OKX_RAW_MARKETS: IOkxMarketSchema[] = [ { - symbol: 'BTC-EUR', - volume: '9.64127008', - quoteVolume: '311825.04145095', - status: OkxMarketStatusEnum.ONLINE, + instType: 'SPOT', + instId: 'BTC-USDT', + last: '41731.5', + lastSz: '0.01201826', + askPx: '41731.6', + askSz: '1.972391', + bidPx: '41731.5', + bidSz: '0.06468108', + open24h: '41524.1', + high24h: '42207.4', + low24h: '41173.8', + volCcy24h: '203402708.53788367', + vol24h: '4886.88830413', + ts: '1650464219049', + sodUtc0: '41489.3', + sodUtc8: '41447.1', }, { - symbol: 'BTC-USD', - volume: '126.19108264', - quoteVolume: '4696409.21649740', - status: OkxMarketStatusEnum.ONLINE, + instType: 'SPOT', + instId: 'ETH-USDT', + last: '3109.74', + lastSz: '0.004685', + askPx: '3109.58', + askSz: '1.780076', + bidPx: '3109.57', + bidSz: '0.645996', + open24h: '3115.65', + high24h: '3168.68', + low24h: '3066.65', + volCcy24h: '177823447.502698', + vol24h: '57002.622196', + ts: '1650464218941', + sodUtc0: '3101.7', + sodUtc8: '3110.2', }, { - symbol: 'BTC-USDT', - volume: '86.32449650', - quoteVolume: '3191538.22788546', - status: OkxMarketStatusEnum.OFFLINE, + instType: 'SPOT', + instId: 'DOGE-USDT', + last: '0.142245', + lastSz: '10000', + askPx: '0.14226', + askSz: '549.80489', + bidPx: '0.142259', + bidSz: '47656.463242', + open24h: '0.1422', + high24h: '0.1467', + low24h: '0.140509', + volCcy24h: '95103538.54248', + vol24h: '662129472.622064', + ts: '1650463466504', + sodUtc0: '0.142577', + sodUtc8: '0.142842', }, ] From f614bd45e9e240db788f3dae78954e10d9f86eec Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Wed, 18 May 2022 22:02:05 -0300 Subject: [PATCH 004/105] Add Okx Key schema and fixtures --- src/exchanges/okx/schemas/IOkxKeySchema.ts | 14 ++++++++++++-- src/exchanges/okx/test/fixtures/okxKey.ts | 17 +++++++++++++++-- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/src/exchanges/okx/schemas/IOkxKeySchema.ts b/src/exchanges/okx/schemas/IOkxKeySchema.ts index 10e55572..deff4900 100644 --- a/src/exchanges/okx/schemas/IOkxKeySchema.ts +++ b/src/exchanges/okx/schemas/IOkxKeySchema.ts @@ -1,8 +1,18 @@ -// TODO: Describe key interface for Okx exchange +export interface IOkxKeyAccountSchema { + acctLv: string + autoLoan: boolean + ctIsoMode: string + greeksType: string + level: string + levelTmp: string + mgnIsoMode: string + posMode: string + uid: string +} + export interface IOkxKeySchema { read: boolean trade: boolean withdraw: boolean accountId?: string - // ... } diff --git a/src/exchanges/okx/test/fixtures/okxKey.ts b/src/exchanges/okx/test/fixtures/okxKey.ts index 67c7bd02..6eb4ee0a 100644 --- a/src/exchanges/okx/test/fixtures/okxKey.ts +++ b/src/exchanges/okx/test/fixtures/okxKey.ts @@ -1,9 +1,22 @@ -import { IOkxKeySchema } from '../../schemas/IOkxKeySchema' +import { IOkxKeyAccountSchema, IOkxKeySchema } from '../../schemas/IOkxKeySchema' export const OKX_KEY_PERMISSIONS: IOkxKeySchema = { read: true, trade: true, - withdraw: true, + withdraw: false, + accountId: '781767883763712', +} + +export const OKX_KEY_ACCOUNT_PERMISSIONS: IOkxKeyAccountSchema = { + acctLv: '3', + autoLoan: true, + ctIsoMode: 'automatic', + greeksType: 'PA', + level: 'Lv3', + levelTmp: '', + mgnIsoMode: 'autonomy', + posMode: 'net_mode', + uid: '781767883763712', } From 38d08aaf6c93a2b44a200450dd681b912c1d3629 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Wed, 18 May 2022 22:02:19 -0300 Subject: [PATCH 005/105] Add Okx Balance schema and fixtures --- .../okx/schemas/IOkxBalanceSchema.ts | 41 ++++++- .../okx/test/fixtures/okxBalances.ts | 104 ++++++++++++++++-- 2 files changed, 130 insertions(+), 15 deletions(-) diff --git a/src/exchanges/okx/schemas/IOkxBalanceSchema.ts b/src/exchanges/okx/schemas/IOkxBalanceSchema.ts index 723a3cac..109e73e6 100644 --- a/src/exchanges/okx/schemas/IOkxBalanceSchema.ts +++ b/src/exchanges/okx/schemas/IOkxBalanceSchema.ts @@ -1,7 +1,38 @@ -// TODO: Describe balance interface for Okx exchange export interface IOkxBalanceSchema { - total: string - available: string - currencySymbol: string - // ... + availBal: string + availEq: string + cashBal: string + ccy: string + crossLiab: string + disEq: string + eq: string + eqUsd: string + frozenBal: string + interest: string + isoEq: string + isoLiab: string + isoUpl: string + liab: string + maxLoan: string + mgnRatio: string + notionalLever: string + ordFrozen: string + twap: string + uTime: string + upl: string + uplLiab: string + stgyEq: string +} + +export interface IOkxBalanceListSchema { + adjEq: string + details: IOkxBalanceSchema[] + imr: string + isoEq: string + mgnRatio: string + mmr: string + notionalUsd: string + ordFroz: string + totalEq: string + uTime: string } diff --git a/src/exchanges/okx/test/fixtures/okxBalances.ts b/src/exchanges/okx/test/fixtures/okxBalances.ts index 3e0cce25..8bb7f093 100644 --- a/src/exchanges/okx/test/fixtures/okxBalances.ts +++ b/src/exchanges/okx/test/fixtures/okxBalances.ts @@ -2,22 +2,106 @@ import { IOkxBalanceSchema } from '../../schemas/IOkxBalanceSchema' -// TODO: Review fixtures export const OKX_RAW_BALANCES: IOkxBalanceSchema[] = [ { - total: '1500.00000000', - available: '1500.00000000', - currencySymbol: 'BTC', + availBal: '2', + availEq: '1', + cashBal: '1', + ccy: 'USDT', + crossLiab: '0', + disEq: '2', + eq: '1', + eqUsd: '2', + frozenBal: '0', + interest: '0', + isoEq: '0', + isoLiab: '0', + isoUpl: '0', + liab: '0', + maxLoan: '10000', + mgnRatio: '', + notionalLever: '', + ordFrozen: '0', + twap: '0', + uTime: '1620722938250', + upl: '0', + uplLiab: '0', + stgyEq: '0', }, { - total: '32.00000000', - available: '32.00000000', - currencySymbol: 'BTC', + availBal: '3', + availEq: '2', + cashBal: '1', + ccy: 'BTC', + crossLiab: '0', + disEq: '2', + eq: '1', + eqUsd: '2', + frozenBal: '2', + interest: '0', + isoEq: '0', + isoLiab: '0', + isoUpl: '0', + liab: '0', + maxLoan: '10000', + mgnRatio: '', + notionalLever: '', + ordFrozen: '0', + twap: '0', + uTime: '1620722938250', + upl: '0', + uplLiab: '0', + stgyEq: '0', }, { - total: '11.00000000', - available: '11.00000000', - currencySymbol: 'BTC', + availBal: '6', + availEq: '6', + cashBal: '1', + ccy: 'ETH', + crossLiab: '0', + disEq: '2', + eq: '1', + eqUsd: '2', + frozenBal: '3', + interest: '0', + isoEq: '0', + isoLiab: '0', + isoUpl: '0', + liab: '0', + maxLoan: '10000', + mgnRatio: '', + notionalLever: '', + ordFrozen: '0', + twap: '0', + uTime: '1620722938250', + upl: '0', + uplLiab: '0', + stgyEq: '0', + }, + { + availBal: '0', + availEq: '0', + cashBal: '0', + ccy: 'LTC', + crossLiab: '0', + disEq: '0', + eq: '1', + eqUsd: '0', + frozenBal: '0', + interest: '0', + isoEq: '0', + isoLiab: '0', + isoUpl: '0', + liab: '0', + maxLoan: '10000', + mgnRatio: '', + notionalLever: '', + ordFrozen: '0', + twap: '0', + uTime: '1620722938250', + upl: '0', + uplLiab: '0', + stgyEq: '0', }, ] From 17e2f0ba4bb71a740d2fa705ce38e551bef426d6 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Wed, 18 May 2022 22:02:40 -0300 Subject: [PATCH 006/105] Add Okx Symbol parse many and test suite --- .../modules/public/symbol/parseMany.spec.ts | 8 ++-- .../okx/modules/public/symbol/parseMany.ts | 48 +++++++++++++++++-- 2 files changed, 48 insertions(+), 8 deletions(-) diff --git a/src/exchanges/okx/modules/public/symbol/parseMany.spec.ts b/src/exchanges/okx/modules/public/symbol/parseMany.spec.ts index b6958b81..7ed4d99a 100644 --- a/src/exchanges/okx/modules/public/symbol/parseMany.spec.ts +++ b/src/exchanges/okx/modules/public/symbol/parseMany.spec.ts @@ -16,7 +16,9 @@ describe(__filename, () => { // mocking const { parse } = mockParse({ module: parseMod }) - each(PARSED_SYMBOLS, (symbol, index) => { + const parsedSymbols = [...PARSED_SYMBOLS, PARSED_SYMBOLS[0]] + + each(parsedSymbols, (symbol, index) => { parse.onCall(index).returns({ symbol }) }) @@ -30,8 +32,8 @@ describe(__filename, () => { // validating - expect(parse.callCount).to.be.eq(OKX_RAW_SYMBOLS.length) - expect(symbols.length).to.be.eq(OKX_RAW_SYMBOLS.length) + expect(parse.callCount).to.be.eq(OKX_RAW_SYMBOLS.length - 1) + expect(symbols.length).to.be.eq(OKX_RAW_SYMBOLS.length - 1) }) diff --git a/src/exchanges/okx/modules/public/symbol/parseMany.ts b/src/exchanges/okx/modules/public/symbol/parseMany.ts index f3769e39..d990b4fb 100644 --- a/src/exchanges/okx/modules/public/symbol/parseMany.ts +++ b/src/exchanges/okx/modules/public/symbol/parseMany.ts @@ -1,11 +1,17 @@ import debug from 'debug' -import { map } from 'lodash' +import { + each, + filter, + values, +} from 'lodash' import { IAlunaExchangePublic } from '../../../../../lib/core/IAlunaExchange' import { IAlunaSymbolParseManyParams, IAlunaSymbolParseManyReturns, } from '../../../../../lib/modules/public/IAlunaSymbolModule' +import { IAlunaSymbolSchema } from '../../../../../lib/schemas/IAlunaSymbolSchema' +import { OkxSymbolStatusEnum } from '../../../enums/OkxSymbolStatusEnum' import { IOkxSymbolSchema } from '../../../schemas/IOkxSymbolSchema' @@ -20,15 +26,47 @@ export const parseMany = (exchange: IAlunaExchangePublic) => ( const { rawSymbols } = params - // TODO: Review implementation - const symbols = map(rawSymbols, (rawSymbol) => { + const parsedSymbolsDict: Record = {} + + const filteredRawActiveSymbols = filter( + rawSymbols, + { + state: OkxSymbolStatusEnum.LIVE || OkxSymbolStatusEnum.PREOPEN, + }, + ) + + each(filteredRawActiveSymbols, (rawSymbol) => { + + const { + baseCcy, + quoteCcy, + } = rawSymbol + + if (!parsedSymbolsDict[baseCcy]) { + + const { symbol } = exchange.symbol.parse({ rawSymbol }) - const { symbol } = exchange.symbol.parse({ rawSymbol }) + parsedSymbolsDict[baseCcy] = symbol - return symbol + } + + if (!parsedSymbolsDict[quoteCcy]) { + + const { symbol } = exchange.symbol.parse({ + rawSymbol: { + ...rawSymbol, + baseCcy: quoteCcy, + }, + }) + + parsedSymbolsDict[quoteCcy] = symbol + + } }) + const symbols = values(parsedSymbolsDict) + log(`parsed ${symbols.length} symbols for Okx`) return { symbols } From b3bdbc24e4f495cb227bc7eefc968db75af98932 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Wed, 18 May 2022 22:02:50 -0300 Subject: [PATCH 007/105] Add Okx Symbol parse and test suite --- .../okx/modules/public/symbol/parse.spec.ts | 16 +++++++--------- src/exchanges/okx/modules/public/symbol/parse.ts | 9 +++------ 2 files changed, 10 insertions(+), 15 deletions(-) diff --git a/src/exchanges/okx/modules/public/symbol/parse.spec.ts b/src/exchanges/okx/modules/public/symbol/parse.spec.ts index 0b074175..fea813b7 100644 --- a/src/exchanges/okx/modules/public/symbol/parse.spec.ts +++ b/src/exchanges/okx/modules/public/symbol/parse.spec.ts @@ -8,14 +8,14 @@ import { OKX_RAW_SYMBOLS } from '../../../test/fixtures/okxSymbols' -describe.skip(__filename, () => { +describe(__filename, () => { it('should parse a Okx symbol just fine (w/ alias)', async () => { // preparing data const rawSymbol = OKX_RAW_SYMBOLS[0] // first fixture - const translatedSymbolId = 'XBT' + const translatedSymbolId = 'USDT' // mocking @@ -36,13 +36,12 @@ describe.skip(__filename, () => { // validating expect(symbol.exchangeId).to.be.eq(okxBaseSpecs.id) expect(symbol.id).to.be.eq(translatedSymbolId) - expect(symbol.name).to.be.eq(rawSymbol.name) - expect(symbol.alias).to.be.eq(rawSymbol.symbol) // should be equal + expect(symbol.alias).to.be.eq(rawSymbol.baseCcy) // should be equal expect(symbol.meta).to.be.eq(rawSymbol) expect(translateSymbolId.callCount).to.be.eq(1) expect(translateSymbolId.firstCall.args[0]).to.deep.eq({ - exchangeSymbolId: rawSymbol.symbol, + exchangeSymbolId: rawSymbol.baseCcy, symbolMappings: settings.symbolMappings, }) @@ -59,7 +58,7 @@ describe.skip(__filename, () => { // mocking const { translateSymbolId } = mockTranslateSymbolId() - translateSymbolId.returns(rawSymbol.symbol) + translateSymbolId.returns(rawSymbol.baseCcy) // executing @@ -70,15 +69,14 @@ describe.skip(__filename, () => { // validating expect(symbol.exchangeId).to.be.eq(okxBaseSpecs.id) - expect(symbol.id).to.be.eq(rawSymbol.symbol) - expect(symbol.name).to.be.eq(rawSymbol.name) + expect(symbol.id).to.be.eq(rawSymbol.baseCcy) expect(symbol.alias).to.be.eq(undefined) // different = undefined expect(symbol.meta).to.be.eq(rawSymbol) expect(translateSymbolId.callCount).to.be.eq(1) expect(translateSymbolId.firstCall.args[0]).to.deep.eq({ - exchangeSymbolId: rawSymbol.symbol, + exchangeSymbolId: rawSymbol.baseCcy, symbolMappings: undefined, }) diff --git a/src/exchanges/okx/modules/public/symbol/parse.ts b/src/exchanges/okx/modules/public/symbol/parse.ts index e78dbf70..76d21b87 100644 --- a/src/exchanges/okx/modules/public/symbol/parse.ts +++ b/src/exchanges/okx/modules/public/symbol/parse.ts @@ -17,21 +17,18 @@ export const parse = (exchange: IAlunaExchangePublic) => ( const { rawSymbol } = params const { - name, - symbol, + baseCcy, } = rawSymbol const id = translateSymbolId({ - exchangeSymbolId: symbol, + exchangeSymbolId: baseCcy, symbolMappings: exchange.settings.symbolMappings, }) - const alias = (id !== symbol ? symbol : undefined) + const alias = (id !== baseCcy ? baseCcy : undefined) - // TODO: Review symbol assembling const parsedSymbol: IAlunaSymbolSchema = { id, - name, alias, exchangeId: okxBaseSpecs.id, meta: rawSymbol, From f5a344026ba7d7cf37749698ef358e537c46b001 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Wed, 18 May 2022 22:03:06 -0300 Subject: [PATCH 008/105] Add Okx Symbol list raw and test suite --- src/exchanges/okx/modules/public/symbol/listRaw.spec.ts | 7 ++++++- src/exchanges/okx/modules/public/symbol/listRaw.ts | 6 ++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/exchanges/okx/modules/public/symbol/listRaw.spec.ts b/src/exchanges/okx/modules/public/symbol/listRaw.spec.ts index b0f2b479..53eea3c7 100644 --- a/src/exchanges/okx/modules/public/symbol/listRaw.spec.ts +++ b/src/exchanges/okx/modules/public/symbol/listRaw.spec.ts @@ -1,6 +1,7 @@ import { expect } from 'chai' import { mockHttp } from '../../../../../../test/mocks/exchange/Http' +import { OkxSymbolTypeEnum } from '../../../enums/OkxSymbolTypeEnum' import { Okx } from '../../../Okx' import { OkxHttp } from '../../../OkxHttp' import { getOkxEndpoints } from '../../../okxSpecs' @@ -12,6 +13,10 @@ describe(__filename, () => { it('should list Okx raw symbols just fine', async () => { + // preparing data + + const type = OkxSymbolTypeEnum.SPOT + // mocking const { publicRequest, @@ -38,7 +43,7 @@ describe(__filename, () => { expect(publicRequest.callCount).to.be.eq(1) expect(publicRequest.firstCall.args[0]).to.deep.eq({ - url: getOkxEndpoints(exchange.settings).symbol.list, + url: getOkxEndpoints(exchange.settings).symbol.list(type), }) expect(authedRequest.callCount).to.be.eq(0) diff --git a/src/exchanges/okx/modules/public/symbol/listRaw.ts b/src/exchanges/okx/modules/public/symbol/listRaw.ts index 83029723..816ec214 100644 --- a/src/exchanges/okx/modules/public/symbol/listRaw.ts +++ b/src/exchanges/okx/modules/public/symbol/listRaw.ts @@ -5,6 +5,7 @@ import { IAlunaSymbolListParams, IAlunaSymbolListRawReturns, } from '../../../../../lib/modules/public/IAlunaSymbolModule' +import { OkxSymbolTypeEnum } from '../../../enums/OkxSymbolTypeEnum' import { OkxHttp } from '../../../OkxHttp' import { getOkxEndpoints } from '../../../okxSpecs' import { IOkxSymbolSchema } from '../../../schemas/IOkxSymbolSchema' @@ -25,9 +26,10 @@ export const listRaw = (exchange: IAlunaExchangePublic) => async ( const { http = new OkxHttp(settings) } = params - // TODO: Implement proper request + const type = OkxSymbolTypeEnum.SPOT + const rawSymbols = await http.publicRequest({ - url: getOkxEndpoints(settings).symbol.list, + url: getOkxEndpoints(settings).symbol.list(type), }) const { requestWeight } = http From 9657a0d6f5b183fdb68b40ac678fe1c9ed05991a Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Wed, 18 May 2022 22:03:18 -0300 Subject: [PATCH 009/105] Add Okx Market parse many --- src/exchanges/okx/modules/public/market/parseMany.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/exchanges/okx/modules/public/market/parseMany.ts b/src/exchanges/okx/modules/public/market/parseMany.ts index 67ede325..4e670bda 100644 --- a/src/exchanges/okx/modules/public/market/parseMany.ts +++ b/src/exchanges/okx/modules/public/market/parseMany.ts @@ -20,7 +20,6 @@ export const parseMany = (exchange: IAlunaExchangePublic) => ( const { rawMarkets } = params - // TODO: Review implementation const markets = map(rawMarkets, (rawMarket) => { const { market } = exchange.market.parse({ From bc963ad18848d51ab5b34e65d1ef952ab2b54d02 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Wed, 18 May 2022 22:03:29 -0300 Subject: [PATCH 010/105] Add Okx Market parse and test suite --- .../okx/modules/public/market/parse.spec.ts | 44 +++++++++++++- .../okx/modules/public/market/parse.ts | 58 ++++++++++++++++++- 2 files changed, 97 insertions(+), 5 deletions(-) diff --git a/src/exchanges/okx/modules/public/market/parse.spec.ts b/src/exchanges/okx/modules/public/market/parse.spec.ts index 8e2be8dc..9d43e37a 100644 --- a/src/exchanges/okx/modules/public/market/parse.spec.ts +++ b/src/exchanges/okx/modules/public/market/parse.spec.ts @@ -6,19 +6,37 @@ import { OKX_RAW_MARKETS } from '../../../test/fixtures/okxMarket' -describe.skip(__filename, () => { +describe(__filename, () => { it('should parse a Okx raw market just fine', async () => { // preparing data - const translatedSymbolId = 'BTC' const rawMarket = OKX_RAW_MARKETS[0] + const { + askPx, + bidPx, + high24h, + instId, + last, + low24h, + open24h, + vol24h, + volCcy24h, + } = rawMarket + + const [ + baseCurrency, + quoteCurrency, + ] = instId.split('-') + + const change = Number(open24h) - Number(last) // mocking const { translateSymbolId } = mockTranslateSymbolId() - translateSymbolId.returns(translatedSymbolId) + translateSymbolId.onFirstCall().returns(baseCurrency) + translateSymbolId.onSecondCall().returns(quoteCurrency) // executing @@ -33,6 +51,26 @@ describe.skip(__filename, () => { // TODO: add proper validations expect(market).to.exist + + expect(market.exchangeId).to.be.eq(exchange.specs.id) + expect(market.baseSymbolId).to.be.eq(baseCurrency) + expect(market.quoteSymbolId).to.be.eq(quoteCurrency) + expect(market.symbolPair).to.be.eq(instId) + + expect(market.ticker.ask).to.be.eq(Number(askPx)) + expect(market.ticker.bid).to.be.eq(Number(bidPx)) + expect(market.ticker.high).to.be.eq(Number(high24h)) + expect(market.ticker.low).to.be.eq(Number(low24h)) + expect(market.ticker.last).to.be.eq(Number(last)) + expect(market.ticker.change).to.be.eq(change) + expect(market.ticker.baseVolume).to.be.eq(Number(vol24h)) + expect(market.ticker.quoteVolume).to.be.eq(Number(volCcy24h)) + + expect(market.spotEnabled).to.be.ok + expect(market.marginEnabled).not.to.be.ok + expect(market.leverageEnabled).not.to.be.ok + expect(market.derivativesEnabled).not.to.be.ok + }) }) diff --git a/src/exchanges/okx/modules/public/market/parse.ts b/src/exchanges/okx/modules/public/market/parse.ts index 3e2d5d43..e0848419 100644 --- a/src/exchanges/okx/modules/public/market/parse.ts +++ b/src/exchanges/okx/modules/public/market/parse.ts @@ -4,6 +4,7 @@ import { IAlunaMarketParseReturns, } from '../../../../../lib/modules/public/IAlunaMarketModule' import { IAlunaMarketSchema } from '../../../../../lib/schemas/IAlunaMarketSchema' +import { translateSymbolId } from '../../../../../utils/mappings/translateSymbolId' import { IOkxMarketSchema } from '../../../schemas/IOkxMarketSchema' @@ -14,8 +15,61 @@ export const parse = (exchange: IAlunaExchangePublic) => ( const { rawMarket } = params - // TODO: Implement proper parser - const market: IAlunaMarketSchema = rawMarket as any + const { + askPx, + bidPx, + high24h, + instId, + last, + low24h, + open24h, + vol24h, + volCcy24h, + } = rawMarket + + const [ + baseCurrency, + quoteCurrency, + ] = instId.split('-') + + const { symbolMappings } = exchange.settings + + const baseSymbolId = translateSymbolId({ + exchangeSymbolId: baseCurrency, + symbolMappings, + }) + + const quoteSymbolId = translateSymbolId({ + exchangeSymbolId: quoteCurrency, + symbolMappings, + }) + + const change = Number(open24h) - Number(last) + + const ticker = { + high: Number(high24h), + low: Number(low24h), + bid: Number(bidPx), + ask: Number(askPx), + last: Number(last), + date: new Date(), + change, + baseVolume: Number(vol24h), + quoteVolume: Number(volCcy24h), + } + + const market: IAlunaMarketSchema = { + symbolPair: instId, + baseSymbolId, + quoteSymbolId, + ticker, + exchangeId: exchange.specs.id, + spotEnabled: true, + derivativesEnabled: false, + leverageEnabled: false, + marginEnabled: false, + meta: rawMarket, + } return { market } From fc1677b2f6718dba244fb6ba0a45d832794def63 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Wed, 18 May 2022 22:03:41 -0300 Subject: [PATCH 011/105] Add Okx Market list raw and test suite --- src/exchanges/okx/modules/public/market/listRaw.spec.ts | 7 ++++++- src/exchanges/okx/modules/public/market/listRaw.ts | 6 ++++-- 2 files changed, 10 insertions(+), 3 deletions(-) diff --git a/src/exchanges/okx/modules/public/market/listRaw.spec.ts b/src/exchanges/okx/modules/public/market/listRaw.spec.ts index d06085ce..62801ea6 100644 --- a/src/exchanges/okx/modules/public/market/listRaw.spec.ts +++ b/src/exchanges/okx/modules/public/market/listRaw.spec.ts @@ -1,6 +1,7 @@ import { expect } from 'chai' import { mockHttp } from '../../../../../../test/mocks/exchange/Http' +import { OkxSymbolTypeEnum } from '../../../enums/OkxSymbolTypeEnum' import { Okx } from '../../../Okx' import { OkxHttp } from '../../../OkxHttp' import { getOkxEndpoints } from '../../../okxSpecs' @@ -12,6 +13,10 @@ describe(__filename, () => { it('should list Okx raw markets just fine', async () => { + // preparing data + + const type = OkxSymbolTypeEnum.SPOT + // mocking const { publicRequest, @@ -38,7 +43,7 @@ describe(__filename, () => { expect(publicRequest.callCount).to.be.eq(1) expect(publicRequest.firstCall.args[0]).to.deep.eq({ - url: getOkxEndpoints(exchange.settings).market.list, + url: getOkxEndpoints(exchange.settings).market.list(type), }) expect(authedRequest.callCount).to.be.eq(0) diff --git a/src/exchanges/okx/modules/public/market/listRaw.ts b/src/exchanges/okx/modules/public/market/listRaw.ts index 78b6481b..3e568b19 100644 --- a/src/exchanges/okx/modules/public/market/listRaw.ts +++ b/src/exchanges/okx/modules/public/market/listRaw.ts @@ -5,6 +5,7 @@ import { IAlunaMarketListParams, IAlunaMarketListRawReturns, } from '../../../../../lib/modules/public/IAlunaMarketModule' +import { OkxSymbolTypeEnum } from '../../../enums/OkxSymbolTypeEnum' import { OkxHttp } from '../../../OkxHttp' import { getOkxEndpoints } from '../../../okxSpecs' import { IOkxMarketSchema } from '../../../schemas/IOkxMarketSchema' @@ -25,9 +26,10 @@ export const listRaw = (exchange: IAlunaExchangePublic) => async ( log('fetching Okx markets') - // TODO: Implement proper request + const type = OkxSymbolTypeEnum.SPOT + const rawMarkets = await http.publicRequest({ - url: getOkxEndpoints(settings).market.list, + url: getOkxEndpoints(settings).market.list(type), }) const { requestWeight } = http From 40bcba31cd75923c8fd84aa6e7108c19d72942b8 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Wed, 18 May 2022 22:04:01 -0300 Subject: [PATCH 012/105] Add Okx Key fetch details and test suite --- .../modules/authed/key/fetchDetails.spec.ts | 150 +++++++++++++++++- .../okx/modules/authed/key/fetchDetails.ts | 61 ++++++- 2 files changed, 203 insertions(+), 8 deletions(-) diff --git a/src/exchanges/okx/modules/authed/key/fetchDetails.spec.ts b/src/exchanges/okx/modules/authed/key/fetchDetails.spec.ts index 1d857063..d252c143 100644 --- a/src/exchanges/okx/modules/authed/key/fetchDetails.spec.ts +++ b/src/exchanges/okx/modules/authed/key/fetchDetails.spec.ts @@ -1,15 +1,18 @@ import { expect } from 'chai' +import { cloneDeep } from 'lodash' import { mockHttp } from '../../../../../../test/mocks/exchange/Http' import { mockParseDetails } from '../../../../../../test/mocks/exchange/modules/key/mockParseDetails' +import { AlunaError } from '../../../../../lib/core/AlunaError' import { AlunaHttpVerbEnum } from '../../../../../lib/enums/AlunaHtttpVerbEnum' import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' import { IAlunaKeySchema } from '../../../../../lib/schemas/IAlunaKeySchema' import { OkxAuthed } from '../../../OkxAuthed' import { OkxHttp } from '../../../OkxHttp' import { getOkxEndpoints } from '../../../okxSpecs' -import { OKX_KEY_PERMISSIONS } from '../../../test/fixtures/okxKey' +import { OKX_KEY_ACCOUNT_PERMISSIONS, OKX_KEY_PERMISSIONS } from '../../../test/fixtures/okxKey' import * as parseDetailsMod from './parseDetails' +import { executeAndCatch } from '../../../../../utils/executeAndCatch' @@ -39,7 +42,13 @@ describe(__filename, () => { authedRequest, } = mockHttp({ classPrototype: OkxHttp.prototype }) - authedRequest.returns(Promise.resolve(OKX_KEY_PERMISSIONS)) + authedRequest + .onFirstCall() + .returns(Promise.resolve([OKX_KEY_ACCOUNT_PERMISSIONS])) + + authedRequest + .onSecondCall() + .returns(Promise.resolve([{ sCode: '51116' }])) const { parseDetails } = mockParseDetails({ module: parseDetailsMod, @@ -62,7 +71,7 @@ describe(__filename, () => { expect(requestWeight).to.deep.eq(http.requestWeight) - expect(authedRequest.callCount).to.be.eq(1) + expect(authedRequest.callCount).to.be.eq(2) expect(authedRequest.firstCall.args[0]).to.deep.eq({ verb: AlunaHttpVerbEnum.GET, url: getOkxEndpoints(exchange.settings).key.fetchDetails, @@ -78,4 +87,139 @@ describe(__filename, () => { }) + it('should fetch Okx key details just fine', async () => { + + // preparing data + const credentials: IAlunaCredentialsSchema = { + key: 'key', + secret: 'secret', + } + + const permissions = cloneDeep(OKX_KEY_PERMISSIONS) + + permissions.read = false + permissions.trade = false + permissions.accountId = undefined + + const parsedKey: IAlunaKeySchema = { + accountId: undefined, + permissions, + meta: {}, + } + + // mocking + const http = new OkxHttp({}) + + const alunaError = new AlunaError({ + message: 'dummy-error', + code: '', + metadata: { + code: '50030', + }, + }) + + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + authedRequest + .onFirstCall() + .returns(Promise.reject(alunaError)) + + authedRequest + .onSecondCall() + .returns(Promise.resolve([{ code: '51116' }])) + + const { parseDetails } = mockParseDetails({ + module: parseDetailsMod, + }) + + parseDetails.returns({ key: parsedKey }) + + + // executing + const exchange = new OkxAuthed({ settings: {}, credentials }) + + const { + key, + requestWeight, + } = await exchange.key.fetchDetails() + + + // validating + expect(key).to.be.eq(key) + + expect(requestWeight).to.deep.eq(http.requestWeight) + + expect(authedRequest.callCount).to.be.eq(2) + expect(authedRequest.firstCall.args[0]).to.deep.eq({ + verb: AlunaHttpVerbEnum.GET, + url: getOkxEndpoints(exchange.settings).key.fetchDetails, + credentials, + }) + + + expect(publicRequest.callCount).to.be.eq(0) + + expect(parseDetails.firstCall.args[0]).to.deep.eq({ + rawKey: permissions, + }) + + }) + + it('should throw an error fetching okx key details', async () => { + + // preparing data + const credentials: IAlunaCredentialsSchema = { + key: 'key', + secret: 'secret', + } + + const permissions = cloneDeep(OKX_KEY_PERMISSIONS) + + // mocking + + const alunaError = new AlunaError({ + message: 'dummy-error', + code: '', + metadata: { + code: 'non-existent', + }, + }) + + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + authedRequest + .onFirstCall() + .returns(Promise.reject(alunaError)) + + // executing + const exchange = new OkxAuthed({ settings: {}, credentials }) + + const { + error, + result, + } = await executeAndCatch(() => exchange.key.fetchDetails()) + + + // validating + expect(result).not.to.be.ok + + expect(error).to.deep.eq(alunaError) + + expect(authedRequest.callCount).to.be.eq(1) + expect(authedRequest.firstCall.args[0]).to.deep.eq({ + verb: AlunaHttpVerbEnum.GET, + url: getOkxEndpoints(exchange.settings).key.fetchDetails, + credentials, + }) + + expect(publicRequest.callCount).to.be.eq(0) + + }) + }) diff --git a/src/exchanges/okx/modules/authed/key/fetchDetails.ts b/src/exchanges/okx/modules/authed/key/fetchDetails.ts index 68976351..2763a228 100644 --- a/src/exchanges/okx/modules/authed/key/fetchDetails.ts +++ b/src/exchanges/okx/modules/authed/key/fetchDetails.ts @@ -8,7 +8,7 @@ import { } from '../../../../../lib/modules/authed/IAlunaKeyModule' import { OkxHttp } from '../../../OkxHttp' import { getOkxEndpoints } from '../../../okxSpecs' -import { IOkxKeySchema } from '../../../schemas/IOkxKeySchema' +import { IOkxKeyAccountSchema, IOkxKeySchema } from '../../../schemas/IOkxKeySchema' @@ -29,13 +29,64 @@ export const fetchDetails = (exchange: IAlunaExchangeAuthed) => async ( const { http = new OkxHttp(settings) } = params - // TODO: Implement proper request - const rawKey = await http.authedRequest({ - verb: AlunaHttpVerbEnum.GET, - url: getOkxEndpoints(settings).key.fetchDetails, + const INVALID_PERMISSION_CODE = '50030' + const INVALID_PARAM_CODE = '51116' + + const rawKey: IOkxKeySchema = { + read: false, + trade: false, + withdraw: false, + accountId: undefined, + } + + try { + + const [ + accountConfig, + ] = await http.authedRequest({ + verb: AlunaHttpVerbEnum.GET, + url: getOkxEndpoints(settings).key.fetchDetails, + credentials, + }) + + rawKey.read = true + rawKey.accountId = accountConfig.uid + + } catch (err) { + + if (err.metadata?.code === INVALID_PERMISSION_CODE) { + + rawKey.read = false + + } else { + + throw err + + } + + } + + const requestBody = { + instId: 'BTC-USDT', + tdMode: 'cash', + side: 'buy', + ordType: 'limit', + sz: '1', + px: '123123123123123', + } + + const [order] = await http.authedRequest<{ sCode: string }[]>({ + url: getOkxEndpoints(settings).order.place, credentials, + body: requestBody, }) + if (order.sCode === INVALID_PARAM_CODE) { + + rawKey.trade = true + + } + const { key } = exchange.key.parseDetails({ rawKey }) const { requestWeight } = http From 3f332253cbcc7789a17710dc3660a08444c16804 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Wed, 18 May 2022 22:04:15 -0300 Subject: [PATCH 013/105] Add Okx Balance list raw and test suite --- .../okx/modules/authed/balance/listRaw.spec.ts | 4 +++- src/exchanges/okx/modules/authed/balance/listRaw.ts | 10 +++++++--- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/exchanges/okx/modules/authed/balance/listRaw.spec.ts b/src/exchanges/okx/modules/authed/balance/listRaw.spec.ts index ce36df0c..14a20a47 100644 --- a/src/exchanges/okx/modules/authed/balance/listRaw.spec.ts +++ b/src/exchanges/okx/modules/authed/balance/listRaw.spec.ts @@ -29,7 +29,9 @@ describe(__filename, () => { authedRequest, } = mockHttp({ classPrototype: OkxHttp.prototype }) - authedRequest.returns(Promise.resolve(mockedBalances)) + authedRequest.returns(Promise.resolve([{ + details: mockedBalances, + }])) // executing diff --git a/src/exchanges/okx/modules/authed/balance/listRaw.ts b/src/exchanges/okx/modules/authed/balance/listRaw.ts index ddd57da5..84a41b50 100644 --- a/src/exchanges/okx/modules/authed/balance/listRaw.ts +++ b/src/exchanges/okx/modules/authed/balance/listRaw.ts @@ -8,7 +8,10 @@ import { } from '../../../../../lib/modules/authed/IAlunaBalanceModule' import { OkxHttp } from '../../../OkxHttp' import { getOkxEndpoints } from '../../../okxSpecs' -import { IOkxBalanceSchema } from '../../../schemas/IOkxBalanceSchema' +import { + IOkxBalanceListSchema, + IOkxBalanceSchema, +} from '../../../schemas/IOkxBalanceSchema' @@ -29,13 +32,14 @@ export const listRaw = (exchange: IAlunaExchangeAuthed) => async ( const { http = new OkxHttp(settings) } = params - // TODO: Implement balance 'listRaw' - const rawBalances = await http.authedRequest({ + const rawBalanceInfo = await http.authedRequest({ verb: AlunaHttpVerbEnum.GET, url: getOkxEndpoints(settings).balance.list, credentials, }) + const { details: rawBalances } = rawBalanceInfo[0] + const { requestWeight } = http return { From 13ed4585d52e8be81c24730de5136b54018a1ea8 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Wed, 18 May 2022 22:04:35 -0300 Subject: [PATCH 014/105] Add Okx Balance parse many --- .../okx/modules/authed/balance/parseMany.ts | 32 +++++++++++++++---- 1 file changed, 26 insertions(+), 6 deletions(-) diff --git a/src/exchanges/okx/modules/authed/balance/parseMany.ts b/src/exchanges/okx/modules/authed/balance/parseMany.ts index 4e5e8544..ad82d6a2 100644 --- a/src/exchanges/okx/modules/authed/balance/parseMany.ts +++ b/src/exchanges/okx/modules/authed/balance/parseMany.ts @@ -1,11 +1,12 @@ import { debug } from 'debug' -import { map } from 'lodash' +import { reduce } from 'lodash' import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' import { IAlunaBalanceParseManyParams, IAlunaBalanceParseManyReturns, } from '../../../../../lib/modules/authed/IAlunaBalanceModule' +import { IAlunaBalanceSchema } from '../../../../../lib/schemas/IAlunaBalanceSchema' import { IOkxBalanceSchema } from '../../../schemas/IOkxBalanceSchema' @@ -20,14 +21,33 @@ export const parseMany = (exchange: IAlunaExchangeAuthed) => ( const { rawBalances } = params - // TODO: Review map implementation - const parsedBalances = map(rawBalances, (rawBalance) => { + type TSrc = IOkxBalanceSchema + type TAcc = IAlunaBalanceSchema[] - const { balance } = exchange.balance.parse({ rawBalance }) + const parsedBalances = reduce( + rawBalances, + (accumulator, rawBalance) => { - return balance + const { + availBal, + frozenBal, + } = rawBalance - }) + const total = Number(availBal) + Number(frozenBal) + + if (total > 0) { + + const { balance } = exchange.balance.parse({ rawBalance }) + + accumulator.push(balance) + + } + + + return accumulator + + }, [], + ) log(`parsed ${parsedBalances.length} balances`) From a8820ec779958509e7831fec9254e5b0e4c77d5a Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Wed, 18 May 2022 22:04:44 -0300 Subject: [PATCH 015/105] Add Okx Balance parse and test suite --- .../okx/modules/authed/balance/parse.spec.ts | 24 ++++++++++--- .../okx/modules/authed/balance/parse.ts | 34 +++++++++++++++++-- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/exchanges/okx/modules/authed/balance/parse.spec.ts b/src/exchanges/okx/modules/authed/balance/parse.spec.ts index 2661ed90..b6e68256 100644 --- a/src/exchanges/okx/modules/authed/balance/parse.spec.ts +++ b/src/exchanges/okx/modules/authed/balance/parse.spec.ts @@ -1,5 +1,6 @@ import { expect } from 'chai' +import { AlunaWalletEnum } from '../../../../../lib/enums/AlunaWalletEnum' import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' import { mockTranslateSymbolId } from '../../../../../utils/mappings/translateSymbolId.mock' import { OkxAuthed } from '../../../OkxAuthed' @@ -7,7 +8,7 @@ import { OKX_RAW_BALANCES } from '../../../test/fixtures/okxBalances' -describe.skip(__filename, () => { +describe(__filename, () => { const credentials: IAlunaCredentialsSchema = { key: 'key', @@ -19,10 +20,21 @@ describe.skip(__filename, () => { // preparing data const rawBalance = OKX_RAW_BALANCES[0] + const { + availBal, + frozenBal, + ccy, + } = rawBalance + + const available = Number(availBal) + + const total = available + Number(frozenBal) + // mocking const { translateSymbolId } = mockTranslateSymbolId() - translateSymbolId.returns(rawBalance.currencySymbol) + + translateSymbolId.returns(ccy) // executing const exchange = new OkxAuthed({ credentials }) @@ -33,8 +45,12 @@ describe.skip(__filename, () => { // validating expect(balance).to.exist - // TODO: add expectations for everything - // expect(balance).to.deep.eq(...) + expect(balance.available).to.be.eq(available) + expect(balance.total).to.be.eq(total) + expect(balance.symbolId).to.be.eq(ccy) + expect(balance.wallet).to.be.eq(AlunaWalletEnum.SPOT) + expect(balance.exchangeId).to.be.eq(exchange.specs.id) + expect(balance.meta).to.be.eq(rawBalance) }) diff --git a/src/exchanges/okx/modules/authed/balance/parse.ts b/src/exchanges/okx/modules/authed/balance/parse.ts index 57eb7092..c99c0f0f 100644 --- a/src/exchanges/okx/modules/authed/balance/parse.ts +++ b/src/exchanges/okx/modules/authed/balance/parse.ts @@ -1,9 +1,11 @@ import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { AlunaWalletEnum } from '../../../../../lib/enums/AlunaWalletEnum' import { IAlunaBalanceParseParams, IAlunaBalanceParseReturns, } from '../../../../../lib/modules/authed/IAlunaBalanceModule' import { IAlunaBalanceSchema } from '../../../../../lib/schemas/IAlunaBalanceSchema' +import { translateSymbolId } from '../../../../../utils/mappings/translateSymbolId' import { IOkxBalanceSchema } from '../../../schemas/IOkxBalanceSchema' @@ -12,9 +14,37 @@ export const parse = (exchange: IAlunaExchangeAuthed) => ( params: IAlunaBalanceParseParams, ): IAlunaBalanceParseReturns => { + const { rawBalance } = params - // TODO: Implement balance parse - const balance: IAlunaBalanceSchema = params as any + const { + availBal, + frozenBal, + ccy, + } = rawBalance + + const { settings, specs } = exchange + + const { symbolMappings } = settings + + const { id: exchangeId } = specs + + const available = Number(availBal) + + const total = available + Number(frozenBal) + + const symbolId = translateSymbolId({ + exchangeSymbolId: ccy, + symbolMappings, + }) + + const balance: IAlunaBalanceSchema = { + available, + symbolId, + total, + wallet: AlunaWalletEnum.SPOT, + exchangeId, + meta: rawBalance, + } return { balance } From 240c9bca3279004e77a55ccf571b5b26759e3922 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Wed, 18 May 2022 22:05:13 -0300 Subject: [PATCH 016/105] Add Okx symbol type enum --- src/exchanges/okx/enums/OkxSymbolTypeEnum.ts | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/exchanges/okx/enums/OkxSymbolTypeEnum.ts diff --git a/src/exchanges/okx/enums/OkxSymbolTypeEnum.ts b/src/exchanges/okx/enums/OkxSymbolTypeEnum.ts new file mode 100644 index 00000000..0e605a89 --- /dev/null +++ b/src/exchanges/okx/enums/OkxSymbolTypeEnum.ts @@ -0,0 +1,7 @@ +export enum OkxSymbolTypeEnum { + SPOT = 'SPOT', + MARGIN = 'MARGIN', + SWAP = 'SWAP', + FUTURES = 'FUTURES', + OPTION = 'OPTION' +} From 86e1d7cca536e9387555c4deafb2dff7a6c5aef6 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Wed, 18 May 2022 22:05:20 -0300 Subject: [PATCH 017/105] Add Okx symbol status enum --- src/exchanges/okx/enums/OkxSymbolStatusEnum.ts | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/exchanges/okx/enums/OkxSymbolStatusEnum.ts diff --git a/src/exchanges/okx/enums/OkxSymbolStatusEnum.ts b/src/exchanges/okx/enums/OkxSymbolStatusEnum.ts new file mode 100644 index 00000000..dd276a23 --- /dev/null +++ b/src/exchanges/okx/enums/OkxSymbolStatusEnum.ts @@ -0,0 +1,6 @@ +export enum OkxSymbolStatusEnum { + LIVE = 'live', + SUSPEND = 'suspend', + PREOPEN = 'preopen', + SETTLEMENT = 'settlement' +} From 92b881a235ff3163b131f09f157f82de0d33893c Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Wed, 18 May 2022 22:05:34 -0300 Subject: [PATCH 018/105] Add Okx specs and test suite --- src/exchanges/okx/okxSpecs.spec.ts | 4 +- src/exchanges/okx/okxSpecs.ts | 82 +++++++++--------------------- 2 files changed, 25 insertions(+), 61 deletions(-) diff --git a/src/exchanges/okx/okxSpecs.spec.ts b/src/exchanges/okx/okxSpecs.spec.ts index c4f51b35..a5e89c69 100644 --- a/src/exchanges/okx/okxSpecs.spec.ts +++ b/src/exchanges/okx/okxSpecs.spec.ts @@ -30,8 +30,8 @@ describe(__filename, () => { } = await executeAndCatch(() => getOkxEndpoints({ useTestNet: true })) // validating - expect(error).not.to.be.ok - expect(result).to.be.ok + expect(result).not.to.be.ok + expect(error).to.be.ok }) diff --git a/src/exchanges/okx/okxSpecs.ts b/src/exchanges/okx/okxSpecs.ts index e219522e..18da25da 100644 --- a/src/exchanges/okx/okxSpecs.ts +++ b/src/exchanges/okx/okxSpecs.ts @@ -1,19 +1,20 @@ import { cloneDeep } from 'lodash' +import { AlunaError } from '../../lib/core/AlunaError' import { AlunaAccountEnum } from '../../lib/enums/AlunaAccountEnum' import { AlunaFeaturesModeEnum } from '../../lib/enums/AlunaFeaturesModeEnum' import { AlunaOrderTypesEnum } from '../../lib/enums/AlunaOrderTypesEnum' +import { AlunaExchangeErrorCodes } from '../../lib/errors/AlunaExchangeErrorCodes' import { IAlunaExchangeOrderSpecsSchema, IAlunaExchangeSchema, } from '../../lib/schemas/IAlunaExchangeSchema' import { IAlunaSettingsSchema } from '../../lib/schemas/IAlunaSettingsSchema' +import { OkxSymbolTypeEnum } from './enums/OkxSymbolTypeEnum' -// TODO: set proper urls -export const OKX_PRODUCTION_URL = 'https://api.okx.com/v3' -export const OKX_TESTNET_URL = 'https://testnet.api.okx.com/v3' +export const OKX_PRODUCTION_URL = 'https://www.okx.com/api/v5' @@ -38,28 +39,6 @@ export const okxExchangeOrderTypes: IAlunaExchangeOrderSpecsSchema[] = [ amount: 1, }, }, - { - type: AlunaOrderTypesEnum.STOP_LIMIT, - supported: true, - implemented: true, - mode: AlunaFeaturesModeEnum.READ, - options: { - rate: 1, - amount: 1, - limitRate: 1, - }, - }, - { - type: AlunaOrderTypesEnum.TRAILING_STOP, - supported: true, - implemented: true, - mode: AlunaFeaturesModeEnum.READ, - options: { - rate: 1, - amount: 1, - limitRate: 1, - }, - }, ] @@ -67,14 +46,11 @@ export const okxExchangeOrderTypes: IAlunaExchangeOrderSpecsSchema[] = [ export const okxBaseSpecs: IAlunaExchangeSchema = { id: 'okx', name: 'Okx', - // TODO: Review 'signupUrl' - signupUrl: 'https://okx.com/account/register', - // TODO: Review 'connectApiUrl' - connectApiUrl: 'https://okx.com/manage?view=api', - // TODO: Review exchange rates limits + signupUrl: 'https://www.okx.com/account/register', + connectApiUrl: 'https://www.okx.com/account/my-api', rateLimitingPerMinute: { - perApiKey: 0, - perIp: 0, + perApiKey: 60, + perIp: 60, }, // TODO: Review supported features features: { @@ -86,7 +62,6 @@ export const okxBaseSpecs: IAlunaExchangeSchema = { order: AlunaFeaturesModeEnum.WRITE, }, accounts: [ - // TODO: Review supported/implemented accounts { type: AlunaAccountEnum.SPOT, supported: true, @@ -141,46 +116,35 @@ export const getOkxEndpoints = ( settings: IAlunaSettingsSchema, ) => { - let baseUrl = OKX_PRODUCTION_URL + const baseUrl = OKX_PRODUCTION_URL if (settings.useTestNet) { - baseUrl = OKX_TESTNET_URL - /* - throw new AlunaError({ - code: ExchangeErrorCodes.EXCHANGE_DONT_PROVIDE_TESTNET, - message: 'Okx don't have a testnet.', - }) - */ + + throw new AlunaError({ + code: AlunaExchangeErrorCodes.EXCHANGE_DONT_HAVE_TESTNET, + message: 'Okx don\'t have a testnet.', + }) + } return { symbol: { - get: `${baseUrl}/`, - list: `${baseUrl}/`, + list: (type: OkxSymbolTypeEnum) => `${baseUrl}/public/instruments?instType=${type}`, }, market: { - get: `${baseUrl}/`, - list: `${baseUrl}/`, + list: (type: OkxSymbolTypeEnum) => `${baseUrl}/market/tickers?instType=${type}`, }, key: { - fetchDetails: `${baseUrl}/`, + fetchDetails: `${baseUrl}/account/config`, }, balance: { - list: `${baseUrl}/`, + list: `${baseUrl}/account/balance`, }, order: { - get: (id: string) => `${baseUrl}//${id}`, - list: `${baseUrl}/`, - place: `${baseUrl}/`, - cancel: (id: string) => `${baseUrl}//${id}`, - edit: `${baseUrl}/`, - }, - position: { - list: `${baseUrl}/`, - get: `${baseUrl}/`, - close: `${baseUrl}/`, - getLeverage: `${baseUrl}/`, - setLeverage: `${baseUrl}/`, + get: (id: string, symbolPair: string) => `${baseUrl}/trade/order?ordId=${id}&$instId=${symbolPair}`, + list: `${baseUrl}/trade/orders-pending`, + place: `${baseUrl}/trade/order`, + cancel: `${baseUrl}/trade/cancel-order`, }, } } From 219a7f1fabb9fb6a166e0a952a49ac837073f3c4 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Wed, 18 May 2022 22:05:44 -0300 Subject: [PATCH 019/105] Add Okx http and test suite --- src/exchanges/okx/OkxHttp.spec.ts | 117 ++++++++++++++++++++++++++---- src/exchanges/okx/OkxHttp.ts | 100 +++++++++++++++++++------ 2 files changed, 179 insertions(+), 38 deletions(-) diff --git a/src/exchanges/okx/OkxHttp.spec.ts b/src/exchanges/okx/OkxHttp.spec.ts index ec39b883..4d5a7e76 100644 --- a/src/exchanges/okx/OkxHttp.spec.ts +++ b/src/exchanges/okx/OkxHttp.spec.ts @@ -1,8 +1,10 @@ import { expect } from 'chai' import { Agent } from 'https' import { random } from 'lodash' +import Sinon from 'sinon' import { ImportMock } from 'ts-mock-imports' +import crypto from 'crypto' import { testCache } from '../../../test/macros/testCache' import { mockAxiosRequest } from '../../../test/mocks/axios/request' import { AlunaHttpVerbEnum } from '../../lib/enums/AlunaHtttpVerbEnum' @@ -20,7 +22,7 @@ import * as OkxHttpMod from './OkxHttp' -describe.skip(__filename, () => { +describe(__filename, () => { const { OkxHttp } = OkxHttpMod @@ -29,13 +31,19 @@ describe.skip(__filename, () => { const body = { data: 'some-data', } + + const date = new Date('2022-01-25T20:35:45.623Z') const credentials: IAlunaCredentialsSchema = { key: 'key', secret: 'key', passphrase: 'key', } const signedHeader = { - 'Api-Key': 'apikey', + 'OK-ACCESS-KEY': credentials.key, + 'OK-ACCESS-PASSPHRASE': credentials.passphrase, + 'OK-ACCESS-SIGN': 'dummy', + 'OK-ACCESS-TIMESTAMP': date, + 'Content-Type': 'application/json', } const proxySettings: IAlunaProxySchema = { host: 'host', @@ -123,7 +131,7 @@ describe.skip(__filename, () => { const okxHttp = new OkxHttp({}) - request.returns(Promise.resolve({ data: response })) + request.returns(Promise.resolve({ data: { data: response } })) // executing @@ -180,7 +188,7 @@ describe.skip(__filename, () => { assembleRequestConfig, } = mockDeps() - request.returns(Promise.resolve({ data: response })) + request.returns(Promise.resolve({ data: { data: response } })) // executing @@ -211,7 +219,6 @@ describe.skip(__filename, () => { expect(generateAuthHeader.callCount).to.be.eq(1) expect(generateAuthHeader.firstCall.args[0]).to.deep.eq({ verb: AlunaHttpVerbEnum.POST, - path: new URL(url).pathname, credentials, body, url, @@ -437,25 +444,36 @@ describe.skip(__filename, () => { }) - it('should generate signed auth header just fine', async () => { + it('should generate signed auth header just fine w/ body', async () => { // preparing data - const path = 'path' const verb = 'verb' as AlunaHttpVerbEnum + const timestamp = date.toISOString() - const currentDate = Date.now() + const meta = [ + timestamp, + verb.toUpperCase(), + new URL(url).pathname, + JSON.stringify(body), + ].join('') // mocking + const createHmacSpy = Sinon.spy(crypto, 'createHmac') + + const updateSpy = Sinon.spy(crypto.Hmac.prototype, 'update') + + const digestSpy = Sinon.spy(crypto.Hmac.prototype, 'digest') + + const dateMock = ImportMock.mockFunction( - Date, - 'now', - currentDate, + Date.prototype, + 'toISOString', + timestamp, ) // executing const signedHash = OkxHttpMod.generateAuthHeader({ credentials, - path, verb, body, url, @@ -463,7 +481,78 @@ describe.skip(__filename, () => { // validating expect(dateMock.callCount).to.be.eq(1) - expect(signedHash['Api-Timestamp']).to.be.eq(currentDate) + + expect(createHmacSpy.callCount).to.be.eq(1) + expect(createHmacSpy.calledWith('sha256', credentials.secret)).to.be.ok + + expect(updateSpy.callCount).to.be.eq(1) + expect(updateSpy.calledWith(meta)).to.be.ok + + expect(digestSpy.callCount).to.be.eq(1) + expect(digestSpy.calledWith('base64')).to.be.ok + + expect(signedHash['OK-ACCESS-KEY']).to.deep.eq(credentials.key) + expect(signedHash['OK-ACCESS-PASSPHRASE']).to.deep.eq(credentials.passphrase) + expect(signedHash['OK-ACCESS-TIMESTAMP']).to.deep.eq(timestamp) + expect(signedHash['OK-ACCESS-SIGN']).to.deep.eq(digestSpy.returnValues[0]) + + Sinon.restore() + + }) + + it('should generate signed auth header just fine w/ query', async () => { + + // preparing data + const verb = 'verb' as AlunaHttpVerbEnum + const timestamp = date.toISOString() + + const urlWithQuery = new URL(`${url}?query=query`) + + const meta = [ + timestamp, + verb.toUpperCase(), + `${urlWithQuery.pathname}${urlWithQuery.search}`, + ].join('') + + // mocking + const createHmacSpy = Sinon.spy(crypto, 'createHmac') + + const updateSpy = Sinon.spy(crypto.Hmac.prototype, 'update') + + const digestSpy = Sinon.spy(crypto.Hmac.prototype, 'digest') + + + const dateMock = ImportMock.mockFunction( + Date.prototype, + 'toISOString', + timestamp, + ) + + // executing + const signedHash = OkxHttpMod.generateAuthHeader({ + credentials, + verb, + url: urlWithQuery.toString(), + }) + + // validating + expect(dateMock.callCount).to.be.eq(1) + + expect(createHmacSpy.callCount).to.be.eq(1) + expect(createHmacSpy.calledWith('sha256', credentials.secret)).to.be.ok + + expect(updateSpy.callCount).to.be.eq(1) + expect(updateSpy.calledWith(meta)).to.be.ok + + expect(digestSpy.callCount).to.be.eq(1) + expect(digestSpy.calledWith('base64')).to.be.ok + + expect(signedHash['OK-ACCESS-KEY']).to.deep.eq(credentials.key) + expect(signedHash['OK-ACCESS-PASSPHRASE']).to.deep.eq(credentials.passphrase) + expect(signedHash['OK-ACCESS-TIMESTAMP']).to.deep.eq(timestamp) + expect(signedHash['OK-ACCESS-SIGN']).to.deep.eq(digestSpy.returnValues[0]) + + Sinon.restore() }) @@ -471,6 +560,6 @@ describe.skip(__filename, () => { /** * Executes macro test. * */ - testCache({ HttpClass: OkxHttp }) + testCache({ HttpClass: OkxHttp, useObjectAsResponse: true }) }) diff --git a/src/exchanges/okx/OkxHttp.ts b/src/exchanges/okx/OkxHttp.ts index 1a190dfe..4b45371a 100644 --- a/src/exchanges/okx/OkxHttp.ts +++ b/src/exchanges/okx/OkxHttp.ts @@ -1,4 +1,6 @@ import axios from 'axios' +import crypto from 'crypto' +import { AlunaError } from '../../lib/core/AlunaError' import { IAlunaHttp, @@ -7,6 +9,7 @@ import { IAlunaHttpRequestCount, } from '../../lib/core/IAlunaHttp' import { AlunaHttpVerbEnum } from '../../lib/enums/AlunaHtttpVerbEnum' +import { AlunaKeyErrorCodes } from '../../lib/errors/AlunaKeyErrorCodes' import { IAlunaCredentialsSchema } from '../../lib/schemas/IAlunaCredentialsSchema' import { IAlunaSettingsSchema } from '../../lib/schemas/IAlunaSettingsSchema' import { assembleRequestConfig } from '../../utils/axios/assembleRequestConfig' @@ -79,13 +82,17 @@ export class OkxHttp implements IAlunaHttp { try { - const { data } = await axios.create().request(requestConfig) + const { data } = await axios + .create() + .request>(requestConfig) + + const { data: response } = data if (!disableCache) { - AlunaCache.cache.set(cacheKey, data, cacheTtlInSeconds) + AlunaCache.cache.set(cacheKey, response, cacheTtlInSeconds) } - return data + return response } catch (error) { @@ -113,7 +120,6 @@ export class OkxHttp implements IAlunaHttp { const signedHash = generateAuthHeader({ verb, - path: new URL(url).pathname, credentials, body, url, @@ -133,9 +139,13 @@ export class OkxHttp implements IAlunaHttp { try { - const { data } = await axios.create().request(requestConfig) + const { data } = await axios + .create() + .request>(requestConfig) + + const { data: response } = data - return data + return response } catch (error) { @@ -149,44 +159,86 @@ export class OkxHttp implements IAlunaHttp { -// TODO: Review interface properties interface ISignedHashParams { verb: AlunaHttpVerbEnum - path: string credentials: IAlunaCredentialsSchema url: string body?: any } -// TODO: Review interface properties export interface IOkxSignedHeaders { - 'Api-Timestamp': number + 'OK-ACCESS-KEY': string + 'OK-ACCESS-SIGN': string + 'OK-ACCESS-TIMESTAMP': string + 'OK-ACCESS-PASSPHRASE': string + 'Content-Type': string +} + +interface IOkxHttpResponse { + code: string + msg: string + data: T } export const generateAuthHeader = ( - _params: ISignedHashParams, + params: ISignedHashParams, ): IOkxSignedHeaders => { - // TODO: Implement method (and rename `_params` to `params`) + const { + credentials, + verb, + body, + url, + } = params + + const { + key, + secret, + passphrase, + } = credentials + + if (!passphrase) { + + throw new AlunaError({ + code: AlunaKeyErrorCodes.INVALID, + message: '\'passphrase\' is required for private requests', + httpStatusCode: 401, + }) + + } + + const methodUrl = new URL(url) + + const { pathname, search } = methodUrl + + const timestamp = new Date().toISOString() + + const pathWithQuery = search + ? `${pathname}${search}` + : pathname - // const { - // credentials, - // verb, - // body, - // url, - // } = params + const includeBody = body ? JSON.stringify(body) : '' - // const { - // key, - // secret, - // } = credentials + const meta = [ + timestamp, + verb.toUpperCase(), + pathWithQuery, + includeBody, + ].join('') - const timestamp = Date.now() + const signedRequest = crypto + .createHmac('sha256', secret) + .update(meta) + .digest('base64') return { - 'Api-Timestamp': timestamp, + 'OK-ACCESS-KEY': key, + 'OK-ACCESS-PASSPHRASE': passphrase, + 'OK-ACCESS-SIGN': signedRequest, + 'OK-ACCESS-TIMESTAMP': timestamp, + 'Content-Type': 'application/json', } } From 23026dfb245baabd290201d5e1b699a324c6b8d7 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Thu, 19 May 2022 09:32:53 -0300 Subject: [PATCH 020/105] Add Okx Order schema and fixtures --- src/exchanges/okx/schemas/IOkxOrderSchema.ts | 42 +++++++++-- src/exchanges/okx/test/fixtures/okxOrders.ts | 78 ++++++++++++++++++-- 2 files changed, 109 insertions(+), 11 deletions(-) diff --git a/src/exchanges/okx/schemas/IOkxOrderSchema.ts b/src/exchanges/okx/schemas/IOkxOrderSchema.ts index a303b656..bd77fa1e 100644 --- a/src/exchanges/okx/schemas/IOkxOrderSchema.ts +++ b/src/exchanges/okx/schemas/IOkxOrderSchema.ts @@ -1,11 +1,43 @@ +import { OkxOrderSideEnum } from '../enums/OkxOrderSideEnum' +import { OkxOrderStatusEnum } from '../enums/OkxOrderStatusEnum' import { OkxOrderTypeEnum } from '../enums/OkxOrderTypeEnum' -// TODO: Describe order interface for Okx exchange export interface IOkxOrderSchema { - id: string - symbol: string - type: OkxOrderTypeEnum - // ... + instType: string + instId: string + ccy: string + ordId: string + clOrdId: string + tag: string + px: string + sz: string + pnl: string + ordType: OkxOrderTypeEnum + side: OkxOrderSideEnum + posSide: string + tdMode: string + accFillSz: string + fillPx: string + tradeId: string + fillSz: string + fillTime: string + state: OkxOrderStatusEnum + avgPx: string + lever: string + tpTriggerPx: string + tpTriggerPxType: string + tpOrdPx: string + slTriggerPx: string + slTriggerPxType: string + slOrdPx: string + feeCcy: string + fee: string + rebateCcy: string + rebate: string + tgtCcy: string + category: string + uTime: string + cTime: string } diff --git a/src/exchanges/okx/test/fixtures/okxOrders.ts b/src/exchanges/okx/test/fixtures/okxOrders.ts index 304be3bf..13362541 100644 --- a/src/exchanges/okx/test/fixtures/okxOrders.ts +++ b/src/exchanges/okx/test/fixtures/okxOrders.ts @@ -1,3 +1,5 @@ +import { OkxOrderSideEnum } from '../../enums/OkxOrderSideEnum' +import { OkxOrderStatusEnum } from '../../enums/OkxOrderStatusEnum' import { OkxOrderTypeEnum } from '../../enums/OkxOrderTypeEnum' import { IOkxOrderSchema } from '../../schemas/IOkxOrderSchema' @@ -6,13 +8,77 @@ import { IOkxOrderSchema } from '../../schemas/IOkxOrderSchema' // TODO: Review fixtures export const OKX_RAW_ORDERS: IOkxOrderSchema[] = [ { - id: '8bc1e59c-77fa-4554-bd11-966e360e4eb7', - symbol: 'BTC/USD', - type: OkxOrderTypeEnum.LIMIT, + instType: 'SPOT', + instId: 'BTC-USD', + ccy: 'BTC', + ordId: '312269865356374016', + clOrdId: 'b1', + tag: '', + px: '999', + sz: '3', + pnl: '5', + ordType: OkxOrderTypeEnum.LIMIT, + side: OkxOrderSideEnum.LONG, + posSide: 'long', + tdMode: 'isolated', + accFillSz: '0', + fillPx: '0', + tradeId: '0', + fillSz: '0', + fillTime: '0', + state: OkxOrderStatusEnum.LIVE, + avgPx: '0', + lever: '20', + tpTriggerPx: '', + tpTriggerPxType: 'last', + tpOrdPx: '', + slTriggerPx: '', + slTriggerPxType: 'last', + slOrdPx: '', + feeCcy: '', + fee: '', + rebateCcy: '', + rebate: '', + tgtCcy: 'USD', + category: '', + uTime: '1597026383085', + cTime: '1597026383085', }, { - id: '8bc1e59c-77fa-4554-bd11-966e360e4eb8', - symbol: 'ETH/USD', - type: OkxOrderTypeEnum.MARKET, + instType: 'SPOT', + instId: 'BTC-USD', + ccy: 'BTC', + ordId: '312269865356374016', + clOrdId: 'b1', + tag: '', + px: '999', + sz: '3', + pnl: '5', + ordType: OkxOrderTypeEnum.MARKET, + side: OkxOrderSideEnum.LONG, + posSide: 'long', + tdMode: 'isolated', + accFillSz: '0', + fillPx: '0', + tradeId: '0', + fillSz: '0', + fillTime: '0', + state: OkxOrderStatusEnum.LIVE, + avgPx: '0', + lever: '20', + tpTriggerPx: '', + tpTriggerPxType: 'last', + tpOrdPx: '', + slTriggerPx: '', + slTriggerPxType: 'last', + slOrdPx: '', + feeCcy: '', + fee: '', + rebateCcy: '', + rebate: '', + tgtCcy: 'USD', + category: '', + uTime: '1597026383085', + cTime: '1597026383085', }, ] From 02f752268c7b7616c73628dbf1d20623f6d94879 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Thu, 19 May 2022 09:33:08 -0300 Subject: [PATCH 021/105] remove okx market status enum --- src/exchanges/okx/enums/OkxMarketStatusEnum.ts | 4 ---- 1 file changed, 4 deletions(-) delete mode 100644 src/exchanges/okx/enums/OkxMarketStatusEnum.ts diff --git a/src/exchanges/okx/enums/OkxMarketStatusEnum.ts b/src/exchanges/okx/enums/OkxMarketStatusEnum.ts deleted file mode 100644 index 2493da2b..00000000 --- a/src/exchanges/okx/enums/OkxMarketStatusEnum.ts +++ /dev/null @@ -1,4 +0,0 @@ -export enum OkxMarketStatusEnum { - ONLINE = 'ONLINE', - OFFLINE = 'OFFLINE', -} From 82938814bd3e8074af82ad34e4bbe1fc06b53214 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Thu, 19 May 2022 09:33:19 -0300 Subject: [PATCH 022/105] add okx order side enum --- src/exchanges/okx/enums/OkxOrderSideEnum.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/exchanges/okx/enums/OkxOrderSideEnum.ts b/src/exchanges/okx/enums/OkxOrderSideEnum.ts index e872efad..1f2b83c9 100644 --- a/src/exchanges/okx/enums/OkxOrderSideEnum.ts +++ b/src/exchanges/okx/enums/OkxOrderSideEnum.ts @@ -1,4 +1,4 @@ export enum OkxOrderSideEnum { - BUY = 'BUY', - SELL = 'SELL' + LONG = 'long', + SHORT = 'short' } From 70eae9fca13e344222bdfb3661f0e97d22772903 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Thu, 19 May 2022 09:33:25 -0300 Subject: [PATCH 023/105] add okx order status enum --- src/exchanges/okx/enums/OkxOrderStatusEnum.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/exchanges/okx/enums/OkxOrderStatusEnum.ts b/src/exchanges/okx/enums/OkxOrderStatusEnum.ts index 21e2fb5a..743f9c70 100644 --- a/src/exchanges/okx/enums/OkxOrderStatusEnum.ts +++ b/src/exchanges/okx/enums/OkxOrderStatusEnum.ts @@ -1,4 +1,6 @@ export enum OkxOrderStatusEnum { - OPEN = 'OPEN', - CLOSED = 'CLOSED' + CANCELED = 'canceled', + LIVE = 'live', + PARTIALLY_FILLED = 'partially_filled', + FILLED = 'filled' } From 65b089d6773a6892e8aeeaa41032a1d982ee7a6b Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Thu, 19 May 2022 09:33:29 -0300 Subject: [PATCH 024/105] add okx order type enum --- src/exchanges/okx/enums/OkxOrderTypeEnum.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/exchanges/okx/enums/OkxOrderTypeEnum.ts b/src/exchanges/okx/enums/OkxOrderTypeEnum.ts index e2ed7d40..a13e9aec 100644 --- a/src/exchanges/okx/enums/OkxOrderTypeEnum.ts +++ b/src/exchanges/okx/enums/OkxOrderTypeEnum.ts @@ -1,6 +1,4 @@ export enum OkxOrderTypeEnum { LIMIT = 'LIMIT', MARKET = 'MARKET', - CEILING_LIMIT = 'CEILING_LIMIT', - CEILING_MARKET = 'CEILING_MARKET' } From 86bc511b3d82ccad28c4c6fd2f9b6a34e503b397 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Thu, 19 May 2022 09:33:41 -0300 Subject: [PATCH 025/105] add okx order side adapter and test suite --- .../okx/enums/adapters/okxOrderSideAdapter.spec.ts | 8 ++++---- src/exchanges/okx/enums/adapters/okxOrderSideAdapter.ts | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/exchanges/okx/enums/adapters/okxOrderSideAdapter.spec.ts b/src/exchanges/okx/enums/adapters/okxOrderSideAdapter.spec.ts index eefdb008..390f9cca 100644 --- a/src/exchanges/okx/enums/adapters/okxOrderSideAdapter.spec.ts +++ b/src/exchanges/okx/enums/adapters/okxOrderSideAdapter.spec.ts @@ -19,11 +19,11 @@ describe(__filename, () => { it('should properly translate Okx order sides to Aluna order sides', () => { expect(translateOrderSideToAluna({ - from: OkxOrderSideEnum.BUY, + from: OkxOrderSideEnum.LONG, })).to.be.eq(AlunaOrderSideEnum.BUY) expect(translateOrderSideToAluna({ - from: OkxOrderSideEnum.SELL, + from: OkxOrderSideEnum.SHORT, })).to.be.eq(AlunaOrderSideEnum.SELL) let result @@ -55,11 +55,11 @@ describe(__filename, () => { expect(translateOrderSideToOkx({ from: AlunaOrderSideEnum.BUY, - })).to.be.eq(OkxOrderSideEnum.BUY) + })).to.be.eq(OkxOrderSideEnum.LONG) expect(translateOrderSideToOkx({ from: AlunaOrderSideEnum.SELL, - })).to.be.eq(OkxOrderSideEnum.SELL) + })).to.be.eq(OkxOrderSideEnum.SHORT) let result let error diff --git a/src/exchanges/okx/enums/adapters/okxOrderSideAdapter.ts b/src/exchanges/okx/enums/adapters/okxOrderSideAdapter.ts index cb22eb6f..a0cf2324 100644 --- a/src/exchanges/okx/enums/adapters/okxOrderSideAdapter.ts +++ b/src/exchanges/okx/enums/adapters/okxOrderSideAdapter.ts @@ -14,8 +14,8 @@ export const translateOrderSideToAluna = buildAdapter< >({ errorMessagePrefix, mappings: { - [OkxOrderSideEnum.BUY]: AlunaOrderSideEnum.BUY, - [OkxOrderSideEnum.SELL]: AlunaOrderSideEnum.SELL, + [OkxOrderSideEnum.LONG]: AlunaOrderSideEnum.BUY, + [OkxOrderSideEnum.SHORT]: AlunaOrderSideEnum.SELL, }, }) @@ -27,7 +27,7 @@ export const translateOrderSideToOkx = buildAdapter< >({ errorMessagePrefix, mappings: { - [AlunaOrderSideEnum.BUY]: OkxOrderSideEnum.BUY, - [AlunaOrderSideEnum.SELL]: OkxOrderSideEnum.SELL, + [AlunaOrderSideEnum.BUY]: OkxOrderSideEnum.LONG, + [AlunaOrderSideEnum.SELL]: OkxOrderSideEnum.SHORT, }, }) From a560790fb4cbb0a0e82f16a0c53ce32d413de3de Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Thu, 19 May 2022 09:33:53 -0300 Subject: [PATCH 026/105] add okx order status adapter and test suite --- .../adapters/okxOrderStatusAdapter.spec.ts | 53 +++++++++-------- .../enums/adapters/okxOrderStatusAdapter.ts | 59 ++++++------------- 2 files changed, 48 insertions(+), 64 deletions(-) diff --git a/src/exchanges/okx/enums/adapters/okxOrderStatusAdapter.spec.ts b/src/exchanges/okx/enums/adapters/okxOrderStatusAdapter.spec.ts index 5c4feb54..68751f26 100644 --- a/src/exchanges/okx/enums/adapters/okxOrderStatusAdapter.spec.ts +++ b/src/exchanges/okx/enums/adapters/okxOrderStatusAdapter.spec.ts @@ -17,34 +17,41 @@ describe(__filename, () => { it('should translate Okx order status to Aluna order status', () => { - const quantity = '5' - const zeroedfillQty = '0' - const partiallyFillQty = '3' - const totalFillQty = '5' - expect(translateOrderStatusToAluna({ - fillQuantity: zeroedfillQty, - quantity, - from: OkxOrderStatusEnum.CLOSED, - })).to.be.eq(AlunaOrderStatusEnum.CANCELED) + from: OkxOrderStatusEnum.LIVE, + })).to.be.eq(AlunaOrderStatusEnum.OPEN) expect(translateOrderStatusToAluna({ - fillQuantity: partiallyFillQty, - quantity, - from: OkxOrderStatusEnum.CLOSED, + from: OkxOrderStatusEnum.PARTIALLY_FILLED, })).to.be.eq(AlunaOrderStatusEnum.PARTIALLY_FILLED) expect(translateOrderStatusToAluna({ - fillQuantity: totalFillQty, - quantity, - from: OkxOrderStatusEnum.CLOSED, + from: OkxOrderStatusEnum.FILLED, })).to.be.eq(AlunaOrderStatusEnum.FILLED) expect(translateOrderStatusToAluna({ - fillQuantity: totalFillQty, - quantity, - from: OkxOrderStatusEnum.OPEN, - })).to.be.eq(AlunaOrderStatusEnum.OPEN) + from: OkxOrderStatusEnum.CANCELED, + })).to.be.eq(AlunaOrderStatusEnum.CANCELED) + + let result + let error + + try { + + result = translateOrderStatusToAluna({ + from: notSupported as OkxOrderStatusEnum, + }) + + } catch (err) { + + error = err + + } + + expect(result).not.to.be.ok + expect(error instanceof AlunaError).to.be.ok + expect(error.message) + .to.be.eq(`Order status not supported: ${notSupported}`) }) @@ -54,19 +61,19 @@ describe(__filename, () => { expect(translateOrderStatusToOkx({ from: AlunaOrderStatusEnum.OPEN, - })).to.be.eq(OkxOrderStatusEnum.OPEN) + })).to.be.eq(OkxOrderStatusEnum.LIVE) expect(translateOrderStatusToOkx({ from: AlunaOrderStatusEnum.PARTIALLY_FILLED, - })).to.be.eq(OkxOrderStatusEnum.OPEN) + })).to.be.eq(OkxOrderStatusEnum.PARTIALLY_FILLED) expect(translateOrderStatusToOkx({ from: AlunaOrderStatusEnum.FILLED, - })).to.be.eq(OkxOrderStatusEnum.CLOSED) + })).to.be.eq(OkxOrderStatusEnum.FILLED) expect(translateOrderStatusToOkx({ from: AlunaOrderStatusEnum.CANCELED, - })).to.be.eq(OkxOrderStatusEnum.CLOSED) + })).to.be.eq(OkxOrderStatusEnum.CANCELED) let result let error diff --git a/src/exchanges/okx/enums/adapters/okxOrderStatusAdapter.ts b/src/exchanges/okx/enums/adapters/okxOrderStatusAdapter.ts index ef1b10dc..c6028a81 100644 --- a/src/exchanges/okx/enums/adapters/okxOrderStatusAdapter.ts +++ b/src/exchanges/okx/enums/adapters/okxOrderStatusAdapter.ts @@ -6,44 +6,20 @@ import { OkxOrderStatusEnum } from '../OkxOrderStatusEnum' const errorMessagePrefix = 'Order status' -export const translateOrderStatusToAluna = ( - params: { - fillQuantity: string - quantity: string - from: OkxOrderStatusEnum - }, -): AlunaOrderStatusEnum => { - - const { fillQuantity, quantity, from } = params - - const isOpen = from === OkxOrderStatusEnum.OPEN - - if (isOpen) { - - return AlunaOrderStatusEnum.OPEN - - } - - const parsedFillQty = parseFloat(fillQuantity) - const parsedQty = parseFloat(quantity) - - if (parsedQty === parsedFillQty) { - - return AlunaOrderStatusEnum.FILLED - - } - - if (parsedFillQty > 0) { - - return AlunaOrderStatusEnum.PARTIALLY_FILLED - - } - - return AlunaOrderStatusEnum.CANCELED - -} - +export const translateOrderStatusToAluna = buildAdapter< + OkxOrderStatusEnum, + AlunaOrderStatusEnum +>({ + errorMessagePrefix, + mappings: { + [OkxOrderStatusEnum.LIVE]: AlunaOrderStatusEnum.OPEN, + [OkxOrderStatusEnum.PARTIALLY_FILLED]: + AlunaOrderStatusEnum.PARTIALLY_FILLED, + [OkxOrderStatusEnum.FILLED]: AlunaOrderStatusEnum.FILLED, + [OkxOrderStatusEnum.CANCELED]: AlunaOrderStatusEnum.CANCELED, + }, +}) export const translateOrderStatusToOkx = buildAdapter< AlunaOrderStatusEnum, @@ -51,9 +27,10 @@ export const translateOrderStatusToOkx = buildAdapter< >({ errorMessagePrefix, mappings: { - [AlunaOrderStatusEnum.OPEN]: OkxOrderStatusEnum.OPEN, - [AlunaOrderStatusEnum.PARTIALLY_FILLED]: OkxOrderStatusEnum.OPEN, - [AlunaOrderStatusEnum.FILLED]: OkxOrderStatusEnum.CLOSED, - [AlunaOrderStatusEnum.CANCELED]: OkxOrderStatusEnum.CLOSED, + [AlunaOrderStatusEnum.OPEN]: OkxOrderStatusEnum.LIVE, + [AlunaOrderStatusEnum.PARTIALLY_FILLED]: + OkxOrderStatusEnum.PARTIALLY_FILLED, + [AlunaOrderStatusEnum.FILLED]: OkxOrderStatusEnum.FILLED, + [AlunaOrderStatusEnum.CANCELED]: OkxOrderStatusEnum.CANCELED, }, }) From 7119d87969fb7aa60c18013e67bf7108a5127951 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Thu, 19 May 2022 09:34:00 -0300 Subject: [PATCH 027/105] add okx order type adapter and test suite --- .../enums/adapters/okxOrderTypeAdapter.spec.ts | 16 ---------------- .../okx/enums/adapters/okxOrderTypeAdapter.ts | 4 ---- 2 files changed, 20 deletions(-) diff --git a/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.spec.ts b/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.spec.ts index d46349b5..4b3d0670 100644 --- a/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.spec.ts +++ b/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.spec.ts @@ -26,14 +26,6 @@ describe(__filename, () => { from: OkxOrderTypeEnum.MARKET, })).to.be.eq(AlunaOrderTypesEnum.MARKET) - expect(translateOrderTypeToAluna({ - from: OkxOrderTypeEnum.CEILING_LIMIT, - })).to.be.eq(AlunaOrderTypesEnum.LIMIT_ORDER_BOOK) - - expect(translateOrderTypeToAluna({ - from: OkxOrderTypeEnum.CEILING_MARKET, - })).to.be.eq(AlunaOrderTypesEnum.TAKE_PROFIT_MARKET) - let result let error @@ -70,14 +62,6 @@ describe(__filename, () => { from: AlunaOrderTypesEnum.MARKET, })).to.be.eq(OkxOrderTypeEnum.MARKET) - expect(translateOrderTypeToOkx({ - from: AlunaOrderTypesEnum.LIMIT_ORDER_BOOK, - })).to.be.eq(OkxOrderTypeEnum.CEILING_LIMIT) - - expect(translateOrderTypeToOkx({ - from: AlunaOrderTypesEnum.TAKE_PROFIT_MARKET, - })).to.be.eq(OkxOrderTypeEnum.CEILING_MARKET) - let result let error diff --git a/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.ts b/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.ts index 73b0217f..04f97395 100644 --- a/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.ts +++ b/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.ts @@ -16,8 +16,6 @@ export const translateOrderTypeToAluna = buildAdapter< mappings: { [OkxOrderTypeEnum.LIMIT]: AlunaOrderTypesEnum.LIMIT, [OkxOrderTypeEnum.MARKET]: AlunaOrderTypesEnum.MARKET, - [OkxOrderTypeEnum.CEILING_LIMIT]: AlunaOrderTypesEnum.LIMIT_ORDER_BOOK, - [OkxOrderTypeEnum.CEILING_MARKET]: AlunaOrderTypesEnum.TAKE_PROFIT_MARKET, }, }) @@ -31,7 +29,5 @@ export const translateOrderTypeToOkx = buildAdapter< mappings: { [AlunaOrderTypesEnum.LIMIT]: OkxOrderTypeEnum.LIMIT, [AlunaOrderTypesEnum.MARKET]: OkxOrderTypeEnum.MARKET, - [AlunaOrderTypesEnum.LIMIT_ORDER_BOOK]: OkxOrderTypeEnum.CEILING_LIMIT, - [AlunaOrderTypesEnum.TAKE_PROFIT_MARKET]: OkxOrderTypeEnum.CEILING_MARKET, }, }) From 16d19db7cdb4c7346f53c2ce5056e44974365e8c Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Thu, 19 May 2022 09:34:11 -0300 Subject: [PATCH 028/105] add okx order cancel and test suite --- .../okx/modules/authed/order/cancel.spec.ts | 32 ++++++++++++------- .../okx/modules/authed/order/cancel.ts | 20 ++++++++---- 2 files changed, 34 insertions(+), 18 deletions(-) diff --git a/src/exchanges/okx/modules/authed/order/cancel.spec.ts b/src/exchanges/okx/modules/authed/order/cancel.spec.ts index b0b43bc3..be865dc6 100644 --- a/src/exchanges/okx/modules/authed/order/cancel.spec.ts +++ b/src/exchanges/okx/modules/authed/order/cancel.spec.ts @@ -2,9 +2,8 @@ import { expect } from 'chai' import { PARSED_ORDERS } from '../../../../../../test/fixtures/parsedOrders' import { mockHttp } from '../../../../../../test/mocks/exchange/Http' -import { mockParse } from '../../../../../../test/mocks/exchange/modules/mockParse' +import { mockGet } from '../../../../../../test/mocks/exchange/modules/mockGet' import { AlunaError } from '../../../../../lib/core/AlunaError' -import { AlunaHttpVerbEnum } from '../../../../../lib/enums/AlunaHtttpVerbEnum' import { AlunaOrderErrorCodes } from '../../../../../lib/errors/AlunaOrderErrorCodes' import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' import { executeAndCatch } from '../../../../../utils/executeAndCatch' @@ -12,7 +11,7 @@ import { OkxAuthed } from '../../../OkxAuthed' import { OkxHttp } from '../../../OkxHttp' import { getOkxEndpoints } from '../../../okxSpecs' import { OKX_RAW_ORDERS } from '../../../test/fixtures/okxOrders' -import * as parseMod from './parse' +import * as getMod from './get' @@ -29,7 +28,12 @@ describe(__filename, () => { const mockedRawOrder = OKX_RAW_ORDERS[0] const mockedParsedOrder = PARSED_ORDERS[0] - const { id } = mockedRawOrder + const { ordId: id } = mockedRawOrder + + const body = { + ordId: id, + instId: '', + } // mocking @@ -38,9 +42,9 @@ describe(__filename, () => { authedRequest, } = mockHttp({ classPrototype: OkxHttp.prototype }) - const { parse } = mockParse({ module: parseMod }) + const { get } = mockGet({ module: getMod }) - parse.returns({ order: mockedParsedOrder }) + get.returns({ order: mockedParsedOrder }) authedRequest.returns(Promise.resolve(mockedRawOrder)) @@ -60,9 +64,9 @@ describe(__filename, () => { expect(authedRequest.callCount).to.be.eq(1) expect(authedRequest.firstCall.args[0]).to.deep.eq({ - verb: AlunaHttpVerbEnum.DELETE, credentials, - url: getOkxEndpoints(exchange.settings).order.cancel(id), + url: getOkxEndpoints(exchange.settings).order.cancel, + body, }) expect(publicRequest.callCount).to.be.eq(0) @@ -73,6 +77,12 @@ describe(__filename, () => { // preparing data const id = 'id' + const symbolPair = 'symbolPair' + + const body = { + ordId: id, + instId: symbolPair, + } // mocking const { @@ -96,7 +106,7 @@ describe(__filename, () => { const { error: responseError } = await executeAndCatch( () => exchange.order.cancel({ id, - symbolPair: 'symbolPair', + symbolPair, }), ) @@ -107,9 +117,9 @@ describe(__filename, () => { expect(authedRequest.callCount).to.be.eq(1) expect(authedRequest.firstCall.args[0]).to.deep.eq({ - verb: AlunaHttpVerbEnum.DELETE, credentials, - url: getOkxEndpoints(exchange.settings).order.cancel(id), + url: getOkxEndpoints(exchange.settings).order.cancel, + body, }) expect(publicRequest.callCount).to.be.eq(0) diff --git a/src/exchanges/okx/modules/authed/order/cancel.ts b/src/exchanges/okx/modules/authed/order/cancel.ts index 2a7285ac..98e09743 100644 --- a/src/exchanges/okx/modules/authed/order/cancel.ts +++ b/src/exchanges/okx/modules/authed/order/cancel.ts @@ -2,7 +2,6 @@ import { debug } from 'debug' import { AlunaError } from '../../../../../lib/core/AlunaError' import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' -import { AlunaHttpVerbEnum } from '../../../../../lib/enums/AlunaHtttpVerbEnum' import { AlunaOrderErrorCodes } from '../../../../../lib/errors/AlunaOrderErrorCodes' import { IAlunaOrderCancelParams, @@ -10,7 +9,6 @@ import { } from '../../../../../lib/modules/authed/IAlunaOrderModule' import { OkxHttp } from '../../../OkxHttp' import { getOkxEndpoints } from '../../../okxSpecs' -import { IOkxOrderSchema } from '../../../schemas/IOkxOrderSchema' @@ -31,19 +29,27 @@ export const cancel = (exchange: IAlunaExchangeAuthed) => async ( const { id, + symbolPair, http = new OkxHttp(settings), } = params + const body = { + ordId: id, + instId: symbolPair, + } + try { - // TODO: Implement proper request - const rawOrder = await http.authedRequest({ - verb: AlunaHttpVerbEnum.DELETE, - url: getOkxEndpoints(settings).order.get(id), + await http.authedRequest({ + url: getOkxEndpoints(settings).order.cancel, credentials, + body, }) - const { order } = exchange.order.parse({ rawOrder }) + const { order } = await exchange.order.get({ + id, + symbolPair, + }) const { requestWeight } = http From 3b719b4a63d5eba5cc92df12ab30de347f79e3ce Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Thu, 19 May 2022 09:34:24 -0300 Subject: [PATCH 029/105] add okx order get raw and test suite --- src/exchanges/okx/modules/authed/order/getRaw.spec.ts | 4 ++-- src/exchanges/okx/modules/authed/order/getRaw.ts | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/exchanges/okx/modules/authed/order/getRaw.spec.ts b/src/exchanges/okx/modules/authed/order/getRaw.spec.ts index 9ae252ab..d931f4f9 100644 --- a/src/exchanges/okx/modules/authed/order/getRaw.spec.ts +++ b/src/exchanges/okx/modules/authed/order/getRaw.spec.ts @@ -22,7 +22,7 @@ describe(__filename, () => { // preparing data const mockedRawOrder = OKX_RAW_ORDERS[0] - const { id } = mockedRawOrder + const { ordId: id } = mockedRawOrder // mocking @@ -51,7 +51,7 @@ describe(__filename, () => { expect(authedRequest.firstCall.args[0]).to.deep.eq({ verb: AlunaHttpVerbEnum.GET, credentials, - url: getOkxEndpoints(exchange.settings).order.get(id), + url: getOkxEndpoints(exchange.settings).order.get(id, ''), }) expect(publicRequest.callCount).to.be.eq(0) diff --git a/src/exchanges/okx/modules/authed/order/getRaw.ts b/src/exchanges/okx/modules/authed/order/getRaw.ts index 1542471e..5c8bd9f6 100644 --- a/src/exchanges/okx/modules/authed/order/getRaw.ts +++ b/src/exchanges/okx/modules/authed/order/getRaw.ts @@ -29,14 +29,14 @@ export const getRaw = (exchange: IAlunaExchangeAuthed) => async ( const { id, + symbolPair, http = new OkxHttp(settings), } = params - // TODO: Implement proper request - const rawOrder = await http.authedRequest({ + const rawOrder = await http.authedRequest({ credentials, verb: AlunaHttpVerbEnum.GET, - url: getOkxEndpoints(settings).order.get(id), + url: getOkxEndpoints(settings).order.get(id, symbolPair), }) const { requestWeight } = http From 5ba9634441886b9041f6554a8c3caee8f6385ea5 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Thu, 19 May 2022 09:34:38 -0300 Subject: [PATCH 030/105] add okx order place and test suite --- .../okx/modules/authed/order/place.spec.ts | 90 +++---------------- .../okx/modules/authed/order/place.ts | 30 ++++--- 2 files changed, 32 insertions(+), 88 deletions(-) diff --git a/src/exchanges/okx/modules/authed/order/place.spec.ts b/src/exchanges/okx/modules/authed/order/place.spec.ts index 0b6f7321..aec140d4 100644 --- a/src/exchanges/okx/modules/authed/order/place.spec.ts +++ b/src/exchanges/okx/modules/authed/order/place.spec.ts @@ -44,13 +44,12 @@ describe(__filename, () => { const translatedOrderType = translateOrderTypeToOkx({ from: type }) const body = { - direction: translatedOrderSide, - marketSymbol: '', - type: translatedOrderType, - quantity: 0.01, - rate: 0, - // limit: 0, - // timeInForce: OkxOrderTimeInForceEnum.GOOD_TIL_CANCELLED, + side: translatedOrderSide, + instId: '', + ordType: translatedOrderType, + sz: '0.01', + tdMode: 'cash', + px: '0', } // mocking @@ -106,8 +105,8 @@ describe(__filename, () => { // preparing data - const mockedRawOrder = OKX_RAW_ORDERS[0] - const mockedParsedOrder = PARSED_ORDERS[0] + const mockedRawOrder = OKX_RAW_ORDERS[1] + const mockedParsedOrder = PARSED_ORDERS[1] const side = AlunaOrderSideEnum.BUY const type = AlunaOrderTypesEnum.MARKET @@ -116,12 +115,11 @@ describe(__filename, () => { const translatedOrderType = translateOrderTypeToOkx({ from: type }) const body = { - direction: translatedOrderSide, - marketSymbol: '', - type: translatedOrderType, - quantity: 0.01, - rate: 0, - // timeInForce: OkxOrderTimeInForceEnum.FILL_OR_KILL, + side: translatedOrderSide, + instId: '', + ordType: translatedOrderType, + sz: '0.01', + tdMode: 'cash', } // mocking @@ -188,7 +186,7 @@ describe(__filename, () => { code: AlunaOrderErrorCodes.PLACE_FAILED, httpStatusCode: 401, metadata: { - code: 'INSUFFICIENT_FUNDS', + code: '59200', }, }) @@ -231,66 +229,6 @@ describe(__filename, () => { }, ) - it( - 'should throw an error placing for minimum size placing new okx order', - async () => { - - // preparing data - // const mockedRawOrder = OKX_RAW_ORDERS[0] - - const side = AlunaOrderSideEnum.BUY - const type = AlunaOrderTypesEnum.MARKET - - const expectedMessage = 'The trade was smaller than the min ' - .concat('trade size quantity for the market') - const expectedCode = AlunaOrderErrorCodes.PLACE_FAILED - - const alunaError = new AlunaError({ - message: 'dummy-error', - code: AlunaOrderErrorCodes.PLACE_FAILED, - httpStatusCode: 401, - metadata: { - code: 'MIN_TRADE_REQUIREMENT_NOT_MET', - }, - }) - - // mocking - const { - publicRequest, - authedRequest, - } = mockHttp({ classPrototype: OkxHttp.prototype }) - - authedRequest.returns(Promise.reject(alunaError)) - - mockValidateParams() - - mockEnsureOrderIsSupported() - - - // executing - const exchange = new OkxAuthed({ credentials }) - - const { error } = await executeAndCatch(() => exchange.order.place({ - symbolPair: '', - account: AlunaAccountEnum.SPOT, - amount: 0.01, - side, - type, - rate: 0, - })) - - // validating - expect(error instanceof AlunaError).to.be.ok - expect(error?.code).to.be.eq(expectedCode) - expect(error?.message).to.be.eq(expectedMessage) - - expect(authedRequest.callCount).to.be.eq(1) - - expect(publicRequest.callCount).to.be.eq(0) - - }, - ) - it('should throw an error placing new okx order', async () => { // preparing data diff --git a/src/exchanges/okx/modules/authed/order/place.ts b/src/exchanges/okx/modules/authed/order/place.ts index 6556517a..7b1dde27 100644 --- a/src/exchanges/okx/modules/authed/order/place.ts +++ b/src/exchanges/okx/modules/authed/order/place.ts @@ -13,6 +13,7 @@ import { placeOrderParamsSchema } from '../../../../../utils/validation/schemas/ import { validateParams } from '../../../../../utils/validation/validateParams' import { translateOrderSideToOkx } from '../../../enums/adapters/okxOrderSideAdapter' import { translateOrderTypeToOkx } from '../../../enums/adapters/okxOrderTypeAdapter' +import { OkxOrderTypeEnum } from '../../../enums/OkxOrderTypeEnum' import { OkxHttp } from '../../../OkxHttp' import { getOkxEndpoints } from '../../../okxSpecs' import { IOkxOrderSchema } from '../../../schemas/IOkxOrderSchema' @@ -58,13 +59,22 @@ export const place = (exchange: IAlunaExchangeAuthed) => async ( from: type, }) - // TODO: Validate all body properties + const translatedOrderSide = translateOrderSideToOkx({ from: side }) + const body = { - direction: translateOrderSideToOkx({ from: side }), - marketSymbol: symbolPair, - type: translatedOrderType, - quantity: Number(amount), - rate, + side: translatedOrderSide, + instId: symbolPair, + ordType: translatedOrderType, + sz: amount.toString(), + tdMode: 'cash', + } + + if (translatedOrderType === OkxOrderTypeEnum.LIMIT) { + + Object.assign(body, { + px: rate!.toString(), + }) + } log('placing new order for Okx') @@ -73,7 +83,6 @@ export const place = (exchange: IAlunaExchangeAuthed) => async ( try { - // TODO: Implement proper request const orderResponse = await http.authedRequest({ url: getOkxEndpoints(settings).order.place, body, @@ -92,19 +101,16 @@ export const place = (exchange: IAlunaExchangeAuthed) => async ( const { metadata } = err // TODO: Review error handlings - if (metadata.code === 'INSUFFICIENT_FUNDS') { + if (metadata.code === '59200') { code = AlunaBalanceErrorCodes.INSUFFICIENT_BALANCE message = 'Account has insufficient balance for requested action.' - } else if (metadata.code === 'MIN_TRADE_REQUIREMENT_NOT_MET') { + } else { code = AlunaOrderErrorCodes.PLACE_FAILED - message = 'The trade was smaller than the min trade size quantity for ' - .concat('the market') - } throw new AlunaError({ From 420e72a736a54f61b6494ee04f45e66ca9de0ace Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Thu, 19 May 2022 09:35:34 -0300 Subject: [PATCH 031/105] add okx order test suite for edit and get --- src/exchanges/okx/modules/authed/order/edit.spec.ts | 2 +- src/exchanges/okx/modules/authed/order/get.spec.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/exchanges/okx/modules/authed/order/edit.spec.ts b/src/exchanges/okx/modules/authed/order/edit.spec.ts index b1bcbb56..83109511 100644 --- a/src/exchanges/okx/modules/authed/order/edit.spec.ts +++ b/src/exchanges/okx/modules/authed/order/edit.spec.ts @@ -33,7 +33,7 @@ describe(__filename, () => { const mockedRawOrder = OKX_RAW_ORDERS[0] const mockedParsedOrder = PARSED_ORDERS[0] - const { id } = mockedRawOrder + const { ordId: id } = mockedRawOrder // mocking diff --git a/src/exchanges/okx/modules/authed/order/get.spec.ts b/src/exchanges/okx/modules/authed/order/get.spec.ts index 720efc1d..b5f54d76 100644 --- a/src/exchanges/okx/modules/authed/order/get.spec.ts +++ b/src/exchanges/okx/modules/authed/order/get.spec.ts @@ -25,7 +25,7 @@ describe(__filename, () => { const mockedRawOrder = OKX_RAW_ORDERS[0] const mockedParsedOrder = PARSED_ORDERS[0] - const { id } = mockedRawOrder + const { ordId: id } = mockedRawOrder const params: IAlunaOrderGetParams = { id, From 760425e57f20f12dffff45d30bc4919d58b18164 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Thu, 19 May 2022 09:35:47 -0300 Subject: [PATCH 032/105] add okx order list raw --- src/exchanges/okx/modules/authed/order/listRaw.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/exchanges/okx/modules/authed/order/listRaw.ts b/src/exchanges/okx/modules/authed/order/listRaw.ts index 08be216d..9518154b 100644 --- a/src/exchanges/okx/modules/authed/order/listRaw.ts +++ b/src/exchanges/okx/modules/authed/order/listRaw.ts @@ -29,7 +29,6 @@ export const listRaw = (exchange: IAlunaExchangeAuthed) => async ( const { http = new OkxHttp(settings) } = params - // TODO: Implement proper request const rawOrders = await http.authedRequest({ verb: AlunaHttpVerbEnum.GET, url: getOkxEndpoints(settings).order.list, From d8db4064c48a5f96ac75c5d85831abfaa318806b Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Thu, 19 May 2022 09:35:55 -0300 Subject: [PATCH 033/105] add okx order parse and test suite --- .../okx/modules/authed/order/parse.spec.ts | 51 ++++++++++- .../okx/modules/authed/order/parse.ts | 89 +++++++++++++++---- 2 files changed, 118 insertions(+), 22 deletions(-) diff --git a/src/exchanges/okx/modules/authed/order/parse.spec.ts b/src/exchanges/okx/modules/authed/order/parse.spec.ts index ed84a187..ae82f016 100644 --- a/src/exchanges/okx/modules/authed/order/parse.spec.ts +++ b/src/exchanges/okx/modules/authed/order/parse.spec.ts @@ -1,12 +1,16 @@ import { expect } from 'chai' import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' +import { translateOrderSideToAluna } from '../../../enums/adapters/okxOrderSideAdapter' +import { translateOrderStatusToAluna } from '../../../enums/adapters/okxOrderStatusAdapter' +import { translateOrderTypeToAluna } from '../../../enums/adapters/okxOrderTypeAdapter' import { OkxAuthed } from '../../../OkxAuthed' import { OKX_RAW_ORDERS } from '../../../test/fixtures/okxOrders' +import { mockTranslateSymbolId } from '../../../../../utils/mappings/translateSymbolId.mock' -describe.skip(__filename, () => { +describe(__filename, () => { const credentials: IAlunaCredentialsSchema = { key: 'key', @@ -18,16 +22,57 @@ describe.skip(__filename, () => { // preparing data const rawOrder = OKX_RAW_ORDERS[0] + const { + side, + cTime, + instId, + ordId, + px, + state, + sz, + ordType, + ccy, + tgtCcy, + } = rawOrder + + const amount = Number(sz) + const rate = Number(px) + const total = amount * rate + + const orderStatus = translateOrderStatusToAluna({ from: state }) + const orderSide = translateOrderSideToAluna({ from: side }) + const orderType = translateOrderTypeToAluna({ from: ordType }) + + // mocking + + const { translateSymbolId } = mockTranslateSymbolId() + + translateSymbolId.onFirstCall().returns(ccy) + + translateSymbolId.onSecondCall().returns(tgtCcy) + const exchange = new OkxAuthed({ credentials }) + + // executing const { order } = exchange.order.parse({ rawOrder }) // validating expect(order).to.exist - // TODO: add expectations for everything - // expect(order).to.deep.eq(...) + expect(order.id).to.be.eq(ordId) + expect(order.symbolPair).to.be.eq(instId) + expect(order.status).to.be.eq(orderStatus) + expect(order.side).to.be.eq(orderSide) + expect(order.type).to.be.eq(orderType) + expect(order.baseSymbolId).to.be.eq(ccy) + expect(order.quoteSymbolId).to.be.eq(tgtCcy) + expect(order.total).to.be.eq(total) + expect(order.amount).to.be.eq(amount) + expect(order.placedAt.getTime()).to.be.eq(new Date(Number(cTime)).getTime()) + + expect(translateSymbolId.callCount).to.be.eq(2) }) diff --git a/src/exchanges/okx/modules/authed/order/parse.ts b/src/exchanges/okx/modules/authed/order/parse.ts index 80644406..175e118f 100644 --- a/src/exchanges/okx/modules/authed/order/parse.ts +++ b/src/exchanges/okx/modules/authed/order/parse.ts @@ -1,10 +1,15 @@ import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { AlunaAccountEnum } from '../../../../../lib/enums/AlunaAccountEnum' +import { AlunaOrderStatusEnum } from '../../../../../lib/enums/AlunaOrderStatusEnum' import { IAlunaOrderParseParams, IAlunaOrderParseReturns, } from '../../../../../lib/modules/authed/IAlunaOrderModule' import { IAlunaOrderSchema } from '../../../../../lib/schemas/IAlunaOrderSchema' import { translateSymbolId } from '../../../../../utils/mappings/translateSymbolId' +import { translateOrderSideToAluna } from '../../../enums/adapters/okxOrderSideAdapter' +import { translateOrderStatusToAluna } from '../../../enums/adapters/okxOrderStatusAdapter' +import { translateOrderTypeToAluna } from '../../../enums/adapters/okxOrderTypeAdapter' import { IOkxOrderSchema } from '../../../schemas/IOkxOrderSchema' @@ -16,37 +21,83 @@ export const parse = (exchange: IAlunaExchangeAuthed) => ( const { rawOrder } = params - const { symbol } = rawOrder + const { + side, + cTime, + instId, + ordId, + px, + state, + sz, + uTime, + ordType, + ccy, + tgtCcy, + } = rawOrder - let [ - baseSymbolId, - quoteSymbolId, - ] = symbol.split('/') - - baseSymbolId = translateSymbolId({ - exchangeSymbolId: baseSymbolId, + const baseSymbolId = translateSymbolId({ + exchangeSymbolId: ccy, symbolMappings: exchange.settings.symbolMappings, }) - quoteSymbolId = translateSymbolId({ - exchangeSymbolId: quoteSymbolId, + const quoteSymbolId = translateSymbolId({ + exchangeSymbolId: tgtCcy, symbolMappings: exchange.settings.symbolMappings, }) - // TODO: Implement proper parser + const updatedAt = uTime ? new Date(Number(uTime)) : undefined + const amount = Number(sz) + const rate = Number(px) + const total = amount * rate + + const orderStatus = translateOrderStatusToAluna({ from: state }) + const orderSide = translateOrderSideToAluna({ from: side }) + const orderType = translateOrderTypeToAluna({ from: ordType }) + + let createdAt: Date + let filledAt: Date | undefined + let canceledAt: Date | undefined + + if (cTime) { + + createdAt = new Date(Number(cTime)) + + } else { + + createdAt = new Date() + + } + + if (orderStatus === AlunaOrderStatusEnum.CANCELED) { + + canceledAt = updatedAt + + } + + if (orderStatus === AlunaOrderStatusEnum.FILLED) { + + filledAt = updatedAt + + } + const order: IAlunaOrderSchema = { - id: rawOrder.id, - symbolPair: rawOrder.id, + id: ordId, + symbolPair: instId, exchangeId: exchange.specs.id, baseSymbolId, quoteSymbolId, - // total: rawOrder.total, - // amount: rawOrder.amount, - // account: AlunaAccountEnum.MARGIN, - // status: rawOrder.status, - // side: rawOrder.side, + total, + amount, + placedAt: new Date(createdAt), + canceledAt, + filledAt, + rate, + account: AlunaAccountEnum.SPOT, + type: orderType, + status: orderStatus, + side: orderSide, meta: rawOrder, - } as any // TODO: Remove casting to any + } return { order } From b6c9257e703c590ed3bcd4bd595b64e73da7033f Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Thu, 19 May 2022 09:40:28 -0300 Subject: [PATCH 034/105] Fix symbol parse many and test suite --- src/exchanges/okx/modules/public/symbol/parseMany.spec.ts | 7 ++++--- src/exchanges/okx/modules/public/symbol/parseMany.ts | 4 +--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/exchanges/okx/modules/public/symbol/parseMany.spec.ts b/src/exchanges/okx/modules/public/symbol/parseMany.spec.ts index 7ed4d99a..3e6abaa2 100644 --- a/src/exchanges/okx/modules/public/symbol/parseMany.spec.ts +++ b/src/exchanges/okx/modules/public/symbol/parseMany.spec.ts @@ -17,6 +17,7 @@ describe(__filename, () => { const { parse } = mockParse({ module: parseMod }) const parsedSymbols = [...PARSED_SYMBOLS, PARSED_SYMBOLS[0]] + const rawSymbols = OKX_RAW_SYMBOLS each(parsedSymbols, (symbol, index) => { parse.onCall(index).returns({ symbol }) @@ -27,13 +28,13 @@ describe(__filename, () => { const exchange = new Okx({}) const { symbols } = exchange.symbol.parseMany({ - rawSymbols: OKX_RAW_SYMBOLS, + rawSymbols, }) // validating - expect(parse.callCount).to.be.eq(OKX_RAW_SYMBOLS.length - 1) - expect(symbols.length).to.be.eq(OKX_RAW_SYMBOLS.length - 1) + expect(parse.callCount).to.be.eq(rawSymbols.length) + expect(symbols.length).to.be.eq(rawSymbols.length) }) diff --git a/src/exchanges/okx/modules/public/symbol/parseMany.ts b/src/exchanges/okx/modules/public/symbol/parseMany.ts index d990b4fb..6d751f3a 100644 --- a/src/exchanges/okx/modules/public/symbol/parseMany.ts +++ b/src/exchanges/okx/modules/public/symbol/parseMany.ts @@ -30,9 +30,7 @@ export const parseMany = (exchange: IAlunaExchangePublic) => ( const filteredRawActiveSymbols = filter( rawSymbols, - { - state: OkxSymbolStatusEnum.LIVE || OkxSymbolStatusEnum.PREOPEN, - }, + (rawSymbol) => rawSymbol.state !== OkxSymbolStatusEnum.SUSPEND, ) each(filteredRawActiveSymbols, (rawSymbol) => { From 1605def4e7d99e03a2aed054b3e2054ff9091119 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Thu, 19 May 2022 09:42:17 -0300 Subject: [PATCH 035/105] linting --- src/exchanges/okx/modules/authed/key/fetchDetails.spec.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/exchanges/okx/modules/authed/key/fetchDetails.spec.ts b/src/exchanges/okx/modules/authed/key/fetchDetails.spec.ts index d252c143..a5ddfe75 100644 --- a/src/exchanges/okx/modules/authed/key/fetchDetails.spec.ts +++ b/src/exchanges/okx/modules/authed/key/fetchDetails.spec.ts @@ -176,16 +176,12 @@ describe(__filename, () => { secret: 'secret', } - const permissions = cloneDeep(OKX_KEY_PERMISSIONS) - // mocking const alunaError = new AlunaError({ message: 'dummy-error', code: '', - metadata: { - code: 'non-existent', - }, + metadata: null, }) const { From f8e8ab5fc8ad0b3006dd23788361d79abaff3717 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Fri, 20 May 2022 10:12:13 -0300 Subject: [PATCH 036/105] updates to okx, add 100% test coverage --- .alunarc | 4 + docs/exchanges-table.md | 1 + src/exchanges/okx/OkxHttp.spec.ts | 34 ++++++- src/exchanges/okx/README.md | 25 ++---- .../okx/modules/authed/order/parse.spec.ts | 90 ++++++++++++++++++- .../okx/modules/authed/order/parse.ts | 2 +- src/exchanges/okx/okxSpecs.ts | 5 -- test/e2e/configs.ts | 12 +++ 8 files changed, 148 insertions(+), 25 deletions(-) diff --git a/.alunarc b/.alunarc index 228ee40d..70de6df1 100644 --- a/.alunarc +++ b/.alunarc @@ -18,3 +18,7 @@ VALR_API_SECRET= GATE_API_KEY= GATE_API_SECRET= + +OKX_API_KEY= +OKX_API_SECRET= +OKX_API_PASSPHRASE= diff --git a/docs/exchanges-table.md b/docs/exchanges-table.md index d9fc66e6..0a2ddba5 100644 --- a/docs/exchanges-table.md +++ b/docs/exchanges-table.md @@ -21,3 +21,4 @@ Full list of currently supported exchanges. |Gate|✅ v4|❌|❌| |Poloniex|✅ v1|—|—| |Valr|✅ v1|—|—| +|Okx|✅ v5|❌|❌| diff --git a/src/exchanges/okx/OkxHttp.spec.ts b/src/exchanges/okx/OkxHttp.spec.ts index 4d5a7e76..ea8dff8e 100644 --- a/src/exchanges/okx/OkxHttp.spec.ts +++ b/src/exchanges/okx/OkxHttp.spec.ts @@ -1,6 +1,6 @@ import { expect } from 'chai' import { Agent } from 'https' -import { random } from 'lodash' +import { omit, random } from 'lodash' import Sinon from 'sinon' import { ImportMock } from 'ts-mock-imports' @@ -19,6 +19,8 @@ import { mockAlunaCache } from '../../utils/cache/AlunaCache.mock' import { executeAndCatch } from '../../utils/executeAndCatch' import * as handleOkxRequestErrorMod from './errors/handleOkxRequestError' import * as OkxHttpMod from './OkxHttp' +import { AlunaError } from '../../lib/core/AlunaError' +import { AlunaKeyErrorCodes } from '../../lib/errors/AlunaKeyErrorCodes' @@ -556,6 +558,36 @@ describe(__filename, () => { }) + it('should throw an error generating signed auth header w/o passphrase', async () => { + + // preparing data + const verb = 'verb' as AlunaHttpVerbEnum + + const expectedErrorMessage = '\'passphrase\' is required for private requests' + const expectedErrorCode = AlunaKeyErrorCodes.INVALID + const expectedErrorStatus = 401 + + // executing + const { + error, + result, + } = await executeAndCatch(() => OkxHttpMod.generateAuthHeader({ + credentials: omit(credentials, 'passphrase'), + verb, + url, + })) + + // validating + + expect(result).not.to.be.ok + + expect(error instanceof AlunaError).to.be.ok + expect(error?.message).to.be.eq(expectedErrorMessage) + expect(error?.code).to.be.eq(expectedErrorCode) + expect(error?.httpStatusCode).to.be.eq(expectedErrorStatus) + + }) + /** * Executes macro test. diff --git a/src/exchanges/okx/README.md b/src/exchanges/okx/README.md index 67c27047..d638f234 100644 --- a/src/exchanges/okx/README.md +++ b/src/exchanges/okx/README.md @@ -1,7 +1,7 @@ # Sample - - API vX: - - https://sample.com/api + - API v5: + - https://www.okx.com/api/v5 ## Usage @@ -15,7 +15,9 @@ import { const settings: IAlunaSettingsSchema = { - // ... + key: 'key', + secret: 'secret', + passphrase: 'passphrase' } const credentials: IAlunaCredentialsSchema = { @@ -23,9 +25,9 @@ const credentials: IAlunaCredentialsSchema = { secret: 'yyy', } -const sample = aluna('sample', { settings }) +const okx = aluna('okx', { settings }) -sample.symbol.list() +okx.symbol.list() ``` ## Features @@ -33,15 +35,4 @@ sample.symbol.list() | Functionality | Supported | | -- | :-: | | `offersOrderEditing` | ✅ | -| `offersPositionId` | ❌ | - -## Notes - -### API Rates - - ... - -### Orders - - ... - -### Positions - - ... +| `offersPositionId` | ❌ | \ No newline at end of file diff --git a/src/exchanges/okx/modules/authed/order/parse.spec.ts b/src/exchanges/okx/modules/authed/order/parse.spec.ts index ae82f016..f6f2d8fd 100644 --- a/src/exchanges/okx/modules/authed/order/parse.spec.ts +++ b/src/exchanges/okx/modules/authed/order/parse.spec.ts @@ -1,5 +1,7 @@ import { expect } from 'chai' +import { cloneDeep } from 'lodash' +import { ImportMock } from 'ts-mock-imports' import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' import { translateOrderSideToAluna } from '../../../enums/adapters/okxOrderSideAdapter' import { translateOrderStatusToAluna } from '../../../enums/adapters/okxOrderStatusAdapter' @@ -7,6 +9,7 @@ import { translateOrderTypeToAluna } from '../../../enums/adapters/okxOrderTypeA import { OkxAuthed } from '../../../OkxAuthed' import { OKX_RAW_ORDERS } from '../../../test/fixtures/okxOrders' import { mockTranslateSymbolId } from '../../../../../utils/mappings/translateSymbolId.mock' +import { OkxOrderStatusEnum } from '../../../enums/OkxOrderStatusEnum' @@ -20,7 +23,81 @@ describe(__filename, () => { it('should parse a Okx raw order just fine', async () => { // preparing data - const rawOrder = OKX_RAW_ORDERS[0] + const rawOrder = cloneDeep(OKX_RAW_ORDERS[0]) + + rawOrder.state = OkxOrderStatusEnum.CANCELED + rawOrder.cTime = null as any + + const { + side, + instId, + ordId, + px, + state, + sz, + ordType, + ccy, + tgtCcy, + uTime, + } = rawOrder + + const amount = Number(sz) + const rate = Number(px) + const total = amount * rate + + const orderStatus = translateOrderStatusToAluna({ from: state }) + const orderSide = translateOrderSideToAluna({ from: side }) + const orderType = translateOrderTypeToAluna({ from: ordType }) + + const timestamp = new Date() + + // mocking + + ImportMock.mockFunction( + global, + 'Date', + timestamp, + ) + + const { translateSymbolId } = mockTranslateSymbolId() + + translateSymbolId.onFirstCall().returns(ccy) + + translateSymbolId.onSecondCall().returns(tgtCcy) + + const exchange = new OkxAuthed({ credentials }) + + + // executing + const { order } = exchange.order.parse({ rawOrder }) + + + // validating + expect(order).to.exist + + expect(order.id).to.be.eq(ordId) + expect(order.symbolPair).to.be.eq(instId) + expect(order.status).to.be.eq(orderStatus) + expect(order.side).to.be.eq(orderSide) + expect(order.type).to.be.eq(orderType) + expect(order.baseSymbolId).to.be.eq(ccy) + expect(order.quoteSymbolId).to.be.eq(tgtCcy) + expect(order.total).to.be.eq(total) + expect(order.amount).to.be.eq(amount) + expect(order.placedAt.getTime()).to.be.eq(timestamp.getTime()) + expect(order.canceledAt?.getTime()).to.be.eq(new Date(Number(uTime)).getTime()) + + expect(translateSymbolId.callCount).to.be.eq(2) + + }) + + it('should parse a Okx raw order just fine', async () => { + + // preparing data + const rawOrder = cloneDeep(OKX_RAW_ORDERS[0]) + + rawOrder.state = OkxOrderStatusEnum.FILLED + rawOrder.uTime = null as any const { side, @@ -33,6 +110,7 @@ describe(__filename, () => { ordType, ccy, tgtCcy, + uTime, } = rawOrder const amount = Number(sz) @@ -43,8 +121,17 @@ describe(__filename, () => { const orderSide = translateOrderSideToAluna({ from: side }) const orderType = translateOrderTypeToAluna({ from: ordType }) + const timestamp = new Date() + + // mocking + ImportMock.mockFunction( + global, + 'Date', + timestamp, + ) + const { translateSymbolId } = mockTranslateSymbolId() translateSymbolId.onFirstCall().returns(ccy) @@ -71,6 +158,7 @@ describe(__filename, () => { expect(order.total).to.be.eq(total) expect(order.amount).to.be.eq(amount) expect(order.placedAt.getTime()).to.be.eq(new Date(Number(cTime)).getTime()) + expect(order.filledAt?.getTime()).to.be.eq(timestamp.getTime()) expect(translateSymbolId.callCount).to.be.eq(2) diff --git a/src/exchanges/okx/modules/authed/order/parse.ts b/src/exchanges/okx/modules/authed/order/parse.ts index 175e118f..093b8de7 100644 --- a/src/exchanges/okx/modules/authed/order/parse.ts +++ b/src/exchanges/okx/modules/authed/order/parse.ts @@ -45,7 +45,7 @@ export const parse = (exchange: IAlunaExchangeAuthed) => ( symbolMappings: exchange.settings.symbolMappings, }) - const updatedAt = uTime ? new Date(Number(uTime)) : undefined + const updatedAt = uTime ? new Date(Number(uTime)) : new Date() const amount = Number(sz) const rate = Number(px) const total = amount * rate diff --git a/src/exchanges/okx/okxSpecs.ts b/src/exchanges/okx/okxSpecs.ts index 18da25da..c94dc39f 100644 --- a/src/exchanges/okx/okxSpecs.ts +++ b/src/exchanges/okx/okxSpecs.ts @@ -52,15 +52,10 @@ export const okxBaseSpecs: IAlunaExchangeSchema = { perApiKey: 60, perIp: 60, }, - // TODO: Review supported features features: { offersOrderEditing: false, offersPositionId: false, }, - modes: { - balance: AlunaFeaturesModeEnum.READ, - order: AlunaFeaturesModeEnum.WRITE, - }, accounts: [ { type: AlunaAccountEnum.SPOT, diff --git a/test/e2e/configs.ts b/test/e2e/configs.ts index 80326198..b43818ee 100644 --- a/test/e2e/configs.ts +++ b/test/e2e/configs.ts @@ -168,6 +168,18 @@ export function getConfig() { orderInsufficientAmount: 2000, orderAccount: AlunaAccountEnum.SPOT, }, + okx: { + key: env.VALR_API_KEY, + secret: env.VALR_API_SECRET, + passphrase: env.VALR_API_PASSPHRASE, + symbolPair: 'BTCUSDC', + delayBetweenTests: 500, + orderRate: 1000, + orderAmount: 0.001, + orderEditAmount: 0.0011, + orderInsufficientAmount: 2000, + orderAccount: AlunaAccountEnum.SPOT, + }, }, } From b6a200a32c8a661fbab226b73fa5999742dd1b58 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Fri, 20 May 2022 10:13:52 -0300 Subject: [PATCH 037/105] linting --- src/exchanges/okx/modules/authed/order/parse.spec.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/exchanges/okx/modules/authed/order/parse.spec.ts b/src/exchanges/okx/modules/authed/order/parse.spec.ts index f6f2d8fd..327bd6a5 100644 --- a/src/exchanges/okx/modules/authed/order/parse.spec.ts +++ b/src/exchanges/okx/modules/authed/order/parse.spec.ts @@ -110,7 +110,6 @@ describe(__filename, () => { ordType, ccy, tgtCcy, - uTime, } = rawOrder const amount = Number(sz) From 9ffd6c907b9b5e9b9349e884a26cd964180c4234 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Fri, 20 May 2022 12:02:27 -0300 Subject: [PATCH 038/105] fix e2e tests and error handling --- src/exchanges/okx/OkxHttp.spec.ts | 112 +++++++++++++++++- src/exchanges/okx/OkxHttp.ts | 27 ++++- src/exchanges/okx/enums/OkxOrderSideEnum.ts | 4 +- src/exchanges/okx/enums/OkxOrderTypeEnum.ts | 4 +- .../adapters/okxOrderSideAdapter.spec.ts | 8 +- .../okx/enums/adapters/okxOrderSideAdapter.ts | 8 +- .../okx/errors/handleOkxRequestError.spec.ts | 18 ++- .../okx/errors/handleOkxRequestError.ts | 22 +++- .../okx/modules/authed/order/place.spec.ts | 2 +- .../okx/modules/authed/order/place.ts | 5 +- src/exchanges/okx/test/fixtures/okxOrders.ts | 4 +- test/e2e/configs.ts | 6 +- 12 files changed, 182 insertions(+), 38 deletions(-) diff --git a/src/exchanges/okx/OkxHttp.spec.ts b/src/exchanges/okx/OkxHttp.spec.ts index ea8dff8e..13d068cf 100644 --- a/src/exchanges/okx/OkxHttp.spec.ts +++ b/src/exchanges/okx/OkxHttp.spec.ts @@ -5,7 +5,6 @@ import Sinon from 'sinon' import { ImportMock } from 'ts-mock-imports' import crypto from 'crypto' -import { testCache } from '../../../test/macros/testCache' import { mockAxiosRequest } from '../../../test/mocks/axios/request' import { AlunaHttpVerbEnum } from '../../lib/enums/AlunaHtttpVerbEnum' import { AlunaProtocolsEnum } from '../../lib/enums/AlunaProxyAgentEnum' @@ -21,6 +20,7 @@ import * as handleOkxRequestErrorMod from './errors/handleOkxRequestError' import * as OkxHttpMod from './OkxHttp' import { AlunaError } from '../../lib/core/AlunaError' import { AlunaKeyErrorCodes } from '../../lib/errors/AlunaKeyErrorCodes' +import { testCache } from '../../../test/macros/testCache' @@ -250,7 +250,7 @@ describe(__filename, () => { // mocking const { request } = mockDeps() - request.returns(Promise.resolve({ data: response })) + request.returns(Promise.resolve({ data: { data: response } })) // executing @@ -285,7 +285,7 @@ describe(__filename, () => { // mocking const { request } = mockDeps() - request.returns(Promise.resolve({ data: response })) + request.returns(Promise.resolve({ data: { data: response } })) // executing @@ -372,6 +372,108 @@ describe(__filename, () => { }) + it('should properly handle request error on authed requests', async () => { + + // preparing data + const okxHttp = new OkxHttp({}) + + const errorResponse = { + sCode: '5000', + sMsg: 'dummy-msg', + } + + const throwedError = { + data: { + data: errorResponse, + }, + } + + // mocking + const { + request, + handleOkxRequestError, + } = mockDeps() + + request.returns(Promise.resolve(throwedError)) + + handleOkxRequestError.returns(throwedError) + + // executing + const { + error, + result, + } = await executeAndCatch(() => okxHttp.authedRequest({ + url, + body, + credentials, + })) + + + // validating + expect(result).not.to.be.ok + + expect(error).to.be.ok + + expect(request.callCount).to.be.eq(1) + + expect(handleOkxRequestError.callCount).to.be.eq(1) + expect(handleOkxRequestError.firstCall.args[0]).to.deep.eq({ + error: errorResponse, + }) + + }) + + it('should properly handle request error on authed requests', async () => { + + // preparing data + const okxHttp = new OkxHttp({}) + + const errorResponse = { + sCode: '5000', + sMsg: 'dummy-msg', + } + + const throwedError = { + data: { + data: [errorResponse], + }, + } + + // mocking + const { + request, + handleOkxRequestError, + } = mockDeps() + + request.returns(Promise.resolve(throwedError)) + + handleOkxRequestError.returns(throwedError) + + // executing + const { + error, + result, + } = await executeAndCatch(() => okxHttp.authedRequest({ + url, + body, + credentials, + })) + + + // validating + expect(result).not.to.be.ok + + expect(error).to.be.ok + + expect(request.callCount).to.be.eq(1) + + expect(handleOkxRequestError.callCount).to.be.eq(1) + expect(handleOkxRequestError.firstCall.args[0]).to.deep.eq({ + error: errorResponse, + }) + + }) + it('should properly use proxy settings on public requests', async () => { // preparing data @@ -384,7 +486,7 @@ describe(__filename, () => { assembleRequestConfig, } = mockDeps() - request.returns(Promise.resolve({ data: response })) + request.returns(Promise.resolve({ data: { data: response } })) // executing @@ -420,7 +522,7 @@ describe(__filename, () => { assembleRequestConfig, } = mockDeps() - request.returns(Promise.resolve({ data: response })) + request.returns(Promise.resolve({ data: { data: response } })) // executing diff --git a/src/exchanges/okx/OkxHttp.ts b/src/exchanges/okx/OkxHttp.ts index 4b45371a..335e823a 100644 --- a/src/exchanges/okx/OkxHttp.ts +++ b/src/exchanges/okx/OkxHttp.ts @@ -14,14 +14,12 @@ import { IAlunaCredentialsSchema } from '../../lib/schemas/IAlunaCredentialsSche import { IAlunaSettingsSchema } from '../../lib/schemas/IAlunaSettingsSchema' import { assembleRequestConfig } from '../../utils/axios/assembleRequestConfig' import { AlunaCache } from '../../utils/cache/AlunaCache' -import { handleOkxRequestError } from './errors/handleOkxRequestError' +import { handleOkxRequestError, IOkxErrorSchema } from './errors/handleOkxRequestError' export const OKX_HTTP_CACHE_KEY_PREFIX = 'OkxHttp.publicRequest' - - export class OkxHttp implements IAlunaHttp { public settings: IAlunaSettingsSchema @@ -139,13 +137,32 @@ export class OkxHttp implements IAlunaHttp { try { + type TOkxResponse = T | IOkxErrorSchema | IOkxErrorSchema[] + const { data } = await axios .create() - .request>(requestConfig) + .request>(requestConfig) const { data: response } = data - return response + if (typeof response === 'object') { + + if ('sCode' in response) { + + throw response + + } + + if ('sCode' in response[0]) { + + throw response[0] + + } + + } + + + return response as T } catch (error) { diff --git a/src/exchanges/okx/enums/OkxOrderSideEnum.ts b/src/exchanges/okx/enums/OkxOrderSideEnum.ts index 1f2b83c9..167cbdeb 100644 --- a/src/exchanges/okx/enums/OkxOrderSideEnum.ts +++ b/src/exchanges/okx/enums/OkxOrderSideEnum.ts @@ -1,4 +1,4 @@ export enum OkxOrderSideEnum { - LONG = 'long', - SHORT = 'short' + BUY = 'buy', + SELL = 'sell' } diff --git a/src/exchanges/okx/enums/OkxOrderTypeEnum.ts b/src/exchanges/okx/enums/OkxOrderTypeEnum.ts index a13e9aec..d154e4c0 100644 --- a/src/exchanges/okx/enums/OkxOrderTypeEnum.ts +++ b/src/exchanges/okx/enums/OkxOrderTypeEnum.ts @@ -1,4 +1,4 @@ export enum OkxOrderTypeEnum { - LIMIT = 'LIMIT', - MARKET = 'MARKET', + LIMIT = 'limit', + MARKET = 'market', } diff --git a/src/exchanges/okx/enums/adapters/okxOrderSideAdapter.spec.ts b/src/exchanges/okx/enums/adapters/okxOrderSideAdapter.spec.ts index 390f9cca..eefdb008 100644 --- a/src/exchanges/okx/enums/adapters/okxOrderSideAdapter.spec.ts +++ b/src/exchanges/okx/enums/adapters/okxOrderSideAdapter.spec.ts @@ -19,11 +19,11 @@ describe(__filename, () => { it('should properly translate Okx order sides to Aluna order sides', () => { expect(translateOrderSideToAluna({ - from: OkxOrderSideEnum.LONG, + from: OkxOrderSideEnum.BUY, })).to.be.eq(AlunaOrderSideEnum.BUY) expect(translateOrderSideToAluna({ - from: OkxOrderSideEnum.SHORT, + from: OkxOrderSideEnum.SELL, })).to.be.eq(AlunaOrderSideEnum.SELL) let result @@ -55,11 +55,11 @@ describe(__filename, () => { expect(translateOrderSideToOkx({ from: AlunaOrderSideEnum.BUY, - })).to.be.eq(OkxOrderSideEnum.LONG) + })).to.be.eq(OkxOrderSideEnum.BUY) expect(translateOrderSideToOkx({ from: AlunaOrderSideEnum.SELL, - })).to.be.eq(OkxOrderSideEnum.SHORT) + })).to.be.eq(OkxOrderSideEnum.SELL) let result let error diff --git a/src/exchanges/okx/enums/adapters/okxOrderSideAdapter.ts b/src/exchanges/okx/enums/adapters/okxOrderSideAdapter.ts index a0cf2324..cb22eb6f 100644 --- a/src/exchanges/okx/enums/adapters/okxOrderSideAdapter.ts +++ b/src/exchanges/okx/enums/adapters/okxOrderSideAdapter.ts @@ -14,8 +14,8 @@ export const translateOrderSideToAluna = buildAdapter< >({ errorMessagePrefix, mappings: { - [OkxOrderSideEnum.LONG]: AlunaOrderSideEnum.BUY, - [OkxOrderSideEnum.SHORT]: AlunaOrderSideEnum.SELL, + [OkxOrderSideEnum.BUY]: AlunaOrderSideEnum.BUY, + [OkxOrderSideEnum.SELL]: AlunaOrderSideEnum.SELL, }, }) @@ -27,7 +27,7 @@ export const translateOrderSideToOkx = buildAdapter< >({ errorMessagePrefix, mappings: { - [AlunaOrderSideEnum.BUY]: OkxOrderSideEnum.LONG, - [AlunaOrderSideEnum.SELL]: OkxOrderSideEnum.SHORT, + [AlunaOrderSideEnum.BUY]: OkxOrderSideEnum.BUY, + [AlunaOrderSideEnum.SELL]: OkxOrderSideEnum.SELL, }, }) diff --git a/src/exchanges/okx/errors/handleOkxRequestError.spec.ts b/src/exchanges/okx/errors/handleOkxRequestError.spec.ts index 9d41885a..f0ebfa9c 100644 --- a/src/exchanges/okx/errors/handleOkxRequestError.spec.ts +++ b/src/exchanges/okx/errors/handleOkxRequestError.spec.ts @@ -52,7 +52,7 @@ describe(__filename, () => { response: { status: 400, data: { - exchangeErroMsg: dummyError, + sMsg: dummyError, }, }, } as AxiosError @@ -82,7 +82,7 @@ describe(__filename, () => { response: { status: 400, data: { - exchangeErroMsg: dummyError, + sMsg: dummyError, }, }, } as AxiosError @@ -143,6 +143,20 @@ describe(__filename, () => { }) + const okxError = { + sCode: '51000', + sMsg: requestMessage, + } as handleOkxMod.IOkxErrorSchema + + alunaError = handleOkxRequestError({ error: okxError }) + + expect(alunaError).to.deep.eq({ + code: AlunaHttpErrorCodes.REQUEST_ERROR, + message: requestMessage, + httpStatusCode: 500, + metadata: okxError, + }) + const unknown = {} as any alunaError = handleOkxRequestError({ error: unknown }) diff --git a/src/exchanges/okx/errors/handleOkxRequestError.ts b/src/exchanges/okx/errors/handleOkxRequestError.ts index 7d6ec8a2..83aabd5f 100644 --- a/src/exchanges/okx/errors/handleOkxRequestError.ts +++ b/src/exchanges/okx/errors/handleOkxRequestError.ts @@ -30,11 +30,14 @@ export const isOkxKeyInvalid = (errorMessage: string) => { } -export interface IHandleOkxRequestErrorsParams { - error: AxiosError | Error +export interface IOkxErrorSchema { + sCode: string + sMsg: string } - +export interface IHandleOkxRequestErrorsParams { + error: AxiosError | Error | IOkxErrorSchema +} export const handleOkxRequestError = ( params: IHandleOkxRequestErrorsParams, @@ -52,16 +55,23 @@ export const handleOkxRequestError = ( const { response } = error as AxiosError - // TODO: Review property `exchangeErroMsg` on request response - message = response?.data?.exchangeErroMsg || message + message = response?.data?.sMsg || message httpStatusCode = response?.status || httpStatusCode metadata = response?.data || metadata + } else if ((error as IOkxErrorSchema).sMsg) { + + const { sMsg } = error as IOkxErrorSchema + + message = sMsg + } else { - message = error.message || message + const { message: errorMsg } = error as Error + + message = errorMsg || message } diff --git a/src/exchanges/okx/modules/authed/order/place.spec.ts b/src/exchanges/okx/modules/authed/order/place.spec.ts index aec140d4..40f66b67 100644 --- a/src/exchanges/okx/modules/authed/order/place.spec.ts +++ b/src/exchanges/okx/modules/authed/order/place.spec.ts @@ -186,7 +186,7 @@ describe(__filename, () => { code: AlunaOrderErrorCodes.PLACE_FAILED, httpStatusCode: 401, metadata: { - code: '59200', + sCode: '51008', }, }) diff --git a/src/exchanges/okx/modules/authed/order/place.ts b/src/exchanges/okx/modules/authed/order/place.ts index 7b1dde27..c922354d 100644 --- a/src/exchanges/okx/modules/authed/order/place.ts +++ b/src/exchanges/okx/modules/authed/order/place.ts @@ -68,6 +68,7 @@ export const place = (exchange: IAlunaExchangeAuthed) => async ( sz: amount.toString(), tdMode: 'cash', } + console.log('🚀 ~ file: place.ts ~ line 71 ~ body', body) if (translatedOrderType === OkxOrderTypeEnum.LIMIT) { @@ -76,6 +77,7 @@ export const place = (exchange: IAlunaExchangeAuthed) => async ( }) } + console.log('🚀 ~ file: place.ts ~ line 71 ~ body', body) log('placing new order for Okx') @@ -100,8 +102,7 @@ export const place = (exchange: IAlunaExchangeAuthed) => async ( const { metadata } = err - // TODO: Review error handlings - if (metadata.code === '59200') { + if (metadata.sCode === '51008') { code = AlunaBalanceErrorCodes.INSUFFICIENT_BALANCE diff --git a/src/exchanges/okx/test/fixtures/okxOrders.ts b/src/exchanges/okx/test/fixtures/okxOrders.ts index 13362541..47b16856 100644 --- a/src/exchanges/okx/test/fixtures/okxOrders.ts +++ b/src/exchanges/okx/test/fixtures/okxOrders.ts @@ -18,7 +18,7 @@ export const OKX_RAW_ORDERS: IOkxOrderSchema[] = [ sz: '3', pnl: '5', ordType: OkxOrderTypeEnum.LIMIT, - side: OkxOrderSideEnum.LONG, + side: OkxOrderSideEnum.BUY, posSide: 'long', tdMode: 'isolated', accFillSz: '0', @@ -55,7 +55,7 @@ export const OKX_RAW_ORDERS: IOkxOrderSchema[] = [ sz: '3', pnl: '5', ordType: OkxOrderTypeEnum.MARKET, - side: OkxOrderSideEnum.LONG, + side: OkxOrderSideEnum.BUY, posSide: 'long', tdMode: 'isolated', accFillSz: '0', diff --git a/test/e2e/configs.ts b/test/e2e/configs.ts index b43818ee..993937e2 100644 --- a/test/e2e/configs.ts +++ b/test/e2e/configs.ts @@ -169,9 +169,9 @@ export function getConfig() { orderAccount: AlunaAccountEnum.SPOT, }, okx: { - key: env.VALR_API_KEY, - secret: env.VALR_API_SECRET, - passphrase: env.VALR_API_PASSPHRASE, + key: env.OKX_API_KEY, + secret: env.OKX_API_SECRET, + passphrase: env.OKX_API_PASSPHRASE, symbolPair: 'BTCUSDC', delayBetweenTests: 500, orderRate: 1000, From f6ccfb475d8a280dc6fa8b06e7d2ab99f005afb3 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Fri, 20 May 2022 12:03:15 -0300 Subject: [PATCH 039/105] linting --- src/exchanges/okx/modules/authed/order/place.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/exchanges/okx/modules/authed/order/place.ts b/src/exchanges/okx/modules/authed/order/place.ts index c922354d..484203b8 100644 --- a/src/exchanges/okx/modules/authed/order/place.ts +++ b/src/exchanges/okx/modules/authed/order/place.ts @@ -68,7 +68,6 @@ export const place = (exchange: IAlunaExchangeAuthed) => async ( sz: amount.toString(), tdMode: 'cash', } - console.log('🚀 ~ file: place.ts ~ line 71 ~ body', body) if (translatedOrderType === OkxOrderTypeEnum.LIMIT) { @@ -77,7 +76,6 @@ export const place = (exchange: IAlunaExchangeAuthed) => async ( }) } - console.log('🚀 ~ file: place.ts ~ line 71 ~ body', body) log('placing new order for Okx') From e7e0af2d446ffafacf14fa7777e238d70aaeeabb Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Fri, 20 May 2022 12:19:16 -0300 Subject: [PATCH 040/105] Add 100% test coverage --- src/exchanges/okx/OkxHttp.spec.ts | 69 +++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/src/exchanges/okx/OkxHttp.spec.ts b/src/exchanges/okx/OkxHttp.spec.ts index 13d068cf..b4817f7b 100644 --- a/src/exchanges/okx/OkxHttp.spec.ts +++ b/src/exchanges/okx/OkxHttp.spec.ts @@ -234,6 +234,75 @@ describe(__filename, () => { }) + it('should execute authed request just fine', async () => { + + // preparing data + const okxHttp = new OkxHttp({}) + + + // mocking + const { + cache, + request, + hashCacheKey, + generateAuthHeader, + assembleRequestConfig, + } = mockDeps() + + request.returns(Promise.resolve({ + data: { + data: [ + { + data: response, + }, + ], + }, + })) + + + // executing + const responseData = await okxHttp.authedRequest({ + verb: AlunaHttpVerbEnum.POST, + url, + body, + credentials, + }) + + + // validating + expect(responseData).to.deep.eq([{ + data: response, + }]) + + expect(okxHttp.requestWeight.public).to.be.eq(0) + expect(okxHttp.requestWeight.authed).to.be.eq(1) + + expect(request.callCount).to.be.eq(1) + expect(request.firstCall.args[0]).to.deep.eq({ + url, + method: AlunaHttpVerbEnum.POST, + data: body, + headers: signedHeader, + }) + + expect(assembleRequestConfig.callCount).to.be.eq(1) + + expect(generateAuthHeader.callCount).to.be.eq(1) + expect(generateAuthHeader.firstCall.args[0]).to.deep.eq({ + verb: AlunaHttpVerbEnum.POST, + credentials, + body, + url, + }) + + expect(hashCacheKey.callCount).to.be.eq(0) + + expect(cache.has.callCount).to.be.eq(0) + expect(cache.get.callCount).to.be.eq(0) + expect(cache.set.callCount).to.be.eq(0) + + }) + it('should properly increment request count on public requests', async () => { // preparing data From 152a9140a4b08d197d73f0787e187b505ee71a72 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Fri, 20 May 2022 12:21:47 -0300 Subject: [PATCH 041/105] add okx to aluna-spec.json --- .playground/aluna-spec.json | 55 +++++++++++++++++++++++++++++-------- 1 file changed, 44 insertions(+), 11 deletions(-) diff --git a/.playground/aluna-spec.json b/.playground/aluna-spec.json index 0c1eecfa..bfd2a995 100644 --- a/.playground/aluna-spec.json +++ b/.playground/aluna-spec.json @@ -1,8 +1,8 @@ { "_type": "export", "__export_format": 4, - "__export_date": "2022-05-19T15:38:33.998Z", - "__export_source": "insomnia.desktop.app:v2022.2.1", + "__export_date": "2022-05-20T15:20:36.520Z", + "__export_source": "insomnia.desktop.app:v2022.3.0", "resources": [ { "_id": "req_638f9118078540b8a309cfcf9a6189ee", @@ -149,7 +149,7 @@ { "_id": "req_f44dbbbcceb3496da709be542a08706b", "parentId": "fld_51ff43abd0e041e7a35891223e170a16", - "modified": 1652316606203, + "modified": 1653053386793, "created": 1651252489507, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "listRaw", @@ -453,7 +453,7 @@ { "_id": "req_b3c64285e4b84d6da77a0046f8224ab3", "parentId": "fld_f3ad5c457d5b4433a90c91d2d9963d18", - "modified": 1652632762679, + "modified": 1653053366612, "created": 1651251269522, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "fetchDetails", @@ -509,7 +509,7 @@ { "_id": "req_e147340c7ecd4eb785265d1b060ff2a6", "parentId": "fld_404131e490e9469898d0a610690c9775", - "modified": 1652632757871, + "modified": 1653053200021, "created": 1652142980199, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "getTradableBalance", @@ -553,7 +553,7 @@ { "_id": "req_f17e55c1a50f477aa3747ee7a7b497ca", "parentId": "fld_404131e490e9469898d0a610690c9775", - "modified": 1652632759741, + "modified": 1653056431259, "created": 1651516666747, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "listRaw", @@ -585,7 +585,7 @@ { "_id": "req_bdd09e4f5a9b4e3a87ecb690dfa739c9", "parentId": "fld_404131e490e9469898d0a610690c9775", - "modified": 1652632760923, + "modified": 1653053355581, "created": 1651505463599, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "list", @@ -617,7 +617,7 @@ { "_id": "req_db9088aa9fcf45b18e85ddd994aa99c1", "parentId": "fld_1eb1fdf26dfd46298cacc08fd05b469e", - "modified": 1652632764897, + "modified": 1653053390709, "created": 1651505373653, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "listRaw", @@ -757,7 +757,7 @@ { "_id": "req_0fe5c5ec55604eb48a47233c69fe1ee4", "parentId": "fld_1eb1fdf26dfd46298cacc08fd05b469e", - "modified": 1652974669549, + "modified": 1653060021597, "created": 1651595326974, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "place:limit", @@ -789,7 +789,7 @@ { "_id": "req_8cc2810f054d4681914645b0e9324e2b", "parentId": "fld_1eb1fdf26dfd46298cacc08fd05b469e", - "modified": 1652974668889, + "modified": 1653060015308, "created": 1651597897328, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "place:market", @@ -1409,6 +1409,39 @@ "isPrivate": false, "metaSortKey": 7, "_type": "environment" + }, + { + "_id": "env_5120e1e15b634398bb1ca97c3b48b386", + "parentId": "env_0294ce45850974a6280b61215be346094da99682", + "modified": 1653060020939, + "created": 1653009615092, + "name": "Okx", + "data": { + "EXCHANGE_ID": "okx", + "KEY": "{% dotenv _.ALUNAJS_RC, 'OKX_API_KEY' %}", + "SECRET": "{% dotenv _.ALUNAJS_RC, 'OKX_API_SECRET' %}", + "PASSPHRASE": "{% dotenv _.ALUNAJS_RC, 'OKX_API_PASSPHRASE' %}", + "ORDER_SYMBOL_PAIR": "BTC-USDT", + "ORDER_RATE": 1000, + "ORDER_AMOUNT": 0.001, + "ORDER_EDITED_AMOUNT": 0.0011 + }, + "dataPropertyOrder": { + "&": [ + "EXCHANGE_ID", + "KEY", + "SECRET", + "PASSPHRASE", + "ORDER_SYMBOL_PAIR", + "ORDER_RATE", + "ORDER_AMOUNT", + "ORDER_EDITED_AMOUNT" + ] + }, + "color": "#0929aa", + "isPrivate": false, + "metaSortKey": 1653009615092, + "_type": "environment" } ] -} +} \ No newline at end of file From 7c48f5698a201426e489c02e0221cb69e0fe1220 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Fri, 20 May 2022 12:29:11 -0300 Subject: [PATCH 042/105] removed TODOs --- src/exchanges/okx/OkxHttp.ts | 2 +- src/exchanges/okx/errors/handleOkxRequestError.ts | 1 - src/exchanges/okx/modules/public/market/parse.spec.ts | 1 - src/exchanges/okx/test/fixtures/okxOrders.ts | 1 - src/exchanges/okx/test/fixtures/okxSymbols.ts | 1 - 5 files changed, 1 insertion(+), 5 deletions(-) diff --git a/src/exchanges/okx/OkxHttp.ts b/src/exchanges/okx/OkxHttp.ts index 335e823a..11f65ca5 100644 --- a/src/exchanges/okx/OkxHttp.ts +++ b/src/exchanges/okx/OkxHttp.ts @@ -129,7 +129,7 @@ export class OkxHttp implements IAlunaHttp { url, method: verb, data: body, - headers: signedHash, // TODO: Review headers injection + headers: signedHash, proxySettings, }) diff --git a/src/exchanges/okx/errors/handleOkxRequestError.ts b/src/exchanges/okx/errors/handleOkxRequestError.ts index 83aabd5f..c464466c 100644 --- a/src/exchanges/okx/errors/handleOkxRequestError.ts +++ b/src/exchanges/okx/errors/handleOkxRequestError.ts @@ -8,7 +8,6 @@ import { AlunaKeyErrorCodes } from '../../../lib/errors/AlunaKeyErrorCodes' export const okxInvalidKeyPatterns: Array = [ - // TODO: Review exchange invalid api key error patterns /api-invalid/mi, ] diff --git a/src/exchanges/okx/modules/public/market/parse.spec.ts b/src/exchanges/okx/modules/public/market/parse.spec.ts index 9d43e37a..dd577277 100644 --- a/src/exchanges/okx/modules/public/market/parse.spec.ts +++ b/src/exchanges/okx/modules/public/market/parse.spec.ts @@ -48,7 +48,6 @@ describe(__filename, () => { // validating - // TODO: add proper validations expect(market).to.exist diff --git a/src/exchanges/okx/test/fixtures/okxOrders.ts b/src/exchanges/okx/test/fixtures/okxOrders.ts index 47b16856..a22d3484 100644 --- a/src/exchanges/okx/test/fixtures/okxOrders.ts +++ b/src/exchanges/okx/test/fixtures/okxOrders.ts @@ -5,7 +5,6 @@ import { IOkxOrderSchema } from '../../schemas/IOkxOrderSchema' -// TODO: Review fixtures export const OKX_RAW_ORDERS: IOkxOrderSchema[] = [ { instType: 'SPOT', diff --git a/src/exchanges/okx/test/fixtures/okxSymbols.ts b/src/exchanges/okx/test/fixtures/okxSymbols.ts index 6d83b861..e4817f58 100644 --- a/src/exchanges/okx/test/fixtures/okxSymbols.ts +++ b/src/exchanges/okx/test/fixtures/okxSymbols.ts @@ -3,7 +3,6 @@ import { IOkxSymbolSchema } from '../../schemas/IOkxSymbolSchema' -// TODO: Review fixtures export const OKX_RAW_SYMBOLS: IOkxSymbolSchema[] = [ { alias: '', From 1ebb574050af59773e60225cf33e994bc1377bf9 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Fri, 20 May 2022 14:14:08 -0300 Subject: [PATCH 043/105] draft: add algo orders to main list --- src/exchanges/okx/enums/OkxOrderTypeEnum.ts | 1 + .../adapters/okxOrderTypeAdapter.spec.ts | 8 ++++ .../okx/enums/adapters/okxOrderTypeAdapter.ts | 2 + .../okx/modules/authed/order/cancel.ts | 37 ++++++++++++++++--- .../okx/modules/authed/order/listRaw.ts | 14 ++++++- .../okx/modules/authed/order/parse.ts | 3 ++ .../okx/modules/authed/order/place.ts | 18 ++++++++- src/exchanges/okx/okxSpecs.ts | 13 +++++++ 8 files changed, 87 insertions(+), 9 deletions(-) diff --git a/src/exchanges/okx/enums/OkxOrderTypeEnum.ts b/src/exchanges/okx/enums/OkxOrderTypeEnum.ts index d154e4c0..921bc709 100644 --- a/src/exchanges/okx/enums/OkxOrderTypeEnum.ts +++ b/src/exchanges/okx/enums/OkxOrderTypeEnum.ts @@ -1,4 +1,5 @@ export enum OkxOrderTypeEnum { LIMIT = 'limit', MARKET = 'market', + CONDITIONAL = 'conditional' } diff --git a/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.spec.ts b/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.spec.ts index 4b3d0670..d1490636 100644 --- a/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.spec.ts +++ b/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.spec.ts @@ -26,6 +26,10 @@ describe(__filename, () => { from: OkxOrderTypeEnum.MARKET, })).to.be.eq(AlunaOrderTypesEnum.MARKET) + expect(translateOrderTypeToAluna({ + from: OkxOrderTypeEnum.CONDITIONAL, + })).to.be.eq(AlunaOrderTypesEnum.STOP_LIMIT) + let result let error @@ -62,6 +66,10 @@ describe(__filename, () => { from: AlunaOrderTypesEnum.MARKET, })).to.be.eq(OkxOrderTypeEnum.MARKET) + expect(translateOrderTypeToOkx({ + from: AlunaOrderTypesEnum.STOP_LIMIT, + })).to.be.eq(OkxOrderTypeEnum.CONDITIONAL) + let result let error diff --git a/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.ts b/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.ts index 04f97395..3bd62bd9 100644 --- a/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.ts +++ b/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.ts @@ -16,6 +16,7 @@ export const translateOrderTypeToAluna = buildAdapter< mappings: { [OkxOrderTypeEnum.LIMIT]: AlunaOrderTypesEnum.LIMIT, [OkxOrderTypeEnum.MARKET]: AlunaOrderTypesEnum.MARKET, + [OkxOrderTypeEnum.CONDITIONAL]: AlunaOrderTypesEnum.STOP_LIMIT, }, }) @@ -29,5 +30,6 @@ export const translateOrderTypeToOkx = buildAdapter< mappings: { [AlunaOrderTypesEnum.LIMIT]: OkxOrderTypeEnum.LIMIT, [AlunaOrderTypesEnum.MARKET]: OkxOrderTypeEnum.MARKET, + [AlunaOrderTypesEnum.STOP_LIMIT]: OkxOrderTypeEnum.CONDITIONAL, }, }) diff --git a/src/exchanges/okx/modules/authed/order/cancel.ts b/src/exchanges/okx/modules/authed/order/cancel.ts index 98e09743..6e8c49f9 100644 --- a/src/exchanges/okx/modules/authed/order/cancel.ts +++ b/src/exchanges/okx/modules/authed/order/cancel.ts @@ -2,6 +2,7 @@ import { debug } from 'debug' import { AlunaError } from '../../../../../lib/core/AlunaError' import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { AlunaOrderTypesEnum } from '../../../../../lib/enums/AlunaOrderTypesEnum' import { AlunaOrderErrorCodes } from '../../../../../lib/errors/AlunaOrderErrorCodes' import { IAlunaOrderCancelParams, @@ -33,23 +34,47 @@ export const cancel = (exchange: IAlunaExchangeAuthed) => async ( http = new OkxHttp(settings), } = params + const { order } = await exchange.order.get({ + id, + symbolPair, + }) + + const { type } = order + + const isStopLimitOrder = type === AlunaOrderTypesEnum.STOP_LIMIT + const body = { - ordId: id, instId: symbolPair, } + if (isStopLimitOrder) { + + Object.assign(body, { + algoId: id, + }) + + } else { + + Object.assign(body, { + ordId: id, + }) + + } + try { + const orderEndpoints = getOkxEndpoints(settings).order + + const url = isStopLimitOrder + ? orderEndpoints.cancelStopLimit + : orderEndpoints.cancel + await http.authedRequest({ - url: getOkxEndpoints(settings).order.cancel, + url, credentials, body, }) - const { order } = await exchange.order.get({ - id, - symbolPair, - }) const { requestWeight } = http diff --git a/src/exchanges/okx/modules/authed/order/listRaw.ts b/src/exchanges/okx/modules/authed/order/listRaw.ts index 9518154b..35144671 100644 --- a/src/exchanges/okx/modules/authed/order/listRaw.ts +++ b/src/exchanges/okx/modules/authed/order/listRaw.ts @@ -29,12 +29,22 @@ export const listRaw = (exchange: IAlunaExchangeAuthed) => async ( const { http = new OkxHttp(settings) } = params - const rawOrders = await http.authedRequest({ + const orderEndpoints = getOkxEndpoints(settings).order + + const rawNormalOrders = await http.authedRequest({ + verb: AlunaHttpVerbEnum.GET, + url: orderEndpoints.list, + credentials, + }) + + const rawStopLimitOrders = await http.authedRequest({ verb: AlunaHttpVerbEnum.GET, - url: getOkxEndpoints(settings).order.list, + url: orderEndpoints.listStopLimit('conditional'), credentials, }) + const rawOrders = [...rawNormalOrders, ...rawStopLimitOrders] + const { requestWeight } = http return { diff --git a/src/exchanges/okx/modules/authed/order/parse.ts b/src/exchanges/okx/modules/authed/order/parse.ts index 093b8de7..bf7c3d5a 100644 --- a/src/exchanges/okx/modules/authed/order/parse.ts +++ b/src/exchanges/okx/modules/authed/order/parse.ts @@ -33,6 +33,7 @@ export const parse = (exchange: IAlunaExchangeAuthed) => ( ordType, ccy, tgtCcy, + slTriggerPx, } = rawOrder const baseSymbolId = translateSymbolId({ @@ -49,6 +50,7 @@ export const parse = (exchange: IAlunaExchangeAuthed) => ( const amount = Number(sz) const rate = Number(px) const total = amount * rate + const stopRate = Number(slTriggerPx) const orderStatus = translateOrderStatusToAluna({ from: state }) const orderSide = translateOrderSideToAluna({ from: side }) @@ -92,6 +94,7 @@ export const parse = (exchange: IAlunaExchangeAuthed) => ( canceledAt, filledAt, rate, + stopRate, account: AlunaAccountEnum.SPOT, type: orderType, status: orderStatus, diff --git a/src/exchanges/okx/modules/authed/order/place.ts b/src/exchanges/okx/modules/authed/order/place.ts index 484203b8..30c9a5ec 100644 --- a/src/exchanges/okx/modules/authed/order/place.ts +++ b/src/exchanges/okx/modules/authed/order/place.ts @@ -2,6 +2,7 @@ import { debug } from 'debug' import { AlunaError } from '../../../../../lib/core/AlunaError' import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { AlunaOrderTypesEnum } from '../../../../../lib/enums/AlunaOrderTypesEnum' import { AlunaBalanceErrorCodes } from '../../../../../lib/errors/AlunaBalanceErrorCodes' import { AlunaOrderErrorCodes } from '../../../../../lib/errors/AlunaOrderErrorCodes' import { @@ -52,6 +53,7 @@ export const place = (exchange: IAlunaExchangeAuthed) => async ( symbolPair, side, type, + stopRate, http = new OkxHttp(settings), } = params @@ -77,14 +79,28 @@ export const place = (exchange: IAlunaExchangeAuthed) => async ( } + if (translatedOrderType === OkxOrderTypeEnum.CONDITIONAL) { + + Object.assign(body, { + slOrdPx: stopRate!.toString(), + }) + + } + log('placing new order for Okx') let placedOrder: IOkxOrderSchema try { + const orderEndpoints = getOkxEndpoints(settings).order + + const url = type === AlunaOrderTypesEnum.STOP_LIMIT + ? orderEndpoints.placeStopLimit + : orderEndpoints.place + const orderResponse = await http.authedRequest({ - url: getOkxEndpoints(settings).order.place, + url, body, credentials, }) diff --git a/src/exchanges/okx/okxSpecs.ts b/src/exchanges/okx/okxSpecs.ts index c94dc39f..1af8150d 100644 --- a/src/exchanges/okx/okxSpecs.ts +++ b/src/exchanges/okx/okxSpecs.ts @@ -39,6 +39,16 @@ export const okxExchangeOrderTypes: IAlunaExchangeOrderSpecsSchema[] = [ amount: 1, }, }, + { + type: AlunaOrderTypesEnum.STOP_LIMIT, + supported: true, + implemented: true, + mode: AlunaFeaturesModeEnum.WRITE, + options: { + rate: 1, + amount: 1, + }, + }, ] @@ -138,8 +148,11 @@ export const getOkxEndpoints = ( order: { get: (id: string, symbolPair: string) => `${baseUrl}/trade/order?ordId=${id}&$instId=${symbolPair}`, list: `${baseUrl}/trade/orders-pending`, + listStopLimit: (type: string) => `${baseUrl}/trade/orders-algo-pending?ordType=${type}`, place: `${baseUrl}/trade/order`, + placeStopLimit: `${baseUrl}/trade/order-algo`, cancel: `${baseUrl}/trade/cancel-order`, + cancelStopLimit: `${baseUrl}/trade/cancel-algos`, }, } } From d237c4d9342c4b0851c6822331ff9d4e92a73de2 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Fri, 20 May 2022 14:23:43 -0300 Subject: [PATCH 044/105] draft: add support to stop-market --- .../enums/adapters/okxOrderTypeAdapter.spec.ts | 4 ++++ .../okx/enums/adapters/okxOrderTypeAdapter.ts | 1 + src/exchanges/okx/modules/authed/order/place.ts | 17 ++++++++++++++--- src/exchanges/okx/okxSpecs.ts | 10 ++++++++++ 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.spec.ts b/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.spec.ts index d1490636..a806914a 100644 --- a/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.spec.ts +++ b/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.spec.ts @@ -70,6 +70,10 @@ describe(__filename, () => { from: AlunaOrderTypesEnum.STOP_LIMIT, })).to.be.eq(OkxOrderTypeEnum.CONDITIONAL) + expect(translateOrderTypeToOkx({ + from: AlunaOrderTypesEnum.STOP_MARKET, + })).to.be.eq(OkxOrderTypeEnum.CONDITIONAL) + let result let error diff --git a/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.ts b/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.ts index 3bd62bd9..bb0d0af3 100644 --- a/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.ts +++ b/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.ts @@ -31,5 +31,6 @@ export const translateOrderTypeToOkx = buildAdapter< [AlunaOrderTypesEnum.LIMIT]: OkxOrderTypeEnum.LIMIT, [AlunaOrderTypesEnum.MARKET]: OkxOrderTypeEnum.MARKET, [AlunaOrderTypesEnum.STOP_LIMIT]: OkxOrderTypeEnum.CONDITIONAL, + [AlunaOrderTypesEnum.STOP_MARKET]: OkxOrderTypeEnum.CONDITIONAL, }, }) diff --git a/src/exchanges/okx/modules/authed/order/place.ts b/src/exchanges/okx/modules/authed/order/place.ts index 30c9a5ec..a13466ce 100644 --- a/src/exchanges/okx/modules/authed/order/place.ts +++ b/src/exchanges/okx/modules/authed/order/place.ts @@ -81,9 +81,20 @@ export const place = (exchange: IAlunaExchangeAuthed) => async ( if (translatedOrderType === OkxOrderTypeEnum.CONDITIONAL) { - Object.assign(body, { - slOrdPx: stopRate!.toString(), - }) + if (type === AlunaOrderTypesEnum.STOP_MARKET) { + + Object.assign(body, { + slOrdPx: '-1', + }) + + } else { + + Object.assign(body, { + slOrdPx: stopRate!.toString(), + }) + + } + } diff --git a/src/exchanges/okx/okxSpecs.ts b/src/exchanges/okx/okxSpecs.ts index 1af8150d..e58ddcdc 100644 --- a/src/exchanges/okx/okxSpecs.ts +++ b/src/exchanges/okx/okxSpecs.ts @@ -49,6 +49,16 @@ export const okxExchangeOrderTypes: IAlunaExchangeOrderSpecsSchema[] = [ amount: 1, }, }, + { + type: AlunaOrderTypesEnum.STOP_MARKET, + supported: true, + implemented: true, + mode: AlunaFeaturesModeEnum.WRITE, + options: { + rate: 1, + amount: 1, + }, + }, ] From 2bf302845bc6ca70fb26e4427b7526f6b7348763 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Fri, 20 May 2022 14:30:17 -0300 Subject: [PATCH 045/105] draft: fix stop limit orders --- src/exchanges/okx/modules/authed/order/place.ts | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/exchanges/okx/modules/authed/order/place.ts b/src/exchanges/okx/modules/authed/order/place.ts index a13466ce..379d01c4 100644 --- a/src/exchanges/okx/modules/authed/order/place.ts +++ b/src/exchanges/okx/modules/authed/order/place.ts @@ -54,6 +54,7 @@ export const place = (exchange: IAlunaExchangeAuthed) => async ( side, type, stopRate, + limitRate, http = new OkxHttp(settings), } = params @@ -84,17 +85,20 @@ export const place = (exchange: IAlunaExchangeAuthed) => async ( if (type === AlunaOrderTypesEnum.STOP_MARKET) { Object.assign(body, { - slOrdPx: '-1', + slTriggerPx: '-1', }) } else { Object.assign(body, { - slOrdPx: stopRate!.toString(), + slTriggerPx: limitRate!.toString(), }) } + Object.assign(body, { + slOrdPx: stopRate!.toString(), + }) } From d9c567e9740c2a0e37d84449a2e16e7024ca9d56 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Fri, 20 May 2022 14:38:15 -0300 Subject: [PATCH 046/105] draft: fix stop order limit --- src/exchanges/okx/modules/authed/order/place.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/exchanges/okx/modules/authed/order/place.ts b/src/exchanges/okx/modules/authed/order/place.ts index 379d01c4..40a17390 100644 --- a/src/exchanges/okx/modules/authed/order/place.ts +++ b/src/exchanges/okx/modules/authed/order/place.ts @@ -85,19 +85,19 @@ export const place = (exchange: IAlunaExchangeAuthed) => async ( if (type === AlunaOrderTypesEnum.STOP_MARKET) { Object.assign(body, { - slTriggerPx: '-1', + slOrdPx: '-1', }) } else { Object.assign(body, { - slTriggerPx: limitRate!.toString(), + slOrdPx: limitRate!.toString(), }) } Object.assign(body, { - slOrdPx: stopRate!.toString(), + slTriggerPx: stopRate!.toString(), }) } From 6da15a5715f8e57fa6dd29791c9a8971ac7f49ed Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Mon, 23 May 2022 07:13:09 -0300 Subject: [PATCH 047/105] add 100% test coverage for okx --- .../modules/authed/order/parse.spec.ts | 21 +-- .../okx/enums/OkxAlgoOrderTypeEnum.ts | 8 + .../okx/modules/authed/order/cancel.spec.ts | 70 ++++++++- .../okx/modules/authed/order/cancel.ts | 5 +- .../okx/modules/authed/order/listRaw.spec.ts | 12 +- .../okx/modules/authed/order/listRaw.ts | 3 +- .../okx/modules/authed/order/place.spec.ts | 146 ++++++++++++++++++ .../okx/modules/authed/order/place.ts | 4 +- src/exchanges/okx/okxSpecs.ts | 7 +- 9 files changed, 256 insertions(+), 20 deletions(-) create mode 100644 src/exchanges/okx/enums/OkxAlgoOrderTypeEnum.ts diff --git a/src/exchanges/binance/modules/authed/order/parse.spec.ts b/src/exchanges/binance/modules/authed/order/parse.spec.ts index 7d7158f5..791885b5 100644 --- a/src/exchanges/binance/modules/authed/order/parse.spec.ts +++ b/src/exchanges/binance/modules/authed/order/parse.spec.ts @@ -1,5 +1,6 @@ import { expect } from 'chai' import { cloneDeep } from 'lodash' +import { ImportMock } from 'ts-mock-imports' import { AlunaAccountEnum } from '../../../../../lib/enums/AlunaAccountEnum' import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' @@ -188,8 +189,16 @@ describe(__filename, () => { const exchange = new BinanceAuthed({ credentials }) + const timestamp = new Date() + // mocking + ImportMock.mockFunction( + global, + 'Date', + timestamp, + ) + const { translateSymbolId } = mockTranslateSymbolId() translateSymbolId.onFirstCall().returns(baseAsset) @@ -214,16 +223,8 @@ describe(__filename, () => { expect(order.status).to.be.eq(orderStatus) expect(order.side).to.be.eq(orderSide) expect(order.type).to.be.eq(orderType) - expect(order.filledAt?.getTime()).to.be.eq(updatedAt.getTime()) - expect(order.placedAt - .getTime() - .toString() - .substring(0, 11)).to.be.eq( - new Date() - .getTime() - .toString() - .substring(0, 11), - ) + expect(order.filledAt?.getTime()).to.be.eq(timestamp.getTime()) + expect(order.placedAt.getTime()).to.be.eq(timestamp.getTime()) }) diff --git a/src/exchanges/okx/enums/OkxAlgoOrderTypeEnum.ts b/src/exchanges/okx/enums/OkxAlgoOrderTypeEnum.ts new file mode 100644 index 00000000..ebe75f03 --- /dev/null +++ b/src/exchanges/okx/enums/OkxAlgoOrderTypeEnum.ts @@ -0,0 +1,8 @@ +export enum OkxAlgoOrderTypeEnum { + CONDITIONAL = 'conditional', + OCO = 'oco', + TRIGGER = 'trigger', + MOVE_ORDER_STOP = 'move_order_stop', + ICEBERG = 'iceberg', + TWAP = 'twap' +} diff --git a/src/exchanges/okx/modules/authed/order/cancel.spec.ts b/src/exchanges/okx/modules/authed/order/cancel.spec.ts index be865dc6..4f9d99dd 100644 --- a/src/exchanges/okx/modules/authed/order/cancel.spec.ts +++ b/src/exchanges/okx/modules/authed/order/cancel.spec.ts @@ -1,9 +1,11 @@ import { expect } from 'chai' +import { cloneDeep } from 'lodash' import { PARSED_ORDERS } from '../../../../../../test/fixtures/parsedOrders' import { mockHttp } from '../../../../../../test/mocks/exchange/Http' import { mockGet } from '../../../../../../test/mocks/exchange/modules/mockGet' import { AlunaError } from '../../../../../lib/core/AlunaError' +import { AlunaOrderTypesEnum } from '../../../../../lib/enums/AlunaOrderTypesEnum' import { AlunaOrderErrorCodes } from '../../../../../lib/errors/AlunaOrderErrorCodes' import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' import { executeAndCatch } from '../../../../../utils/executeAndCatch' @@ -73,9 +75,65 @@ describe(__filename, () => { }) + it('should cancel a Okx stop order just fine', async () => { + + // preparing data + const mockedRawOrder = OKX_RAW_ORDERS[0] + const mockedParsedOrder = cloneDeep(PARSED_ORDERS[0]) + + mockedParsedOrder.type = AlunaOrderTypesEnum.STOP_LIMIT + + const { ordId: id } = mockedRawOrder + + const body = { + algoId: id, + instId: '', + } + + + // mocking + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + const { get } = mockGet({ module: getMod }) + + get.returns({ order: mockedParsedOrder }) + + authedRequest.returns(Promise.resolve(mockedRawOrder)) + + + // executing + const exchange = new OkxAuthed({ credentials }) + + const { order } = await exchange.order.cancel({ + id, + symbolPair: '', + }) + + + // validating + expect(order).to.deep.eq(mockedParsedOrder) + + expect(authedRequest.callCount).to.be.eq(1) + + expect(authedRequest.firstCall.args[0]).to.deep.eq({ + credentials, + url: getOkxEndpoints(exchange.settings).order.cancelStop, + body, + }) + + expect(publicRequest.callCount).to.be.eq(0) + + }) + it('should throw an error when canceling a Okx order', async () => { // preparing data + + const mockedOrder = cloneDeep(OKX_RAW_ORDERS[0]) + const id = 'id' const symbolPair = 'symbolPair' @@ -97,8 +155,11 @@ describe(__filename, () => { metadata: {}, }) - authedRequest.returns(Promise.reject(error)) + const { get } = mockGet({ module: getMod }) + + get.returns({ order: mockedOrder }) + authedRequest.returns(Promise.reject(error)) // executing const exchange = new OkxAuthed({ credentials }) @@ -114,6 +175,13 @@ describe(__filename, () => { // validating expect(responseError).to.deep.eq(error) + expect(get.callCount).to.be.eq(1) + + expect(get.firstCall.args[0]).to.deep.eq({ + id, + symbolPair, + }) + expect(authedRequest.callCount).to.be.eq(1) expect(authedRequest.firstCall.args[0]).to.deep.eq({ diff --git a/src/exchanges/okx/modules/authed/order/cancel.ts b/src/exchanges/okx/modules/authed/order/cancel.ts index 6e8c49f9..db2ac162 100644 --- a/src/exchanges/okx/modules/authed/order/cancel.ts +++ b/src/exchanges/okx/modules/authed/order/cancel.ts @@ -2,6 +2,7 @@ import { debug } from 'debug' import { AlunaError } from '../../../../../lib/core/AlunaError' import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { AlunaOrderStatusEnum } from '../../../../../lib/enums/AlunaOrderStatusEnum' import { AlunaOrderTypesEnum } from '../../../../../lib/enums/AlunaOrderTypesEnum' import { AlunaOrderErrorCodes } from '../../../../../lib/errors/AlunaOrderErrorCodes' import { @@ -65,7 +66,7 @@ export const cancel = (exchange: IAlunaExchangeAuthed) => async ( const orderEndpoints = getOkxEndpoints(settings).order const url = isStopLimitOrder - ? orderEndpoints.cancelStopLimit + ? orderEndpoints.cancelStop : orderEndpoints.cancel @@ -75,6 +76,8 @@ export const cancel = (exchange: IAlunaExchangeAuthed) => async ( body, }) + // assign canceled if the cancel request works fine + order.status = AlunaOrderStatusEnum.CANCELED const { requestWeight } = http diff --git a/src/exchanges/okx/modules/authed/order/listRaw.spec.ts b/src/exchanges/okx/modules/authed/order/listRaw.spec.ts index 07ae629d..72974a96 100644 --- a/src/exchanges/okx/modules/authed/order/listRaw.spec.ts +++ b/src/exchanges/okx/modules/authed/order/listRaw.spec.ts @@ -3,6 +3,7 @@ import { expect } from 'chai' import { mockHttp } from '../../../../../../test/mocks/exchange/Http' import { AlunaHttpVerbEnum } from '../../../../../lib/enums/AlunaHtttpVerbEnum' import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' +import { OkxAlgoOrderTypeEnum } from '../../../enums/OkxAlgoOrderTypeEnum' import { OkxAuthed } from '../../../OkxAuthed' import { OkxHttp } from '../../../OkxHttp' import { getOkxEndpoints } from '../../../okxSpecs' @@ -37,9 +38,9 @@ describe(__filename, () => { // validating - expect(rawOrders).to.deep.eq(mockedRawOrders) + expect(rawOrders).to.deep.eq([...mockedRawOrders, ...mockedRawOrders]) - expect(authedRequest.callCount).to.be.eq(1) + expect(authedRequest.callCount).to.be.eq(2) expect(authedRequest.firstCall.args[0]).to.deep.eq({ verb: AlunaHttpVerbEnum.GET, @@ -47,6 +48,13 @@ describe(__filename, () => { url: getOkxEndpoints(exchange.settings).order.list, }) + // add enum to url type + expect(authedRequest.secondCall.args[0]).to.deep.eq({ + verb: AlunaHttpVerbEnum.GET, + credentials, + url: getOkxEndpoints(exchange.settings).order.listStop(OkxAlgoOrderTypeEnum.CONDITIONAL), + }) + expect(publicRequest.callCount).to.be.eq(0) }) diff --git a/src/exchanges/okx/modules/authed/order/listRaw.ts b/src/exchanges/okx/modules/authed/order/listRaw.ts index 35144671..e0dd54fd 100644 --- a/src/exchanges/okx/modules/authed/order/listRaw.ts +++ b/src/exchanges/okx/modules/authed/order/listRaw.ts @@ -6,6 +6,7 @@ import { IAlunaOrderListParams, IAlunaOrderListRawReturns, } from '../../../../../lib/modules/authed/IAlunaOrderModule' +import { OkxAlgoOrderTypeEnum } from '../../../enums/OkxAlgoOrderTypeEnum' import { OkxHttp } from '../../../OkxHttp' import { getOkxEndpoints } from '../../../okxSpecs' import { IOkxOrderSchema } from '../../../schemas/IOkxOrderSchema' @@ -39,7 +40,7 @@ export const listRaw = (exchange: IAlunaExchangeAuthed) => async ( const rawStopLimitOrders = await http.authedRequest({ verb: AlunaHttpVerbEnum.GET, - url: orderEndpoints.listStopLimit('conditional'), + url: orderEndpoints.listStop(OkxAlgoOrderTypeEnum.CONDITIONAL), credentials, }) diff --git a/src/exchanges/okx/modules/authed/order/place.spec.ts b/src/exchanges/okx/modules/authed/order/place.spec.ts index 40f66b67..1e8ac189 100644 --- a/src/exchanges/okx/modules/authed/order/place.spec.ts +++ b/src/exchanges/okx/modules/authed/order/place.spec.ts @@ -101,6 +101,152 @@ describe(__filename, () => { }) + it('should place a Okx conditional stop limit order just fine', async () => { + + // preparing data + const mockedRawOrder = OKX_RAW_ORDERS[0] + const mockedParsedOrder = PARSED_ORDERS[0] + + const side = AlunaOrderSideEnum.BUY + const type = AlunaOrderTypesEnum.STOP_LIMIT + + const translatedOrderSide = translateOrderSideToOkx({ from: side }) + const translatedOrderType = translateOrderTypeToOkx({ from: type }) + + const body = { + side: translatedOrderSide, + instId: '', + ordType: translatedOrderType, + sz: '0.01', + tdMode: 'cash', + slOrdPx: '2', + slTriggerPx: '1', + } + + // mocking + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + const { parse } = mockParse({ module: parseMod }) + + parse.returns({ order: mockedParsedOrder }) + + authedRequest.returns(Promise.resolve(mockedRawOrder)) + + mockValidateParams() + + const { ensureOrderIsSupported } = mockEnsureOrderIsSupported() + + + // executing + const exchange = new OkxAuthed({ credentials }) + + const params: IAlunaOrderPlaceParams = { + symbolPair: '', + account: AlunaAccountEnum.SPOT, + amount: 0.01, + side, + type, + rate: 0, + stopRate: 1, + limitRate: 2, + } + + const { order } = await exchange.order.place(params) + + + // validating + expect(order).to.deep.eq(mockedParsedOrder) + + expect(authedRequest.callCount).to.be.eq(1) + + expect(authedRequest.firstCall.args[0]).to.deep.eq({ + body, + credentials, + url: getOkxEndpoints(exchange.settings).order.placeStop, + }) + + expect(publicRequest.callCount).to.be.eq(0) + + expect(ensureOrderIsSupported.callCount).to.be.eq(1) + + }) + + it('should place a Okx conditional stop market order just fine', async () => { + + // preparing data + const mockedRawOrder = OKX_RAW_ORDERS[0] + const mockedParsedOrder = PARSED_ORDERS[0] + + const side = AlunaOrderSideEnum.BUY + const type = AlunaOrderTypesEnum.STOP_MARKET + + const translatedOrderSide = translateOrderSideToOkx({ from: side }) + const translatedOrderType = translateOrderTypeToOkx({ from: type }) + + const body = { + side: translatedOrderSide, + instId: '', + ordType: translatedOrderType, + sz: '0.01', + tdMode: 'cash', + slOrdPx: '-1', + slTriggerPx: '1', + } + + // mocking + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + const { parse } = mockParse({ module: parseMod }) + + parse.returns({ order: mockedParsedOrder }) + + authedRequest.returns(Promise.resolve(mockedRawOrder)) + + mockValidateParams() + + const { ensureOrderIsSupported } = mockEnsureOrderIsSupported() + + + // executing + const exchange = new OkxAuthed({ credentials }) + + const params: IAlunaOrderPlaceParams = { + symbolPair: '', + account: AlunaAccountEnum.SPOT, + amount: 0.01, + side, + type, + rate: 0, + stopRate: 1, + limitRate: 2, + } + + const { order } = await exchange.order.place(params) + + + // validating + expect(order).to.deep.eq(mockedParsedOrder) + + expect(authedRequest.callCount).to.be.eq(1) + + expect(authedRequest.firstCall.args[0]).to.deep.eq({ + body, + credentials, + url: getOkxEndpoints(exchange.settings).order.placeStop, + }) + + expect(publicRequest.callCount).to.be.eq(0) + + expect(ensureOrderIsSupported.callCount).to.be.eq(1) + + }) + it('should place a Okx market order just fine', async () => { // preparing data diff --git a/src/exchanges/okx/modules/authed/order/place.ts b/src/exchanges/okx/modules/authed/order/place.ts index 40a17390..139324d0 100644 --- a/src/exchanges/okx/modules/authed/order/place.ts +++ b/src/exchanges/okx/modules/authed/order/place.ts @@ -110,8 +110,8 @@ export const place = (exchange: IAlunaExchangeAuthed) => async ( const orderEndpoints = getOkxEndpoints(settings).order - const url = type === AlunaOrderTypesEnum.STOP_LIMIT - ? orderEndpoints.placeStopLimit + const url = translatedOrderType === OkxOrderTypeEnum.CONDITIONAL + ? orderEndpoints.placeStop : orderEndpoints.place const orderResponse = await http.authedRequest({ diff --git a/src/exchanges/okx/okxSpecs.ts b/src/exchanges/okx/okxSpecs.ts index e58ddcdc..8efdb527 100644 --- a/src/exchanges/okx/okxSpecs.ts +++ b/src/exchanges/okx/okxSpecs.ts @@ -10,6 +10,7 @@ import { IAlunaExchangeSchema, } from '../../lib/schemas/IAlunaExchangeSchema' import { IAlunaSettingsSchema } from '../../lib/schemas/IAlunaSettingsSchema' +import { OkxAlgoOrderTypeEnum } from './enums/OkxAlgoOrderTypeEnum' import { OkxSymbolTypeEnum } from './enums/OkxSymbolTypeEnum' @@ -158,11 +159,11 @@ export const getOkxEndpoints = ( order: { get: (id: string, symbolPair: string) => `${baseUrl}/trade/order?ordId=${id}&$instId=${symbolPair}`, list: `${baseUrl}/trade/orders-pending`, - listStopLimit: (type: string) => `${baseUrl}/trade/orders-algo-pending?ordType=${type}`, + listStop: (type: OkxAlgoOrderTypeEnum) => `${baseUrl}/trade/orders-algo-pending?ordType=${type}`, place: `${baseUrl}/trade/order`, - placeStopLimit: `${baseUrl}/trade/order-algo`, + placeStop: `${baseUrl}/trade/order-algo`, cancel: `${baseUrl}/trade/cancel-order`, - cancelStopLimit: `${baseUrl}/trade/cancel-algos`, + cancelStop: `${baseUrl}/trade/cancel-algos`, }, } } From 4309dc46d7639c9decc16e78bea862f99823c6e1 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Mon, 23 May 2022 08:14:02 -0300 Subject: [PATCH 048/105] linting --- src/exchanges/binance/modules/authed/order/parse.spec.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/exchanges/binance/modules/authed/order/parse.spec.ts b/src/exchanges/binance/modules/authed/order/parse.spec.ts index 791885b5..e0c54393 100644 --- a/src/exchanges/binance/modules/authed/order/parse.spec.ts +++ b/src/exchanges/binance/modules/authed/order/parse.spec.ts @@ -182,11 +182,8 @@ describe(__filename, () => { symbol, type, status, - updateTime, } = rawOrder - const updatedAt = new Date(updateTime!) - const exchange = new BinanceAuthed({ credentials }) const timestamp = new Date() From a75733ade8ef160b8eccdf3587ce1f3f4f62efd0 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Fri, 27 May 2022 21:58:37 -0300 Subject: [PATCH 049/105] review okx symbols and markets to support margin --- .../public/helpers/fetchInstruments.spec.ts | 56 +++++++++++++++++++ .../public/helpers/fetchInstruments.ts | 24 ++++++++ .../okx/modules/public/market/listRaw.spec.ts | 24 +++++++- .../okx/modules/public/market/listRaw.ts | 15 ++++- .../okx/modules/public/market/parse.spec.ts | 11 +++- .../okx/modules/public/market/parse.ts | 19 +++++-- .../modules/public/market/parseMany.spec.ts | 10 +++- .../okx/modules/public/market/parseMany.ts | 31 ++++++++-- .../okx/modules/public/symbol/listRaw.spec.ts | 17 +++--- .../okx/modules/public/symbol/listRaw.ts | 20 +++++-- src/exchanges/okx/schemas/IOkxHelperSchema.ts | 16 ++++++ src/exchanges/okx/schemas/IOkxMarketSchema.ts | 15 +++++ 12 files changed, 230 insertions(+), 28 deletions(-) create mode 100644 src/exchanges/okx/modules/public/helpers/fetchInstruments.spec.ts create mode 100644 src/exchanges/okx/modules/public/helpers/fetchInstruments.ts create mode 100644 src/exchanges/okx/schemas/IOkxHelperSchema.ts diff --git a/src/exchanges/okx/modules/public/helpers/fetchInstruments.spec.ts b/src/exchanges/okx/modules/public/helpers/fetchInstruments.spec.ts new file mode 100644 index 00000000..6004c16f --- /dev/null +++ b/src/exchanges/okx/modules/public/helpers/fetchInstruments.spec.ts @@ -0,0 +1,56 @@ +import { expect } from 'chai' +import { mockHttp } from '../../../../../../test/mocks/exchange/Http' + +import { OkxSymbolTypeEnum } from '../../../enums/OkxSymbolTypeEnum' +import { Okx } from '../../../Okx' +import { OkxHttp } from '../../../OkxHttp' +import { getOkxEndpoints } from '../../../okxSpecs' +import { OKX_RAW_SYMBOLS } from '../../../test/fixtures/okxSymbols' +import * as fetchInstrumentsMod from './fetchInstruments' + + + +describe(__filename, () => { + + const { + fetchInstruments, + } = fetchInstrumentsMod + + it('should get a Okx raw order status just fine', async () => { + + // preparing data + const mockedRawSymbols = OKX_RAW_SYMBOLS + + // mocking + + const http = new OkxHttp({ }) + + const { + publicRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + publicRequest + .onFirstCall() + .returns(Promise.resolve(mockedRawSymbols)) + + // executing + const exchange = new Okx({}) + + const { instruments } = await fetchInstruments({ + settings: exchange.settings, + http, + type: OkxSymbolTypeEnum.SPOT, + }) + + // validating + expect(instruments).to.deep.eq(OKX_RAW_SYMBOLS) + + expect(publicRequest.callCount).to.be.eq(1) + + expect(publicRequest.firstCall.args[0]).to.deep.eq({ + url: getOkxEndpoints(exchange.settings).symbol.list(OkxSymbolTypeEnum.SPOT), + }) + + }) + +}) diff --git a/src/exchanges/okx/modules/public/helpers/fetchInstruments.ts b/src/exchanges/okx/modules/public/helpers/fetchInstruments.ts new file mode 100644 index 00000000..1e8f4355 --- /dev/null +++ b/src/exchanges/okx/modules/public/helpers/fetchInstruments.ts @@ -0,0 +1,24 @@ +import { getOkxEndpoints } from '../../../okxSpecs' +import { IOkxFetchInstrumentsHelperParams, IOkxFetchInstrumentsHelperReturns } from '../../../schemas/IOkxHelperSchema' +import { IOkxSymbolSchema } from '../../../schemas/IOkxSymbolSchema' + + + +export const fetchInstruments = async ( + params: IOkxFetchInstrumentsHelperParams, +): Promise => { + + const { + http, + settings, + type, + } = params + + const instruments = await http.publicRequest({ + url: getOkxEndpoints(settings).symbol.list(type), + }) + + return { + instruments, + } +} diff --git a/src/exchanges/okx/modules/public/market/listRaw.spec.ts b/src/exchanges/okx/modules/public/market/listRaw.spec.ts index 62801ea6..063225a3 100644 --- a/src/exchanges/okx/modules/public/market/listRaw.spec.ts +++ b/src/exchanges/okx/modules/public/market/listRaw.spec.ts @@ -1,11 +1,14 @@ import { expect } from 'chai' import { mockHttp } from '../../../../../../test/mocks/exchange/Http' +import { mockListRaw } from '../../../../../../test/mocks/exchange/modules/mockListRaw' import { OkxSymbolTypeEnum } from '../../../enums/OkxSymbolTypeEnum' import { Okx } from '../../../Okx' import { OkxHttp } from '../../../OkxHttp' import { getOkxEndpoints } from '../../../okxSpecs' import { OKX_RAW_MARKETS } from '../../../test/fixtures/okxMarket' +import { OKX_RAW_SYMBOLS } from '../../../test/fixtures/okxSymbols' +import * as listRawMod from '../symbol/listRaw' @@ -18,6 +21,8 @@ describe(__filename, () => { const type = OkxSymbolTypeEnum.SPOT // mocking + const http = new OkxHttp({}) + const { publicRequest, authedRequest, @@ -26,6 +31,14 @@ describe(__filename, () => { publicRequest.returns(Promise.resolve(OKX_RAW_MARKETS)) + const { listRaw: listRawSymbols } = mockListRaw({ + module: listRawMod, + }) + + listRawSymbols.returns(Promise.resolve({ + rawSymbols: OKX_RAW_SYMBOLS, + })) + // executing const exchange = new Okx({}) @@ -36,7 +49,16 @@ describe(__filename, () => { // validating - expect(rawMarkets).to.deep.eq(OKX_RAW_MARKETS) + expect(rawMarkets).to.deep.eq({ + rawMarkets: OKX_RAW_MARKETS, + rawSymbols: OKX_RAW_SYMBOLS, + }) + + expect(listRawSymbols.callCount).to.be.eq(1) + + expect(listRawSymbols.firstCall.args[0]).to.deep.eq({ + http, + }) expect(requestWeight).to.be.ok diff --git a/src/exchanges/okx/modules/public/market/listRaw.ts b/src/exchanges/okx/modules/public/market/listRaw.ts index 3e568b19..75c72fd5 100644 --- a/src/exchanges/okx/modules/public/market/listRaw.ts +++ b/src/exchanges/okx/modules/public/market/listRaw.ts @@ -8,7 +8,7 @@ import { import { OkxSymbolTypeEnum } from '../../../enums/OkxSymbolTypeEnum' import { OkxHttp } from '../../../OkxHttp' import { getOkxEndpoints } from '../../../okxSpecs' -import { IOkxMarketSchema } from '../../../schemas/IOkxMarketSchema' +import { IOkxMarketSchema, IOkxMarketsResponseSchema } from '../../../schemas/IOkxMarketSchema' @@ -18,7 +18,7 @@ const log = debug('alunajs:okx/market/listRaw') export const listRaw = (exchange: IAlunaExchangePublic) => async ( params: IAlunaMarketListParams = {}, -): Promise> => { +): Promise> => { const { settings } = exchange @@ -32,10 +32,19 @@ export const listRaw = (exchange: IAlunaExchangePublic) => async ( url: getOkxEndpoints(settings).market.list(type), }) + const { rawSymbols } = await exchange.symbol.listRaw({ + http, + }) + const { requestWeight } = http - return { + const rawMarketsResponse: IOkxMarketsResponseSchema = { rawMarkets, + rawSymbols, + } + + return { + rawMarkets: rawMarketsResponse, requestWeight, } diff --git a/src/exchanges/okx/modules/public/market/parse.spec.ts b/src/exchanges/okx/modules/public/market/parse.spec.ts index dd577277..314256c7 100644 --- a/src/exchanges/okx/modules/public/market/parse.spec.ts +++ b/src/exchanges/okx/modules/public/market/parse.spec.ts @@ -3,6 +3,7 @@ import { expect } from 'chai' import { mockTranslateSymbolId } from '../../../../../utils/mappings/translateSymbolId.mock' import { Okx } from '../../../Okx' import { OKX_RAW_MARKETS } from '../../../test/fixtures/okxMarket' +import { OKX_RAW_SYMBOLS } from '../../../test/fixtures/okxSymbols' @@ -13,6 +14,13 @@ describe(__filename, () => { // preparing data const rawMarket = OKX_RAW_MARKETS[0] + const rawSpotSymbol = OKX_RAW_SYMBOLS[0] + + const rawMarketRequest = { + rawMarket, + rawSpotSymbol, + } + const { askPx, bidPx, @@ -43,14 +51,13 @@ describe(__filename, () => { const exchange = new Okx({}) const { market } = exchange.market.parse({ - rawMarket, + rawMarket: rawMarketRequest, }) // validating expect(market).to.exist - expect(market.exchangeId).to.be.eq(exchange.specs.id) expect(market.baseSymbolId).to.be.eq(baseCurrency) expect(market.quoteSymbolId).to.be.eq(quoteCurrency) diff --git a/src/exchanges/okx/modules/public/market/parse.ts b/src/exchanges/okx/modules/public/market/parse.ts index e0848419..cfd09d2e 100644 --- a/src/exchanges/okx/modules/public/market/parse.ts +++ b/src/exchanges/okx/modules/public/market/parse.ts @@ -5,15 +5,21 @@ import { } from '../../../../../lib/modules/public/IAlunaMarketModule' import { IAlunaMarketSchema } from '../../../../../lib/schemas/IAlunaMarketSchema' import { translateSymbolId } from '../../../../../utils/mappings/translateSymbolId' -import { IOkxMarketSchema } from '../../../schemas/IOkxMarketSchema' +import { IOkxMarketResponseSchema } from '../../../schemas/IOkxMarketSchema' export const parse = (exchange: IAlunaExchangePublic) => ( - params: IAlunaMarketParseParams, + params: IAlunaMarketParseParams, ): IAlunaMarketParseReturns => { - const { rawMarket } = params + const { rawMarket: rawMarketResponse } = params + + const { + rawMarket, + rawMarginSymbol, + rawSpotSymbol, + } = rawMarketResponse const { askPx, @@ -58,16 +64,19 @@ export const parse = (exchange: IAlunaExchangePublic) => ( quoteVolume: Number(volCcy24h), } + const spotEnabled = !!rawSpotSymbol + const marginEnabled = !!rawMarginSymbol + const market: IAlunaMarketSchema = { symbolPair: instId, baseSymbolId, quoteSymbolId, ticker, exchangeId: exchange.specs.id, - spotEnabled: true, + spotEnabled, + marginEnabled, derivativesEnabled: false, leverageEnabled: false, - marginEnabled: false, meta: rawMarket, } diff --git a/src/exchanges/okx/modules/public/market/parseMany.spec.ts b/src/exchanges/okx/modules/public/market/parseMany.spec.ts index 5349a5a1..59c2c6df 100644 --- a/src/exchanges/okx/modules/public/market/parseMany.spec.ts +++ b/src/exchanges/okx/modules/public/market/parseMany.spec.ts @@ -5,6 +5,7 @@ import { PARSED_MARKETS } from '../../../../../../test/fixtures/parsedMarkets' import { mockParse } from '../../../../../../test/mocks/exchange/modules/mockParse' import { Okx } from '../../../Okx' import { OKX_RAW_MARKETS } from '../../../test/fixtures/okxMarket' +import { OKX_RAW_SYMBOLS } from '../../../test/fixtures/okxSymbols' import * as parseMod from './parse' @@ -16,6 +17,13 @@ describe(__filename, () => { // preparing data const rawMarkets = OKX_RAW_MARKETS + const rawSymbols = OKX_RAW_SYMBOLS + + const rawMarketsRequest = { + rawMarkets, + rawSymbols, + } + // mocking const { parse } = mockParse({ module: parseMod }) @@ -28,7 +36,7 @@ describe(__filename, () => { const exchange = new Okx({}) const { markets } = exchange.market.parseMany({ - rawMarkets, + rawMarkets: rawMarketsRequest, }) diff --git a/src/exchanges/okx/modules/public/market/parseMany.ts b/src/exchanges/okx/modules/public/market/parseMany.ts index 4e670bda..72ec1428 100644 --- a/src/exchanges/okx/modules/public/market/parseMany.ts +++ b/src/exchanges/okx/modules/public/market/parseMany.ts @@ -1,12 +1,12 @@ import debug from 'debug' -import { map } from 'lodash' +import { find, map } from 'lodash' import { IAlunaExchangePublic } from '../../../../../lib/core/IAlunaExchange' import { IAlunaMarketParseManyParams, IAlunaMarketParseManyReturns, } from '../../../../../lib/modules/public/IAlunaMarketModule' -import { IOkxMarketSchema } from '../../../schemas/IOkxMarketSchema' +import { IOkxMarketResponseSchema, IOkxMarketsResponseSchema } from '../../../schemas/IOkxMarketSchema' @@ -15,15 +15,36 @@ const log = debug('alunajs:okx/market/parseMany') export const parseMany = (exchange: IAlunaExchangePublic) => ( - params: IAlunaMarketParseManyParams, + params: IAlunaMarketParseManyParams, ): IAlunaMarketParseManyReturns => { - const { rawMarkets } = params + const { rawMarkets: rawMarketsResponse } = params + + const { + rawMarkets, + rawSymbols, + } = rawMarketsResponse const markets = map(rawMarkets, (rawMarket) => { - const { market } = exchange.market.parse({ + const { instId } = rawMarket + + const rawSpotSymbol = find(rawSymbols, { + instId, + }) + + const rawMarginSymbol = find(rawSymbols, { + instId, + }) + + const rawMarketRequest: IOkxMarketResponseSchema = { rawMarket, + rawSpotSymbol, + rawMarginSymbol, + } + + const { market } = exchange.market.parse({ + rawMarket: rawMarketRequest, }) return market diff --git a/src/exchanges/okx/modules/public/symbol/listRaw.spec.ts b/src/exchanges/okx/modules/public/symbol/listRaw.spec.ts index 53eea3c7..1744e9c9 100644 --- a/src/exchanges/okx/modules/public/symbol/listRaw.spec.ts +++ b/src/exchanges/okx/modules/public/symbol/listRaw.spec.ts @@ -13,10 +13,6 @@ describe(__filename, () => { it('should list Okx raw symbols just fine', async () => { - // preparing data - - const type = OkxSymbolTypeEnum.SPOT - // mocking const { publicRequest, @@ -36,14 +32,21 @@ describe(__filename, () => { // validating - expect(rawSymbols).to.deep.eq(OKX_RAW_SYMBOLS) + expect(rawSymbols).to.deep.eq([ + ...OKX_RAW_SYMBOLS, + ...OKX_RAW_SYMBOLS, + ]) expect(requestWeight).to.be.ok - expect(publicRequest.callCount).to.be.eq(1) + expect(publicRequest.callCount).to.be.eq(2) expect(publicRequest.firstCall.args[0]).to.deep.eq({ - url: getOkxEndpoints(exchange.settings).symbol.list(type), + url: getOkxEndpoints(exchange.settings).symbol.list(OkxSymbolTypeEnum.SPOT), + }) + + expect(publicRequest.secondCall.args[0]).to.deep.eq({ + url: getOkxEndpoints(exchange.settings).symbol.list(OkxSymbolTypeEnum.MARGIN), }) expect(authedRequest.callCount).to.be.eq(0) diff --git a/src/exchanges/okx/modules/public/symbol/listRaw.ts b/src/exchanges/okx/modules/public/symbol/listRaw.ts index 816ec214..6c22437d 100644 --- a/src/exchanges/okx/modules/public/symbol/listRaw.ts +++ b/src/exchanges/okx/modules/public/symbol/listRaw.ts @@ -7,8 +7,8 @@ import { } from '../../../../../lib/modules/public/IAlunaSymbolModule' import { OkxSymbolTypeEnum } from '../../../enums/OkxSymbolTypeEnum' import { OkxHttp } from '../../../OkxHttp' -import { getOkxEndpoints } from '../../../okxSpecs' import { IOkxSymbolSchema } from '../../../schemas/IOkxSymbolSchema' +import { fetchInstruments } from '../helpers/fetchInstruments' @@ -26,14 +26,26 @@ export const listRaw = (exchange: IAlunaExchangePublic) => async ( const { http = new OkxHttp(settings) } = params - const type = OkxSymbolTypeEnum.SPOT + const { + instruments: rawSpotSymbols, + } = await fetchInstruments({ + http, + settings, + type: OkxSymbolTypeEnum.SPOT, + }) - const rawSymbols = await http.publicRequest({ - url: getOkxEndpoints(settings).symbol.list(type), + const { + instruments: rawMarginSymbols, + } = await fetchInstruments({ + http, + settings, + type: OkxSymbolTypeEnum.MARGIN, }) const { requestWeight } = http + const rawSymbols = [...rawSpotSymbols, ...rawMarginSymbols] + return { requestWeight, rawSymbols, diff --git a/src/exchanges/okx/schemas/IOkxHelperSchema.ts b/src/exchanges/okx/schemas/IOkxHelperSchema.ts new file mode 100644 index 00000000..3fa7117e --- /dev/null +++ b/src/exchanges/okx/schemas/IOkxHelperSchema.ts @@ -0,0 +1,16 @@ +import { IAlunaHttp } from '../../../lib/core/IAlunaHttp' +import { IAlunaSettingsSchema } from '../../../lib/schemas/IAlunaSettingsSchema' +import { OkxSymbolTypeEnum } from '../enums/OkxSymbolTypeEnum' +import { IOkxSymbolSchema } from './IOkxSymbolSchema' + + + +export interface IOkxFetchInstrumentsHelperParams { + http: IAlunaHttp + settings: IAlunaSettingsSchema + type: OkxSymbolTypeEnum +} + +export interface IOkxFetchInstrumentsHelperReturns { + instruments: IOkxSymbolSchema[] +} diff --git a/src/exchanges/okx/schemas/IOkxMarketSchema.ts b/src/exchanges/okx/schemas/IOkxMarketSchema.ts index 5445e036..e248e5e1 100644 --- a/src/exchanges/okx/schemas/IOkxMarketSchema.ts +++ b/src/exchanges/okx/schemas/IOkxMarketSchema.ts @@ -1,3 +1,7 @@ +import { IOkxSymbolSchema } from './IOkxSymbolSchema' + + + export interface IOkxMarketSchema { instType: string instId: string @@ -16,3 +20,14 @@ export interface IOkxMarketSchema { sodUtc0: string sodUtc8: string } + +export interface IOkxMarketsResponseSchema { + rawMarkets: IOkxMarketSchema[] + rawSymbols: IOkxSymbolSchema[] +} + +export interface IOkxMarketResponseSchema { + rawMarket: IOkxMarketSchema + rawSpotSymbol?: IOkxSymbolSchema + rawMarginSymbol?: IOkxSymbolSchema +} From 767f25c2bb3961cf0fbb532c13812ccd9d9a4871 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Fri, 27 May 2022 22:09:00 -0300 Subject: [PATCH 050/105] add 100% test coverage --- src/aluna.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/aluna.spec.ts b/src/aluna.spec.ts index 953f9b3d..88826caa 100644 --- a/src/aluna.spec.ts +++ b/src/aluna.spec.ts @@ -20,7 +20,7 @@ describe(__filename, () => { const exchangeIds = Object.keys(exchanges) - expect(exchangeIds.length).to.eq(8) + expect(exchangeIds.length).to.eq(9) each(exchangeIds, (exchangeId) => { @@ -51,7 +51,7 @@ describe(__filename, () => { const exchangeIds = Object.keys(exchanges) - expect(exchangeIds.length).to.eq(8) + expect(exchangeIds.length).to.eq(9) each(exchangeIds, (exchangeId) => { From 1f3007708966ec4290235fd17bea75ec5c22c4af Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Sun, 29 May 2022 13:55:25 -0300 Subject: [PATCH 051/105] add okx position initial files --- src/exchanges/okx/OkxAuthed.spec.ts | 1 + src/exchanges/okx/OkxAuthed.ts | 4 + src/exchanges/okx/modules/authed/position.ts | 29 ++++ .../okx/modules/authed/position/close.spec.ts | 150 ++++++++++++++++++ .../okx/modules/authed/position/close.ts | 72 +++++++++ .../okx/modules/authed/position/get.spec.ts | 69 ++++++++ .../okx/modules/authed/position/get.ts | 43 +++++ .../authed/position/getLeverage.spec.ts | 63 ++++++++ .../modules/authed/position/getLeverage.ts | 48 ++++++ .../modules/authed/position/getRaw.spec.ts | 115 ++++++++++++++ .../okx/modules/authed/position/getRaw.ts | 61 +++++++ .../okx/modules/authed/position/list.spec.ts | 22 +++ .../okx/modules/authed/position/list.ts | 35 ++++ .../modules/authed/position/listRaw.spec.ts | 52 ++++++ .../okx/modules/authed/position/listRaw.ts | 45 ++++++ .../okx/modules/authed/position/parse.spec.ts | 33 ++++ .../okx/modules/authed/position/parse.ts | 62 ++++++++ .../modules/authed/position/parseMany.spec.ts | 48 ++++++ .../okx/modules/authed/position/parseMany.ts | 36 +++++ .../authed/position/setLeverage.spec.ts | 64 ++++++++ .../modules/authed/position/setLeverage.ts | 49 ++++++ src/exchanges/okx/okxSpecs.ts | 9 ++ .../okx/schemas/IOkxPositionSchema.ts | 6 + .../okx/test/fixtures/okxPositions.ts | 15 ++ 24 files changed, 1131 insertions(+) create mode 100644 src/exchanges/okx/modules/authed/position.ts create mode 100644 src/exchanges/okx/modules/authed/position/close.spec.ts create mode 100644 src/exchanges/okx/modules/authed/position/close.ts create mode 100644 src/exchanges/okx/modules/authed/position/get.spec.ts create mode 100644 src/exchanges/okx/modules/authed/position/get.ts create mode 100644 src/exchanges/okx/modules/authed/position/getLeverage.spec.ts create mode 100644 src/exchanges/okx/modules/authed/position/getLeverage.ts create mode 100644 src/exchanges/okx/modules/authed/position/getRaw.spec.ts create mode 100644 src/exchanges/okx/modules/authed/position/getRaw.ts create mode 100644 src/exchanges/okx/modules/authed/position/list.spec.ts create mode 100644 src/exchanges/okx/modules/authed/position/list.ts create mode 100644 src/exchanges/okx/modules/authed/position/listRaw.spec.ts create mode 100644 src/exchanges/okx/modules/authed/position/listRaw.ts create mode 100644 src/exchanges/okx/modules/authed/position/parse.spec.ts create mode 100644 src/exchanges/okx/modules/authed/position/parse.ts create mode 100644 src/exchanges/okx/modules/authed/position/parseMany.spec.ts create mode 100644 src/exchanges/okx/modules/authed/position/parseMany.ts create mode 100644 src/exchanges/okx/modules/authed/position/setLeverage.spec.ts create mode 100644 src/exchanges/okx/modules/authed/position/setLeverage.ts create mode 100644 src/exchanges/okx/schemas/IOkxPositionSchema.ts create mode 100644 src/exchanges/okx/test/fixtures/okxPositions.ts diff --git a/src/exchanges/okx/OkxAuthed.spec.ts b/src/exchanges/okx/OkxAuthed.spec.ts index d9dd3d1e..a92c39cd 100644 --- a/src/exchanges/okx/OkxAuthed.spec.ts +++ b/src/exchanges/okx/OkxAuthed.spec.ts @@ -29,6 +29,7 @@ describe(__filename, () => { expect(okx.key).to.be.ok expect(okx.balance).to.be.ok expect(okx.order).to.be.ok + expect(okx.position).to.be.ok expect(okx.specs).to.be.ok expect(okx.settings).to.deep.eq(okx.settings) diff --git a/src/exchanges/okx/OkxAuthed.ts b/src/exchanges/okx/OkxAuthed.ts index c2c30ddb..76a4f665 100644 --- a/src/exchanges/okx/OkxAuthed.ts +++ b/src/exchanges/okx/OkxAuthed.ts @@ -2,11 +2,13 @@ import { IAlunaExchangeAuthed } from '../../lib/core/IAlunaExchange' import { IAlunaBalanceModule } from '../../lib/modules/authed/IAlunaBalanceModule' import { IAlunaKeyModule } from '../../lib/modules/authed/IAlunaKeyModule' import { IAlunaOrderWriteModule } from '../../lib/modules/authed/IAlunaOrderModule' +import { IAlunaPositionModule } from '../../lib/modules/authed/IAlunaPositionModule' import { IAlunaCredentialsSchema } from '../../lib/schemas/IAlunaCredentialsSchema' import { IAlunaSettingsSchema } from '../../lib/schemas/IAlunaSettingsSchema' import { balance } from './modules/authed/balance' import { key } from './modules/authed/key' import { order } from './modules/authed/order' +import { position } from './modules/authed/position' import { Okx } from './Okx' @@ -18,6 +20,7 @@ export class OkxAuthed extends Okx implements IAlunaExchangeAuthed { public key: IAlunaKeyModule public order: IAlunaOrderWriteModule public balance: IAlunaBalanceModule + public position: IAlunaPositionModule @@ -38,6 +41,7 @@ export class OkxAuthed extends Okx implements IAlunaExchangeAuthed { this.key = key(this) this.balance = balance(this) this.order = order(this) + this.position = position(this) return this diff --git a/src/exchanges/okx/modules/authed/position.ts b/src/exchanges/okx/modules/authed/position.ts new file mode 100644 index 00000000..8be5b222 --- /dev/null +++ b/src/exchanges/okx/modules/authed/position.ts @@ -0,0 +1,29 @@ +import { IAlunaExchangeAuthed } from '../../../../lib/core/IAlunaExchange' +import { IAlunaPositionModule } from '../../../../lib/modules/authed/IAlunaPositionModule' +import { close } from './position/close' +import { get } from './position/get' +import { getLeverage } from './position/getLeverage' +import { getRaw } from './position/getRaw' +import { list } from './position/list' +import { listRaw } from './position/listRaw' +import { parse } from './position/parse' +import { parseMany } from './position/parseMany' +import { setLeverage } from './position/setLeverage' + + + +export function position(exchange: IAlunaExchangeAuthed): IAlunaPositionModule { + + return { + close: close(exchange), + get: get(exchange), + getRaw: getRaw(exchange), + list: list(exchange), + listRaw: listRaw(exchange), + parse: parse(exchange), + parseMany: parseMany(exchange), + getLeverage: getLeverage(exchange), + setLeverage: setLeverage(exchange), + } + +} diff --git a/src/exchanges/okx/modules/authed/position/close.spec.ts b/src/exchanges/okx/modules/authed/position/close.spec.ts new file mode 100644 index 00000000..aeef1df3 --- /dev/null +++ b/src/exchanges/okx/modules/authed/position/close.spec.ts @@ -0,0 +1,150 @@ +import { expect } from 'chai' +import { cloneDeep } from 'lodash' +import { ImportMock } from 'ts-mock-imports' + +import { PARSED_POSITIONS } from '../../../../../../test/fixtures/parsedPositions' +import { mockHttp } from '../../../../../../test/mocks/exchange/Http' +import { mockGet } from '../../../../../../test/mocks/exchange/modules/mockGet' +import { AlunaPositionStatusEnum } from '../../../../../lib/enums/AlunaPositionStatusEnum' +import { AlunaPositionErrorCodes } from '../../../../../lib/errors/AlunaPositionErrorCodes' +import { IAlunaPositionCloseParams } from '../../../../../lib/modules/authed/IAlunaPositionModule' +import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' +import { executeAndCatch } from '../../../../../utils/executeAndCatch' +import { OkxAuthed } from '../../../OkxAuthed' +import { OkxHttp } from '../../../OkxHttp' +import { getOkxEndpoints } from '../../../okxSpecs' +import * as getMod from './get' + + + +describe(__filename, () => { + + const credentials: IAlunaCredentialsSchema = { + key: 'key', + secret: 'secret', + } + + it('should close position just fine', async () => { + + // preparing data + const mockedPosition = cloneDeep(PARSED_POSITIONS[0]) + mockedPosition.status = AlunaPositionStatusEnum.OPEN + const { + id, + symbolPair, + } = mockedPosition + + + // mocking + const { get } = mockGet({ module: getMod }) + get.returns(Promise.resolve({ position: mockedPosition })) + + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + authedRequest.returns(Promise.resolve(mockedPosition)) + + const mockedDate = new Date() + + ImportMock.mockFunction( + global, + 'Date', + mockedDate, + ) + + + // executing + const exchange = new OkxAuthed({ credentials }) + + const params: IAlunaPositionCloseParams = { + id, + symbolPair, + } + + const { position } = await exchange.position!.close(params) + + + // validating + expect(position).to.deep.eq({ + ...position, + status: AlunaPositionStatusEnum.CLOSED, + closedAt: mockedDate, + }) + + expect(get.callCount).to.be.eq(1) + expect(get.firstCall.args[0]).to.deep.eq({ + id: params.id, + symbolPair: params.symbolPair, + http: new OkxHttp(exchange.settings), + }) + + expect(authedRequest.callCount).to.be.eq(1) + expect(authedRequest.firstCall.args[0]).to.deep.eq({ + credentials, + url: getOkxEndpoints({}).position.get, + body: { id, symbolPair }, + }) + + expect(publicRequest.callCount).to.be.eq(0) + + }) + + it('should throw error if position is already closed', async () => { + + // preparing data + const mockedPosition = cloneDeep(PARSED_POSITIONS[0]) + mockedPosition.status = AlunaPositionStatusEnum.CLOSED + const { + id, + symbolPair, + } = mockedPosition + + + // mocking + const { get } = mockGet({ module: getMod }) + get.returns(Promise.resolve({ position: mockedPosition })) + + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + authedRequest.returns(Promise.resolve(mockedPosition)) + + + // executing + const exchange = new OkxAuthed({ credentials }) + + const params: IAlunaPositionCloseParams = { + id, + symbolPair, + } + + const { + error, + result, + } = await executeAndCatch(() => exchange.position!.close(params)) + + + // validating + expect(result).not.to.be.ok + + expect(error!.code).to.be.eq(AlunaPositionErrorCodes.ALREADY_CLOSED) + expect(error!.message).to.be.eq('Position is already closed') + expect(error!.httpStatusCode).to.be.eq(200) + + expect(get.callCount).to.be.eq(1) + expect(get.firstCall.args[0]).to.deep.eq({ + id: params.id, + symbolPair: params.symbolPair, + http: new OkxHttp(exchange.settings), + }) + + expect(authedRequest.callCount).to.be.eq(0) + expect(publicRequest.callCount).to.be.eq(0) + + }) + +}) diff --git a/src/exchanges/okx/modules/authed/position/close.ts b/src/exchanges/okx/modules/authed/position/close.ts new file mode 100644 index 00000000..989e69fa --- /dev/null +++ b/src/exchanges/okx/modules/authed/position/close.ts @@ -0,0 +1,72 @@ +import debug from 'debug' + +import { AlunaError } from '../../../../../lib/core/AlunaError' +import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { AlunaPositionStatusEnum } from '../../../../../lib/enums/AlunaPositionStatusEnum' +import { AlunaPositionErrorCodes } from '../../../../../lib/errors/AlunaPositionErrorCodes' +import { + IAlunaPositionCloseParams, + IAlunaPositionCloseReturns, +} from '../../../../../lib/modules/authed/IAlunaPositionModule' +import { IAlunaPositionSchema } from '../../../../../lib/schemas/IAlunaPositionSchema' +import { OkxHttp } from '../../../OkxHttp' +import { getOkxEndpoints } from '../../../okxSpecs' +import { IOkxPositionSchema } from '../../../schemas/IOkxPositionSchema' + + + +const log = debug('alunajs:okx/position/close') + + + +export const close = (exchange: IAlunaExchangeAuthed) => async ( + params: IAlunaPositionCloseParams, +): Promise => { + + const { + settings, + credentials, + } = exchange + + const { + id, + symbolPair, + http = new OkxHttp(settings), + } = params + + log('closing position', { id, symbolPair }) + + const { position } = await exchange.position!.get({ id, symbolPair, http }) + + if (position.status === AlunaPositionStatusEnum.CLOSED) { + + throw new AlunaError({ + code: AlunaPositionErrorCodes.ALREADY_CLOSED, + message: 'Position is already closed', + httpStatusCode: 200, + metadata: position, + }) + + } + + // TODO: Properly close position + await http.authedRequest({ + credentials, + url: getOkxEndpoints(settings).position.close, + body: { id, symbolPair }, + }) + + const closedPosition: IAlunaPositionSchema = { + ...position, + status: AlunaPositionStatusEnum.CLOSED, + closedAt: new Date(), + } + + const { requestWeight } = http + + return { + position: closedPosition, + requestWeight, + } + +} diff --git a/src/exchanges/okx/modules/authed/position/get.spec.ts b/src/exchanges/okx/modules/authed/position/get.spec.ts new file mode 100644 index 00000000..5e8a65d3 --- /dev/null +++ b/src/exchanges/okx/modules/authed/position/get.spec.ts @@ -0,0 +1,69 @@ +import { expect } from 'chai' + +import { PARSED_POSITIONS } from '../../../../../../test/fixtures/parsedPositions' +import { mockGetRaw } from '../../../../../../test/mocks/exchange/modules/mockGetRaw' +import { mockParse } from '../../../../../../test/mocks/exchange/modules/mockParse' +import { IAlunaPositionGetParams } from '../../../../../lib/modules/authed/IAlunaPositionModule' +import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' +import { OkxAuthed } from '../../../OkxAuthed' +import { OkxHttp } from '../../../OkxHttp' +import { OKX_RAW_POSITIONS } from '../../../test/fixtures/okxPositions' +import * as getRawMod from './getRaw' +import * as parseMod from './parse' + + + +describe(__filename, () => { + + const credentials: IAlunaCredentialsSchema = { + key: 'key', + secret: 'secret', + } + + it('should get position just fine', async () => { + + // preparing data + const mockedRawPosition = OKX_RAW_POSITIONS[0] + const mockedParsedPosition = PARSED_POSITIONS[0] + + const { id } = mockedRawPosition + + + // mocking + const { getRaw } = mockGetRaw({ module: getRawMod }) + getRaw.returns(Promise.resolve({ rawPosition: mockedRawPosition })) + + const { parse } = mockParse({ module: parseMod }) + + parse.returns({ position: mockedParsedPosition }) + + + // executing + const exchange = new OkxAuthed({ credentials }) + + const params: IAlunaPositionGetParams = { + id, + symbolPair: mockedRawPosition[0], + } + + const { position } = await exchange.position!.get(params) + + + // validating + expect(position).to.deep.eq(mockedParsedPosition) + + expect(getRaw.callCount).to.be.eq(1) + expect(getRaw.firstCall.args[0]).to.deep.eq({ + id, + symbolPair: mockedRawPosition[0], + http: new OkxHttp({}), + }) + + expect(parse.callCount).to.be.eq(1) + expect(parse.firstCall.args[0]).to.deep.eq({ + rawPosition: mockedRawPosition, + }) + + }) + +}) diff --git a/src/exchanges/okx/modules/authed/position/get.ts b/src/exchanges/okx/modules/authed/position/get.ts new file mode 100644 index 00000000..7a5d86e6 --- /dev/null +++ b/src/exchanges/okx/modules/authed/position/get.ts @@ -0,0 +1,43 @@ +import debug from 'debug' + +import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { + IAlunaPositionGetParams, + IAlunaPositionGetReturns, +} from '../../../../../lib/modules/authed/IAlunaPositionModule' +import { OkxHttp } from '../../../OkxHttp' + + + +const log = debug('alunajs:okx/position/get') + + + +export const get = (exchange: IAlunaExchangeAuthed) => async ( + params: IAlunaPositionGetParams, +): Promise => { + + const { + id, + symbolPair, + http = new OkxHttp(exchange.settings), + } = params + + log('getting position', { id, symbolPair }) + + const { rawPosition } = await exchange.position!.getRaw({ + id, + http, + symbolPair, + }) + + const { position } = exchange.position!.parse({ rawPosition }) + + const { requestWeight } = http + + return { + position, + requestWeight, + } + +} diff --git a/src/exchanges/okx/modules/authed/position/getLeverage.spec.ts b/src/exchanges/okx/modules/authed/position/getLeverage.spec.ts new file mode 100644 index 00000000..fe632dc2 --- /dev/null +++ b/src/exchanges/okx/modules/authed/position/getLeverage.spec.ts @@ -0,0 +1,63 @@ +import { expect } from 'chai' + +import { mockHttp } from '../../../../../../test/mocks/exchange/Http' +import { IAlunaPositionGetLeverageParams } from '../../../../../lib/modules/authed/IAlunaPositionModule' +import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' +import { OkxAuthed } from '../../../OkxAuthed' +import { OkxHttp } from '../../../OkxHttp' +import { getOkxEndpoints } from '../../../okxSpecs' +import { getLeverage } from './getLeverage' + + + +describe(__filename, () => { + + const credentials: IAlunaCredentialsSchema = { + key: 'key', + secret: 'secret', + } + + it('should get leverage just fine', async () => { + + // preparing data + const mockedLeverage = 10 + + + // mocking + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + authedRequest.returns(Promise.resolve(mockedLeverage)) + expect(getLeverage).to.exist + + + // executing + const exchange = new OkxAuthed({ + credentials, + }) + + const params: IAlunaPositionGetLeverageParams = { + id: 'id', + symbolPair: 'symbolPair', + } + + const { leverage } = await exchange.position!.getLeverage!(params) + + + // validating + expect(leverage).to.deep.eq(mockedLeverage) + + expect(authedRequest.callCount).to.be.eq(1) + expect(authedRequest.firstCall.args[0]).to.deep.eq({ + credentials, + url: getOkxEndpoints({}).position.getLeverage, + body: params, + }) + + expect(publicRequest.callCount).to.be.eq(0) + + }) + +}) diff --git a/src/exchanges/okx/modules/authed/position/getLeverage.ts b/src/exchanges/okx/modules/authed/position/getLeverage.ts new file mode 100644 index 00000000..e7f53575 --- /dev/null +++ b/src/exchanges/okx/modules/authed/position/getLeverage.ts @@ -0,0 +1,48 @@ +import debug from 'debug' + +import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { + IAlunaPositionGetLeverageParams, + IAlunaPositionGetLeverageReturns, +} from '../../../../../lib/modules/authed/IAlunaPositionModule' +import { OkxHttp } from '../../../OkxHttp' +import { getOkxEndpoints } from '../../../okxSpecs' + + + +const log = debug('alunajs:okx/position/getLeverage') + + + +export const getLeverage = (exchange: IAlunaExchangeAuthed) => async ( + params: IAlunaPositionGetLeverageParams, +): Promise => { + + const { + settings, + credentials, + } = exchange + + const { + id, + symbolPair, + http = new OkxHttp(settings), + } = params + + log('getting leverage', { id, symbolPair }) + + // TODO: Implement proper getter + const leverage = await http.authedRequest({ + credentials, + url: getOkxEndpoints(settings).position.getLeverage, + body: { id, symbolPair }, + }) + + const { requestWeight } = http + + return { + leverage, + requestWeight, + } + +} diff --git a/src/exchanges/okx/modules/authed/position/getRaw.spec.ts b/src/exchanges/okx/modules/authed/position/getRaw.spec.ts new file mode 100644 index 00000000..0fbec8a9 --- /dev/null +++ b/src/exchanges/okx/modules/authed/position/getRaw.spec.ts @@ -0,0 +1,115 @@ +import { expect } from 'chai' + +import { mockHttp } from '../../../../../../test/mocks/exchange/Http' +import { AlunaPositionErrorCodes } from '../../../../../lib/errors/AlunaPositionErrorCodes' +import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' +import { executeAndCatch } from '../../../../../utils/executeAndCatch' +import { OkxAuthed } from '../../../OkxAuthed' +import { OkxHttp } from '../../../OkxHttp' +import { getOkxEndpoints } from '../../../okxSpecs' +import { OKX_RAW_POSITIONS } from '../../../test/fixtures/okxPositions' + + + +describe(__filename, () => { + + const credentials: IAlunaCredentialsSchema = { + key: 'key', + secret: 'secret', + } + + it('should get raw position just fine', async () => { + + // preparing data + const mockedRawPosition = OKX_RAW_POSITIONS[0] + + const { id } = mockedRawPosition + + + // mocking + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + authedRequest.returns(Promise.resolve(mockedRawPosition)) + + + // executing + const exchange = new OkxAuthed({ + credentials, + }) + + const { rawPosition } = await exchange.position!.getRaw({ + id, + symbolPair: '', + }) + + + // validating + expect(rawPosition).to.deep.eq(mockedRawPosition) + + expect(authedRequest.callCount).to.be.eq(1) + expect(authedRequest.firstCall.args[0]).to.deep.eq({ + credentials, + url: getOkxEndpoints({}).position.get, + body: { + id, + symbolPair: '', + }, + }) + + expect(publicRequest.callCount).to.be.eq(0) + + }) + + it('should throw error if position not found', async () => { + + // preparing data + const mockedRawPosition = OKX_RAW_POSITIONS[0] + const { id } = mockedRawPosition + + + // mocking + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + authedRequest.returns(Promise.resolve(undefined)) + + + // executing + const exchange = new OkxAuthed({ credentials }) + + const { + error, + result, + } = await executeAndCatch(() => exchange.position!.getRaw({ + id, + symbolPair: '', + })) + + + // validating + expect(result).not.to.be.ok + + expect(error!.code).to.be.eq(AlunaPositionErrorCodes.NOT_FOUND) + expect(error!.message).to.be.eq('Position not found') + expect(error!.httpStatusCode).to.be.eq(200) + + + expect(authedRequest.callCount).to.be.eq(1) + expect(authedRequest.firstCall.args[0]).to.deep.eq({ + credentials, + url: getOkxEndpoints(exchange.settings).position.get, + body: { + id, + symbolPair: '', + }, + }) + + expect(publicRequest.callCount).to.be.eq(0) + + }) + +}) diff --git a/src/exchanges/okx/modules/authed/position/getRaw.ts b/src/exchanges/okx/modules/authed/position/getRaw.ts new file mode 100644 index 00000000..653cc8bc --- /dev/null +++ b/src/exchanges/okx/modules/authed/position/getRaw.ts @@ -0,0 +1,61 @@ +import debug from 'debug' + +import { AlunaError } from '../../../../../lib/core/AlunaError' +import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { AlunaPositionErrorCodes } from '../../../../../lib/errors/AlunaPositionErrorCodes' +import { + IAlunaPositionGetParams, + IAlunaPositionGetRawReturns, +} from '../../../../../lib/modules/authed/IAlunaPositionModule' +import { OkxHttp } from '../../../OkxHttp' +import { getOkxEndpoints } from '../../../okxSpecs' +import { IOkxPositionSchema } from '../../../schemas/IOkxPositionSchema' + + + +const log = debug('alunajs:okx/position/getRaw') + + + +export const getRaw = (exchange: IAlunaExchangeAuthed) => async ( + params: IAlunaPositionGetParams, +): Promise> => { + + const { + settings, + credentials, + } = exchange + + const { + id, + symbolPair, + http = new OkxHttp(settings), + } = params + + log('getting raw position', { id }) + + // TODO: Implement proper request + const rawPosition = await http.authedRequest({ + credentials, + url: getOkxEndpoints(settings).position.get, + body: { id, symbolPair }, + }) + + if (!rawPosition) { + + throw new AlunaError({ + code: AlunaPositionErrorCodes.NOT_FOUND, + message: 'Position not found', + httpStatusCode: 200, + }) + + } + + const { requestWeight } = http + + return { + rawPosition, + requestWeight, + } + +} diff --git a/src/exchanges/okx/modules/authed/position/list.spec.ts b/src/exchanges/okx/modules/authed/position/list.spec.ts new file mode 100644 index 00000000..991409dd --- /dev/null +++ b/src/exchanges/okx/modules/authed/position/list.spec.ts @@ -0,0 +1,22 @@ +import { PARSED_POSITIONS } from '../../../../../../test/fixtures/parsedPositions' +import { testList } from '../../../../../../test/macros/testList' +import { OkxAuthed } from '../../../OkxAuthed' +import { OKX_RAW_POSITIONS } from '../../../test/fixtures/okxPositions' +import * as listRawMod from './listRaw' +import * as parseManyMod from './parseMany' + + + +describe(__filename, () => { + + testList({ + AuthedClass: OkxAuthed, + exchangeId: 'okx', + methodModuleName: 'position', + listModule: listRawMod, + parseManyModule: parseManyMod, + rawList: { rawPositions: OKX_RAW_POSITIONS }, + parsedList: { positions: PARSED_POSITIONS }, + }) + +}) diff --git a/src/exchanges/okx/modules/authed/position/list.ts b/src/exchanges/okx/modules/authed/position/list.ts new file mode 100644 index 00000000..dbfe8158 --- /dev/null +++ b/src/exchanges/okx/modules/authed/position/list.ts @@ -0,0 +1,35 @@ +import debug from 'debug' + +import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { + IAlunaPositionListParams, + IAlunaPositionListReturns, +} from '../../../../../lib/modules/authed/IAlunaPositionModule' +import { OkxHttp } from '../../../OkxHttp' + + + +const log = debug('alunajs:okx/position/list') + + + +export const list = (exchange: IAlunaExchangeAuthed) => async ( + params: IAlunaPositionListParams = {}, +): Promise => { + + log('listing positions') + + const { http = new OkxHttp(exchange.settings) } = params + + const { rawPositions } = await exchange.position!.listRaw({ http }) + + const { positions } = exchange.position!.parseMany({ rawPositions }) + + const { requestWeight } = http + + return { + positions, + requestWeight, + } + +} diff --git a/src/exchanges/okx/modules/authed/position/listRaw.spec.ts b/src/exchanges/okx/modules/authed/position/listRaw.spec.ts new file mode 100644 index 00000000..b3c184d9 --- /dev/null +++ b/src/exchanges/okx/modules/authed/position/listRaw.spec.ts @@ -0,0 +1,52 @@ +import { expect } from 'chai' + +import { mockHttp } from '../../../../../../test/mocks/exchange/Http' +import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' +import { OkxAuthed } from '../../../OkxAuthed' +import { OkxHttp } from '../../../OkxHttp' +import { getOkxEndpoints } from '../../../okxSpecs' +import { OKX_RAW_POSITIONS } from '../../../test/fixtures/okxPositions' + + + +describe(__filename, () => { + + const credentials: IAlunaCredentialsSchema = { + key: 'key', + secret: 'secret', + } + + it('should list raw positions just fine', async () => { + + // preparing data + const mockedRawPositions = OKX_RAW_POSITIONS + + + // mocking + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + authedRequest.returns(Promise.resolve(mockedRawPositions)) + + + // executing + const exchange = new OkxAuthed({ credentials }) + + const { rawPositions } = await exchange.position!.listRaw() + + + // validating + expect(rawPositions).to.deep.eq(mockedRawPositions) + + expect(authedRequest.callCount).to.be.eq(1) + expect(authedRequest.firstCall.args[0]).to.deep.eq({ + credentials, + url: getOkxEndpoints(exchange.settings).position.list, + }) + + expect(publicRequest.callCount).to.be.eq(0) + + }) + +}) diff --git a/src/exchanges/okx/modules/authed/position/listRaw.ts b/src/exchanges/okx/modules/authed/position/listRaw.ts new file mode 100644 index 00000000..c8e8b900 --- /dev/null +++ b/src/exchanges/okx/modules/authed/position/listRaw.ts @@ -0,0 +1,45 @@ +import debug from 'debug' + +import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { + IAlunaPositionListParams, + IAlunaPositionListRawReturns, +} from '../../../../../lib/modules/authed/IAlunaPositionModule' +import { OkxHttp } from '../../../OkxHttp' +import { getOkxEndpoints } from '../../../okxSpecs' +import { IOkxPositionSchema } from '../../../schemas/IOkxPositionSchema' + + + +const log = debug('alunajs:okx/position/listRaw') + + + +export const listRaw = (exchange: IAlunaExchangeAuthed) => async ( + params: IAlunaPositionListParams = {}, +): Promise> => { + + log('listing raw positions', params) + + const { + settings, + credentials, + } = exchange + + const { + http = new OkxHttp(settings), + } = params + + const rawPositions = await http.authedRequest({ + credentials, + url: getOkxEndpoints(settings).position.list, + }) + + const { requestWeight } = http + + return { + rawPositions, + requestWeight, + } + +} diff --git a/src/exchanges/okx/modules/authed/position/parse.spec.ts b/src/exchanges/okx/modules/authed/position/parse.spec.ts new file mode 100644 index 00000000..945d809d --- /dev/null +++ b/src/exchanges/okx/modules/authed/position/parse.spec.ts @@ -0,0 +1,33 @@ +import { expect } from 'chai' + +import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' +import { OkxAuthed } from '../../../OkxAuthed' +import { OKX_RAW_POSITIONS } from '../../../test/fixtures/okxPositions' + + + +describe(__filename, () => { + + const credentials: IAlunaCredentialsSchema = { + key: 'key', + secret: 'secret', + } + + it('should parse a Okx raw position just fine', async () => { + + // preparing data + const rawPosition = OKX_RAW_POSITIONS[0] + + const exchange = new OkxAuthed({ credentials }) + + const { position } = exchange.position!.parse({ rawPosition }) + + + // validating + expect(position).to.exist + + // TODO: add expectations for everything + // expect(position).to.deep.eq(...) + + }) +}) diff --git a/src/exchanges/okx/modules/authed/position/parse.ts b/src/exchanges/okx/modules/authed/position/parse.ts new file mode 100644 index 00000000..9c52be0f --- /dev/null +++ b/src/exchanges/okx/modules/authed/position/parse.ts @@ -0,0 +1,62 @@ +import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { + IAlunaPositionParseParams, + IAlunaPositionParseReturns, +} from '../../../../../lib/modules/authed/IAlunaPositionModule' +import { IAlunaPositionSchema } from '../../../../../lib/schemas/IAlunaPositionSchema' +import { translateSymbolId } from '../../../../../utils/mappings/translateSymbolId' +import { IOkxPositionSchema } from '../../../schemas/IOkxPositionSchema' + + + +export const parse = (exchange: IAlunaExchangeAuthed) => ( + params: IAlunaPositionParseParams, +): IAlunaPositionParseReturns => { + + const { rawPosition } = params + + const { symbolPair } = rawPosition + + let [ + baseSymbolId, + quoteSymbolId, + ] = symbolPair.split('/') + + + baseSymbolId = translateSymbolId({ + exchangeSymbolId: baseSymbolId, + symbolMappings: exchange.settings.symbolMappings, + }) + + quoteSymbolId = translateSymbolId({ + exchangeSymbolId: quoteSymbolId, + symbolMappings: exchange.settings.symbolMappings, + }) + + // TODO: Implement proper parser + const position: IAlunaPositionSchema = { + // id: rawPosition.id, + symbolPair: rawPosition.symbolPair, + // exchangeId: exchange.specs.id, + baseSymbolId, + quoteSymbolId, + // total: rawPosition.total, + // amount: rawPosition.amount, + // account: AlunaAccountEnum.MARGIN, + // status: rawPosition.status, + // side: rawPosition.side, + // basePrice: rawPosition.basePrice, + // openPrice: rawPosition.openPrice, + // pl: rawPosition.pl, + // plPercentage: rawPosition.plPercentage, + // leverage: rawPosition.leverage, + // liquidationPrice: rawPosition.liquidationPrice, + // openedAt: rawPosition.openedAt, + // closedAt: rawPosition.closedAt, + // closePrice: rawPosition.closePrice, + // meta: rawPosition, + } as any // TODO: Remove casting to any + + return { position } + +} diff --git a/src/exchanges/okx/modules/authed/position/parseMany.spec.ts b/src/exchanges/okx/modules/authed/position/parseMany.spec.ts new file mode 100644 index 00000000..f94c5d02 --- /dev/null +++ b/src/exchanges/okx/modules/authed/position/parseMany.spec.ts @@ -0,0 +1,48 @@ +import { expect } from 'chai' +import { each } from 'lodash' + +import { PARSED_POSITIONS } from '../../../../../../test/fixtures/parsedPositions' +import { mockParse } from '../../../../../../test/mocks/exchange/modules/mockParse' +import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' +import { OkxAuthed } from '../../../OkxAuthed' +import { OKX_RAW_POSITIONS } from '../../../test/fixtures/okxPositions' +import * as parseMod from './parse' + + + +describe(__filename, () => { + + const credentials: IAlunaCredentialsSchema = { + key: 'key', + secret: 'secret', + } + + it('should parse many positions just fine', async () => { + + // preparing data + const parsedPositions = PARSED_POSITIONS + const rawPositions = OKX_RAW_POSITIONS + + + // mocking + const { parse } = mockParse({ module: parseMod }) + + each(parsedPositions, (position, index) => { + parse.onCall(index).returns({ position }) + }) + + + // executing + const exchange = new OkxAuthed({ credentials }) + + const { positions } = exchange.position!.parseMany({ rawPositions }) + + + // validating + expect(positions).to.deep.eq(parsedPositions) + + expect(parse.callCount).to.be.eq(rawPositions.length) + + }) + +}) diff --git a/src/exchanges/okx/modules/authed/position/parseMany.ts b/src/exchanges/okx/modules/authed/position/parseMany.ts new file mode 100644 index 00000000..6e9008b7 --- /dev/null +++ b/src/exchanges/okx/modules/authed/position/parseMany.ts @@ -0,0 +1,36 @@ +import debug from 'debug' +import { map } from 'lodash' + +import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { + IAlunaPositionParseManyParams, + IAlunaPositionParseManyReturns, +} from '../../../../../lib/modules/authed/IAlunaPositionModule' +import { IAlunaPositionSchema } from '../../../../../lib/schemas/IAlunaPositionSchema' +import { IOkxPositionSchema } from '../../../schemas/IOkxPositionSchema' + + + +const log = debug('alunajs:okx/position/parseMany') + + + +export const parseMany = (exchange: IAlunaExchangeAuthed) => ( + params: IAlunaPositionParseManyParams, +): IAlunaPositionParseManyReturns => { + + const { rawPositions } = params + + const positions: IAlunaPositionSchema[] = map(rawPositions, (rawPosition) => { + + const { position } = exchange.position!.parse({ rawPosition }) + + return position + + }) + + log(`parsed ${positions.length} positions`) + + return { positions } + +} diff --git a/src/exchanges/okx/modules/authed/position/setLeverage.spec.ts b/src/exchanges/okx/modules/authed/position/setLeverage.spec.ts new file mode 100644 index 00000000..70e7719a --- /dev/null +++ b/src/exchanges/okx/modules/authed/position/setLeverage.spec.ts @@ -0,0 +1,64 @@ +import { expect } from 'chai' + +import { mockHttp } from '../../../../../../test/mocks/exchange/Http' +import { IAlunaPositionSetLeverageParams } from '../../../../../lib/modules/authed/IAlunaPositionModule' +import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' +import { OkxAuthed } from '../../../OkxAuthed' +import { OkxHttp } from '../../../OkxHttp' +import { getOkxEndpoints } from '../../../okxSpecs' +import { getLeverage } from './getLeverage' + + + +describe(__filename, () => { + + const credentials: IAlunaCredentialsSchema = { + key: 'key', + secret: 'secret', + } + + it('should set leverage just fine', async () => { + + // preparing data + const mockedLeverage = 10 + + + // mocking + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + authedRequest.returns(Promise.resolve(mockedLeverage)) + expect(getLeverage).to.exist + + + // executing + const exchange = new OkxAuthed({ + credentials, + }) + + const params: IAlunaPositionSetLeverageParams = { + id: 'id', + symbolPair: 'symbolPair', + leverage: 10, + } + + const { leverage } = await exchange.position!.setLeverage!(params) + + + // validating + expect(leverage).to.deep.eq(mockedLeverage) + + expect(authedRequest.callCount).to.be.eq(1) + expect(authedRequest.firstCall.args[0]).to.deep.eq({ + credentials, + url: getOkxEndpoints({}).position.setLeverage, + body: params, + }) + + expect(publicRequest.callCount).to.be.eq(0) + + }) + +}) diff --git a/src/exchanges/okx/modules/authed/position/setLeverage.ts b/src/exchanges/okx/modules/authed/position/setLeverage.ts new file mode 100644 index 00000000..066f8e9f --- /dev/null +++ b/src/exchanges/okx/modules/authed/position/setLeverage.ts @@ -0,0 +1,49 @@ +import debug from 'debug' + +import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { + IAlunaPositionSetLeverageParams, + IAlunaPositionSetLeverageReturns, +} from '../../../../../lib/modules/authed/IAlunaPositionModule' +import { OkxHttp } from '../../../OkxHttp' +import { getOkxEndpoints } from '../../../okxSpecs' + + + +const log = debug('alunajs:okx/position/setLeverage') + + + +export const setLeverage = (exchange: IAlunaExchangeAuthed) => async ( + params: IAlunaPositionSetLeverageParams, +): Promise => { + + const { + settings, + credentials, + } = exchange + + const { + id, + symbolPair, + leverage, + http = new OkxHttp(settings), + } = params + + log('setting leverage', { id, symbolPair }) + + // TODO: Implement proper getter + const settedLeverage = await http.authedRequest({ + credentials, + url: getOkxEndpoints(settings).position.setLeverage, + body: { id, symbolPair, leverage }, + }) + + const { requestWeight } = http + + return { + leverage: settedLeverage, + requestWeight, + } + +} diff --git a/src/exchanges/okx/okxSpecs.ts b/src/exchanges/okx/okxSpecs.ts index 8efdb527..8343c1ff 100644 --- a/src/exchanges/okx/okxSpecs.ts +++ b/src/exchanges/okx/okxSpecs.ts @@ -165,5 +165,14 @@ export const getOkxEndpoints = ( cancel: `${baseUrl}/trade/cancel-order`, cancelStop: `${baseUrl}/trade/cancel-algos`, }, + position: { + get: (id: string, symbolPair: string) => `${baseUrl}/trade/order?ordId=${id}&$instId=${symbolPair}`, + list: `${baseUrl}/trade/orders-pending`, + listStop: (type: OkxAlgoOrderTypeEnum) => `${baseUrl}/trade/orders-algo-pending?ordType=${type}`, + place: `${baseUrl}/trade/order`, + placeStop: `${baseUrl}/trade/order-algo`, + close: `${baseUrl}/trade/cancel-order`, + cancelStop: `${baseUrl}/trade/cancel-algos`, + }, } } diff --git a/src/exchanges/okx/schemas/IOkxPositionSchema.ts b/src/exchanges/okx/schemas/IOkxPositionSchema.ts new file mode 100644 index 00000000..0faae7b4 --- /dev/null +++ b/src/exchanges/okx/schemas/IOkxPositionSchema.ts @@ -0,0 +1,6 @@ +// TODO: Describe position interface for Okx exchange +export interface IOkxPositionSchema { + id?: string + symbolPair: string + // ... +} diff --git a/src/exchanges/okx/test/fixtures/okxPositions.ts b/src/exchanges/okx/test/fixtures/okxPositions.ts new file mode 100644 index 00000000..edc6c5fe --- /dev/null +++ b/src/exchanges/okx/test/fixtures/okxPositions.ts @@ -0,0 +1,15 @@ +import { IOkxPositionSchema } from '../../schemas/IOkxPositionSchema' + + + +// TODO: Review fixtures +export const OKX_RAW_POSITIONS: IOkxPositionSchema[] = [ + { + id: '8bc1e59c-77fa-4554-bd11-966e360e4eb7', + symbolPair: 'BTC/USD', + }, + { + id: '8bc1e59c-77fa-4554-bd11-966e360e4eb8', + symbolPair: 'ETH/USD', + }, +] From c4ca0b3ecd06ae097fab40a375f65c3a589142f7 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Sun, 29 May 2022 13:55:52 -0300 Subject: [PATCH 052/105] add new okx specs for positions --- src/exchanges/okx/okxSpecs.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/exchanges/okx/okxSpecs.ts b/src/exchanges/okx/okxSpecs.ts index 8343c1ff..d41f0a49 100644 --- a/src/exchanges/okx/okxSpecs.ts +++ b/src/exchanges/okx/okxSpecs.ts @@ -157,7 +157,7 @@ export const getOkxEndpoints = ( list: `${baseUrl}/account/balance`, }, order: { - get: (id: string, symbolPair: string) => `${baseUrl}/trade/order?ordId=${id}&$instId=${symbolPair}`, + get: (id: string, symbolPair: string) => `${baseUrl}/trade/order?ordId=${id}&instId=${symbolPair}`, list: `${baseUrl}/trade/orders-pending`, listStop: (type: OkxAlgoOrderTypeEnum) => `${baseUrl}/trade/orders-algo-pending?ordType=${type}`, place: `${baseUrl}/trade/order`, @@ -166,13 +166,10 @@ export const getOkxEndpoints = ( cancelStop: `${baseUrl}/trade/cancel-algos`, }, position: { - get: (id: string, symbolPair: string) => `${baseUrl}/trade/order?ordId=${id}&$instId=${symbolPair}`, - list: `${baseUrl}/trade/orders-pending`, - listStop: (type: OkxAlgoOrderTypeEnum) => `${baseUrl}/trade/orders-algo-pending?ordType=${type}`, - place: `${baseUrl}/trade/order`, - placeStop: `${baseUrl}/trade/order-algo`, + get: (symbolPair: string) => `${baseUrl}/account/positions&instId=${symbolPair}`, + list: `${baseUrl}/account/positions`, close: `${baseUrl}/trade/cancel-order`, - cancelStop: `${baseUrl}/trade/cancel-algos`, + setLeverage: `${baseUrl}/account/set-leverage`, }, } } From b31f47778e90920a8d352bc180850d91a7ce8cf9 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Sun, 29 May 2022 13:56:00 -0300 Subject: [PATCH 053/105] add okx position side enum --- src/exchanges/okx/enums/OkxPositionSideEnum.ts | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 src/exchanges/okx/enums/OkxPositionSideEnum.ts diff --git a/src/exchanges/okx/enums/OkxPositionSideEnum.ts b/src/exchanges/okx/enums/OkxPositionSideEnum.ts new file mode 100644 index 00000000..cfc01978 --- /dev/null +++ b/src/exchanges/okx/enums/OkxPositionSideEnum.ts @@ -0,0 +1,4 @@ +export enum OkxPositionSideEnum { + LONG = 'long', + SHORT = 'short' +} From fff139db4b35ea163538cc70f09b0f3d82b9fe43 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Sun, 29 May 2022 13:56:12 -0300 Subject: [PATCH 054/105] add okx position side adapter and test suite --- .../adapters/okxPositionSideAdapter.spec.ts | 86 +++++++++++++++++++ .../enums/adapters/okxPositionSideAdapter.ts | 33 +++++++ 2 files changed, 119 insertions(+) create mode 100644 src/exchanges/okx/enums/adapters/okxPositionSideAdapter.spec.ts create mode 100644 src/exchanges/okx/enums/adapters/okxPositionSideAdapter.ts diff --git a/src/exchanges/okx/enums/adapters/okxPositionSideAdapter.spec.ts b/src/exchanges/okx/enums/adapters/okxPositionSideAdapter.spec.ts new file mode 100644 index 00000000..96d867eb --- /dev/null +++ b/src/exchanges/okx/enums/adapters/okxPositionSideAdapter.spec.ts @@ -0,0 +1,86 @@ +import { expect } from 'chai' + +import { AlunaError } from '../../../../lib/core/AlunaError' +import { AlunaPositionSideEnum } from '../../../../lib/enums/AlunaPositionSideEnum' +import { OkxPositionSideEnum } from '../OkxPositionSideEnum' +import { + translatePositionSideToAluna, + translatePositionSideToOkx, +} from './okxPositionSideAdapter' + + + +describe(__filename, () => { + + const notSupported = 'not-supported' + + + + it('should properly translate Okx position sides to Aluna position sides', () => { + + expect(translatePositionSideToAluna({ + from: OkxPositionSideEnum.LONG, + })).to.be.eq(AlunaPositionSideEnum.LONG) + + expect(translatePositionSideToAluna({ + from: OkxPositionSideEnum.SHORT, + })).to.be.eq(AlunaPositionSideEnum.SHORT) + + let result + let error + + try { + + result = translatePositionSideToAluna({ + from: notSupported as OkxPositionSideEnum, + }) + + } catch (err) { + + error = err + + } + + expect(result).not.to.be.ok + + expect(error instanceof AlunaError).to.be.ok + expect(error.message) + .to.be.eq(`Position side not supported: ${notSupported}`) + + }) + + + + it('should properly translate Aluna position sides to Okx position sides', () => { + + expect(translatePositionSideToOkx({ + from: AlunaPositionSideEnum.LONG, + })).to.be.eq(OkxPositionSideEnum.LONG) + + expect(translatePositionSideToOkx({ + from: AlunaPositionSideEnum.SHORT, + })).to.be.eq(OkxPositionSideEnum.SHORT) + + let result + let error + + try { + + result = translatePositionSideToOkx({ + from: notSupported as AlunaPositionSideEnum, + }) + + } catch (err) { + + error = err + + } + + expect(result).not.to.be.ok + expect(error instanceof AlunaError).to.be.ok + expect(error.message) + .to.be.eq(`Position side not supported: ${notSupported}`) + + }) + +}) diff --git a/src/exchanges/okx/enums/adapters/okxPositionSideAdapter.ts b/src/exchanges/okx/enums/adapters/okxPositionSideAdapter.ts new file mode 100644 index 00000000..49a0bab8 --- /dev/null +++ b/src/exchanges/okx/enums/adapters/okxPositionSideAdapter.ts @@ -0,0 +1,33 @@ +import { buildAdapter } from '../../../../lib/enums/adapters/buildAdapter' +import { AlunaPositionSideEnum } from '../../../../lib/enums/AlunaPositionSideEnum' +import { OkxPositionSideEnum } from '../OkxPositionSideEnum' + + + +const errorMessagePrefix = 'Position side' + + + +export const translatePositionSideToAluna = buildAdapter< + OkxPositionSideEnum, + AlunaPositionSideEnum +>({ + errorMessagePrefix, + mappings: { + [OkxPositionSideEnum.LONG]: AlunaPositionSideEnum.LONG, + [OkxPositionSideEnum.SHORT]: AlunaPositionSideEnum.SHORT, + }, +}) + + + +export const translatePositionSideToOkx = buildAdapter< + AlunaPositionSideEnum, + OkxPositionSideEnum +>({ + errorMessagePrefix, + mappings: { + [AlunaPositionSideEnum.LONG]: OkxPositionSideEnum.LONG, + [AlunaPositionSideEnum.SHORT]: OkxPositionSideEnum.SHORT, + }, +}) From e5a568137a0cf1d69a97496b3713e9f2cb416847 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Sun, 29 May 2022 13:56:28 -0300 Subject: [PATCH 055/105] add okx position schema --- .../okx/schemas/IOkxPositionSchema.ts | 63 +++++++++++++++++-- 1 file changed, 59 insertions(+), 4 deletions(-) diff --git a/src/exchanges/okx/schemas/IOkxPositionSchema.ts b/src/exchanges/okx/schemas/IOkxPositionSchema.ts index 0faae7b4..3211d090 100644 --- a/src/exchanges/okx/schemas/IOkxPositionSchema.ts +++ b/src/exchanges/okx/schemas/IOkxPositionSchema.ts @@ -1,6 +1,61 @@ -// TODO: Describe position interface for Okx exchange +import { OkxPositionSideEnum } from '../enums/OkxPositionSideEnum' + + + export interface IOkxPositionSchema { - id?: string - symbolPair: string - // ... + adl: string + availPos: string + avgPx: string + cTime: string + ccy: string + deltaBS: string + deltaPA: string + gammaBS: string + gammaPA: string + imr: string + instId: string + instType: string + interest: string + usdPx: string + last: string + lever: string + liab: string + liabCcy: string + liqPx: string + markPx: string + margin: string + mgnMode: string + mgnRatio: string + mmr: string + notionalUsd: string + optVal: string + pTime: string + pos: string + posCcy: string + posId: string + posSide: OkxPositionSideEnum + thetaBS: string + thetaPA: string + tradeId: string + uTime: string + upl: string + uplRatio: string + vegaBS: string + vegaPA: string + baseBal: string + quoteBal: string +} + +export interface IOkxPositionCloseResponseSchema { + clOrdId: string + ordId: string + sCode: string + sMsg: string +} + +export interface IOkxSetPositionLeverageResponseSchema { + lever: string + mgnMode: string + instId: string + posSide: string } From 613c44a9f410c22b4bd54b1305108b281a1fc386 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Sun, 29 May 2022 13:56:41 -0300 Subject: [PATCH 056/105] add okx position close --- src/exchanges/okx/modules/authed/position/close.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/src/exchanges/okx/modules/authed/position/close.ts b/src/exchanges/okx/modules/authed/position/close.ts index 989e69fa..4c4f1d8e 100644 --- a/src/exchanges/okx/modules/authed/position/close.ts +++ b/src/exchanges/okx/modules/authed/position/close.ts @@ -11,7 +11,7 @@ import { import { IAlunaPositionSchema } from '../../../../../lib/schemas/IAlunaPositionSchema' import { OkxHttp } from '../../../OkxHttp' import { getOkxEndpoints } from '../../../okxSpecs' -import { IOkxPositionSchema } from '../../../schemas/IOkxPositionSchema' +import { IOkxPositionCloseResponseSchema } from '../../../schemas/IOkxPositionSchema' @@ -49,11 +49,15 @@ export const close = (exchange: IAlunaExchangeAuthed) => async ( } - // TODO: Properly close position - await http.authedRequest({ + const body = { + ordId: id, + instId: symbolPair, + } + + await http.authedRequest({ credentials, url: getOkxEndpoints(settings).position.close, - body: { id, symbolPair }, + body, }) const closedPosition: IAlunaPositionSchema = { From 851ba96b74ae1821cdfbfed29ab855c1495d4b2c Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Sun, 29 May 2022 13:56:49 -0300 Subject: [PATCH 057/105] add okx position getLeverage --- src/exchanges/okx/modules/authed/position/getLeverage.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/exchanges/okx/modules/authed/position/getLeverage.ts b/src/exchanges/okx/modules/authed/position/getLeverage.ts index e7f53575..b85a35a3 100644 --- a/src/exchanges/okx/modules/authed/position/getLeverage.ts +++ b/src/exchanges/okx/modules/authed/position/getLeverage.ts @@ -7,6 +7,7 @@ import { } from '../../../../../lib/modules/authed/IAlunaPositionModule' import { OkxHttp } from '../../../OkxHttp' import { getOkxEndpoints } from '../../../okxSpecs' +import { IOkxPositionSchema } from '../../../schemas/IOkxPositionSchema' @@ -31,15 +32,16 @@ export const getLeverage = (exchange: IAlunaExchangeAuthed) => async ( log('getting leverage', { id, symbolPair }) - // TODO: Implement proper getter - const leverage = await http.authedRequest({ + const { lever } = await http.authedRequest({ credentials, - url: getOkxEndpoints(settings).position.getLeverage, + url: getOkxEndpoints(settings).position.get(symbolPair), body: { id, symbolPair }, }) const { requestWeight } = http + const leverage = Number(lever) + return { leverage, requestWeight, From 031b3d965bb48dd16bd702dd1bb0889278e06e2e Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Sun, 29 May 2022 13:57:00 -0300 Subject: [PATCH 058/105] add okx position set leverage --- src/exchanges/okx/modules/authed/position/setLeverage.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/exchanges/okx/modules/authed/position/setLeverage.ts b/src/exchanges/okx/modules/authed/position/setLeverage.ts index 066f8e9f..d47a03c6 100644 --- a/src/exchanges/okx/modules/authed/position/setLeverage.ts +++ b/src/exchanges/okx/modules/authed/position/setLeverage.ts @@ -32,11 +32,16 @@ export const setLeverage = (exchange: IAlunaExchangeAuthed) => async ( log('setting leverage', { id, symbolPair }) - // TODO: Implement proper getter + const body = { + instId: symbolPair, + lever: leverage.toString(), + mgnMode: 'cross', + } + const settedLeverage = await http.authedRequest({ credentials, url: getOkxEndpoints(settings).position.setLeverage, - body: { id, symbolPair, leverage }, + body, }) const { requestWeight } = http From 5cf5d11256c73b7152ca8a71a3c7a6ff10ede35a Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Sun, 29 May 2022 13:57:08 -0300 Subject: [PATCH 059/105] add okx position parse --- .../okx/modules/authed/position/parse.ts | 73 +++++++++++++------ 1 file changed, 51 insertions(+), 22 deletions(-) diff --git a/src/exchanges/okx/modules/authed/position/parse.ts b/src/exchanges/okx/modules/authed/position/parse.ts index 9c52be0f..47f29ca2 100644 --- a/src/exchanges/okx/modules/authed/position/parse.ts +++ b/src/exchanges/okx/modules/authed/position/parse.ts @@ -1,10 +1,13 @@ import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { AlunaAccountEnum } from '../../../../../lib/enums/AlunaAccountEnum' +import { AlunaPositionStatusEnum } from '../../../../../lib/enums/AlunaPositionStatusEnum' import { IAlunaPositionParseParams, IAlunaPositionParseReturns, } from '../../../../../lib/modules/authed/IAlunaPositionModule' import { IAlunaPositionSchema } from '../../../../../lib/schemas/IAlunaPositionSchema' import { translateSymbolId } from '../../../../../utils/mappings/translateSymbolId' +import { translatePositionSideToAluna } from '../../../enums/adapters/okxPositionSideAdapter' import { IOkxPositionSchema } from '../../../schemas/IOkxPositionSchema' @@ -15,12 +18,24 @@ export const parse = (exchange: IAlunaExchangeAuthed) => ( const { rawPosition } = params - const { symbolPair } = rawPosition + const { + instId, + posSide, + avgPx, + last, + posId, + upl, + uplRatio, + lever, + cTime, + baseBal, + liqPx, + } = rawPosition let [ baseSymbolId, quoteSymbolId, - ] = symbolPair.split('/') + ] = instId.split('/') baseSymbolId = translateSymbolId({ @@ -33,29 +48,43 @@ export const parse = (exchange: IAlunaExchangeAuthed) => ( symbolMappings: exchange.settings.symbolMappings, }) - // TODO: Implement proper parser + const side = translatePositionSideToAluna({ + from: posSide, + }) + + const basePrice = Number(last) + const openPrice = Number(avgPx) + const amount = Number(baseBal) + const total = amount * basePrice + const pl = parseFloat(upl) + const plPercentage = parseFloat(uplRatio) + const leverage = Number(lever) + const openedAt = new Date(Number(cTime)) + const liquidationPrice = Number(liqPx) + const status = AlunaPositionStatusEnum.OPEN + const position: IAlunaPositionSchema = { - // id: rawPosition.id, - symbolPair: rawPosition.symbolPair, - // exchangeId: exchange.specs.id, + id: posId, + symbolPair: instId, + exchangeId: exchange.specs.id, baseSymbolId, quoteSymbolId, - // total: rawPosition.total, - // amount: rawPosition.amount, - // account: AlunaAccountEnum.MARGIN, - // status: rawPosition.status, - // side: rawPosition.side, - // basePrice: rawPosition.basePrice, - // openPrice: rawPosition.openPrice, - // pl: rawPosition.pl, - // plPercentage: rawPosition.plPercentage, - // leverage: rawPosition.leverage, - // liquidationPrice: rawPosition.liquidationPrice, - // openedAt: rawPosition.openedAt, - // closedAt: rawPosition.closedAt, - // closePrice: rawPosition.closePrice, - // meta: rawPosition, - } as any // TODO: Remove casting to any + total, + amount, + account: AlunaAccountEnum.MARGIN, + status, + side, + basePrice, + openPrice, + pl, + plPercentage, + leverage, + liquidationPrice, + openedAt, + closedAt: undefined, // @TODO -> Verify value + closePrice: undefined, // @TODO -> Verify value + meta: rawPosition, + } return { position } From 613ed24e2b2a6d0f834d88e39b27de2f4450e648 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Sun, 29 May 2022 13:57:16 -0300 Subject: [PATCH 060/105] add okx position get raw --- .../okx/modules/authed/position/get.spec.ts | 2 +- .../okx/modules/authed/position/getRaw.ts | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/src/exchanges/okx/modules/authed/position/get.spec.ts b/src/exchanges/okx/modules/authed/position/get.spec.ts index 5e8a65d3..12c92f05 100644 --- a/src/exchanges/okx/modules/authed/position/get.spec.ts +++ b/src/exchanges/okx/modules/authed/position/get.spec.ts @@ -26,7 +26,7 @@ describe(__filename, () => { const mockedRawPosition = OKX_RAW_POSITIONS[0] const mockedParsedPosition = PARSED_POSITIONS[0] - const { id } = mockedRawPosition + const { posId: id } = mockedRawPosition // mocking diff --git a/src/exchanges/okx/modules/authed/position/getRaw.ts b/src/exchanges/okx/modules/authed/position/getRaw.ts index 653cc8bc..510afafd 100644 --- a/src/exchanges/okx/modules/authed/position/getRaw.ts +++ b/src/exchanges/okx/modules/authed/position/getRaw.ts @@ -2,6 +2,7 @@ import debug from 'debug' import { AlunaError } from '../../../../../lib/core/AlunaError' import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { AlunaGenericErrorCodes } from '../../../../../lib/errors/AlunaGenericErrorCodes' import { AlunaPositionErrorCodes } from '../../../../../lib/errors/AlunaPositionErrorCodes' import { IAlunaPositionGetParams, @@ -32,12 +33,21 @@ export const getRaw = (exchange: IAlunaExchangeAuthed) => async ( http = new OkxHttp(settings), } = params + if (!symbolPair) { + + throw new AlunaError({ + code: AlunaGenericErrorCodes.PARAM_ERROR, + message: 'Symbol is required to get okx position', + httpStatusCode: 400, + }) + + } + log('getting raw position', { id }) - // TODO: Implement proper request - const rawPosition = await http.authedRequest({ + const [rawPosition] = await http.authedRequest({ credentials, - url: getOkxEndpoints(settings).position.get, + url: getOkxEndpoints(settings).position.get(symbolPair), body: { id, symbolPair }, }) From f1be453a85e0fbbdb2d75177c8e1b1969025fa24 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Sun, 29 May 2022 13:58:21 -0300 Subject: [PATCH 061/105] add okx position fixtures --- .../okx/test/fixtures/okxPositions.ts | 88 +++++++++++++++++-- 1 file changed, 83 insertions(+), 5 deletions(-) diff --git a/src/exchanges/okx/test/fixtures/okxPositions.ts b/src/exchanges/okx/test/fixtures/okxPositions.ts index edc6c5fe..4c3ab36d 100644 --- a/src/exchanges/okx/test/fixtures/okxPositions.ts +++ b/src/exchanges/okx/test/fixtures/okxPositions.ts @@ -1,15 +1,93 @@ +import { OkxPositionSideEnum } from '../../enums/OkxPositionSideEnum' import { IOkxPositionSchema } from '../../schemas/IOkxPositionSchema' -// TODO: Review fixtures export const OKX_RAW_POSITIONS: IOkxPositionSchema[] = [ { - id: '8bc1e59c-77fa-4554-bd11-966e360e4eb7', - symbolPair: 'BTC/USD', + adl: '1', + availPos: '1', + avgPx: '2566.31', + cTime: '1619507758793', + ccy: 'ETH', + deltaBS: '', + deltaPA: '', + gammaBS: '', + gammaPA: '', + imr: '', + instId: 'ETH-USD-210430', + instType: 'FUTURES', + interest: '0', + usdPx: '', + last: '2566.22', + lever: '10', + liab: '', + liabCcy: '', + liqPx: '2352.8496681818233', + markPx: '2353.849', + margin: '0.0003896645377994', + mgnMode: 'isolated', + mgnRatio: '11.731726509588816', + mmr: '0.0000311811092368', + notionalUsd: '2276.2546609009605', + optVal: '', + pTime: '1619507761462', + pos: '1', + posCcy: '', + posId: '307173036051017730', + posSide: OkxPositionSideEnum.LONG, + thetaBS: '', + thetaPA: '', + tradeId: '109844', + uTime: '1619507761462', + upl: '-0.0000009932766034', + uplRatio: '-0.0025490556801078', + vegaBS: '', + vegaPA: '', + baseBal: '123456', + quoteBal: '1234', }, { - id: '8bc1e59c-77fa-4554-bd11-966e360e4eb8', - symbolPair: 'ETH/USD', + adl: '1', + availPos: '1', + avgPx: '2566.31', + cTime: '1619507758793', + ccy: 'ETH', + deltaBS: '', + deltaPA: '', + gammaBS: '', + gammaPA: '', + imr: '', + instId: 'ETH-USD-210430', + instType: 'FUTURES', + interest: '0', + usdPx: '', + last: '2566.22', + lever: '10', + liab: '', + liabCcy: '', + liqPx: '2352.8496681818233', + markPx: '2353.849', + margin: '0.0003896645377994', + mgnMode: 'isolated', + mgnRatio: '11.731726509588816', + mmr: '0.0000311811092368', + notionalUsd: '2276.2546609009605', + optVal: '', + pTime: '1619507761462', + pos: '1', + posCcy: '', + posId: '307173036051017730', + posSide: OkxPositionSideEnum.LONG, + thetaBS: '', + thetaPA: '', + tradeId: '109844', + uTime: '1619507761462', + upl: '-0.0000009932766034', + uplRatio: '-0.0025490556801078', + vegaBS: '', + vegaPA: '', + baseBal: '123456', + quoteBal: '1234', }, ] From 4b41f91d46fec5eb579648c0b4f66c5ee47f9328 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Sun, 29 May 2022 14:31:12 -0300 Subject: [PATCH 062/105] fix test coverage for okx positions --- .../okx/modules/authed/position/close.spec.ts | 4 +- .../authed/position/getLeverage.spec.ts | 8 +-- .../modules/authed/position/getRaw.spec.ts | 56 +++++++++++++------ .../okx/modules/authed/position/getRaw.ts | 1 - .../authed/position/setLeverage.spec.ts | 8 ++- 5 files changed, 53 insertions(+), 24 deletions(-) diff --git a/src/exchanges/okx/modules/authed/position/close.spec.ts b/src/exchanges/okx/modules/authed/position/close.spec.ts index aeef1df3..8813ecd5 100644 --- a/src/exchanges/okx/modules/authed/position/close.spec.ts +++ b/src/exchanges/okx/modules/authed/position/close.spec.ts @@ -83,8 +83,8 @@ describe(__filename, () => { expect(authedRequest.callCount).to.be.eq(1) expect(authedRequest.firstCall.args[0]).to.deep.eq({ credentials, - url: getOkxEndpoints({}).position.get, - body: { id, symbolPair }, + url: getOkxEndpoints({}).position.close, + body: { ordId: id, instId: symbolPair }, }) expect(publicRequest.callCount).to.be.eq(0) diff --git a/src/exchanges/okx/modules/authed/position/getLeverage.spec.ts b/src/exchanges/okx/modules/authed/position/getLeverage.spec.ts index fe632dc2..d22345d5 100644 --- a/src/exchanges/okx/modules/authed/position/getLeverage.spec.ts +++ b/src/exchanges/okx/modules/authed/position/getLeverage.spec.ts @@ -20,7 +20,7 @@ describe(__filename, () => { it('should get leverage just fine', async () => { // preparing data - const mockedLeverage = 10 + const mockedLeverage = '10' // mocking @@ -29,7 +29,7 @@ describe(__filename, () => { authedRequest, } = mockHttp({ classPrototype: OkxHttp.prototype }) - authedRequest.returns(Promise.resolve(mockedLeverage)) + authedRequest.returns(Promise.resolve({ lever: mockedLeverage })) expect(getLeverage).to.exist @@ -47,12 +47,12 @@ describe(__filename, () => { // validating - expect(leverage).to.deep.eq(mockedLeverage) + expect(leverage).to.deep.eq(Number(mockedLeverage)) expect(authedRequest.callCount).to.be.eq(1) expect(authedRequest.firstCall.args[0]).to.deep.eq({ credentials, - url: getOkxEndpoints({}).position.getLeverage, + url: getOkxEndpoints({}).position.get('symbolPair'), body: params, }) diff --git a/src/exchanges/okx/modules/authed/position/getRaw.spec.ts b/src/exchanges/okx/modules/authed/position/getRaw.spec.ts index 0fbec8a9..37f734db 100644 --- a/src/exchanges/okx/modules/authed/position/getRaw.spec.ts +++ b/src/exchanges/okx/modules/authed/position/getRaw.spec.ts @@ -1,6 +1,7 @@ import { expect } from 'chai' import { mockHttp } from '../../../../../../test/mocks/exchange/Http' +import { AlunaGenericErrorCodes } from '../../../../../lib/errors/AlunaGenericErrorCodes' import { AlunaPositionErrorCodes } from '../../../../../lib/errors/AlunaPositionErrorCodes' import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' import { executeAndCatch } from '../../../../../utils/executeAndCatch' @@ -23,7 +24,7 @@ describe(__filename, () => { // preparing data const mockedRawPosition = OKX_RAW_POSITIONS[0] - const { id } = mockedRawPosition + const { posId: id } = mockedRawPosition // mocking @@ -32,7 +33,7 @@ describe(__filename, () => { authedRequest, } = mockHttp({ classPrototype: OkxHttp.prototype }) - authedRequest.returns(Promise.resolve(mockedRawPosition)) + authedRequest.returns(Promise.resolve([mockedRawPosition])) // executing @@ -42,7 +43,7 @@ describe(__filename, () => { const { rawPosition } = await exchange.position!.getRaw({ id, - symbolPair: '', + symbolPair: 'symbolPair', }) @@ -52,11 +53,7 @@ describe(__filename, () => { expect(authedRequest.callCount).to.be.eq(1) expect(authedRequest.firstCall.args[0]).to.deep.eq({ credentials, - url: getOkxEndpoints({}).position.get, - body: { - id, - symbolPair: '', - }, + url: getOkxEndpoints({}).position.get('symbolPair'), }) expect(publicRequest.callCount).to.be.eq(0) @@ -67,7 +64,7 @@ describe(__filename, () => { // preparing data const mockedRawPosition = OKX_RAW_POSITIONS[0] - const { id } = mockedRawPosition + const { posId: id } = mockedRawPosition // mocking @@ -75,7 +72,7 @@ describe(__filename, () => { publicRequest, authedRequest, } = mockHttp({ classPrototype: OkxHttp.prototype }) - authedRequest.returns(Promise.resolve(undefined)) + authedRequest.returns(Promise.resolve([undefined])) // executing @@ -86,7 +83,7 @@ describe(__filename, () => { result, } = await executeAndCatch(() => exchange.position!.getRaw({ id, - symbolPair: '', + symbolPair: 'symbolPair', })) @@ -101,15 +98,42 @@ describe(__filename, () => { expect(authedRequest.callCount).to.be.eq(1) expect(authedRequest.firstCall.args[0]).to.deep.eq({ credentials, - url: getOkxEndpoints(exchange.settings).position.get, - body: { - id, - symbolPair: '', - }, + url: getOkxEndpoints(exchange.settings).position.get('symbolPair'), }) expect(publicRequest.callCount).to.be.eq(0) }) + it('should throw error if param symbolPair not sent', async () => { + + // mocking + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + + // executing + const exchange = new OkxAuthed({ credentials }) + + const { + error, + result, + } = await executeAndCatch(() => exchange.position!.getRaw({})) + + + // validating + expect(result).not.to.be.ok + + expect(error!.code).to.be.eq(AlunaGenericErrorCodes.PARAM_ERROR) + expect(error!.message).to.be.eq('Symbol is required to get okx position') + expect(error!.httpStatusCode).to.be.eq(400) + + + expect(authedRequest.callCount).to.be.eq(0) + expect(publicRequest.callCount).to.be.eq(0) + + }) + }) diff --git a/src/exchanges/okx/modules/authed/position/getRaw.ts b/src/exchanges/okx/modules/authed/position/getRaw.ts index 510afafd..017c2e44 100644 --- a/src/exchanges/okx/modules/authed/position/getRaw.ts +++ b/src/exchanges/okx/modules/authed/position/getRaw.ts @@ -48,7 +48,6 @@ export const getRaw = (exchange: IAlunaExchangeAuthed) => async ( const [rawPosition] = await http.authedRequest({ credentials, url: getOkxEndpoints(settings).position.get(symbolPair), - body: { id, symbolPair }, }) if (!rawPosition) { diff --git a/src/exchanges/okx/modules/authed/position/setLeverage.spec.ts b/src/exchanges/okx/modules/authed/position/setLeverage.spec.ts index 70e7719a..6beb16f2 100644 --- a/src/exchanges/okx/modules/authed/position/setLeverage.spec.ts +++ b/src/exchanges/okx/modules/authed/position/setLeverage.spec.ts @@ -44,6 +44,12 @@ describe(__filename, () => { leverage: 10, } + const body = { + instId: 'symbolPair', + mgnMode: 'cross', + lever: '10', + } + const { leverage } = await exchange.position!.setLeverage!(params) @@ -54,7 +60,7 @@ describe(__filename, () => { expect(authedRequest.firstCall.args[0]).to.deep.eq({ credentials, url: getOkxEndpoints({}).position.setLeverage, - body: params, + body, }) expect(publicRequest.callCount).to.be.eq(0) From 75e69627c63d90670ee4ce4818bb13ccc3967a6e Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Mon, 30 May 2022 11:31:45 -0300 Subject: [PATCH 063/105] update okx place and test suite --- .../okx/modules/authed/order/place.spec.ts | 70 +++++++++++++++++++ .../okx/modules/authed/order/place.ts | 6 +- 2 files changed, 75 insertions(+), 1 deletion(-) diff --git a/src/exchanges/okx/modules/authed/order/place.spec.ts b/src/exchanges/okx/modules/authed/order/place.spec.ts index 1e8ac189..a3c10268 100644 --- a/src/exchanges/okx/modules/authed/order/place.spec.ts +++ b/src/exchanges/okx/modules/authed/order/place.spec.ts @@ -101,6 +101,76 @@ describe(__filename, () => { }) + it('should place a Okx margin limit order just fine', async () => { + + // preparing data + const mockedRawOrder = OKX_RAW_ORDERS[0] + const mockedParsedOrder = PARSED_ORDERS[0] + + const side = AlunaOrderSideEnum.BUY + const type = AlunaOrderTypesEnum.LIMIT + + const translatedOrderSide = translateOrderSideToOkx({ from: side }) + const translatedOrderType = translateOrderTypeToOkx({ from: type }) + + const body = { + side: translatedOrderSide, + instId: '', + ordType: translatedOrderType, + sz: '0.01', + tdMode: 'cross', + px: '0', + } + + // mocking + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + const { parse } = mockParse({ module: parseMod }) + + parse.returns({ order: mockedParsedOrder }) + + authedRequest.returns(Promise.resolve(mockedRawOrder)) + + mockValidateParams() + + const { ensureOrderIsSupported } = mockEnsureOrderIsSupported() + + + // executing + const exchange = new OkxAuthed({ credentials }) + + const params: IAlunaOrderPlaceParams = { + symbolPair: '', + account: AlunaAccountEnum.MARGIN, + amount: 0.01, + side, + type, + rate: 0, + } + + const { order } = await exchange.order.place(params) + + + // validating + expect(order).to.deep.eq(mockedParsedOrder) + + expect(authedRequest.callCount).to.be.eq(1) + + expect(authedRequest.firstCall.args[0]).to.deep.eq({ + body, + credentials, + url: getOkxEndpoints(exchange.settings).order.place, + }) + + expect(publicRequest.callCount).to.be.eq(0) + + expect(ensureOrderIsSupported.callCount).to.be.eq(1) + + }) + it('should place a Okx conditional stop limit order just fine', async () => { // preparing data diff --git a/src/exchanges/okx/modules/authed/order/place.ts b/src/exchanges/okx/modules/authed/order/place.ts index 139324d0..c38833c1 100644 --- a/src/exchanges/okx/modules/authed/order/place.ts +++ b/src/exchanges/okx/modules/authed/order/place.ts @@ -2,6 +2,7 @@ import { debug } from 'debug' import { AlunaError } from '../../../../../lib/core/AlunaError' import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' +import { AlunaAccountEnum } from '../../../../../lib/enums/AlunaAccountEnum' import { AlunaOrderTypesEnum } from '../../../../../lib/enums/AlunaOrderTypesEnum' import { AlunaBalanceErrorCodes } from '../../../../../lib/errors/AlunaBalanceErrorCodes' import { AlunaOrderErrorCodes } from '../../../../../lib/errors/AlunaOrderErrorCodes' @@ -51,6 +52,7 @@ export const place = (exchange: IAlunaExchangeAuthed) => async ( amount, rate, symbolPair, + account, side, type, stopRate, @@ -64,12 +66,14 @@ export const place = (exchange: IAlunaExchangeAuthed) => async ( const translatedOrderSide = translateOrderSideToOkx({ from: side }) + const tdMode = account === AlunaAccountEnum.MARGIN ? 'cross' : 'cash' + const body = { side: translatedOrderSide, instId: symbolPair, ordType: translatedOrderType, sz: amount.toString(), - tdMode: 'cash', + tdMode, } if (translatedOrderType === OkxOrderTypeEnum.LIMIT) { From 1f361eab41375603fe6d58ed496fa04fc2f5ce4c Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Mon, 30 May 2022 11:31:59 -0300 Subject: [PATCH 064/105] update okx balance parse and test suite --- src/exchanges/okx/modules/authed/balance/parse.spec.ts | 2 +- src/exchanges/okx/modules/authed/balance/parse.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/exchanges/okx/modules/authed/balance/parse.spec.ts b/src/exchanges/okx/modules/authed/balance/parse.spec.ts index b6e68256..788cea5e 100644 --- a/src/exchanges/okx/modules/authed/balance/parse.spec.ts +++ b/src/exchanges/okx/modules/authed/balance/parse.spec.ts @@ -48,7 +48,7 @@ describe(__filename, () => { expect(balance.available).to.be.eq(available) expect(balance.total).to.be.eq(total) expect(balance.symbolId).to.be.eq(ccy) - expect(balance.wallet).to.be.eq(AlunaWalletEnum.SPOT) + expect(balance.wallet).to.be.eq(AlunaWalletEnum.ACCOUNT) expect(balance.exchangeId).to.be.eq(exchange.specs.id) expect(balance.meta).to.be.eq(rawBalance) diff --git a/src/exchanges/okx/modules/authed/balance/parse.ts b/src/exchanges/okx/modules/authed/balance/parse.ts index c99c0f0f..d967205a 100644 --- a/src/exchanges/okx/modules/authed/balance/parse.ts +++ b/src/exchanges/okx/modules/authed/balance/parse.ts @@ -41,7 +41,7 @@ export const parse = (exchange: IAlunaExchangeAuthed) => ( available, symbolId, total, - wallet: AlunaWalletEnum.SPOT, + wallet: AlunaWalletEnum.ACCOUNT, exchangeId, meta: rawBalance, } From 29376f78e4777194ea63fe57eab76759707218bf Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Mon, 30 May 2022 11:32:08 -0300 Subject: [PATCH 065/105] update okx specs --- src/exchanges/okx/okxSpecs.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/exchanges/okx/okxSpecs.ts b/src/exchanges/okx/okxSpecs.ts index d41f0a49..ea038c3e 100644 --- a/src/exchanges/okx/okxSpecs.ts +++ b/src/exchanges/okx/okxSpecs.ts @@ -86,9 +86,9 @@ export const okxBaseSpecs: IAlunaExchangeSchema = { }, { type: AlunaAccountEnum.MARGIN, - supported: false, - implemented: false, - orderTypes: [], + supported: true, + implemented: true, + orderTypes: okxExchangeOrderTypes, }, { type: AlunaAccountEnum.DERIVATIVES, From f688fcb44d611f0f4e2276e3d0380855e53be25d Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Mon, 30 May 2022 11:32:26 -0300 Subject: [PATCH 066/105] add `ACCOUNT` property to aluna wallet enum --- src/lib/enums/AlunaWalletEnum.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/lib/enums/AlunaWalletEnum.ts b/src/lib/enums/AlunaWalletEnum.ts index 22579eb7..e4c72c03 100644 --- a/src/lib/enums/AlunaWalletEnum.ts +++ b/src/lib/enums/AlunaWalletEnum.ts @@ -5,4 +5,5 @@ export enum AlunaWalletEnum { FUNDING = 'funding', TRADING = 'trading', WEB3 = 'web3', + ACCOUNT = 'account', } From 824fa09a858b2edd96df0190f986927f24e7d428 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Mon, 30 May 2022 11:33:24 -0300 Subject: [PATCH 067/105] update test suite for position parse --- .../okx/modules/authed/position/parse.spec.ts | 76 ++++++++++++++++++- 1 file changed, 73 insertions(+), 3 deletions(-) diff --git a/src/exchanges/okx/modules/authed/position/parse.spec.ts b/src/exchanges/okx/modules/authed/position/parse.spec.ts index 945d809d..646d44d8 100644 --- a/src/exchanges/okx/modules/authed/position/parse.spec.ts +++ b/src/exchanges/okx/modules/authed/position/parse.spec.ts @@ -1,6 +1,10 @@ import { expect } from 'chai' +import { AlunaAccountEnum } from '../../../../../lib/enums/AlunaAccountEnum' +import { AlunaPositionStatusEnum } from '../../../../../lib/enums/AlunaPositionStatusEnum' import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' +import { mockTranslateSymbolId } from '../../../../../utils/mappings/translateSymbolId.mock' +import { translatePositionSideToAluna } from '../../../enums/adapters/okxPositionSideAdapter' import { OkxAuthed } from '../../../OkxAuthed' import { OKX_RAW_POSITIONS } from '../../../test/fixtures/okxPositions' @@ -18,16 +22,82 @@ describe(__filename, () => { // preparing data const rawPosition = OKX_RAW_POSITIONS[0] + const { + instId, + posSide, + avgPx, + last, + posId, + upl, + uplRatio, + lever, + cTime, + baseBal, + liqPx, + } = rawPosition + + const [baseSymbolId, quoteSymbolId] = instId.split('/') + + const side = translatePositionSideToAluna({ + from: posSide, + }) + + const basePrice = Number(last) + const openPrice = Number(avgPx) + const amount = Number(baseBal) + const total = amount * basePrice + const pl = parseFloat(upl) + const plPercentage = parseFloat(uplRatio) + const leverage = Number(lever) + const openedAt = new Date(Number(cTime)) + const liquidationPrice = Number(liqPx) + const status = AlunaPositionStatusEnum.OPEN + + // mocking + + const { translateSymbolId } = mockTranslateSymbolId() + + translateSymbolId.onFirstCall().returns(baseSymbolId) + + translateSymbolId.onSecondCall().returns(quoteSymbolId) + + // executing + const exchange = new OkxAuthed({ credentials }) const { position } = exchange.position!.parse({ rawPosition }) - // validating expect(position).to.exist - // TODO: add expectations for everything - // expect(position).to.deep.eq(...) + expect(position.id).to.be.eq(posId) + expect(position.symbolPair).to.be.eq(instId) + expect(position.baseSymbolId).to.be.eq(baseSymbolId) + expect(position.quoteSymbolId).to.be.eq(quoteSymbolId) + expect(position.account).to.be.eq(AlunaAccountEnum.MARGIN) + expect(position.amount).to.be.eq(amount) + expect(position.status).to.be.eq(status) + expect(position.liquidationPrice).to.be.eq(liquidationPrice) + expect(position.openedAt.getTime()).to.be.eq(openedAt.getTime()) + expect(position.leverage).to.be.eq(leverage) + expect(position.plPercentage).to.be.eq(plPercentage) + expect(position.pl).to.be.eq(pl) + expect(position.total).to.be.eq(total) + expect(position.openPrice).to.be.eq(openPrice) + expect(position.basePrice).to.be.eq(basePrice) + expect(position.side).to.be.eq(side) + + expect(translateSymbolId.callCount).to.be.eq(2) + + expect(translateSymbolId.firstCall.args[0]).to.deep.eq({ + exchangeSymbolId: baseSymbolId, + symbolMappings: exchange.settings.symbolMappings, + }) + + expect(translateSymbolId.secondCall.args[0]).to.deep.eq({ + exchangeSymbolId: quoteSymbolId, + symbolMappings: exchange.settings.symbolMappings, + }) }) }) From 79131e8cab628428114e153526846da38d18a7e7 Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 7 Jun 2022 07:52:07 -0300 Subject: [PATCH 068/105] fix 'handleOkxRequestError' --- src/exchanges/okx/errors/handleOkxRequestError.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/src/exchanges/okx/errors/handleOkxRequestError.ts b/src/exchanges/okx/errors/handleOkxRequestError.ts index c464466c..02a36869 100644 --- a/src/exchanges/okx/errors/handleOkxRequestError.ts +++ b/src/exchanges/okx/errors/handleOkxRequestError.ts @@ -8,7 +8,9 @@ import { AlunaKeyErrorCodes } from '../../../lib/errors/AlunaKeyErrorCodes' export const okxInvalidKeyPatterns: Array = [ - /api-invalid/mi, + /Request header “OK_ACCESS_PASSPHRASE“ incorrect./mi, + /Invalid OK-ACCESS-KEY/mi, + /Invalid Sign/mi, ] @@ -54,7 +56,7 @@ export const handleOkxRequestError = ( const { response } = error as AxiosError - message = response?.data?.sMsg || message + message = response?.data?.msg || message httpStatusCode = response?.status || httpStatusCode From 71bcc736f2c39fca337c7e1a92fd3c5f49ba85ae Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 7 Jun 2022 07:53:09 -0300 Subject: [PATCH 069/105] refact 'handleOkxRequestError' test suite --- .../okx/errors/handleOkxRequestError.spec.ts | 89 +++++++------------ 1 file changed, 30 insertions(+), 59 deletions(-) diff --git a/src/exchanges/okx/errors/handleOkxRequestError.spec.ts b/src/exchanges/okx/errors/handleOkxRequestError.spec.ts index f0ebfa9c..155397fc 100644 --- a/src/exchanges/okx/errors/handleOkxRequestError.spec.ts +++ b/src/exchanges/okx/errors/handleOkxRequestError.spec.ts @@ -1,6 +1,6 @@ import { AxiosError } from 'axios' import { expect } from 'chai' -import { ImportMock } from 'ts-mock-imports' +import { each } from 'lodash' import { AlunaHttpErrorCodes } from '../../../lib/errors/AlunaHttpErrorCodes' import { AlunaKeyErrorCodes } from '../../../lib/errors/AlunaKeyErrorCodes' @@ -10,67 +10,48 @@ import * as handleOkxMod from './handleOkxRequestError' describe(__filename, () => { - const { - isOkxKeyInvalid, - handleOkxRequestError, - } = handleOkxMod + const { handleOkxRequestError } = handleOkxMod const requestMessage = 'Error while executing request.' - const mockDeps = ( - params: { - isInvalid: boolean - } = { - isInvalid: false, - }, - ) => { + it('should return Okx key invalid error when applicable', async () => { - const { - isInvalid, - } = params + // preparing data + const matchingString = [ + 'Request header “OK_ACCESS_PASSPHRASE“ incorrect.', + 'Invalid OK-ACCESS-KEY', + 'Invalid Sign', + ] - const isOkxKeyInvalidMock = ImportMock.mockFunction( - handleOkxMod, - 'isOkxKeyInvalid', - isInvalid, - ) + each(matchingString, (string) => { - return { - isOkxKeyInvalidMock, - } + const msg = 'Lorem Ipsum is simply '.concat(string) - } + const axiosError = { + isAxiosError: true, + response: { + status: 400, + data: { + msg, + }, + }, + } as AxiosError - it('should return Okx key invalid error when applicable', async () => { - const { isOkxKeyInvalidMock } = mockDeps({ isInvalid: true }) + // executing + const alunaError = handleOkxRequestError({ error: axiosError }) - const dummyError = 'Key is invalid' - const axiosError1 = { - isAxiosError: true, - response: { - status: 400, - data: { - sMsg: dummyError, - }, - }, - } as AxiosError + // validating + expect(alunaError).to.deep.eq({ + code: AlunaKeyErrorCodes.INVALID, + message: msg, + httpStatusCode: axiosError.response!.status, + metadata: axiosError.response!.data, + }) - const alunaError = handleOkxRequestError({ error: axiosError1 }) - - expect(isOkxKeyInvalidMock.callCount).to.be.eq(1) - - expect(alunaError).to.deep.eq({ - code: AlunaKeyErrorCodes.INVALID, - message: dummyError, - httpStatusCode: axiosError1.response?.status, - metadata: axiosError1.response?.data, }) - expect(isOkxKeyInvalidMock.callCount).to.be.eq(1) - expect(isOkxKeyInvalidMock.args[0][0]).to.be.eq(dummyError) - }) it('should ensure Okx request error is being handle', async () => { @@ -82,7 +63,7 @@ describe(__filename, () => { response: { status: 400, data: { - sMsg: dummyError, + msg: dummyError, }, }, } as AxiosError @@ -170,14 +151,4 @@ describe(__filename, () => { }) - it( - 'should ensure Okx invalid api patterns work as expected', - async () => { - - const message = 'api-invalid' - expect(isOkxKeyInvalid(message)).to.be.ok - - }, - ) - }) From 80203badca34fbc5c777cce72cee04b926be460e Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 7 Jun 2022 07:54:48 -0300 Subject: [PATCH 070/105] made Okx 'fetchDetails' properly verify api key --- .../okx/modules/authed/key/fetchDetails.ts | 68 +++++++++---------- 1 file changed, 32 insertions(+), 36 deletions(-) diff --git a/src/exchanges/okx/modules/authed/key/fetchDetails.ts b/src/exchanges/okx/modules/authed/key/fetchDetails.ts index 2763a228..f3116cb6 100644 --- a/src/exchanges/okx/modules/authed/key/fetchDetails.ts +++ b/src/exchanges/okx/modules/authed/key/fetchDetails.ts @@ -8,7 +8,10 @@ import { } from '../../../../../lib/modules/authed/IAlunaKeyModule' import { OkxHttp } from '../../../OkxHttp' import { getOkxEndpoints } from '../../../okxSpecs' -import { IOkxKeyAccountSchema, IOkxKeySchema } from '../../../schemas/IOkxKeySchema' +import { + IOkxKeyAccountSchema, + IOkxKeySchema, +} from '../../../schemas/IOkxKeySchema' @@ -29,9 +32,6 @@ export const fetchDetails = (exchange: IAlunaExchangeAuthed) => async ( const { http = new OkxHttp(settings) } = params - const INVALID_PERMISSION_CODE = '50030' - const INVALID_PARAM_CODE = '51116' - const rawKey: IOkxKeySchema = { read: false, trade: false, @@ -39,51 +39,47 @@ export const fetchDetails = (exchange: IAlunaExchangeAuthed) => async ( accountId: undefined, } + + const [ + accountConfig, + ] = await http.authedRequest({ + verb: AlunaHttpVerbEnum.GET, + url: getOkxEndpoints(settings).key.fetchDetails, + credentials, + }) + + rawKey.read = true + rawKey.accountId = accountConfig.uid + try { - const [ - accountConfig, - ] = await http.authedRequest({ - verb: AlunaHttpVerbEnum.GET, - url: getOkxEndpoints(settings).key.fetchDetails, + // Since OKX does not have a request to get API keys permissions, we + // execute a request to place an order with invalid body params to force + // the exchange validates if the API key has permission to trade + await http.authedRequest({ + url: getOkxEndpoints(settings).order.place, credentials, + body: {}, }) - rawKey.read = true - rawKey.accountId = accountConfig.uid - } catch (err) { - if (err.metadata?.code === INVALID_PERMISSION_CODE) { + const { metadata } = err - rawKey.read = false + const INVALID_AUTHORIZATION = '50114' + const EMPTY_REQUIRED_PARAMETER = '50014' - } else { + // If the error is related to required parameters, it means OKX already + // ensured the given user API key has permission to trade + if (metadata.sCode === EMPTY_REQUIRED_PARAMETER) { - throw err + rawKey.trade = true - } + } else if (metadata.sCode !== INVALID_AUTHORIZATION) { - } - - const requestBody = { - instId: 'BTC-USDT', - tdMode: 'cash', - side: 'buy', - ordType: 'limit', - sz: '1', - px: '123123123123123', - } - - const [order] = await http.authedRequest<{ sCode: string }[]>({ - url: getOkxEndpoints(settings).order.place, - credentials, - body: requestBody, - }) - - if (order.sCode === INVALID_PARAM_CODE) { + throw err - rawKey.trade = true + } } From 47c4e82642153b104d25be7c4cf461552ca62145 Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 7 Jun 2022 07:54:59 -0300 Subject: [PATCH 071/105] refact test suite for Okx fetchDetails --- .../modules/authed/key/fetchDetails.spec.ts | 114 +++++++++++------- 1 file changed, 71 insertions(+), 43 deletions(-) diff --git a/src/exchanges/okx/modules/authed/key/fetchDetails.spec.ts b/src/exchanges/okx/modules/authed/key/fetchDetails.spec.ts index a5ddfe75..081ee9a0 100644 --- a/src/exchanges/okx/modules/authed/key/fetchDetails.spec.ts +++ b/src/exchanges/okx/modules/authed/key/fetchDetails.spec.ts @@ -1,36 +1,40 @@ import { expect } from 'chai' -import { cloneDeep } from 'lodash' import { mockHttp } from '../../../../../../test/mocks/exchange/Http' import { mockParseDetails } from '../../../../../../test/mocks/exchange/modules/key/mockParseDetails' import { AlunaError } from '../../../../../lib/core/AlunaError' import { AlunaHttpVerbEnum } from '../../../../../lib/enums/AlunaHtttpVerbEnum' +import { AlunaGenericErrorCodes } from '../../../../../lib/errors/AlunaGenericErrorCodes' import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' import { IAlunaKeySchema } from '../../../../../lib/schemas/IAlunaKeySchema' +import { executeAndCatch } from '../../../../../utils/executeAndCatch' import { OkxAuthed } from '../../../OkxAuthed' import { OkxHttp } from '../../../OkxHttp' import { getOkxEndpoints } from '../../../okxSpecs' -import { OKX_KEY_ACCOUNT_PERMISSIONS, OKX_KEY_PERMISSIONS } from '../../../test/fixtures/okxKey' +import { OKX_KEY_ACCOUNT_PERMISSIONS } from '../../../test/fixtures/okxKey' import * as parseDetailsMod from './parseDetails' -import { executeAndCatch } from '../../../../../utils/executeAndCatch' describe(__filename, () => { - it('should fetch Okx key details just fine', async () => { + const credentials: IAlunaCredentialsSchema = { + key: 'key', + secret: 'secret', + } - // preparing data - const credentials: IAlunaCredentialsSchema = { - key: 'key', - secret: 'secret', - } + it('should fetch OKX key details just fine (KEY W/ TRADE PERMISSION)', async () => { + // preparing data const accountId = 'accountId' const parsedKey: IAlunaKeySchema = { accountId, - permissions: OKX_KEY_PERMISSIONS, + permissions: { + read: true, + trade: true, + withdraw: false, + }, meta: {}, } @@ -48,7 +52,11 @@ describe(__filename, () => { authedRequest .onSecondCall() - .returns(Promise.resolve([{ sCode: '51116' }])) + .returns(Promise.reject(new AlunaError({ + code: AlunaGenericErrorCodes.UNKNOWN, + message: '', + metadata: { sCode: '50014' }, + }))) const { parseDetails } = mockParseDetails({ module: parseDetailsMod, @@ -77,47 +85,43 @@ describe(__filename, () => { url: getOkxEndpoints(exchange.settings).key.fetchDetails, credentials, }) - + expect(authedRequest.secondCall.args[0]).to.deep.eq({ + url: getOkxEndpoints(exchange.settings).order.place, + credentials, + body: {}, + }) expect(publicRequest.callCount).to.be.eq(0) expect(parseDetails.firstCall.args[0]).to.deep.eq({ - rawKey: OKX_KEY_PERMISSIONS, + rawKey: { + read: true, + trade: true, + withdraw: false, + accountId: OKX_KEY_ACCOUNT_PERMISSIONS.uid, + }, }) }) - it('should fetch Okx key details just fine', async () => { + it('should fetch OKX key details just fine (KEY W/O TRADE PERMISSION)', async () => { // preparing data - const credentials: IAlunaCredentialsSchema = { - key: 'key', - secret: 'secret', - } - - const permissions = cloneDeep(OKX_KEY_PERMISSIONS) - - permissions.read = false - permissions.trade = false - permissions.accountId = undefined + const accountId = 'accountId' const parsedKey: IAlunaKeySchema = { - accountId: undefined, - permissions, + accountId, + permissions: { + read: true, + trade: false, + withdraw: false, + }, meta: {}, } // mocking const http = new OkxHttp({}) - const alunaError = new AlunaError({ - message: 'dummy-error', - code: '', - metadata: { - code: '50030', - }, - }) - const { publicRequest, authedRequest, @@ -125,11 +129,16 @@ describe(__filename, () => { authedRequest .onFirstCall() - .returns(Promise.reject(alunaError)) + .returns(Promise.resolve([OKX_KEY_ACCOUNT_PERMISSIONS])) authedRequest .onSecondCall() - .returns(Promise.resolve([{ code: '51116' }])) + .returns(Promise.reject(new AlunaError({ + code: AlunaGenericErrorCodes.UNKNOWN, + message: '', + metadata: { sCode: '50114' }, + }))) + const { parseDetails } = mockParseDetails({ module: parseDetailsMod, @@ -158,17 +167,27 @@ describe(__filename, () => { url: getOkxEndpoints(exchange.settings).key.fetchDetails, credentials, }) + expect(authedRequest.secondCall.args[0]).to.deep.eq({ + url: getOkxEndpoints(exchange.settings).order.place, + credentials, + body: {}, + }) expect(publicRequest.callCount).to.be.eq(0) expect(parseDetails.firstCall.args[0]).to.deep.eq({ - rawKey: permissions, + rawKey: { + read: true, + trade: false, + withdraw: false, + accountId: OKX_KEY_ACCOUNT_PERMISSIONS.uid, + }, }) }) - it('should throw an error fetching okx key details', async () => { + it('should throw an error if OKX request fails somehow', async () => { // preparing data const credentials: IAlunaCredentialsSchema = { @@ -177,11 +196,10 @@ describe(__filename, () => { } // mocking - const alunaError = new AlunaError({ - message: 'dummy-error', - code: '', - metadata: null, + code: AlunaGenericErrorCodes.UNKNOWN, + message: '', + metadata: { sCode: '1' }, }) const { @@ -191,8 +209,13 @@ describe(__filename, () => { authedRequest .onFirstCall() + .returns(Promise.resolve([OKX_KEY_ACCOUNT_PERMISSIONS])) + + authedRequest + .onSecondCall() .returns(Promise.reject(alunaError)) + // executing const exchange = new OkxAuthed({ settings: {}, credentials }) @@ -207,12 +230,17 @@ describe(__filename, () => { expect(error).to.deep.eq(alunaError) - expect(authedRequest.callCount).to.be.eq(1) + expect(authedRequest.callCount).to.be.eq(2) expect(authedRequest.firstCall.args[0]).to.deep.eq({ verb: AlunaHttpVerbEnum.GET, url: getOkxEndpoints(exchange.settings).key.fetchDetails, credentials, }) + expect(authedRequest.secondCall.args[0]).to.deep.eq({ + url: getOkxEndpoints(exchange.settings).order.place, + credentials, + body: {}, + }) expect(publicRequest.callCount).to.be.eq(0) From 92fd817a84497bb15a437c54003e96eb494cc41c Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 7 Jun 2022 07:56:19 -0300 Subject: [PATCH 072/105] refact code --- src/exchanges/okx/OkxHttp.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/exchanges/okx/OkxHttp.ts b/src/exchanges/okx/OkxHttp.ts index 11f65ca5..9eaf3eb8 100644 --- a/src/exchanges/okx/OkxHttp.ts +++ b/src/exchanges/okx/OkxHttp.ts @@ -1,7 +1,7 @@ import axios from 'axios' import crypto from 'crypto' -import { AlunaError } from '../../lib/core/AlunaError' +import { AlunaError } from '../../lib/core/AlunaError' import { IAlunaHttp, IAlunaHttpAuthedParams, @@ -14,7 +14,10 @@ import { IAlunaCredentialsSchema } from '../../lib/schemas/IAlunaCredentialsSche import { IAlunaSettingsSchema } from '../../lib/schemas/IAlunaSettingsSchema' import { assembleRequestConfig } from '../../utils/axios/assembleRequestConfig' import { AlunaCache } from '../../utils/cache/AlunaCache' -import { handleOkxRequestError, IOkxErrorSchema } from './errors/handleOkxRequestError' +import { + handleOkxRequestError, + IOkxErrorSchema, +} from './errors/handleOkxRequestError' From 4fb51a77ffe534cf22713714242796ae167d9fa2 Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 7 Jun 2022 09:19:58 -0300 Subject: [PATCH 073/105] conforming with merged changes --- src/exchanges/okx/okxSpecs.ts | 33 +++++---------------------------- 1 file changed, 5 insertions(+), 28 deletions(-) diff --git a/src/exchanges/okx/okxSpecs.ts b/src/exchanges/okx/okxSpecs.ts index ea038c3e..de287317 100644 --- a/src/exchanges/okx/okxSpecs.ts +++ b/src/exchanges/okx/okxSpecs.ts @@ -2,8 +2,8 @@ import { cloneDeep } from 'lodash' import { AlunaError } from '../../lib/core/AlunaError' import { AlunaAccountEnum } from '../../lib/enums/AlunaAccountEnum' -import { AlunaFeaturesModeEnum } from '../../lib/enums/AlunaFeaturesModeEnum' import { AlunaOrderTypesEnum } from '../../lib/enums/AlunaOrderTypesEnum' +import { AlunaWalletEnum } from '../../lib/enums/AlunaWalletEnum' import { AlunaExchangeErrorCodes } from '../../lib/errors/AlunaExchangeErrorCodes' import { IAlunaExchangeOrderSpecsSchema, @@ -24,41 +24,21 @@ export const okxExchangeOrderTypes: IAlunaExchangeOrderSpecsSchema[] = [ type: AlunaOrderTypesEnum.LIMIT, supported: true, implemented: true, - mode: AlunaFeaturesModeEnum.WRITE, - options: { - rate: 1, - amount: 1, - }, }, { type: AlunaOrderTypesEnum.MARKET, supported: true, implemented: true, - mode: AlunaFeaturesModeEnum.WRITE, - options: { - rate: 1, - amount: 1, - }, }, { type: AlunaOrderTypesEnum.STOP_LIMIT, supported: true, implemented: true, - mode: AlunaFeaturesModeEnum.WRITE, - options: { - rate: 1, - amount: 1, - }, }, { type: AlunaOrderTypesEnum.STOP_MARKET, supported: true, implemented: true, - mode: AlunaFeaturesModeEnum.WRITE, - options: { - rate: 1, - amount: 1, - }, }, ] @@ -83,24 +63,21 @@ export const okxBaseSpecs: IAlunaExchangeSchema = { supported: true, implemented: true, orderTypes: okxExchangeOrderTypes, + wallet: AlunaWalletEnum.TRADING, }, { type: AlunaAccountEnum.MARGIN, supported: true, implemented: true, orderTypes: okxExchangeOrderTypes, + wallet: AlunaWalletEnum.TRADING, }, { type: AlunaAccountEnum.DERIVATIVES, - supported: false, - implemented: false, - orderTypes: [], - }, - { - type: AlunaAccountEnum.LENDING, - supported: false, + supported: true, implemented: false, orderTypes: [], + wallet: AlunaWalletEnum.TRADING, }, ], settings: {}, From 6dacba14b130109edefce92e96310f6e16488243 Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 7 Jun 2022 09:29:47 -0300 Subject: [PATCH 074/105] removing account wallet --- src/lib/enums/AlunaWalletEnum.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/lib/enums/AlunaWalletEnum.ts b/src/lib/enums/AlunaWalletEnum.ts index 047e7bad..850ef978 100644 --- a/src/lib/enums/AlunaWalletEnum.ts +++ b/src/lib/enums/AlunaWalletEnum.ts @@ -6,5 +6,4 @@ export enum AlunaWalletEnum { TRADING = 'trading', DEFAULT = 'default', WEB3 = 'web3', - ACCOUNT = 'account', } From f2d26093b94e2b9d8f9aead75ec107fe076f6f36 Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 7 Jun 2022 09:30:03 -0300 Subject: [PATCH 075/105] using proper wallet type for OXK balances --- src/exchanges/okx/modules/authed/balance/parse.spec.ts | 2 +- src/exchanges/okx/modules/authed/balance/parse.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/exchanges/okx/modules/authed/balance/parse.spec.ts b/src/exchanges/okx/modules/authed/balance/parse.spec.ts index 788cea5e..8fca0229 100644 --- a/src/exchanges/okx/modules/authed/balance/parse.spec.ts +++ b/src/exchanges/okx/modules/authed/balance/parse.spec.ts @@ -48,7 +48,7 @@ describe(__filename, () => { expect(balance.available).to.be.eq(available) expect(balance.total).to.be.eq(total) expect(balance.symbolId).to.be.eq(ccy) - expect(balance.wallet).to.be.eq(AlunaWalletEnum.ACCOUNT) + expect(balance.wallet).to.be.eq(AlunaWalletEnum.TRADING) expect(balance.exchangeId).to.be.eq(exchange.specs.id) expect(balance.meta).to.be.eq(rawBalance) diff --git a/src/exchanges/okx/modules/authed/balance/parse.ts b/src/exchanges/okx/modules/authed/balance/parse.ts index d967205a..db621510 100644 --- a/src/exchanges/okx/modules/authed/balance/parse.ts +++ b/src/exchanges/okx/modules/authed/balance/parse.ts @@ -41,7 +41,7 @@ export const parse = (exchange: IAlunaExchangeAuthed) => ( available, symbolId, total, - wallet: AlunaWalletEnum.ACCOUNT, + wallet: AlunaWalletEnum.TRADING, exchangeId, meta: rawBalance, } From b58d371fa3b26d2d07ac70cbd1b5c80da3c634c7 Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 7 Jun 2022 12:17:04 -0300 Subject: [PATCH 076/105] improve Okx request reponse handling --- src/exchanges/okx/OkxHttp.ts | 78 +++++++++++++++++++++--------------- 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/src/exchanges/okx/OkxHttp.ts b/src/exchanges/okx/OkxHttp.ts index 9eaf3eb8..05f98bdf 100644 --- a/src/exchanges/okx/OkxHttp.ts +++ b/src/exchanges/okx/OkxHttp.ts @@ -23,6 +23,16 @@ import { export const OKX_HTTP_CACHE_KEY_PREFIX = 'OkxHttp.publicRequest' + + +export interface IOkxHttpResponse { + code: string + msg: string + data: T | IOkxErrorSchema | IOkxErrorSchema[] +} + + + export class OkxHttp implements IAlunaHttp { public settings: IAlunaSettingsSchema @@ -83,17 +93,17 @@ export class OkxHttp implements IAlunaHttp { try { - const { data } = await axios + const { data: okxResponseData } = await axios .create() .request>(requestConfig) - const { data: response } = data + const data = validateOkxResponse({ okxResponseData }) if (!disableCache) { - AlunaCache.cache.set(cacheKey, response, cacheTtlInSeconds) + AlunaCache.cache.set(cacheKey, data, cacheTtlInSeconds) } - return response + return data } catch (error) { @@ -140,32 +150,13 @@ export class OkxHttp implements IAlunaHttp { try { - type TOkxResponse = T | IOkxErrorSchema | IOkxErrorSchema[] - - const { data } = await axios + const { data: okxResponseData } = await axios .create() - .request>(requestConfig) - - const { data: response } = data - - if (typeof response === 'object') { - - if ('sCode' in response) { - - throw response - - } - - if ('sCode' in response[0]) { - - throw response[0] - - } - - } + .request>(requestConfig) + const data = validateOkxResponse({ okxResponseData }) - return response as T + return data } catch (error) { @@ -194,12 +185,6 @@ export interface IOkxSignedHeaders { 'Content-Type': string } -interface IOkxHttpResponse { - code: string - msg: string - data: T -} - export const generateAuthHeader = ( @@ -262,3 +247,30 @@ export const generateAuthHeader = ( } } + + + +export const validateOkxResponse = ( + params: { + okxResponseData: IOkxHttpResponse + }, +): T => { + + const { + okxResponseData: { + code, + data, + }, + } = params + + const didOkxRequestFailed = code !== '0' + + if (didOkxRequestFailed) { + + throw data + + } + + return data as T + +} From a6944b3831b4d0c34dac478bdeecbbb1f24eb281 Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 7 Jun 2022 12:17:31 -0300 Subject: [PATCH 077/105] refact para for macro test 'testCache' --- test/macros/testCache.ts | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/test/macros/testCache.ts b/test/macros/testCache.ts index 371f4b00..57449dc8 100644 --- a/test/macros/testCache.ts +++ b/test/macros/testCache.ts @@ -9,7 +9,7 @@ import { mockAxiosRequest } from '../mocks/axios/request' export interface ITestCacheParams { HttpClass: any // class constructor - useObjectAsResponse?: boolean + customRequestResponse?: any } @@ -18,7 +18,7 @@ export const testCache = async (params: ITestCacheParams) => { const { HttpClass, - useObjectAsResponse = false, + customRequestResponse, } = params const requestParams: IAlunaHttpPublicParams = { @@ -27,9 +27,9 @@ export const testCache = async (params: ITestCacheParams) => { verb: AlunaHttpVerbEnum.GET, } - const response = useObjectAsResponse - ? { data: 'mocked response' } - : 'mocked response' + const response = customRequestResponse + ? { data: customRequestResponse } + : { data: 'mocked response' } From 2224bba6e681316759c0e5fb49b97f10774a1b3a Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 7 Jun 2022 12:18:04 -0300 Subject: [PATCH 078/105] refact 'OkxHttp' test suite --- src/exchanges/okx/OkxHttp.spec.ts | 314 +++++++++++++----------------- 1 file changed, 132 insertions(+), 182 deletions(-) diff --git a/src/exchanges/okx/OkxHttp.spec.ts b/src/exchanges/okx/OkxHttp.spec.ts index b4817f7b..d9ee6d19 100644 --- a/src/exchanges/okx/OkxHttp.spec.ts +++ b/src/exchanges/okx/OkxHttp.spec.ts @@ -1,13 +1,19 @@ import { expect } from 'chai' +import crypto from 'crypto' import { Agent } from 'https' -import { omit, random } from 'lodash' +import { + omit, + random, +} from 'lodash' import Sinon from 'sinon' import { ImportMock } from 'ts-mock-imports' -import crypto from 'crypto' +import { testCache } from '../../../test/macros/testCache' import { mockAxiosRequest } from '../../../test/mocks/axios/request' +import { AlunaError } from '../../lib/core/AlunaError' import { AlunaHttpVerbEnum } from '../../lib/enums/AlunaHtttpVerbEnum' import { AlunaProtocolsEnum } from '../../lib/enums/AlunaProxyAgentEnum' +import { AlunaKeyErrorCodes } from '../../lib/errors/AlunaKeyErrorCodes' import { IAlunaCredentialsSchema } from '../../lib/schemas/IAlunaCredentialsSchema' import { IAlunaProxySchema, @@ -18,18 +24,21 @@ import { mockAlunaCache } from '../../utils/cache/AlunaCache.mock' import { executeAndCatch } from '../../utils/executeAndCatch' import * as handleOkxRequestErrorMod from './errors/handleOkxRequestError' import * as OkxHttpMod from './OkxHttp' -import { AlunaError } from '../../lib/core/AlunaError' -import { AlunaKeyErrorCodes } from '../../lib/errors/AlunaKeyErrorCodes' -import { testCache } from '../../../test/macros/testCache' describe(__filename, () => { - const { OkxHttp } = OkxHttpMod + const { + OkxHttp, + validateOkxResponse, + } = OkxHttpMod const url = 'https://okx.com/api/path' - const response = 'response' + const response = { + data: 'response', + code: '0', + } const body = { data: 'some-data', } @@ -62,6 +71,7 @@ describe(__filename, () => { const mockDeps = ( params: { mockGenerateAuthHeader?: boolean + mockValidateOkxResponse?: boolean cacheParams?: { get?: any has?: boolean @@ -76,6 +86,7 @@ describe(__filename, () => { const { mockGenerateAuthHeader = true, + mockValidateOkxResponse = true, cacheParams = { get: {}, has: false, @@ -89,6 +100,11 @@ describe(__filename, () => { signedHeader, ) + const validateOkxResponse = ImportMock.mockFunction( + OkxHttpMod, + 'validateOkxResponse', + ) + const handleOkxRequestError = ImportMock.mockFunction( handleOkxRequestErrorMod, 'handleOkxRequestError', @@ -100,6 +116,12 @@ describe(__filename, () => { } + if (!mockValidateOkxResponse) { + + validateOkxResponse.restore() + + } + const { cache, hashCacheKey, @@ -110,6 +132,7 @@ describe(__filename, () => { request, hashCacheKey, generateAuthHeader, + validateOkxResponse, assembleRequestConfig, handleOkxRequestError, } @@ -128,12 +151,14 @@ describe(__filename, () => { request, hashCacheKey, generateAuthHeader, + validateOkxResponse, assembleRequestConfig, } = mockDeps() const okxHttp = new OkxHttp({}) + request.returns(Promise.resolve({ data: response })) - request.returns(Promise.resolve({ data: { data: response } })) + validateOkxResponse.returns(response) // executing @@ -171,6 +196,11 @@ describe(__filename, () => { proxySettings: undefined, }) + expect(validateOkxResponse.callCount).to.be.eq(1) + expect(validateOkxResponse.firstCall.args[0]).to.deep.eq({ + okxResponseData: response, + }) + expect(generateAuthHeader.callCount).to.be.eq(0) }) @@ -188,9 +218,12 @@ describe(__filename, () => { hashCacheKey, generateAuthHeader, assembleRequestConfig, + validateOkxResponse, } = mockDeps() - request.returns(Promise.resolve({ data: { data: response } })) + request.returns(Promise.resolve({ data: response })) + + validateOkxResponse.returns(response) // executing @@ -218,75 +251,11 @@ describe(__filename, () => { expect(assembleRequestConfig.callCount).to.be.eq(1) - expect(generateAuthHeader.callCount).to.be.eq(1) - expect(generateAuthHeader.firstCall.args[0]).to.deep.eq({ - verb: AlunaHttpVerbEnum.POST, - credentials, - body, - url, + expect(validateOkxResponse.callCount).to.be.eq(1) + expect(validateOkxResponse.firstCall.args[0]).to.deep.eq({ + okxResponseData: response, }) - expect(hashCacheKey.callCount).to.be.eq(0) - - expect(cache.has.callCount).to.be.eq(0) - expect(cache.get.callCount).to.be.eq(0) - expect(cache.set.callCount).to.be.eq(0) - - }) - - it('should execute authed request just fine', async () => { - - // preparing data - const okxHttp = new OkxHttp({}) - - - // mocking - const { - cache, - request, - hashCacheKey, - generateAuthHeader, - assembleRequestConfig, - } = mockDeps() - - request.returns(Promise.resolve({ - data: { - data: [ - { - data: response, - }, - ], - }, - })) - - - // executing - const responseData = await okxHttp.authedRequest({ - verb: AlunaHttpVerbEnum.POST, - url, - body, - credentials, - }) - - - // validating - expect(responseData).to.deep.eq([{ - data: response, - }]) - - expect(okxHttp.requestWeight.public).to.be.eq(0) - expect(okxHttp.requestWeight.authed).to.be.eq(1) - - expect(request.callCount).to.be.eq(1) - expect(request.firstCall.args[0]).to.deep.eq({ - url, - method: AlunaHttpVerbEnum.POST, - data: body, - headers: signedHeader, - }) - - expect(assembleRequestConfig.callCount).to.be.eq(1) - expect(generateAuthHeader.callCount).to.be.eq(1) expect(generateAuthHeader.firstCall.args[0]).to.deep.eq({ verb: AlunaHttpVerbEnum.POST, @@ -317,9 +286,14 @@ describe(__filename, () => { // mocking - const { request } = mockDeps() + const { + request, + validateOkxResponse, + } = mockDeps() - request.returns(Promise.resolve({ data: { data: response } })) + request.returns(Promise.resolve({ data: response })) + + validateOkxResponse.returns(response) // executing @@ -336,6 +310,11 @@ describe(__filename, () => { expect(request.callCount).to.be.eq(1) + expect(validateOkxResponse.callCount).to.be.eq(1) + expect(validateOkxResponse.firstCall.args[0]).to.deep.eq({ + okxResponseData: response, + }) + }) it('should properly increment request count on authed requests', async () => { @@ -352,9 +331,12 @@ describe(__filename, () => { // mocking - const { request } = mockDeps() + const { + request, + validateOkxResponse, + } = mockDeps() - request.returns(Promise.resolve({ data: { data: response } })) + request.returns(Promise.resolve({ data: response })) // executing @@ -372,6 +354,11 @@ describe(__filename, () => { expect(request.callCount).to.be.eq(1) + expect(validateOkxResponse.callCount).to.be.eq(1) + expect(validateOkxResponse.firstCall.args[0]).to.deep.eq({ + okxResponseData: response, + }) + }) it('should properly handle request error on public requests', async () => { @@ -385,6 +372,7 @@ describe(__filename, () => { // mocking const { request, + validateOkxResponse, handleOkxRequestError, } = mockDeps() @@ -405,6 +393,8 @@ describe(__filename, () => { expect(handleOkxRequestError.callCount).to.be.eq(1) + expect(validateOkxResponse.callCount).to.be.eq(0) + }) it('should properly handle request error on authed requests', async () => { @@ -418,6 +408,7 @@ describe(__filename, () => { // mocking const { request, + validateOkxResponse, handleOkxRequestError, } = mockDeps() @@ -437,109 +428,9 @@ describe(__filename, () => { expect(request.callCount).to.be.eq(1) - expect(handleOkxRequestError.callCount).to.be.eq(1) - - }) - - it('should properly handle request error on authed requests', async () => { - - // preparing data - const okxHttp = new OkxHttp({}) - - const errorResponse = { - sCode: '5000', - sMsg: 'dummy-msg', - } - - const throwedError = { - data: { - data: errorResponse, - }, - } - - // mocking - const { - request, - handleOkxRequestError, - } = mockDeps() - - request.returns(Promise.resolve(throwedError)) - - handleOkxRequestError.returns(throwedError) - - // executing - const { - error, - result, - } = await executeAndCatch(() => okxHttp.authedRequest({ - url, - body, - credentials, - })) - - - // validating - expect(result).not.to.be.ok - - expect(error).to.be.ok - - expect(request.callCount).to.be.eq(1) - - expect(handleOkxRequestError.callCount).to.be.eq(1) - expect(handleOkxRequestError.firstCall.args[0]).to.deep.eq({ - error: errorResponse, - }) - - }) - - it('should properly handle request error on authed requests', async () => { - - // preparing data - const okxHttp = new OkxHttp({}) - - const errorResponse = { - sCode: '5000', - sMsg: 'dummy-msg', - } - - const throwedError = { - data: { - data: [errorResponse], - }, - } - - // mocking - const { - request, - handleOkxRequestError, - } = mockDeps() - - request.returns(Promise.resolve(throwedError)) - - handleOkxRequestError.returns(throwedError) - - // executing - const { - error, - result, - } = await executeAndCatch(() => okxHttp.authedRequest({ - url, - body, - credentials, - })) - - - // validating - expect(result).not.to.be.ok - - expect(error).to.be.ok - - expect(request.callCount).to.be.eq(1) + expect(validateOkxResponse.callCount).to.be.eq(0) expect(handleOkxRequestError.callCount).to.be.eq(1) - expect(handleOkxRequestError.firstCall.args[0]).to.deep.eq({ - error: errorResponse, - }) }) @@ -759,10 +650,69 @@ describe(__filename, () => { }) + it("should expect 'validateOkxResponse' validates Okx response", async () => { + + // preparing data + const okxResponse = { orders: [] } + + const okxResponseData: OkxHttpMod.IOkxHttpResponse<{ orders: any[] }> = { + data: okxResponse, + code: '0', + msg: '', + } + + + // executing + const data = validateOkxResponse({ + okxResponseData, + }) + + + // validating + expect(data).to.deep.eq(okxResponse) + + }) + + it("should expect 'validateOkxResponse' throws if Okx code differs from 1", async () => { + + // preparing data + const errorMsg = 'Unknown error' + + const okxErrorResponse = { + sCode: '666', + msg: errorMsg, + } + + const okxResponseData: OkxHttpMod.IOkxHttpResponse = { + data: okxErrorResponse, + code: '8', + msg: '', + } + + + // executing + const { + error, + result, + } = await executeAndCatch(() => validateOkxResponse({ + okxResponseData, + })) + + + // validating + expect(result).not.to.be.ok + + expect(error).to.deep.eq(okxErrorResponse) + + }) + /** * Executes macro test. * */ - testCache({ HttpClass: OkxHttp, useObjectAsResponse: true }) + testCache({ + HttpClass: OkxHttp, + customRequestResponse: response, + }) }) From e3b9bdb2adfcdf8f65fa0d6aca57845d40e38b6b Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 7 Jun 2022 12:18:36 -0300 Subject: [PATCH 079/105] properly handle okx request errors on 'handleOkxRequestError' --- .../okx/errors/handleOkxRequestError.ts | 20 +++++++++++++++++-- 1 file changed, 18 insertions(+), 2 deletions(-) diff --git a/src/exchanges/okx/errors/handleOkxRequestError.ts b/src/exchanges/okx/errors/handleOkxRequestError.ts index 02a36869..170d10fc 100644 --- a/src/exchanges/okx/errors/handleOkxRequestError.ts +++ b/src/exchanges/okx/errors/handleOkxRequestError.ts @@ -1,5 +1,9 @@ import { AxiosError } from 'axios' -import { some } from 'lodash' +import { + each, + isArray, + some, +} from 'lodash' import { AlunaError } from '../../../lib/core/AlunaError' import { AlunaHttpErrorCodes } from '../../../lib/errors/AlunaHttpErrorCodes' @@ -37,7 +41,7 @@ export interface IOkxErrorSchema { } export interface IHandleOkxRequestErrorsParams { - error: AxiosError | Error | IOkxErrorSchema + error: AxiosError | Error | IOkxErrorSchema | IOkxErrorSchema[] } export const handleOkxRequestError = ( @@ -62,6 +66,18 @@ export const handleOkxRequestError = ( metadata = response?.data || metadata + } else if (isArray(error)) { + + message = '' + + each(error, (err) => { + + message = err.sMsg + ? message.concat(`${err.sMsg}. `) + : message + + }) + } else if ((error as IOkxErrorSchema).sMsg) { const { sMsg } = error as IOkxErrorSchema From 4390aef0ffde3d0b924cc500718543a3ef890f15 Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 7 Jun 2022 12:18:46 -0300 Subject: [PATCH 080/105] refact test suite for 'handleOkxRequestError' --- .../okx/errors/handleOkxRequestError.spec.ts | 22 +++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/exchanges/okx/errors/handleOkxRequestError.spec.ts b/src/exchanges/okx/errors/handleOkxRequestError.spec.ts index 155397fc..e015e0cc 100644 --- a/src/exchanges/okx/errors/handleOkxRequestError.spec.ts +++ b/src/exchanges/okx/errors/handleOkxRequestError.spec.ts @@ -138,6 +138,28 @@ describe(__filename, () => { metadata: okxError, }) + + const okxErrors = [ + { + sCode: '51000', + sMsg: requestMessage, + }, + { + sCode: '666', + }, + ] as handleOkxMod.IOkxErrorSchema[] + + alunaError = handleOkxRequestError({ error: okxErrors }) + + const expectedErrMsgs = `${requestMessage}. ` + + expect(alunaError).to.deep.eq({ + code: AlunaHttpErrorCodes.REQUEST_ERROR, + message: expectedErrMsgs, + httpStatusCode: 500, + metadata: okxErrors, + }) + const unknown = {} as any alunaError = handleOkxRequestError({ error: unknown }) From 3083bac091974a5179297c0d950f355062b35969 Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 7 Jun 2022 12:19:09 -0300 Subject: [PATCH 081/105] fix macro test call on FtxHttp --- src/exchanges/ftx/FtxHttp.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/exchanges/ftx/FtxHttp.spec.ts b/src/exchanges/ftx/FtxHttp.spec.ts index 13c97227..a3374692 100644 --- a/src/exchanges/ftx/FtxHttp.spec.ts +++ b/src/exchanges/ftx/FtxHttp.spec.ts @@ -551,7 +551,7 @@ describe(__filename, () => { * */ testCache({ HttpClass: FtxHttp, - useObjectAsResponse: true, + customRequestResponse: { result: response }, }) }) From cad020eeb2caeb419046a7a60a77a9a486f54509 Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 7 Jun 2022 12:19:52 -0300 Subject: [PATCH 082/105] refact Okx key 'fetchDetails' to conform with last changes --- src/exchanges/okx/modules/authed/key/fetchDetails.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/exchanges/okx/modules/authed/key/fetchDetails.ts b/src/exchanges/okx/modules/authed/key/fetchDetails.ts index f3116cb6..8491b6af 100644 --- a/src/exchanges/okx/modules/authed/key/fetchDetails.ts +++ b/src/exchanges/okx/modules/authed/key/fetchDetails.ts @@ -64,18 +64,20 @@ export const fetchDetails = (exchange: IAlunaExchangeAuthed) => async ( } catch (err) { - const { metadata } = err + const { + metadata: [okxError], + } = err const INVALID_AUTHORIZATION = '50114' const EMPTY_REQUIRED_PARAMETER = '50014' // If the error is related to required parameters, it means OKX already // ensured the given user API key has permission to trade - if (metadata.sCode === EMPTY_REQUIRED_PARAMETER) { + if (okxError.sCode === EMPTY_REQUIRED_PARAMETER) { rawKey.trade = true - } else if (metadata.sCode !== INVALID_AUTHORIZATION) { + } else if (okxError.sCode !== INVALID_AUTHORIZATION) { throw err From b428fd9101f5b6c30ae889da939f99bbb87cfea3 Mon Sep 17 00:00:00 2001 From: Sergio Date: Tue, 7 Jun 2022 12:20:06 -0300 Subject: [PATCH 083/105] refact test suite for Okx key 'fetchDetails' --- src/exchanges/okx/modules/authed/key/fetchDetails.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/exchanges/okx/modules/authed/key/fetchDetails.spec.ts b/src/exchanges/okx/modules/authed/key/fetchDetails.spec.ts index 081ee9a0..1706760a 100644 --- a/src/exchanges/okx/modules/authed/key/fetchDetails.spec.ts +++ b/src/exchanges/okx/modules/authed/key/fetchDetails.spec.ts @@ -55,7 +55,7 @@ describe(__filename, () => { .returns(Promise.reject(new AlunaError({ code: AlunaGenericErrorCodes.UNKNOWN, message: '', - metadata: { sCode: '50014' }, + metadata: [{ sCode: '50014' }], }))) const { parseDetails } = mockParseDetails({ @@ -136,7 +136,7 @@ describe(__filename, () => { .returns(Promise.reject(new AlunaError({ code: AlunaGenericErrorCodes.UNKNOWN, message: '', - metadata: { sCode: '50114' }, + metadata: [{ sCode: '50114' }], }))) @@ -199,7 +199,7 @@ describe(__filename, () => { const alunaError = new AlunaError({ code: AlunaGenericErrorCodes.UNKNOWN, message: '', - metadata: { sCode: '1' }, + metadata: [{ sCode: '1' }], }) const { From 0aeb5941cf5cf56f09f36ef3021fbb0549d1a798 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Sun, 12 Jun 2022 10:20:26 -0300 Subject: [PATCH 084/105] fixes to okx configs, schema and fixtures --- src/exchanges/okx/schemas/IOkxOrderSchema.ts | 8 ++++++++ src/exchanges/okx/test/fixtures/okxBalances.ts | 2 +- test/e2e/configs.ts | 2 +- 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/exchanges/okx/schemas/IOkxOrderSchema.ts b/src/exchanges/okx/schemas/IOkxOrderSchema.ts index bd77fa1e..5cc97a40 100644 --- a/src/exchanges/okx/schemas/IOkxOrderSchema.ts +++ b/src/exchanges/okx/schemas/IOkxOrderSchema.ts @@ -41,3 +41,11 @@ export interface IOkxOrderSchema { uTime: string cTime: string } + +export interface IOkxOrderPlaceResponseSchema { + clOrdId: string + ordId: string + sCode: string + sMsg: string + tag: string +} diff --git a/src/exchanges/okx/test/fixtures/okxBalances.ts b/src/exchanges/okx/test/fixtures/okxBalances.ts index 8bb7f093..1d7bfa5c 100644 --- a/src/exchanges/okx/test/fixtures/okxBalances.ts +++ b/src/exchanges/okx/test/fixtures/okxBalances.ts @@ -85,7 +85,7 @@ export const OKX_RAW_BALANCES: IOkxBalanceSchema[] = [ ccy: 'LTC', crossLiab: '0', disEq: '0', - eq: '1', + eq: '0', eqUsd: '0', frozenBal: '0', interest: '0', diff --git a/test/e2e/configs.ts b/test/e2e/configs.ts index 2ec10880..15ae1e12 100644 --- a/test/e2e/configs.ts +++ b/test/e2e/configs.ts @@ -172,7 +172,7 @@ export function getConfig() { key: env.OKX_API_KEY, secret: env.OKX_API_SECRET, passphrase: env.OKX_API_PASSPHRASE, - symbolPair: 'BTCUSDC', + symbolPair: 'BTC-USDT', delayBetweenTests: 500, orderRate: 1000, orderAmount: 0.001, From 8f749a37010fc2fa82160f2db7d3445e6fa7fcd7 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Sun, 12 Jun 2022 10:20:48 -0300 Subject: [PATCH 085/105] fix okx place order --- src/exchanges/okx/modules/authed/order/place.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/src/exchanges/okx/modules/authed/order/place.ts b/src/exchanges/okx/modules/authed/order/place.ts index c38833c1..77d51aaf 100644 --- a/src/exchanges/okx/modules/authed/order/place.ts +++ b/src/exchanges/okx/modules/authed/order/place.ts @@ -18,7 +18,7 @@ import { translateOrderTypeToOkx } from '../../../enums/adapters/okxOrderTypeAda import { OkxOrderTypeEnum } from '../../../enums/OkxOrderTypeEnum' import { OkxHttp } from '../../../OkxHttp' import { getOkxEndpoints } from '../../../okxSpecs' -import { IOkxOrderSchema } from '../../../schemas/IOkxOrderSchema' +import { IOkxOrderPlaceResponseSchema } from '../../../schemas/IOkxOrderSchema' @@ -108,7 +108,7 @@ export const place = (exchange: IAlunaExchangeAuthed) => async ( log('placing new order for Okx') - let placedOrder: IOkxOrderSchema + let placedOrderId: string try { @@ -118,13 +118,13 @@ export const place = (exchange: IAlunaExchangeAuthed) => async ( ? orderEndpoints.placeStop : orderEndpoints.place - const orderResponse = await http.authedRequest({ + const [orderResponse] = await http.authedRequest({ url, body, credentials, }) - placedOrder = orderResponse + placedOrderId = orderResponse.ordId } catch (err) { @@ -155,7 +155,11 @@ export const place = (exchange: IAlunaExchangeAuthed) => async ( } - const { order } = exchange.order.parse({ rawOrder: placedOrder }) + const { order } = await exchange.order.get({ + id: placedOrderId, + symbolPair, + http, + }) const { requestWeight } = http From 1c2f2d2cc8b25e95788d83195012e3e83e5aa0a5 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Sun, 12 Jun 2022 10:21:01 -0300 Subject: [PATCH 086/105] fix okx get raw --- src/exchanges/okx/modules/authed/order/getRaw.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/exchanges/okx/modules/authed/order/getRaw.ts b/src/exchanges/okx/modules/authed/order/getRaw.ts index 5c8bd9f6..9b5f1d5e 100644 --- a/src/exchanges/okx/modules/authed/order/getRaw.ts +++ b/src/exchanges/okx/modules/authed/order/getRaw.ts @@ -33,7 +33,7 @@ export const getRaw = (exchange: IAlunaExchangeAuthed) => async ( http = new OkxHttp(settings), } = params - const rawOrder = await http.authedRequest({ + const [rawOrder] = await http.authedRequest({ credentials, verb: AlunaHttpVerbEnum.GET, url: getOkxEndpoints(settings).order.get(id, symbolPair), From d80ed31cda901d698473c9595f43350d36edecc4 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Sun, 12 Jun 2022 10:21:09 -0300 Subject: [PATCH 087/105] fix okx parse many calculation --- src/exchanges/okx/modules/authed/balance/parseMany.ts | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/exchanges/okx/modules/authed/balance/parseMany.ts b/src/exchanges/okx/modules/authed/balance/parseMany.ts index ad82d6a2..9c9d8fcd 100644 --- a/src/exchanges/okx/modules/authed/balance/parseMany.ts +++ b/src/exchanges/okx/modules/authed/balance/parseMany.ts @@ -29,11 +29,10 @@ export const parseMany = (exchange: IAlunaExchangeAuthed) => ( (accumulator, rawBalance) => { const { - availBal, - frozenBal, + eq, } = rawBalance - const total = Number(availBal) + Number(frozenBal) + const total = Number(eq) if (total > 0) { From 27bfef565313fa5832e82c0e49a28b4f829a18e9 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Sun, 12 Jun 2022 10:21:25 -0300 Subject: [PATCH 088/105] fix okx order parse to include baseSymbolId and quoteSymbolId --- src/exchanges/okx/modules/authed/order/parse.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/exchanges/okx/modules/authed/order/parse.ts b/src/exchanges/okx/modules/authed/order/parse.ts index bf7c3d5a..36acb34f 100644 --- a/src/exchanges/okx/modules/authed/order/parse.ts +++ b/src/exchanges/okx/modules/authed/order/parse.ts @@ -31,18 +31,18 @@ export const parse = (exchange: IAlunaExchangeAuthed) => ( sz, uTime, ordType, - ccy, - tgtCcy, slTriggerPx, } = rawOrder - const baseSymbolId = translateSymbolId({ - exchangeSymbolId: ccy, + let [baseSymbolId, quoteSymbolId] = instId.split('-') + + baseSymbolId = translateSymbolId({ + exchangeSymbolId: baseSymbolId, symbolMappings: exchange.settings.symbolMappings, }) - const quoteSymbolId = translateSymbolId({ - exchangeSymbolId: tgtCcy, + quoteSymbolId = translateSymbolId({ + exchangeSymbolId: quoteSymbolId, symbolMappings: exchange.settings.symbolMappings, }) From 45aed94e083bcbbec6f410aab4a888ca2e149953 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Mon, 13 Jun 2022 08:51:53 -0300 Subject: [PATCH 089/105] update e2e configs for okx --- test/e2e/configs.ts | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/test/e2e/configs.ts b/test/e2e/configs.ts index 9f0b3798..21a768f2 100644 --- a/test/e2e/configs.ts +++ b/test/e2e/configs.ts @@ -179,6 +179,10 @@ export function getConfig() { symbolPair: 'BTC-USDT', delayBetweenTests: 500, orderRate: 1000, + defaultLeverage: 1, + orderLimitRate: 1100, + orderStopRate: 30000, + leverageToSet: 1, orderAmount: 0.001, orderEditAmount: 0.0011, orderInsufficientAmount: 2000, From 2f0750720634968cac3142c8528cd9f6ea7c6dd9 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Mon, 13 Jun 2022 08:52:03 -0300 Subject: [PATCH 090/105] update okx order fixtures and schemas --- src/exchanges/okx/schemas/IOkxOrderSchema.ts | 2 + src/exchanges/okx/test/fixtures/okxOrders.ts | 81 ++++++++++++++++++++ 2 files changed, 83 insertions(+) diff --git a/src/exchanges/okx/schemas/IOkxOrderSchema.ts b/src/exchanges/okx/schemas/IOkxOrderSchema.ts index 5cc97a40..8cb67d57 100644 --- a/src/exchanges/okx/schemas/IOkxOrderSchema.ts +++ b/src/exchanges/okx/schemas/IOkxOrderSchema.ts @@ -7,6 +7,7 @@ import { OkxOrderTypeEnum } from '../enums/OkxOrderTypeEnum' export interface IOkxOrderSchema { instType: string instId: string + algoId: string ccy: string ordId: string clOrdId: string @@ -45,6 +46,7 @@ export interface IOkxOrderSchema { export interface IOkxOrderPlaceResponseSchema { clOrdId: string ordId: string + algoId: string sCode: string sMsg: string tag: string diff --git a/src/exchanges/okx/test/fixtures/okxOrders.ts b/src/exchanges/okx/test/fixtures/okxOrders.ts index a22d3484..edbdc919 100644 --- a/src/exchanges/okx/test/fixtures/okxOrders.ts +++ b/src/exchanges/okx/test/fixtures/okxOrders.ts @@ -11,6 +11,7 @@ export const OKX_RAW_ORDERS: IOkxOrderSchema[] = [ instId: 'BTC-USD', ccy: 'BTC', ordId: '312269865356374016', + algoId: '312269865356374016', clOrdId: 'b1', tag: '', px: '999', @@ -48,6 +49,7 @@ export const OKX_RAW_ORDERS: IOkxOrderSchema[] = [ instId: 'BTC-USD', ccy: 'BTC', ordId: '312269865356374016', + algoId: '312269865356374016', clOrdId: 'b1', tag: '', px: '999', @@ -81,3 +83,82 @@ export const OKX_RAW_ORDERS: IOkxOrderSchema[] = [ cTime: '1597026383085', }, ] + +export const OKX_RAW_CONDITIONAL_ORDERS: IOkxOrderSchema[] = [ + { + algoId: '456269805404971018', + cTime: '1655055198691', + uTime: '1655055198691', + ccy: '', + instId: 'BTC-USDT', + instType: 'SPOT', + lever: '', + ordId: '0', + ordType: OkxOrderTypeEnum.CONDITIONAL, + posSide: 'net', + side: OkxOrderSideEnum.BUY, + slOrdPx: '25000', + slTriggerPx: '30000', + slTriggerPxType: 'last', + state: OkxOrderStatusEnum.LIVE, + sz: '0.00007812', + tag: '', + tdMode: 'cash', + tgtCcy: '', + tpOrdPx: '', + tpTriggerPx: '', + tpTriggerPxType: '', + clOrdId: 'b1', + px: '999', + pnl: '5', + accFillSz: '0', + fillPx: '0', + tradeId: '0', + fillSz: '0', + fillTime: '0', + avgPx: '0', + feeCcy: '', + fee: '', + rebateCcy: '', + rebate: '', + category: '', + }, + { + algoId: '456269805404971018', + cTime: '1655055198691', + uTime: '1655055198691', + ccy: '', + instId: 'BTC-USDT', + instType: 'SPOT', + lever: '', + ordId: '0', + ordType: OkxOrderTypeEnum.CONDITIONAL, + posSide: 'net', + side: OkxOrderSideEnum.BUY, + slOrdPx: '-1', + slTriggerPx: '30000', + slTriggerPxType: 'last', + state: OkxOrderStatusEnum.LIVE, + sz: '0.00007812', + tag: '', + tdMode: 'cash', + tgtCcy: '', + tpOrdPx: '', + tpTriggerPx: '', + tpTriggerPxType: '', + clOrdId: 'b1', + px: '999', + pnl: '5', + accFillSz: '0', + fillPx: '0', + tradeId: '0', + fillSz: '0', + fillTime: '0', + avgPx: '0', + feeCcy: '', + fee: '', + rebateCcy: '', + rebate: '', + category: '', + }, +] From 3e29fd00c82c304485e3fd830f63ceaeea4f699d Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Mon, 13 Jun 2022 08:52:18 -0300 Subject: [PATCH 091/105] update okx list test suites --- src/exchanges/okx/modules/authed/position/list.spec.ts | 2 +- src/exchanges/okx/modules/public/market/list.spec.ts | 2 +- src/exchanges/okx/modules/public/symbol/list.spec.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/exchanges/okx/modules/authed/position/list.spec.ts b/src/exchanges/okx/modules/authed/position/list.spec.ts index 991409dd..45a9996c 100644 --- a/src/exchanges/okx/modules/authed/position/list.spec.ts +++ b/src/exchanges/okx/modules/authed/position/list.spec.ts @@ -13,7 +13,7 @@ describe(__filename, () => { AuthedClass: OkxAuthed, exchangeId: 'okx', methodModuleName: 'position', - listModule: listRawMod, + listRawModule: listRawMod, parseManyModule: parseManyMod, rawList: { rawPositions: OKX_RAW_POSITIONS }, parsedList: { positions: PARSED_POSITIONS }, diff --git a/src/exchanges/okx/modules/public/market/list.spec.ts b/src/exchanges/okx/modules/public/market/list.spec.ts index 5d0a6733..ec387c2e 100644 --- a/src/exchanges/okx/modules/public/market/list.spec.ts +++ b/src/exchanges/okx/modules/public/market/list.spec.ts @@ -13,7 +13,7 @@ describe(__filename, () => { AuthedClass: OkxAuthed, exchangeId: 'okx', methodModuleName: 'market', - listModule: listRawMod, + listRawModule: listRawMod, parseManyModule: parseManyMod, rawList: { rawMarkets: OKX_RAW_MARKETS }, parsedList: { markets: PARSED_MARKETS }, diff --git a/src/exchanges/okx/modules/public/symbol/list.spec.ts b/src/exchanges/okx/modules/public/symbol/list.spec.ts index 3ac359e8..7ffb9c66 100644 --- a/src/exchanges/okx/modules/public/symbol/list.spec.ts +++ b/src/exchanges/okx/modules/public/symbol/list.spec.ts @@ -13,7 +13,7 @@ describe(__filename, () => { AuthedClass: OkxAuthed, exchangeId: 'okx', methodModuleName: 'symbol', - listModule: listRawMod, + listRawModule: listRawMod, parseManyModule: parseManyMod, rawList: { rawSymbols: OKX_RAW_SYMBOLS }, parsedList: { symbols: PARSED_SYMBOLS }, From 4e06a9a41c712ab9eee238f2bb6fabb1197f329e Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Mon, 13 Jun 2022 08:52:49 -0300 Subject: [PATCH 092/105] update okx order place and test suite --- .../okx/modules/authed/order/place.spec.ts | 106 ++++++++++++++---- .../okx/modules/authed/order/place.ts | 15 ++- 2 files changed, 99 insertions(+), 22 deletions(-) diff --git a/src/exchanges/okx/modules/authed/order/place.spec.ts b/src/exchanges/okx/modules/authed/order/place.spec.ts index a3c10268..a7ada0f3 100644 --- a/src/exchanges/okx/modules/authed/order/place.spec.ts +++ b/src/exchanges/okx/modules/authed/order/place.spec.ts @@ -1,8 +1,9 @@ import { expect } from 'chai' +import { cloneDeep } from 'lodash' import { PARSED_ORDERS } from '../../../../../../test/fixtures/parsedOrders' import { mockHttp } from '../../../../../../test/mocks/exchange/Http' -import { mockParse } from '../../../../../../test/mocks/exchange/modules/mockParse' +import { mockGet } from '../../../../../../test/mocks/exchange/modules/mockGet' import { AlunaError } from '../../../../../lib/core/AlunaError' import { AlunaAccountEnum } from '../../../../../lib/enums/AlunaAccountEnum' import { AlunaOrderSideEnum } from '../../../../../lib/enums/AlunaOrderSideEnum' @@ -19,8 +20,8 @@ import { translateOrderTypeToOkx } from '../../../enums/adapters/okxOrderTypeAda import { OkxAuthed } from '../../../OkxAuthed' import { OkxHttp } from '../../../OkxHttp' import { getOkxEndpoints } from '../../../okxSpecs' -import { OKX_RAW_ORDERS } from '../../../test/fixtures/okxOrders' -import * as parseMod from './parse' +import { OKX_RAW_CONDITIONAL_ORDERS, OKX_RAW_ORDERS } from '../../../test/fixtures/okxOrders' +import * as getMod from './get' @@ -58,11 +59,11 @@ describe(__filename, () => { authedRequest, } = mockHttp({ classPrototype: OkxHttp.prototype }) - const { parse } = mockParse({ module: parseMod }) + const { get } = mockGet({ module: getMod }) - parse.returns({ order: mockedParsedOrder }) + get.returns({ order: mockedParsedOrder }) - authedRequest.returns(Promise.resolve(mockedRawOrder)) + authedRequest.returns(Promise.resolve([mockedRawOrder])) mockValidateParams() @@ -128,11 +129,11 @@ describe(__filename, () => { authedRequest, } = mockHttp({ classPrototype: OkxHttp.prototype }) - const { parse } = mockParse({ module: parseMod }) + const { get } = mockGet({ module: getMod }) - parse.returns({ order: mockedParsedOrder }) + get.returns({ order: mockedParsedOrder }) - authedRequest.returns(Promise.resolve(mockedRawOrder)) + authedRequest.returns(Promise.resolve([mockedRawOrder])) mockValidateParams() @@ -174,7 +175,10 @@ describe(__filename, () => { it('should place a Okx conditional stop limit order just fine', async () => { // preparing data - const mockedRawOrder = OKX_RAW_ORDERS[0] + const mockedRawOrder = cloneDeep(OKX_RAW_CONDITIONAL_ORDERS[0]) + + mockedRawOrder.ordId = undefined as any + const mockedParsedOrder = PARSED_ORDERS[0] const side = AlunaOrderSideEnum.BUY @@ -199,11 +203,11 @@ describe(__filename, () => { authedRequest, } = mockHttp({ classPrototype: OkxHttp.prototype }) - const { parse } = mockParse({ module: parseMod }) + const { get } = mockGet({ module: getMod }) - parse.returns({ order: mockedParsedOrder }) + get.returns({ order: mockedParsedOrder }) - authedRequest.returns(Promise.resolve(mockedRawOrder)) + authedRequest.returns(Promise.resolve([mockedRawOrder])) mockValidateParams() @@ -272,11 +276,11 @@ describe(__filename, () => { authedRequest, } = mockHttp({ classPrototype: OkxHttp.prototype }) - const { parse } = mockParse({ module: parseMod }) + const { get } = mockGet({ module: getMod }) - parse.returns({ order: mockedParsedOrder }) + get.returns({ order: mockedParsedOrder }) - authedRequest.returns(Promise.resolve(mockedRawOrder)) + authedRequest.returns(Promise.resolve([mockedRawOrder])) mockValidateParams() @@ -344,11 +348,11 @@ describe(__filename, () => { authedRequest, } = mockHttp({ classPrototype: OkxHttp.prototype }) - const { parse } = mockParse({ module: parseMod }) + const { get } = mockGet({ module: getMod }) - parse.returns({ order: mockedParsedOrder }) + get.returns({ order: mockedParsedOrder }) - authedRequest.returns(Promise.resolve(mockedRawOrder)) + authedRequest.returns(Promise.resolve([mockedRawOrder])) mockValidateParams() @@ -445,6 +449,70 @@ describe(__filename, () => { }, ) + it( + 'should throw error for insufficient funds when placing new okx order', + async () => { + + // preparing data + // const mockedRawOrder = OKX_RAW_ORDERS[0] + + const side = AlunaOrderSideEnum.BUY + const type = AlunaOrderTypesEnum.MARKET + + const expectedMessage = 'Account has insufficient balance ' + .concat('for requested action.') + const expectedCode = AlunaBalanceErrorCodes.INSUFFICIENT_BALANCE + + const alunaError = new AlunaError({ + message: 'dummy-error', + code: AlunaOrderErrorCodes.PLACE_FAILED, + httpStatusCode: 401, + metadata: [ + { + sCode: '51008', + }, + ], + }) + + // mocking + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + authedRequest.returns(Promise.reject(alunaError)) + + mockValidateParams() + + mockEnsureOrderIsSupported() + + + // executing + const exchange = new OkxAuthed({ credentials }) + + const { error } = await executeAndCatch(() => exchange.order.place({ + symbolPair: '', + account: AlunaAccountEnum.SPOT, + amount: 0.01, + side, + type, + rate: 0, + })) + + + // validating + + expect(error instanceof AlunaError).to.be.ok + expect(error?.code).to.be.eq(expectedCode) + expect(error?.message).to.be.eq(expectedMessage) + + expect(authedRequest.callCount).to.be.eq(1) + + expect(publicRequest.callCount).to.be.eq(0) + + }, + ) + it('should throw an error placing new okx order', async () => { // preparing data diff --git a/src/exchanges/okx/modules/authed/order/place.ts b/src/exchanges/okx/modules/authed/order/place.ts index 77d51aaf..9b9b18ab 100644 --- a/src/exchanges/okx/modules/authed/order/place.ts +++ b/src/exchanges/okx/modules/authed/order/place.ts @@ -1,4 +1,5 @@ import { debug } from 'debug' +import { isArray } from 'lodash' import { AlunaError } from '../../../../../lib/core/AlunaError' import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' @@ -45,7 +46,7 @@ export const place = (exchange: IAlunaExchangeAuthed) => async ( ensureOrderIsSupported({ exchangeSpecs: specs, - orderPlaceParams: params, + orderParams: params, }) const { @@ -124,7 +125,7 @@ export const place = (exchange: IAlunaExchangeAuthed) => async ( credentials, }) - placedOrderId = orderResponse.ordId + placedOrderId = orderResponse.ordId || orderResponse.algoId } catch (err) { @@ -135,7 +136,14 @@ export const place = (exchange: IAlunaExchangeAuthed) => async ( const { metadata } = err - if (metadata.sCode === '51008') { + const INSUFFICIENT_BALANCE_CODE = '51008' + + const isInsufficientBalanceError = isArray(metadata) + ? metadata[0].sCode === INSUFFICIENT_BALANCE_CODE + : metadata.sCode === INSUFFICIENT_BALANCE_CODE + + + if (isInsufficientBalanceError) { code = AlunaBalanceErrorCodes.INSUFFICIENT_BALANCE @@ -158,6 +166,7 @@ export const place = (exchange: IAlunaExchangeAuthed) => async ( const { order } = await exchange.order.get({ id: placedOrderId, symbolPair, + type, http, }) From 3a4aac9a5c91ad798a0f26e7714494c175700701 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Mon, 13 Jun 2022 08:53:08 -0300 Subject: [PATCH 093/105] update okx specs --- src/exchanges/okx/okxSpecs.ts | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/exchanges/okx/okxSpecs.ts b/src/exchanges/okx/okxSpecs.ts index de287317..1b438235 100644 --- a/src/exchanges/okx/okxSpecs.ts +++ b/src/exchanges/okx/okxSpecs.ts @@ -11,6 +11,7 @@ import { } from '../../lib/schemas/IAlunaExchangeSchema' import { IAlunaSettingsSchema } from '../../lib/schemas/IAlunaSettingsSchema' import { OkxAlgoOrderTypeEnum } from './enums/OkxAlgoOrderTypeEnum' +import { OkxOrderTypeEnum } from './enums/OkxOrderTypeEnum' import { OkxSymbolTypeEnum } from './enums/OkxSymbolTypeEnum' @@ -135,6 +136,8 @@ export const getOkxEndpoints = ( }, order: { get: (id: string, symbolPair: string) => `${baseUrl}/trade/order?ordId=${id}&instId=${symbolPair}`, + getStop: (ordType: OkxOrderTypeEnum) => `${baseUrl}/trade/orders-algo-pending?ordType=${ordType}`, + getStopHistory: (id: string, ordType: OkxOrderTypeEnum) => `${baseUrl}/trade/orders-algo-history?algoId=${id}&ordType=${ordType}`, list: `${baseUrl}/trade/orders-pending`, listStop: (type: OkxAlgoOrderTypeEnum) => `${baseUrl}/trade/orders-algo-pending?ordType=${type}`, place: `${baseUrl}/trade/order`, From 4b5f48317587766fec3477cc6cbe923509b5b650 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Mon, 13 Jun 2022 08:53:28 -0300 Subject: [PATCH 094/105] update okx order type adapter to include `STOP_MARKET` orders --- .../adapters/okxOrderTypeAdapter.spec.ts | 5 ++ .../okx/enums/adapters/okxOrderTypeAdapter.ts | 48 +++++++++++++++---- 2 files changed, 44 insertions(+), 9 deletions(-) diff --git a/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.spec.ts b/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.spec.ts index a806914a..3fc7b039 100644 --- a/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.spec.ts +++ b/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.spec.ts @@ -30,6 +30,11 @@ describe(__filename, () => { from: OkxOrderTypeEnum.CONDITIONAL, })).to.be.eq(AlunaOrderTypesEnum.STOP_LIMIT) + expect(translateOrderTypeToAluna({ + from: OkxOrderTypeEnum.CONDITIONAL, + slOrdPx: '-1', + })).to.be.eq(AlunaOrderTypesEnum.STOP_MARKET) + let result let error diff --git a/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.ts b/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.ts index bb0d0af3..29396fd2 100644 --- a/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.ts +++ b/src/exchanges/okx/enums/adapters/okxOrderTypeAdapter.ts @@ -1,5 +1,7 @@ +import { AlunaError } from '../../../../lib/core/AlunaError' import { buildAdapter } from '../../../../lib/enums/adapters/buildAdapter' import { AlunaOrderTypesEnum } from '../../../../lib/enums/AlunaOrderTypesEnum' +import { AlunaAdaptersErrorCodes } from '../../../../lib/errors/AlunaAdaptersErrorCodes' import { OkxOrderTypeEnum } from '../OkxOrderTypeEnum' @@ -8,17 +10,45 @@ const errorMessagePrefix = 'Order type' -export const translateOrderTypeToAluna = buildAdapter< - OkxOrderTypeEnum, - AlunaOrderTypesEnum ->({ - errorMessagePrefix, - mappings: { +export const translateOrderTypeToAluna = (params: { + from: OkxOrderTypeEnum + slOrdPx?: string +}) => { + + const { from, slOrdPx } = params + + const mappings = { [OkxOrderTypeEnum.LIMIT]: AlunaOrderTypesEnum.LIMIT, [OkxOrderTypeEnum.MARKET]: AlunaOrderTypesEnum.MARKET, - [OkxOrderTypeEnum.CONDITIONAL]: AlunaOrderTypesEnum.STOP_LIMIT, - }, -}) + } + + const translated: AlunaOrderTypesEnum = mappings[from] + + if (translated) { + + return translated + + } + + const isConditionalOrder = from === OkxOrderTypeEnum.CONDITIONAL + + if (isConditionalOrder) { + + return slOrdPx === '-1' + ? AlunaOrderTypesEnum.STOP_MARKET + : AlunaOrderTypesEnum.STOP_LIMIT + + } + + + const error = new AlunaError({ + message: `${errorMessagePrefix} not supported: ${from}`, + code: AlunaAdaptersErrorCodes.NOT_SUPPORTED, + }) + + throw error + +} From 23e57b5c4d92b811d02da0e2b3564b6d79423760 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Mon, 13 Jun 2022 08:54:19 -0300 Subject: [PATCH 095/105] update okx request error to test `code` --- .../okx/errors/handleOkxRequestError.spec.ts | 14 +++++++ .../okx/errors/handleOkxRequestError.ts | 40 ++++++++++++++++--- 2 files changed, 48 insertions(+), 6 deletions(-) diff --git a/src/exchanges/okx/errors/handleOkxRequestError.spec.ts b/src/exchanges/okx/errors/handleOkxRequestError.spec.ts index e015e0cc..a054c021 100644 --- a/src/exchanges/okx/errors/handleOkxRequestError.spec.ts +++ b/src/exchanges/okx/errors/handleOkxRequestError.spec.ts @@ -138,6 +138,20 @@ describe(__filename, () => { metadata: okxError, }) + const okxError2 = { + code: '51000', + msg: requestMessage, + } as handleOkxMod.IOkxErrorSchema + + alunaError = handleOkxRequestError({ error: okxError2 }) + + expect(alunaError).to.deep.eq({ + code: AlunaHttpErrorCodes.REQUEST_ERROR, + message: requestMessage, + httpStatusCode: 500, + metadata: okxError2, + }) + const okxErrors = [ { diff --git a/src/exchanges/okx/errors/handleOkxRequestError.ts b/src/exchanges/okx/errors/handleOkxRequestError.ts index 170d10fc..b7421fb0 100644 --- a/src/exchanges/okx/errors/handleOkxRequestError.ts +++ b/src/exchanges/okx/errors/handleOkxRequestError.ts @@ -17,6 +17,11 @@ export const okxInvalidKeyPatterns: Array = [ /Invalid Sign/mi, ] +export const okxInvalidKeyCodes: Array = [ + '50103', + '50113', +] + export const okxDownErrorPatterns: Array = [ @@ -24,19 +29,37 @@ export const okxDownErrorPatterns: Array = [ ] -export const isOkxKeyInvalid = (errorMessage: string) => { +export const isOkxKeyInvalid = (errorMessage: string, code?: string) => { - return some(okxInvalidKeyPatterns, (pattern) => { + const isInvalidRegex = some(okxInvalidKeyPatterns, (pattern) => { return pattern.test(errorMessage) }) + if (isInvalidRegex) { + + return true + + } + + if (code) { + + return some(okxInvalidKeyCodes, (sCode) => { + + return sCode === code + + }) + + } + } export interface IOkxErrorSchema { sCode: string + code: string + msg: string sMsg: string } @@ -78,11 +101,11 @@ export const handleOkxRequestError = ( }) - } else if ((error as IOkxErrorSchema).sMsg) { + } else if ((error as IOkxErrorSchema).msg) { - const { sMsg } = error as IOkxErrorSchema + const { msg, sMsg } = error as IOkxErrorSchema - message = sMsg + message = sMsg || msg } else { @@ -93,7 +116,12 @@ export const handleOkxRequestError = ( } - if (isOkxKeyInvalid(message)) { + if (isOkxKeyInvalid( + message, + (error as IOkxErrorSchema).code + || metadata.code + || (error as IOkxErrorSchema).sCode, + )) { code = AlunaKeyErrorCodes.INVALID From 698b87695bda4236249566c9271c5e2147a647c1 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Mon, 13 Jun 2022 08:54:48 -0300 Subject: [PATCH 096/105] update cancel order to support `STOP` orders --- .../okx/modules/authed/balance/list.spec.ts | 2 +- .../okx/modules/authed/key/fetchDetails.ts | 3 +- .../okx/modules/authed/order/cancel.spec.ts | 20 ++++---- .../okx/modules/authed/order/cancel.ts | 48 +++++++++---------- 4 files changed, 36 insertions(+), 37 deletions(-) diff --git a/src/exchanges/okx/modules/authed/balance/list.spec.ts b/src/exchanges/okx/modules/authed/balance/list.spec.ts index 589b37b2..3fc9e443 100644 --- a/src/exchanges/okx/modules/authed/balance/list.spec.ts +++ b/src/exchanges/okx/modules/authed/balance/list.spec.ts @@ -13,7 +13,7 @@ describe(__filename, () => { AuthedClass: OkxAuthed, exchangeId: 'okx', methodModuleName: 'balance', - listModule: listRawMod, + listRawModule: listRawMod, parseManyModule: parseManyMod, rawList: { rawBalances: OKX_RAW_BALANCES }, parsedList: { balances: PARSED_BALANCES }, diff --git a/src/exchanges/okx/modules/authed/key/fetchDetails.ts b/src/exchanges/okx/modules/authed/key/fetchDetails.ts index 8491b6af..4b0c5fe5 100644 --- a/src/exchanges/okx/modules/authed/key/fetchDetails.ts +++ b/src/exchanges/okx/modules/authed/key/fetchDetails.ts @@ -39,7 +39,6 @@ export const fetchDetails = (exchange: IAlunaExchangeAuthed) => async ( accountId: undefined, } - const [ accountConfig, ] = await http.authedRequest({ @@ -51,6 +50,7 @@ export const fetchDetails = (exchange: IAlunaExchangeAuthed) => async ( rawKey.read = true rawKey.accountId = accountConfig.uid + try { // Since OKX does not have a request to get API keys permissions, we @@ -63,7 +63,6 @@ export const fetchDetails = (exchange: IAlunaExchangeAuthed) => async ( }) } catch (err) { - const { metadata: [okxError], } = err diff --git a/src/exchanges/okx/modules/authed/order/cancel.spec.ts b/src/exchanges/okx/modules/authed/order/cancel.spec.ts index 4f9d99dd..f1d3f420 100644 --- a/src/exchanges/okx/modules/authed/order/cancel.spec.ts +++ b/src/exchanges/okx/modules/authed/order/cancel.spec.ts @@ -57,6 +57,7 @@ describe(__filename, () => { const { order } = await exchange.order.cancel({ id, symbolPair: '', + type: AlunaOrderTypesEnum.LIMIT, }) @@ -85,10 +86,12 @@ describe(__filename, () => { const { ordId: id } = mockedRawOrder - const body = { - algoId: id, - instId: '', - } + const body = [ + { + algoId: id, + instId: '', + }, + ] // mocking @@ -110,6 +113,7 @@ describe(__filename, () => { const { order } = await exchange.order.cancel({ id, symbolPair: '', + type: AlunaOrderTypesEnum.STOP_LIMIT, }) @@ -168,6 +172,7 @@ describe(__filename, () => { () => exchange.order.cancel({ id, symbolPair, + type: AlunaOrderTypesEnum.LIMIT, }), ) @@ -175,12 +180,7 @@ describe(__filename, () => { // validating expect(responseError).to.deep.eq(error) - expect(get.callCount).to.be.eq(1) - - expect(get.firstCall.args[0]).to.deep.eq({ - id, - symbolPair, - }) + expect(get.callCount).to.be.eq(0) expect(authedRequest.callCount).to.be.eq(1) diff --git a/src/exchanges/okx/modules/authed/order/cancel.ts b/src/exchanges/okx/modules/authed/order/cancel.ts index db2ac162..3867da90 100644 --- a/src/exchanges/okx/modules/authed/order/cancel.ts +++ b/src/exchanges/okx/modules/authed/order/cancel.ts @@ -2,7 +2,6 @@ import { debug } from 'debug' import { AlunaError } from '../../../../../lib/core/AlunaError' import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' -import { AlunaOrderStatusEnum } from '../../../../../lib/enums/AlunaOrderStatusEnum' import { AlunaOrderTypesEnum } from '../../../../../lib/enums/AlunaOrderTypesEnum' import { AlunaOrderErrorCodes } from '../../../../../lib/errors/AlunaOrderErrorCodes' import { @@ -32,43 +31,41 @@ export const cancel = (exchange: IAlunaExchangeAuthed) => async ( const { id, symbolPair, + type, http = new OkxHttp(settings), } = params - const { order } = await exchange.order.get({ - id, - symbolPair, - }) + const isConditionalOrder = type === AlunaOrderTypesEnum.STOP_LIMIT || type === AlunaOrderTypesEnum.STOP_MARKET - const { type } = order + const orderEndpoints = getOkxEndpoints(settings).order - const isStopLimitOrder = type === AlunaOrderTypesEnum.STOP_LIMIT + let url - const body = { - instId: symbolPair, - } + let body - if (isStopLimitOrder) { + if (isConditionalOrder) { - Object.assign(body, { - algoId: id, - }) + body = [ + { + instId: symbolPair, + algoId: id, + }, + ] + + url = orderEndpoints.cancelStop } else { - Object.assign(body, { + body = { + instId: symbolPair, ordId: id, - }) + } + + url = orderEndpoints.cancel } try { - const orderEndpoints = getOkxEndpoints(settings).order - - const url = isStopLimitOrder - ? orderEndpoints.cancelStop - : orderEndpoints.cancel - await http.authedRequest({ url, @@ -76,8 +73,11 @@ export const cancel = (exchange: IAlunaExchangeAuthed) => async ( body, }) - // assign canceled if the cancel request works fine - order.status = AlunaOrderStatusEnum.CANCELED + const { order } = await exchange.order.get({ + id, + symbolPair, + type, + }) const { requestWeight } = http From 681177f861e5d1139ebfb449d31054d200af841e Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Mon, 13 Jun 2022 08:55:08 -0300 Subject: [PATCH 097/105] update okx `edit` order to support `STOP` orders --- src/exchanges/okx/modules/authed/order/edit.spec.ts | 3 +++ src/exchanges/okx/modules/authed/order/edit.ts | 5 +++++ 2 files changed, 8 insertions(+) diff --git a/src/exchanges/okx/modules/authed/order/edit.spec.ts b/src/exchanges/okx/modules/authed/order/edit.spec.ts index 83109511..99705ec6 100644 --- a/src/exchanges/okx/modules/authed/order/edit.spec.ts +++ b/src/exchanges/okx/modules/authed/order/edit.spec.ts @@ -76,6 +76,7 @@ describe(__filename, () => { expect(cancel.firstCall.args[0]).to.deep.eq({ http, id, + type: params.type, symbolPair: params.symbolPair, }) @@ -89,6 +90,8 @@ describe(__filename, () => { amount: params.amount, account: params.account, symbolPair: params.symbolPair, + stopRate: undefined, + limitRate: undefined, }) }) diff --git a/src/exchanges/okx/modules/authed/order/edit.ts b/src/exchanges/okx/modules/authed/order/edit.ts index 31c8132f..8ada2e56 100644 --- a/src/exchanges/okx/modules/authed/order/edit.ts +++ b/src/exchanges/okx/modules/authed/order/edit.ts @@ -36,6 +36,8 @@ export const edit = (exchange: IAlunaExchangeAuthed) => async ( amount, account, symbolPair, + limitRate, + stopRate, http = new OkxHttp(exchange.settings), } = params @@ -43,6 +45,7 @@ export const edit = (exchange: IAlunaExchangeAuthed) => async ( id, symbolPair, http, + type, }) const { order: newOrder } = await exchange.order.place({ @@ -53,6 +56,8 @@ export const edit = (exchange: IAlunaExchangeAuthed) => async ( account, symbolPair, http, + limitRate, + stopRate, }) const { requestWeight } = http From 6ac8b0bd5e241cdf9c9f98ffac26c408137672e5 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Mon, 13 Jun 2022 08:55:34 -0300 Subject: [PATCH 098/105] update get okx raw order to support closed and open `STOP` orders --- .../okx/modules/authed/order/get.spec.ts | 8 +- .../okx/modules/authed/order/getRaw.spec.ts | 152 +++++++++++++++++- .../okx/modules/authed/order/getRaw.ts | 54 ++++++- 3 files changed, 206 insertions(+), 8 deletions(-) diff --git a/src/exchanges/okx/modules/authed/order/get.spec.ts b/src/exchanges/okx/modules/authed/order/get.spec.ts index b5f54d76..c55091b6 100644 --- a/src/exchanges/okx/modules/authed/order/get.spec.ts +++ b/src/exchanges/okx/modules/authed/order/get.spec.ts @@ -3,6 +3,7 @@ import { expect } from 'chai' import { PARSED_ORDERS } from '../../../../../../test/fixtures/parsedOrders' import { mockGetRaw } from '../../../../../../test/mocks/exchange/modules/mockGetRaw' import { mockParse } from '../../../../../../test/mocks/exchange/modules/mockParse' +import { AlunaOrderTypesEnum } from '../../../../../lib/enums/AlunaOrderTypesEnum' import { IAlunaOrderGetParams } from '../../../../../lib/modules/authed/IAlunaOrderModule' import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' import { OkxAuthed } from '../../../OkxAuthed' @@ -30,6 +31,7 @@ describe(__filename, () => { const params: IAlunaOrderGetParams = { id, symbolPair: '', + type: AlunaOrderTypesEnum.LIMIT, } @@ -46,11 +48,7 @@ describe(__filename, () => { // executing const exchange = new OkxAuthed({ credentials }) - const { order } = await exchange.order.get({ - id, - symbolPair: '', - }) - + const { order } = await exchange.order.get(params) // validating expect(order).to.deep.eq(mockedParsedOrder) diff --git a/src/exchanges/okx/modules/authed/order/getRaw.spec.ts b/src/exchanges/okx/modules/authed/order/getRaw.spec.ts index d931f4f9..edf72b2d 100644 --- a/src/exchanges/okx/modules/authed/order/getRaw.spec.ts +++ b/src/exchanges/okx/modules/authed/order/getRaw.spec.ts @@ -1,8 +1,13 @@ import { expect } from 'chai' import { mockHttp } from '../../../../../../test/mocks/exchange/Http' +import { AlunaError } from '../../../../../lib/core/AlunaError' import { AlunaHttpVerbEnum } from '../../../../../lib/enums/AlunaHtttpVerbEnum' +import { AlunaOrderTypesEnum } from '../../../../../lib/enums/AlunaOrderTypesEnum' +import { AlunaOrderErrorCodes } from '../../../../../lib/errors/AlunaOrderErrorCodes' import { IAlunaCredentialsSchema } from '../../../../../lib/schemas/IAlunaCredentialsSchema' +import { executeAndCatch } from '../../../../../utils/executeAndCatch' +import { OkxOrderTypeEnum } from '../../../enums/OkxOrderTypeEnum' import { OkxAuthed } from '../../../OkxAuthed' import { OkxHttp } from '../../../OkxHttp' import { getOkxEndpoints } from '../../../okxSpecs' @@ -31,7 +36,7 @@ describe(__filename, () => { authedRequest, } = mockHttp({ classPrototype: OkxHttp.prototype }) - authedRequest.returns(Promise.resolve(mockedRawOrder)) + authedRequest.returns(Promise.resolve([mockedRawOrder])) // executing @@ -40,6 +45,7 @@ describe(__filename, () => { const { rawOrder } = await exchange.order.getRaw({ id, symbolPair: '', + type: AlunaOrderTypesEnum.LIMIT, }) @@ -58,4 +64,148 @@ describe(__filename, () => { }) + it('should get a Okx raw stop limit order just fine', async () => { + + // preparing data + const mockedRawOrder = OKX_RAW_ORDERS[0] + + const { ordId: id } = mockedRawOrder + + + // mocking + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + authedRequest.returns(Promise.resolve([mockedRawOrder])) + + + // executing + const exchange = new OkxAuthed({ credentials }) + + const { rawOrder } = await exchange.order.getRaw({ + id, + symbolPair: '', + type: AlunaOrderTypesEnum.STOP_LIMIT, + }) + + + // validating + expect(rawOrder).to.deep.eq(mockedRawOrder) + + expect(authedRequest.callCount).to.be.eq(1) + + expect(authedRequest.firstCall.args[0]).to.deep.eq({ + verb: AlunaHttpVerbEnum.GET, + credentials, + url: getOkxEndpoints(exchange.settings).order.getStop(OkxOrderTypeEnum.CONDITIONAL), + }) + + expect(publicRequest.callCount).to.be.eq(0) + + }) + + it('should get a Okx raw closed stop limit order just fine', async () => { + + // preparing data + const mockedRawOrder = OKX_RAW_ORDERS[0] + + const { ordId: id } = mockedRawOrder + + + // mocking + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + authedRequest.onFirstCall().returns(Promise.resolve([])) + authedRequest.onSecondCall().returns(Promise.resolve([mockedRawOrder])) + + + // executing + const exchange = new OkxAuthed({ credentials }) + + const { rawOrder } = await exchange.order.getRaw({ + id, + symbolPair: '', + type: AlunaOrderTypesEnum.STOP_LIMIT, + }) + + // validating + expect(rawOrder).to.deep.eq(mockedRawOrder) + + expect(authedRequest.callCount).to.be.eq(2) + + expect(authedRequest.firstCall.args[0]).to.deep.eq({ + verb: AlunaHttpVerbEnum.GET, + credentials, + url: getOkxEndpoints(exchange.settings).order.getStop(OkxOrderTypeEnum.CONDITIONAL), + }) + + expect(authedRequest.secondCall.args[0]).to.deep.eq({ + verb: AlunaHttpVerbEnum.GET, + credentials, + url: getOkxEndpoints(exchange.settings).order.getStopHistory(id, OkxOrderTypeEnum.CONDITIONAL), + }) + + expect(publicRequest.callCount).to.be.eq(0) + + }) + + it('should throw an error when an okx order is not found', async () => { + + // preparing data + const mockedRawOrder = OKX_RAW_ORDERS[0] + + const { ordId: id } = mockedRawOrder + + + // mocking + const { + publicRequest, + authedRequest, + } = mockHttp({ classPrototype: OkxHttp.prototype }) + + authedRequest.returns(Promise.resolve([])) + + // executing + const exchange = new OkxAuthed({ credentials }) + + const { + error, + result, + } = await executeAndCatch(() => exchange.order.getRaw({ + id, + symbolPair: '', + type: AlunaOrderTypesEnum.STOP_LIMIT, + })) + + // validating + expect(result).not.to.be.ok + + expect(error instanceof AlunaError).to.be.ok + expect(error?.code).to.be.eq(AlunaOrderErrorCodes.NOT_FOUND) + expect(error?.message).to.be.eq('Order not found') + expect(error?.httpStatusCode).to.be.eq(200) + + expect(authedRequest.callCount).to.be.eq(2) + + expect(authedRequest.firstCall.args[0]).to.deep.eq({ + verb: AlunaHttpVerbEnum.GET, + credentials, + url: getOkxEndpoints(exchange.settings).order.getStop(OkxOrderTypeEnum.CONDITIONAL), + }) + + expect(authedRequest.secondCall.args[0]).to.deep.eq({ + verb: AlunaHttpVerbEnum.GET, + credentials, + url: getOkxEndpoints(exchange.settings).order.getStopHistory(id, OkxOrderTypeEnum.CONDITIONAL), + }) + + expect(publicRequest.callCount).to.be.eq(0) + + }) + }) diff --git a/src/exchanges/okx/modules/authed/order/getRaw.ts b/src/exchanges/okx/modules/authed/order/getRaw.ts index 9b5f1d5e..e6eccde2 100644 --- a/src/exchanges/okx/modules/authed/order/getRaw.ts +++ b/src/exchanges/okx/modules/authed/order/getRaw.ts @@ -1,11 +1,15 @@ import { debug } from 'debug' +import { AlunaError } from '../../../../../lib/core/AlunaError' import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' import { AlunaHttpVerbEnum } from '../../../../../lib/enums/AlunaHtttpVerbEnum' +import { AlunaOrderTypesEnum } from '../../../../../lib/enums/AlunaOrderTypesEnum' +import { AlunaOrderErrorCodes } from '../../../../../lib/errors/AlunaOrderErrorCodes' import { IAlunaOrderGetParams, IAlunaOrderGetRawReturns, } from '../../../../../lib/modules/authed/IAlunaOrderModule' +import { OkxOrderTypeEnum } from '../../../enums/OkxOrderTypeEnum' import { OkxHttp } from '../../../OkxHttp' import { getOkxEndpoints } from '../../../okxSpecs' import { IOkxOrderSchema } from '../../../schemas/IOkxOrderSchema' @@ -30,15 +34,61 @@ export const getRaw = (exchange: IAlunaExchangeAuthed) => async ( const { id, symbolPair, + type, http = new OkxHttp(settings), } = params - const [rawOrder] = await http.authedRequest({ + const orderEndpoints = getOkxEndpoints(settings).order + + const isConditionalOrder = type === AlunaOrderTypesEnum.STOP_LIMIT || type === AlunaOrderTypesEnum.STOP_MARKET + + let url = orderEndpoints.get(id, symbolPair) + + if (isConditionalOrder) { + + url = orderEndpoints.getStop(OkxOrderTypeEnum.CONDITIONAL) + + } + + const resp = await http.authedRequest({ credentials, verb: AlunaHttpVerbEnum.GET, - url: getOkxEndpoints(settings).order.get(id, symbolPair), + url, }) + let [rawOrder] = resp + + if (isConditionalOrder) { + + let conditionalOrder = resp.find((order) => order.algoId === id) + + if (!conditionalOrder) { + + const ordersHistory = await await http.authedRequest({ + credentials, + verb: AlunaHttpVerbEnum.GET, + url: orderEndpoints.getStopHistory(id, OkxOrderTypeEnum.CONDITIONAL), + }) + + conditionalOrder = ordersHistory.find((order) => order.algoId === id) + + } + + if (!conditionalOrder) { + + throw new AlunaError({ + code: AlunaOrderErrorCodes.NOT_FOUND, + message: 'Order not found', + httpStatusCode: 200, + metadata: resp, + }) + + } + + rawOrder = conditionalOrder + + } + const { requestWeight } = http return { From 175ec16076f577be5022de2ce4ecfd26885beba1 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Mon, 13 Jun 2022 08:55:55 -0300 Subject: [PATCH 099/105] update okx `parse` to include proper fields for each order type --- .../okx/modules/authed/order/list.spec.ts | 2 +- .../okx/modules/authed/order/parse.spec.ts | 147 +++++++++++++++++- .../okx/modules/authed/order/parse.ts | 60 +++++-- 3 files changed, 195 insertions(+), 14 deletions(-) diff --git a/src/exchanges/okx/modules/authed/order/list.spec.ts b/src/exchanges/okx/modules/authed/order/list.spec.ts index 8b0961c0..968d48d3 100644 --- a/src/exchanges/okx/modules/authed/order/list.spec.ts +++ b/src/exchanges/okx/modules/authed/order/list.spec.ts @@ -13,7 +13,7 @@ describe(__filename, () => { AuthedClass: OkxAuthed, exchangeId: 'okx', methodModuleName: 'order', - listModule: listRawMod, + listRawModule: listRawMod, parseManyModule: parseManyMod, rawList: { rawOrders: OKX_RAW_ORDERS }, parsedList: { orders: PARSED_ORDERS }, diff --git a/src/exchanges/okx/modules/authed/order/parse.spec.ts b/src/exchanges/okx/modules/authed/order/parse.spec.ts index 327bd6a5..66253dee 100644 --- a/src/exchanges/okx/modules/authed/order/parse.spec.ts +++ b/src/exchanges/okx/modules/authed/order/parse.spec.ts @@ -7,7 +7,7 @@ import { translateOrderSideToAluna } from '../../../enums/adapters/okxOrderSideA import { translateOrderStatusToAluna } from '../../../enums/adapters/okxOrderStatusAdapter' import { translateOrderTypeToAluna } from '../../../enums/adapters/okxOrderTypeAdapter' import { OkxAuthed } from '../../../OkxAuthed' -import { OKX_RAW_ORDERS } from '../../../test/fixtures/okxOrders' +import { OKX_RAW_CONDITIONAL_ORDERS, OKX_RAW_ORDERS } from '../../../test/fixtures/okxOrders' import { mockTranslateSymbolId } from '../../../../../utils/mappings/translateSymbolId.mock' import { OkxOrderStatusEnum } from '../../../enums/OkxOrderStatusEnum' @@ -91,10 +91,150 @@ describe(__filename, () => { }) + it('should parse a Okx raw stop limit order just fine', async () => { + + // preparing data + const rawOrder = cloneDeep(OKX_RAW_CONDITIONAL_ORDERS[0]) + + const { + side, + instId, + px, + state, + sz, + ordType, + ccy, + tgtCcy, + algoId, + slOrdPx, + slTriggerPx, + } = rawOrder + + const amount = Number(sz) + const limitRate = Number(slTriggerPx) + const stopRate = Number(slOrdPx) + const total = amount * limitRate + + const orderStatus = translateOrderStatusToAluna({ from: state }) + const orderSide = translateOrderSideToAluna({ from: side }) + const orderType = translateOrderTypeToAluna({ from: ordType, slOrdPx }) + + const timestamp = new Date() + + // mocking + + ImportMock.mockFunction( + global, + 'Date', + timestamp, + ) + + const { translateSymbolId } = mockTranslateSymbolId() + + translateSymbolId.onFirstCall().returns(ccy) + + translateSymbolId.onSecondCall().returns(tgtCcy) + + const exchange = new OkxAuthed({ credentials }) + + + // executing + const { order } = exchange.order.parse({ rawOrder }) + + + // validating + expect(order).to.exist + + expect(order.id).to.be.eq(algoId) + expect(order.symbolPair).to.be.eq(instId) + expect(order.status).to.be.eq(orderStatus) + expect(order.side).to.be.eq(orderSide) + expect(order.type).to.be.eq(orderType) + expect(order.baseSymbolId).to.be.eq(ccy) + expect(order.quoteSymbolId).to.be.eq(tgtCcy) + expect(order.total).to.be.eq(total) + expect(order.limitRate).to.be.eq(limitRate) + expect(order.stopRate).to.be.eq(stopRate) + expect(order.amount).to.be.eq(amount) + expect(order.placedAt.getTime()).to.be.eq(timestamp.getTime()) + + expect(translateSymbolId.callCount).to.be.eq(2) + + }) + + it('should parse a Okx raw stop market order just fine', async () => { + + // preparing data + const rawOrder = cloneDeep(OKX_RAW_CONDITIONAL_ORDERS[1]) + + const { + side, + instId, + px, + state, + sz, + ordType, + ccy, + tgtCcy, + algoId, + slOrdPx, + slTriggerPx, + } = rawOrder + + const amount = Number(sz) + const stopRate = Number(slTriggerPx) + const total = amount * stopRate + + const orderStatus = translateOrderStatusToAluna({ from: state }) + const orderSide = translateOrderSideToAluna({ from: side }) + const orderType = translateOrderTypeToAluna({ from: ordType, slOrdPx }) + + const timestamp = new Date() + + // mocking + + ImportMock.mockFunction( + global, + 'Date', + timestamp, + ) + + const { translateSymbolId } = mockTranslateSymbolId() + + translateSymbolId.onFirstCall().returns(ccy) + + translateSymbolId.onSecondCall().returns(tgtCcy) + + const exchange = new OkxAuthed({ credentials }) + + + // executing + const { order } = exchange.order.parse({ rawOrder }) + + + // validating + expect(order).to.exist + + expect(order.id).to.be.eq(algoId) + expect(order.symbolPair).to.be.eq(instId) + expect(order.status).to.be.eq(orderStatus) + expect(order.side).to.be.eq(orderSide) + expect(order.type).to.be.eq(orderType) + expect(order.baseSymbolId).to.be.eq(ccy) + expect(order.quoteSymbolId).to.be.eq(tgtCcy) + expect(order.total).to.be.eq(total) + expect(order.stopRate).to.be.eq(stopRate) + expect(order.amount).to.be.eq(amount) + expect(order.placedAt.getTime()).to.be.eq(timestamp.getTime()) + + expect(translateSymbolId.callCount).to.be.eq(2) + + }) + it('should parse a Okx raw order just fine', async () => { // preparing data - const rawOrder = cloneDeep(OKX_RAW_ORDERS[0]) + const rawOrder = cloneDeep(OKX_RAW_ORDERS[1]) rawOrder.state = OkxOrderStatusEnum.FILLED rawOrder.uTime = null as any @@ -113,8 +253,7 @@ describe(__filename, () => { } = rawOrder const amount = Number(sz) - const rate = Number(px) - const total = amount * rate + const total = amount const orderStatus = translateOrderStatusToAluna({ from: state }) const orderSide = translateOrderSideToAluna({ from: side }) diff --git a/src/exchanges/okx/modules/authed/order/parse.ts b/src/exchanges/okx/modules/authed/order/parse.ts index 36acb34f..24d94871 100644 --- a/src/exchanges/okx/modules/authed/order/parse.ts +++ b/src/exchanges/okx/modules/authed/order/parse.ts @@ -1,6 +1,7 @@ import { IAlunaExchangeAuthed } from '../../../../../lib/core/IAlunaExchange' import { AlunaAccountEnum } from '../../../../../lib/enums/AlunaAccountEnum' import { AlunaOrderStatusEnum } from '../../../../../lib/enums/AlunaOrderStatusEnum' +import { AlunaOrderTypesEnum } from '../../../../../lib/enums/AlunaOrderTypesEnum' import { IAlunaOrderParseParams, IAlunaOrderParseReturns, @@ -31,7 +32,9 @@ export const parse = (exchange: IAlunaExchangeAuthed) => ( sz, uTime, ordType, + slOrdPx, slTriggerPx, + algoId, } = rawOrder let [baseSymbolId, quoteSymbolId] = instId.split('-') @@ -48,13 +51,54 @@ export const parse = (exchange: IAlunaExchangeAuthed) => ( const updatedAt = uTime ? new Date(Number(uTime)) : new Date() const amount = Number(sz) - const rate = Number(px) - const total = amount * rate - const stopRate = Number(slTriggerPx) - const orderStatus = translateOrderStatusToAluna({ from: state }) const orderSide = translateOrderSideToAluna({ from: side }) - const orderType = translateOrderTypeToAluna({ from: ordType }) + const orderType = translateOrderTypeToAluna({ from: ordType, slOrdPx }) + + const priceFields = { + total: amount, + } + + let id = ordId + + switch (orderType) { + + case AlunaOrderTypesEnum.STOP_LIMIT: + Object.assign(priceFields, { + stopRate: Number(slOrdPx), + limitRate: Number(slTriggerPx), + total: Number(slTriggerPx) * amount, + }) + + id = algoId + break + + case AlunaOrderTypesEnum.STOP_MARKET: + Object.assign(priceFields, { + stopRate: Number(slTriggerPx), + total: Number(slTriggerPx) * amount, + }) + + id = algoId + break + + case AlunaOrderTypesEnum.MARKET: + Object.assign(priceFields, { + total: amount, + }) + + id = algoId + break + + default: + Object.assign(priceFields, { + rate: Number(px), + total: Number(px) * amount, + }) + break + + } + let createdAt: Date let filledAt: Date | undefined @@ -83,18 +127,16 @@ export const parse = (exchange: IAlunaExchangeAuthed) => ( } const order: IAlunaOrderSchema = { - id: ordId, + id, symbolPair: instId, exchangeId: exchange.specs.id, baseSymbolId, quoteSymbolId, - total, amount, placedAt: new Date(createdAt), canceledAt, filledAt, - rate, - stopRate, + ...priceFields, account: AlunaAccountEnum.SPOT, type: orderType, status: orderStatus, From e95d491c05889afc1f3a99bd148c6b7880ba7038 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Mon, 13 Jun 2022 09:00:40 -0300 Subject: [PATCH 100/105] linting --- src/exchanges/okx/modules/authed/order/parse.spec.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/exchanges/okx/modules/authed/order/parse.spec.ts b/src/exchanges/okx/modules/authed/order/parse.spec.ts index 66253dee..c8c24879 100644 --- a/src/exchanges/okx/modules/authed/order/parse.spec.ts +++ b/src/exchanges/okx/modules/authed/order/parse.spec.ts @@ -99,7 +99,6 @@ describe(__filename, () => { const { side, instId, - px, state, sz, ordType, @@ -170,7 +169,6 @@ describe(__filename, () => { const { side, instId, - px, state, sz, ordType, @@ -244,7 +242,6 @@ describe(__filename, () => { cTime, instId, ordId, - px, state, sz, ordType, From 66ddd14e96111d219038313e6d95887dd8229df8 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Mon, 13 Jun 2022 09:07:05 -0300 Subject: [PATCH 101/105] update `aluna-spec` file --- .playground/aluna-spec.json | 115 ++++++++++++++++++------------------ 1 file changed, 58 insertions(+), 57 deletions(-) diff --git a/.playground/aluna-spec.json b/.playground/aluna-spec.json index 78f67748..2c8f7811 100644 --- a/.playground/aluna-spec.json +++ b/.playground/aluna-spec.json @@ -1,8 +1,8 @@ { "_type": "export", "__export_format": 4, - "__export_date": "2022-06-03T10:11:02.331Z", - "__export_source": "insomnia.desktop.app:v2022.2.1", + "__export_date": "2022-06-13T12:05:28.433Z", + "__export_source": "insomnia.desktop.app:v2022.4.0", "resources": [ { "_id": "req_638f9118078540b8a309cfcf9a6189ee", @@ -149,7 +149,7 @@ { "_id": "req_f44dbbbcceb3496da709be542a08706b", "parentId": "fld_51ff43abd0e041e7a35891223e170a16", - "modified": 1653053386793, + "modified": 1654996162523, "created": 1651252489507, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "listRaw", @@ -217,7 +217,7 @@ { "_id": "req_c284cfcbcce94ecf865cfb5da8ea9e9f", "parentId": "fld_51ff43abd0e041e7a35891223e170a16", - "modified": 1652315175668, + "modified": 1654996175649, "created": 1651252547687, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "list", @@ -313,7 +313,7 @@ { "_id": "req_069acff72d9d4c0bb863897b2ffa4549", "parentId": "fld_0c48cd3014cd494980167bb149f9e7b0", - "modified": 1652315191065, + "modified": 1654996182095, "created": 1651252596523, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "listRaw", @@ -357,7 +357,7 @@ { "_id": "req_17ebfd8cc4dd4ccbb97242469a1026c9", "parentId": "fld_0c48cd3014cd494980167bb149f9e7b0", - "modified": 1652714534204, + "modified": 1654996189159, "created": 1651252596530, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "list", @@ -389,7 +389,7 @@ { "_id": "req_c589c7d2612e4a70a317f7ed214ac94d", "parentId": "fld_0c48cd3014cd494980167bb149f9e7b0", - "modified": 1652147734766, + "modified": 1654996195326, "created": 1651252596535, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "getRaw", @@ -421,7 +421,7 @@ { "_id": "req_1af87ba42cb940adab464a0241927001", "parentId": "fld_0c48cd3014cd494980167bb149f9e7b0", - "modified": 1652147735288, + "modified": 1654998468121, "created": 1651252596538, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "get", @@ -453,7 +453,7 @@ { "_id": "req_b3c64285e4b84d6da77a0046f8224ab3", "parentId": "fld_f3ad5c457d5b4433a90c91d2d9963d18", - "modified": 1654250978980, + "modified": 1655045242120, "created": 1651251269522, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "fetchDetails", @@ -509,7 +509,7 @@ { "_id": "req_e147340c7ecd4eb785265d1b060ff2a6", "parentId": "fld_404131e490e9469898d0a610690c9775", - "modified": 1654250981015, + "modified": 1654996210016, "created": 1652142980199, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "getTradableBalance", @@ -553,7 +553,7 @@ { "_id": "req_f17e55c1a50f477aa3747ee7a7b497ca", "parentId": "fld_404131e490e9469898d0a610690c9775", - "modified": 1654250981531, + "modified": 1655121714100, "created": 1651516666747, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "listRaw", @@ -585,7 +585,7 @@ { "_id": "req_bdd09e4f5a9b4e3a87ecb690dfa739c9", "parentId": "fld_404131e490e9469898d0a610690c9775", - "modified": 1654250981889, + "modified": 1655078996914, "created": 1651505463599, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "list", @@ -617,7 +617,7 @@ { "_id": "req_db9088aa9fcf45b18e85ddd994aa99c1", "parentId": "fld_1eb1fdf26dfd46298cacc08fd05b469e", - "modified": 1654250984461, + "modified": 1655055210523, "created": 1651505373653, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "listRaw", @@ -661,7 +661,7 @@ { "_id": "req_219792123c134e5dbf76ebdafc0edf2e", "parentId": "fld_1eb1fdf26dfd46298cacc08fd05b469e", - "modified": 1654250983782, + "modified": 1655055233196, "created": 1651519029538, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "list", @@ -693,7 +693,7 @@ { "_id": "req_61340a5851b040e09612c173691f44ba", "parentId": "fld_1eb1fdf26dfd46298cacc08fd05b469e", - "modified": 1654250984924, + "modified": 1655076677533, "created": 1651516690271, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "getRaw", @@ -701,7 +701,7 @@ "method": "GET", "body": { "mimeType": "application/json", - "text": "{\n\t\"method\": \"order.getRaw\",\n\t\"exchangeId\" : \"{{ _.EXCHANGE_ID }}\",\n\t\"key\" : \"{{ _.KEY }}\",\n\t\"secret\" : \"{{ _.SECRET }}\",\n\t\"passphrase\": \"{{ _.PASSPHRASE }}\",\n\t\"id\": \"ed85fca7-c383-4865-9021-64723149aae3\",\n\t\"symbolPair\": \"{{ _.ORDER_SYMBOL_PAIR }}\"\n}" + "text": "{\n\t\"method\": \"order.getRaw\",\n\t\"exchangeId\" : \"{{ _.EXCHANGE_ID }}\",\n\t\"key\" : \"{{ _.KEY }}\",\n\t\"secret\" : \"{{ _.SECRET }}\",\n\t\"passphrase\": \"{{ _.PASSPHRASE }}\",\n\t\"id\": \"456243064531738638\",\n\t\"type\": \"limit\",\n\t\"symbolPair\": \"{{ _.ORDER_SYMBOL_PAIR }}\"\n}" }, "parameters": [], "headers": [ @@ -725,7 +725,7 @@ { "_id": "req_4944714ffe964c12a008929b3214f0c1", "parentId": "fld_1eb1fdf26dfd46298cacc08fd05b469e", - "modified": 1654250985513, + "modified": 1655121842972, "created": 1651519053540, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "get", @@ -733,7 +733,7 @@ "method": "GET", "body": { "mimeType": "application/json", - "text": "{\n\t\"method\": \"order.get\",\n\t\"exchangeId\" : \"{{ _.EXCHANGE_ID }}\",\n\t\"key\" : \"{{ _.KEY }}\",\n\t\"secret\" : \"{{ _.SECRET }}\",\n\t\"passphrase\": \"{{ _.PASSPHRASE }}\",\n\t\"id\": \"ed85fca7-c383-4865-9021-64723149aae3\",\n\t\"symbolPair\": \"{{ _.ORDER_SYMBOL_PAIR }}\"\n}" + "text": "{\n\t\"method\": \"order.get\",\n\t\"exchangeId\" : \"{{ _.EXCHANGE_ID }}\",\n\t\"key\" : \"{{ _.KEY }}\",\n\t\"secret\" : \"{{ _.SECRET }}\",\n\t\"passphrase\": \"{{ _.PASSPHRASE }}\",\n\t\"id\": \"456263884180185101\",\n\t\"type\": \"limit\",\n\t\"symbolPair\": \"{{ _.ORDER_SYMBOL_PAIR }}\"\n}" }, "parameters": [], "headers": [ @@ -757,7 +757,7 @@ { "_id": "req_0fe5c5ec55604eb48a47233c69fe1ee4", "parentId": "fld_1eb1fdf26dfd46298cacc08fd05b469e", - "modified": 1654250994123, + "modified": 1655121899383, "created": 1651595326974, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "place:limit", @@ -789,7 +789,7 @@ { "_id": "req_8cc2810f054d4681914645b0e9324e2b", "parentId": "fld_1eb1fdf26dfd46298cacc08fd05b469e", - "modified": 1654250995672, + "modified": 1655121909188, "created": 1651597897328, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "place:market", @@ -797,7 +797,7 @@ "method": "GET", "body": { "mimeType": "application/json", - "text": "{\n\t\"method\": \"order.place\",\n\t\"exchangeId\": \"{{ _.EXCHANGE_ID }}\",\n\t\"key\": \"{{ _.KEY }}\",\n\t\"secret\": \"{{ _.SECRET }}\",\n\t\"passphrase\": \"{{ _.PASSPHRASE }}\",\n\t\"account\": \"{{ _.ACCOUNTS.MARGIN }}\",\n\t\"type\": \"{{ _.ORDER_TYPES.MARKET }}\",\n\t\"side\": \"{{ _.ORDER_SIDES.BUY }}\",\n\t\"symbolPair\": \"{{ _.ORDER_SYMBOL_PAIR }}\",\n\t\"amount\": \"{{ _.ORDER_AMOUNT }}\"\n}" + "text": "{\n\t\"method\": \"order.place\",\n\t\"exchangeId\": \"{{ _.EXCHANGE_ID }}\",\n\t\"key\": \"{{ _.KEY }}\",\n\t\"secret\": \"{{ _.SECRET }}\",\n\t\"passphrase\": \"{{ _.PASSPHRASE }}\",\n\t\"account\": \"{{ _.ACCOUNTS.SPOT }}\",\n\t\"type\": \"{{ _.ORDER_TYPES.MARKET }}\",\n\t\"side\": \"{{ _.ORDER_SIDES.BUY }}\",\n\t\"symbolPair\": \"{{ _.ORDER_SYMBOL_PAIR }}\",\n\t\"amount\": \"{{ _.ORDER_AMOUNT }}\"\n}" }, "parameters": [], "headers": [ @@ -821,7 +821,7 @@ { "_id": "req_14b8a678fd9347e4b378d68441c5fee9", "parentId": "fld_1eb1fdf26dfd46298cacc08fd05b469e", - "modified": 1652974685964, + "modified": 1655076687876, "created": 1651595618759, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "edit", @@ -853,7 +853,7 @@ { "_id": "req_ed3f0bccc94147bd83ed5695c747b517", "parentId": "fld_1eb1fdf26dfd46298cacc08fd05b469e", - "modified": 1654250996195, + "modified": 1655076688376, "created": 1651595579937, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "cancel", @@ -861,7 +861,7 @@ "method": "GET", "body": { "mimeType": "application/json", - "text": "{\n\t\"method\": \"order.cancel\",\n\t\"exchangeId\": \"{{ _.EXCHANGE_ID }}\",\n\t\"key\": \"{{ _.KEY }}\",\n\t\"secret\": \"{{ _.SECRET }}\",\n\t\"passphrase\": \"{{ _.PASSPHRASE }}\",\n\t\"id\": \"507161317238\",\n\t\"symbolPair\": \"{{ _.ORDER_SYMBOL_PAIR }}\"\n}\n" + "text": "{\n\t\"method\": \"order.cancel\",\n\t\"exchangeId\": \"{{ _.EXCHANGE_ID }}\",\n\t\"key\": \"{{ _.KEY }}\",\n\t\"secret\": \"{{ _.SECRET }}\",\n\t\"passphrase\": \"{{ _.PASSPHRASE }}\",\n\t\"id\": \"456263884180185101\",\n\t\"type\": \"stop-market\",\n\t\"symbolPair\": \"{{ _.ORDER_SYMBOL_PAIR }}\"\n}\n" }, "parameters": [], "headers": [ @@ -885,7 +885,7 @@ { "_id": "req_e4a51247e67544b792bd07ca2844e9d6", "parentId": "fld_62d54578963348579dbdb82bd6a5292c", - "modified": 1654251004397, + "modified": 1652632786058, "created": 1651928091972, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "listRaw", @@ -961,7 +961,7 @@ { "_id": "req_6e801707cf614c5faba6781ff1cc71cf", "parentId": "fld_62d54578963348579dbdb82bd6a5292c", - "modified": 1654251004025, + "modified": 1652632788093, "created": 1651928091980, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "getRaw", @@ -993,7 +993,7 @@ { "_id": "req_4c7d5579e1b8495ab536e467bce146cd", "parentId": "fld_62d54578963348579dbdb82bd6a5292c", - "modified": 1654251003675, + "modified": 1652632788505, "created": 1651928091991, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "get", @@ -1025,7 +1025,7 @@ { "_id": "req_bd0959425e7644dea3b1b2b3d65ea27d", "parentId": "fld_62d54578963348579dbdb82bd6a5292c", - "modified": 1654251003321, + "modified": 1652632793336, "created": 1651928091994, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "close", @@ -1057,7 +1057,7 @@ { "_id": "req_1aceb5256fa34c14b2bb8c2191d0340c", "parentId": "fld_62d54578963348579dbdb82bd6a5292c", - "modified": 1654251002358, + "modified": 1652787653706, "created": 1652787328202, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "getLeverage", @@ -1089,7 +1089,7 @@ { "_id": "req_7b87a38e7bc641629387e9ede6d3ef9e", "parentId": "fld_62d54578963348579dbdb82bd6a5292c", - "modified": 1654251002872, + "modified": 1652787663654, "created": 1652787288178, "url": "{{ _.PLAYGROUND_URL }}/exchanges", "name": "setLeverage", @@ -1121,7 +1121,7 @@ { "_id": "env_0294ce45850974a6280b61215be346094da99682", "parentId": "wrk_c410cf8f299748d49a8201c84597a282", - "modified": 1652632745420, + "modified": 1655121734512, "created": 1651251253382, "name": "Base Environment", "data": { @@ -1411,17 +1411,16 @@ "_type": "environment" }, { - "_id": "env_5120e1e15b634398bb1ca97c3b48b386", + "_id": "env_e524d6c1fb6247348832a850cae3e36d", "parentId": "env_0294ce45850974a6280b61215be346094da99682", - "modified": 1653060020939, - "created": 1653009615092, - "name": "Okx", + "modified": 1655121788205, + "created": 1652833243571, + "name": "Ftx", "data": { - "EXCHANGE_ID": "okx", - "KEY": "{% dotenv _.ALUNAJS_RC, 'OKX_API_KEY' %}", - "SECRET": "{% dotenv _.ALUNAJS_RC, 'OKX_API_SECRET' %}", - "PASSPHRASE": "{% dotenv _.ALUNAJS_RC, 'OKX_API_PASSPHRASE' %}", - "ORDER_SYMBOL_PAIR": "BTC-USDT", + "EXCHANGE_ID": "ftx", + "KEY": "{% dotenv _.ALUNAJS_RC, 'FTX_API_KEY' %}", + "SECRET": "{% dotenv _.ALUNAJS_RC, 'FTX_API_SECRET' %}", + "ORDER_SYMBOL_PAIR": "BTC/USD", "ORDER_RATE": 1000, "ORDER_AMOUNT": 0.001, "ORDER_EDITED_AMOUNT": 0.0011 @@ -1431,47 +1430,49 @@ "EXCHANGE_ID", "KEY", "SECRET", - "PASSPHRASE", "ORDER_SYMBOL_PAIR", "ORDER_RATE", "ORDER_AMOUNT", "ORDER_EDITED_AMOUNT" ] }, - "color": "#0929aa", + "color": "#00ffd5", "isPrivate": false, - "metaSortKey": 1653009615092 + "metaSortKey": 1652833243571, + "_type": "environment" }, { - "_id": "env_e524d6c1fb6247348832a850cae3e36d", + "_id": "env_7d5e1482cf2e447aa158c9ad75dbf4a3", "parentId": "env_0294ce45850974a6280b61215be346094da99682", - "modified": 1654250969030, - "created": 1652833243571, - "name": "Ftx", + "modified": 1655121826507, + "created": 1655121740252, + "name": "Okx", "data": { - "EXCHANGE_ID": "ftx", - "KEY": "{% dotenv _.ALUNAJS_RC, 'FTX_API_KEY' %}", - "SECRET": "{% dotenv _.ALUNAJS_RC, 'FTX_API_SECRET' %}", - "ORDER_SYMBOL_PAIR": "BTC-PERP", - "ORDER_RATE": 10000, - "ORDER_AMOUNT": 0.0001, - "ORDER_EDITED_AMOUNT": 0.0002 + "EXCHANGE_ID": "okx", + "KEY": "{% dotenv _.ALUNAJS_RC, 'OKX_API_KEY' %}", + "SECRET": "{% dotenv _.ALUNAJS_RC, 'OKX_API_SECRET' %}", + "PASSPHRASE": "{% dotenv _.ALUNAJS_RC, 'OKX_API_PASSPHRASE' %}", + "ORDER_SYMBOL_PAIR": "BTC-USDT", + "ORDER_RATE": 1000, + "ORDER_AMOUNT": 0.001, + "ORDER_EDITED_AMOUNT": 0.0011 }, "dataPropertyOrder": { "&": [ "EXCHANGE_ID", "KEY", "SECRET", + "PASSPHRASE", "ORDER_SYMBOL_PAIR", "ORDER_RATE", "ORDER_AMOUNT", "ORDER_EDITED_AMOUNT" ] }, - "color": "#00ffd5", + "color": "#276db0", "isPrivate": false, - "metaSortKey": 1652833243571, + "metaSortKey": 1655121740252, "_type": "environment" } ] -} +} \ No newline at end of file From 66094cae3f3381a6b639a75303370e8045424934 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Tue, 14 Jun 2022 21:44:31 -0300 Subject: [PATCH 102/105] update exchanges table --- docs/exchanges-table.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/exchanges-table.md b/docs/exchanges-table.md index 3c7eb536..97e0b61a 100644 --- a/docs/exchanges-table.md +++ b/docs/exchanges-table.md @@ -21,5 +21,5 @@ Full list of currently supported exchanges. |Gate|✅ v4|❌|❌| |Poloniex|✅ v1|—|—| |Valr|✅ v1|—|—| -|Okx|✅ v5|❌|❌| +|Okx|✅ v5|v5|❌| |Ftx|✅ v1|❌|✅ v1| From 444d079ebcc7c2b08841031c516d9857f7ea0e12 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Tue, 14 Jun 2022 21:44:41 -0300 Subject: [PATCH 103/105] fix order parse for market orders --- src/exchanges/okx/modules/authed/order/parse.ts | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/exchanges/okx/modules/authed/order/parse.ts b/src/exchanges/okx/modules/authed/order/parse.ts index 24d94871..e822cc48 100644 --- a/src/exchanges/okx/modules/authed/order/parse.ts +++ b/src/exchanges/okx/modules/authed/order/parse.ts @@ -86,8 +86,6 @@ export const parse = (exchange: IAlunaExchangeAuthed) => ( Object.assign(priceFields, { total: amount, }) - - id = algoId break default: From d5d6c233277a86ee5f80d25f65af0d44e6dccbd6 Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Tue, 14 Jun 2022 21:45:01 -0300 Subject: [PATCH 104/105] fix okx stop rate on stop limit orders --- src/exchanges/okx/modules/authed/order/place.spec.ts | 6 +++--- src/exchanges/okx/modules/authed/order/place.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/exchanges/okx/modules/authed/order/place.spec.ts b/src/exchanges/okx/modules/authed/order/place.spec.ts index a7ada0f3..514bc250 100644 --- a/src/exchanges/okx/modules/authed/order/place.spec.ts +++ b/src/exchanges/okx/modules/authed/order/place.spec.ts @@ -193,8 +193,8 @@ describe(__filename, () => { ordType: translatedOrderType, sz: '0.01', tdMode: 'cash', - slOrdPx: '2', - slTriggerPx: '1', + slOrdPx: '1', + slTriggerPx: '2', } // mocking @@ -267,7 +267,7 @@ describe(__filename, () => { sz: '0.01', tdMode: 'cash', slOrdPx: '-1', - slTriggerPx: '1', + slTriggerPx: '2', } // mocking diff --git a/src/exchanges/okx/modules/authed/order/place.ts b/src/exchanges/okx/modules/authed/order/place.ts index 9b9b18ab..e0b8dfef 100644 --- a/src/exchanges/okx/modules/authed/order/place.ts +++ b/src/exchanges/okx/modules/authed/order/place.ts @@ -96,13 +96,13 @@ export const place = (exchange: IAlunaExchangeAuthed) => async ( } else { Object.assign(body, { - slOrdPx: limitRate!.toString(), + slOrdPx: stopRate!.toString(), }) } Object.assign(body, { - slTriggerPx: stopRate!.toString(), + slTriggerPx: limitRate!.toString(), }) } From 090d29fd843e0c94568646fe6895071d0f7ce15a Mon Sep 17 00:00:00 2001 From: LucianoPierdona Date: Tue, 14 Jun 2022 21:45:12 -0300 Subject: [PATCH 105/105] update okx e2e configs --- test/e2e/configs.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/e2e/configs.ts b/test/e2e/configs.ts index 21a768f2..c82cdd31 100644 --- a/test/e2e/configs.ts +++ b/test/e2e/configs.ts @@ -180,8 +180,8 @@ export function getConfig() { delayBetweenTests: 500, orderRate: 1000, defaultLeverage: 1, - orderLimitRate: 1100, - orderStopRate: 30000, + orderLimitRate: 30000, + orderStopRate: 1100, leverageToSet: 1, orderAmount: 0.001, orderEditAmount: 0.0011,