/* eslint-disable @typescript-eslint/no-explicit-any */ import { Log } from "./Log" import { Service } from "typedi" import Big from "big.js" import fetch from "node-fetch" import { CexService } from "./CexService" import { AccountInfo, CexPosition, PlaceOrderPayload, CexMarket,CexPositionRisk } from "./Types" import { Mutex } from "async-mutex" import { sleep } from "./util" @Service() export class FtxService implements CexService { private readonly log = Log.getLogger(FtxService.name) private readonly txMutex = new Mutex() async getMarket(marketName: string): Promise<CexMarket> { const response = await fetch(`https://ftx.com/api/markets/${marketName}`) const result: any[] = (await response.json()).result const cexMarket = this.toCexMarket(result) return cexMarket } async getAccountInfo(ftxClient: any): Promise<AccountInfo> { const data = await ftxClient.request({ method: "GET", path: "/account", }) // this.log.jinfo({ // event: "GetAccountInfo", // params: data, // }) const positionsMap: Record<string, CexPosition> = {} for (let i = 0; i < data.result.positions.length; i++) { const positionEntity = data.result.positions[i] const position = this.toCexPosition(positionEntity) positionsMap[position.future] = position } return { freeCollateral: Big(data.result.freeCollateral), totalAccountValue: Big(data.result.totalAccountValue), // marginFraction is null if the account has no open positions marginFraction: Big(data.result.marginFraction ? data.result.marginFraction : 0), maintenanceMarginRequirement: Big(data.result.maintenanceMarginRequirement), positionsMap: positionsMap, } } async getPosition(ftxClient: any, marketId: string): Promise<CexPosition> { const data = await ftxClient.request({ method: "GET", path: "/positions", }) // this.log.jinfo({ // event: "GetPositions", // params: data, // }) const positions: Record<string, CexPosition> = {} for (let i = 0; i < data.result.length; i++) { const positionEntity = data.result[i] if (positionEntity.future === marketId) { const position = this.toCexPosition(positionEntity) positions[position.future] = position } } return positions[marketId] } async getTotalPnLs(ftxClient: any): Promise<Record<string, number>> { const data = await ftxClient.request({ method: "GET", path: "/pnl/historical_changes", }) return data.result.totalPnl } async placeOrder(ftxClient: any, payload: PlaceOrderPayload): Promise<void> { const release = await this.txMutex.acquire() try { const data = await ftxClient.request({ method: "POST", path: "/orders", data: payload, }) this.log.jinfo({ event: "PlaceOrder", params: data, }) await sleep(200) } finally { release() } } // noinspection JSMethodCanBeStatic private toCexMarket(market: any): CexMarket { return { name: market.name, last: market.last ? Big(market.last) : undefined, } } private toCexPosition(positionEntity: any): CexPosition { return { future: positionEntity.future, netSize: Big(positionEntity.netSize), entryPrice: Big(positionEntity.entryPrice ? positionEntity.entryPrice : 0), realizedPnl: Big(positionEntity.realizedPnl ? positionEntity.realizedPnl : 0), cost: Big(positionEntity.cost ? positionEntity.cost : 0), } } async transferFromSpot(amount: Big) : Promise<void> { this.log.jerror({ event: "transferFromSpotNotImplmented", amount: +amount.toFixed(), }) return } async positionRisk(asset: string): Promise<CexPositionRisk> { return { markPrice: Big(0), liquidationPrice: Big(0) } } }