From 95e0f546d9c68f5c0db3bf5857388b0d145d79b1 Mon Sep 17 00:00:00 2001 From: Thomas <4159106+dtslvr@users.noreply.github.com> Date: Wed, 22 Feb 2023 21:08:54 +0100 Subject: [PATCH] Refactoring --- apps/api/src/app/order/order.service.ts | 3 - .../coingecko/coingecko.service.ts | 210 +++++++++--------- 2 files changed, 107 insertions(+), 106 deletions(-) diff --git a/apps/api/src/app/order/order.service.ts b/apps/api/src/app/order/order.service.ts index 077f2227b..2d0cb7376 100644 --- a/apps/api/src/app/order/order.service.ts +++ b/apps/api/src/app/order/order.service.ts @@ -110,9 +110,6 @@ export class OrderService { dataSource, symbol: id }; - } else { - data.SymbolProfile.connectOrCreate.create.symbol = - data.SymbolProfile.connectOrCreate.create.symbol.toUpperCase(); } await this.dataGatheringService.addJobToQueue( diff --git a/apps/api/src/services/data-provider/coingecko/coingecko.service.ts b/apps/api/src/services/data-provider/coingecko/coingecko.service.ts index 43830023a..7bd852f5d 100644 --- a/apps/api/src/services/data-provider/coingecko/coingecko.service.ts +++ b/apps/api/src/services/data-provider/coingecko/coingecko.service.ts @@ -15,88 +15,58 @@ import { SymbolProfile } from '@prisma/client'; import bent from 'bent'; -import { format, differenceInDays, addDays, subDays } from 'date-fns'; +import { format, fromUnixTime, getUnixTime } from 'date-fns'; @Injectable() export class CoinGeckoService implements DataProviderInterface { - private readonly URL = 'https://api.coingecko.com/api/v3'; private baseCurrency: string; - private DB = {}; + private readonly URL = 'https://api.coingecko.com/api/v3'; public constructor( private readonly configurationService: ConfigurationService ) { - this.baseCurrency = this.configurationService - .get('BASE_CURRENCY') - .toUpperCase(); - this.DB = {}; + this.baseCurrency = this.configurationService.get('BASE_CURRENCY'); } public canHandle(symbol: string) { return true; } - public async getDividends({ - from, - granularity = 'day', - symbol, - to - }: { - from: Date; - granularity: Granularity; - symbol: string; - to: Date; - }) { - return {}; - } - public async getAssetProfile( aSymbol: string ): Promise> { - return { + const response: Partial = { assetClass: AssetClass.CASH, assetSubClass: AssetSubClass.CRYPTOCURRENCY, - currency: this.baseCurrency.toUpperCase(), + currency: this.baseCurrency, dataSource: this.getName(), - name: aSymbol, symbol: aSymbol }; - } - private async populateDatabase(datefrom: Date, symbol: string) { - let start_day; - let end_day; - datefrom.setHours(0, 0, 1); - start_day = Math.round(datefrom.getTime() / 1000); - end_day = Math.round(new Date().getTime() / 1000); - const targeturl = `${ - this.URL - }/coins/${symbol.toLowerCase()}/market_chart/range?vs_currency=${this.baseCurrency.toLowerCase()}&from=${start_day}&to=${end_day}`; - const req = bent(targeturl, 'GET', 'json', 200); - const response = await req(); - if (response.prices.length) { - for (const iter of response.prices) { - let day = new Date(iter[0]); - day.setHours(0, 0, 1, 1); - let dayepoch = Math.round(day.getTime() / 1000); - this.DB[dayepoch] = iter[1]; - } + try { + const get = bent(`${this.URL}/coins/${aSymbol}`, 'GET', 'json', 200); + const { name } = await get(); + + response.name = name; + } catch (error) { + Logger.error(error, 'CoinGeckoService'); } + + return response; } - private async getDayStat(datein: Date, symbol: string) { - let out = { marketPrice: 0 }; - let prevday = subDays(datein, 1); - datein.setHours(0, 0, 1, 1); - let start_day = Math.round(datein.getTime() / 1000); - let prev_day = Math.round(prevday.getTime() / 1000); - out['marketPrice'] = this.DB[start_day]; - if (prev_day in this.DB) { - out['performance'] = this.DB[start_day] / this.DB[prev_day]; - } else { - out['performance'] = 0; - } - return out; + public async getDividends({ + from, + granularity = 'day', + symbol, + to + }: { + from: Date; + granularity: Granularity; + symbol: string; + to: Date; + }) { + return {}; } public async getHistorical( @@ -107,22 +77,44 @@ export class CoinGeckoService implements DataProviderInterface { ): Promise<{ [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; }> { - let out = {}; - out[aSymbol] = {}; - const totalDays = Math.abs(differenceInDays(from, to)) + 1; - await this.populateDatabase(from, aSymbol); - for (const iter of Array(totalDays).keys()) { - let day = addDays(from, iter); - let datestr = format(day, DATE_FORMAT); - out[aSymbol][datestr] = await this.getDayStat(day, aSymbol); - } + try { + const get = bent( + `${ + this.URL + }/coins/${aSymbol}/market_chart/range?vs_currency=${this.baseCurrency.toLowerCase()}&from=${getUnixTime( + from + )}&to=${getUnixTime(to)}`, + 'GET', + 'json', + 200 + ); + const { prices } = await get(); + + const result: { + [symbol: string]: { [date: string]: IDataProviderHistoricalResponse }; + } = { + [aSymbol]: {} + }; - return out; + for (const [timestamp, marketPrice] of prices) { + result[aSymbol][format(fromUnixTime(timestamp / 1000), DATE_FORMAT)] = { + marketPrice + }; + } + + return result; + } catch (error) { + throw new Error( + `Could not get historical market data for ${aSymbol} (${this.getName()}) from ${format( + from, + DATE_FORMAT + )} to ${format(to, DATE_FORMAT)}: [${error.name}] ${error.message}` + ); + } } public getMaxNumberOfSymbolsPerRequest() { - // Safe Rate Limit: https://www.coingecko.com/en/api/pricing#general - return 20; + return 50; } public getName(): DataSource { @@ -132,56 +124,68 @@ export class CoinGeckoService implements DataProviderInterface { public async getQuotes( aSymbols: string[] ): Promise<{ [symbol: string]: IDataProviderResponse }> { - var results = {}; + const results: { [symbol: string]: IDataProviderResponse } = {}; + if (aSymbols.length <= 0) { return {}; } + try { - for (const coin of aSymbols) { - const req = bent( - `${ - this.URL - }/simple/price?ids=${coin.toLowerCase()}&vs_currencies=${this.baseCurrency.toLowerCase()}`, - 'GET', - 'json', - 200 - ); - const response = await req(); - const price = - response[coin.toLowerCase()][this.baseCurrency.toLowerCase()]; - - results[coin] = { - currency: this.baseCurrency, - dataSource: DataSource.COINGECKO, - marketPrice: price, - marketState: 'closed' - }; - } + const get = bent( + `${this.URL}/simple/price?ids=${aSymbols.join( + ',' + )}&vs_currencies=${this.baseCurrency.toLowerCase()}`, + 'GET', + 'json', + 200 + ); + const response = await get(); - return results; + for (const symbol in response) { + if (Object.prototype.hasOwnProperty.call(response, symbol)) { + results[symbol] = { + currency: this.baseCurrency, + dataSource: DataSource.COINGECKO, + marketPrice: response[symbol][this.baseCurrency.toLowerCase()], + marketState: 'open' + }; + } + } } catch (error) { - Logger.error(error, 'CoinGecko'); - return {}; + Logger.error(error, 'CoinGeckoService'); } + + return results; } public async search(aQuery: string): Promise<{ items: LookupItem[] }> { - const items: LookupItem[] = []; + let items: LookupItem[] = []; + if (aQuery.length <= 2) { return { items }; } - const req = bent(`${this.URL}/search?query=${aQuery}`, 'GET', 'json', 200); - const response = await req(); - for (const coiniter of response.coins) { - if (coiniter.id.toLowerCase().includes(aQuery)) { - items.push({ - symbol: coiniter.id.toUpperCase(), + + try { + const get = bent( + `${this.URL}/search?query=${aQuery}`, + 'GET', + 'json', + 200 + ); + const { coins } = await get(); + + items = coins.map(({ id: symbol, name }) => { + return { + name, + symbol, currency: this.baseCurrency, - dataSource: this.getName(), - name: `${coiniter.name} (From CoinGecko)` - }); - } + dataSource: this.getName() + }; + }); + } catch (error) { + Logger.error(error, 'CoinGeckoService'); } + return { items }; } }