diff --git a/apps/api/src/app/benchmark/benchmark.service.ts b/apps/api/src/app/benchmark/benchmark.service.ts index f67d69416..d43b528c7 100644 --- a/apps/api/src/app/benchmark/benchmark.service.ts +++ b/apps/api/src/app/benchmark/benchmark.service.ts @@ -19,7 +19,7 @@ import { BenchmarkResponse, UniqueAsset } from '@ghostfolio/common/interfaces'; -import { BenchmarkTrend } from '@ghostfolio/common/types/benchmark-trend-type.type'; +import { BenchmarkTrend } from '@ghostfolio/common/types'; import { Injectable, Logger } from '@nestjs/common'; import { DataSource, SymbolProfile } from '@prisma/client'; import Big from 'big.js'; @@ -49,29 +49,28 @@ export class BenchmarkService { return 0; } - public async getBenchMarkTrends(dataSource: DataSource, symbol: string) { - return this.marketDataService - .marketDataItems({ - orderBy: { - date: 'desc' - }, - select: { - date: true, - marketPrice: true - }, - where: { - dataSource, - symbol, - date: { gte: subDays(new Date(), 400) } - } - }) - .then((historicalData) => { - const fiftyDayAvg = calculateBenchmarkTrend(historicalData, 50); - const twoHundrredDayAvg = calculateBenchmarkTrend(historicalData, 200); - Logger.debug(`50d: ${fiftyDayAvg} and 200d: ${twoHundrredDayAvg}`); + public async getBenchmarkTrends({ dataSource, symbol }: UniqueAsset) { + const historicalData = await this.marketDataService.marketDataItems({ + orderBy: { + date: 'desc' + }, + where: { + dataSource, + symbol, + date: { gte: subDays(new Date(), 400) } + } + }); - return { trend200d: twoHundrredDayAvg, trend50d: fiftyDayAvg }; - }); + const fiftyDayAverage = calculateBenchmarkTrend({ + historicalData, + days: 50 + }); + const twoHundredDayAverage = calculateBenchmarkTrend({ + historicalData, + days: 200 + }); + + return { trend50d: fiftyDayAverage, trend200d: twoHundredDayAverage }; } public async getBenchmarks({ useCache = true } = {}): Promise< @@ -94,7 +93,7 @@ export class BenchmarkService { const benchmarkAssetProfiles = await this.getBenchmarkAssetProfiles(); const promises: Promise<{ date: Date; marketPrice: number }>[] = []; - const movingAvgPromises: Promise<{ + const movingAveragePromises: Promise<{ trend50d: BenchmarkTrend; trend200d: BenchmarkTrend; }>[] = []; @@ -107,12 +106,14 @@ export class BenchmarkService { for (const { dataSource, symbol } of benchmarkAssetProfiles) { promises.push(this.marketDataService.getMax({ dataSource, symbol })); - movingAvgPromises.push(this.getBenchMarkTrends(dataSource, symbol)); + movingAveragePromises.push( + this.getBenchmarkTrends({ dataSource, symbol }) + ); } const [allTimeHighs, benchmarkTrends] = await Promise.all([ Promise.all(promises), - Promise.all(movingAvgPromises) + Promise.all(movingAveragePromises) ]); let storeInCache = true; diff --git a/libs/common/src/lib/helper.ts b/libs/common/src/lib/helper.ts index 05a077e58..ecab8ae5c 100644 --- a/libs/common/src/lib/helper.ts +++ b/libs/common/src/lib/helper.ts @@ -14,8 +14,7 @@ import { de, es, fr, it, nl, pl, pt, tr } from 'date-fns/locale'; import { ghostfolioScraperApiSymbolPrefix, locale } from './config'; import { Benchmark, UniqueAsset } from './interfaces'; -import { ColorScheme } from './types'; -import { BenchmarkTrend } from './types/benchmark-trend-type.type'; +import { BenchmarkTrend, ColorScheme } from './types'; export const DATE_FORMAT = 'yyyy-MM-dd'; export const DATE_FORMAT_MONTHLY = 'MMMM yyyy'; @@ -23,32 +22,50 @@ export const DATE_FORMAT_YEARLY = 'yyyy'; const NUMERIC_REGEXP = /[-]{0,1}[\d]*[.,]{0,1}[\d]+/g; -export function calculateBenchmarkTrend( - historicalData: MarketData[], - days: number -): BenchmarkTrend { +export function calculateBenchmarkTrend({ + days, + historicalData +}: { + days: number; + historicalData: MarketData[]; +}): BenchmarkTrend { const hasEnoughData = historicalData.length >= 2 * days; - if (!hasEnoughData) return null; - const latestDataAvg = calculateMovingAverage( - historicalData.slice(0, days).map((hData) => new Big(hData.marketPrice)), - days - ); - const oldDataAvg = calculateMovingAverage( - historicalData - .slice(days, 2 * days) - .map((hData) => new Big(hData.marketPrice)), - days - ); - return latestDataAvg > oldDataAvg + + if (!hasEnoughData) { + return null; + } + + const latestDataAverage = calculateMovingAverage({ + days, + prices: historicalData.slice(0, days).map(({ marketPrice }) => { + return new Big(marketPrice); + }) + }); + const oldDataAverage = calculateMovingAverage({ + days, + prices: historicalData.slice(days, 2 * days).map(({ marketPrice }) => { + return new Big(marketPrice); + }) + }); + + return latestDataAverage > oldDataAverage ? 'UP' - : latestDataAvg < oldDataAvg + : latestDataAverage < oldDataAverage ? 'DOWN' : 'NEUTRAL'; } -export function calculateMovingAverage(prices: Big[], days: number) { +export function calculateMovingAverage({ + days, + prices +}: { + days: number; + prices: Big[]; +}) { return prices - .reduce((prev, curr) => prev.add(curr), new Big(0)) + .reduce((previous, current) => { + return previous.add(current); + }, new Big(0)) .div(days) .toNumber(); } diff --git a/libs/common/src/lib/interfaces/benchmark.interface.ts b/libs/common/src/lib/interfaces/benchmark.interface.ts index e92070a8d..d95ae7868 100644 --- a/libs/common/src/lib/interfaces/benchmark.interface.ts +++ b/libs/common/src/lib/interfaces/benchmark.interface.ts @@ -1,4 +1,5 @@ -import { BenchmarkTrend } from '../types/benchmark-trend-type.type'; +import { BenchmarkTrend } from '@ghostfolio/common/types/'; + import { EnhancedSymbolProfile } from './enhanced-symbol-profile.interface'; export interface Benchmark { diff --git a/libs/common/src/lib/types/benchmark-trend-type.type.ts b/libs/common/src/lib/types/benchmark-trend.type.ts similarity index 100% rename from libs/common/src/lib/types/benchmark-trend-type.type.ts rename to libs/common/src/lib/types/benchmark-trend.type.ts diff --git a/libs/common/src/lib/types/index.ts b/libs/common/src/lib/types/index.ts index 2af65d404..e99bd50b6 100644 --- a/libs/common/src/lib/types/index.ts +++ b/libs/common/src/lib/types/index.ts @@ -1,6 +1,7 @@ import type { AccessWithGranteeUser } from './access-with-grantee-user.type'; import type { AccountWithPlatform } from './account-with-platform.type'; import type { AccountWithValue } from './account-with-value.type'; +import type { BenchmarkTrend } from './benchmark-trend.type'; import type { ColorScheme } from './color-scheme.type'; import type { DateRange } from './date-range.type'; import type { Granularity } from './granularity.type'; @@ -20,6 +21,7 @@ export type { AccessWithGranteeUser, AccountWithPlatform, AccountWithValue, + BenchmarkTrend, ColorScheme, DateRange, Granularity,