import { UpdateMarketDataDto } from '@ghostfolio/api/app/admin/update-market-data.dto'; import { DateQuery } from '@ghostfolio/api/app/portfolio/interfaces/date-query.interface'; import { DateRange } from '@ghostfolio/api/app/portfolio/interfaces/date-range.interface'; import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; import { resetHours } from '@ghostfolio/common/helper'; import { UniqueAsset } from '@ghostfolio/common/interfaces'; import { Injectable } from '@nestjs/common'; import { DataSource, MarketData, MarketDataState, Prisma } from '@prisma/client'; @Injectable() export class MarketDataService { public constructor(private readonly prismaService: PrismaService) {} public async deleteMany({ dataSource, symbol }: UniqueAsset) { return this.prismaService.marketData.deleteMany({ where: { dataSource, symbol } }); } public async get({ dataSource, date = new Date(), symbol }: IDataGatheringItem): Promise { return await this.prismaService.marketData.findFirst({ where: { dataSource, symbol, date: resetHours(date) } }); } public async getMax({ dataSource, symbol }: UniqueAsset) { return this.prismaService.marketData.findFirst({ select: { date: true, marketPrice: true }, orderBy: [ { marketPrice: 'desc' } ], where: { dataSource, symbol } }); } public async getRange({ dateQuery, uniqueAssets }: { dateQuery: DateQuery; uniqueAssets: UniqueAsset[]; }): Promise { return this.prismaService.marketData.findMany({ orderBy: [ { date: 'asc' }, { symbol: 'asc' } ], where: { dataSource: { in: uniqueAssets.map(({ dataSource }) => { return dataSource; }) }, date: dateQuery, symbol: { in: uniqueAssets.map(({ symbol }) => { return symbol; }) } } }); } public async getDecimatedRange({ dateRange: { start, end, step }, symbols, dataSources }: { dateRange: DateRange; symbols: string[]; dataSources: DataSource[]; }): Promise { return this.prismaService.$queryRaw` WITH "lastDate" AS ( SELECT "date" FROM "MarketData" WHERE "date" BETWEEN ${start} AND ${end} ORDER BY "date" DESC LIMIT 1 ), "lastRows" AS ( SELECT * FROM "MarketData" WHERE "date" = (SELECT "date" FROM "lastDate") AND "symbol" IN (${Prisma.join(symbols)}) AND "dataSource"::text IN (${Prisma.join(dataSources)}) ) SELECT DISTINCT FIRST_VALUE("createdAt") OVER w AS "createdAt", FIRST_VALUE("dataSource") OVER w AS "dataSource", FIRST_VALUE("date") OVER w AS "date", FIRST_VALUE("id") OVER w AS "id", FIRST_VALUE("marketPrice") OVER w AS "marketPrice", FIRST_VALUE("state") OVER w AS "state", FIRST_VALUE("symbol") OVER w AS "symbol" FROM "MarketData" WHERE "date" BETWEEN ${start} AND ${end} AND "symbol" IN (${Prisma.join(symbols)}) AND "dataSource"::text IN (${Prisma.join(dataSources)}) WINDOW w AS ( PARTITION BY "symbol", FLOOR( (EXTRACT(EPOCH FROM "date") - EXTRACT(EPOCH FROM ${start})) -- Subtract {start} to make it first value / (60 * 60 * 24 * ${step}) -- Divide by {step} number of days ) ORDER BY "date" -- Round down to make every {step} values equal ) UNION SELECT * FROM "lastRows" -- Add rows with the end date ORDER BY "date" `; } public async marketDataItems(params: { select?: Prisma.MarketDataSelectScalar; skip?: number; take?: number; cursor?: Prisma.MarketDataWhereUniqueInput; where?: Prisma.MarketDataWhereInput; orderBy?: Prisma.MarketDataOrderByWithRelationInput; }): Promise { const { select, skip, take, cursor, where, orderBy } = params; return this.prismaService.marketData.findMany({ select, cursor, orderBy, skip, take, where }); } public async updateMarketData(params: { data: { state: MarketDataState; } & UpdateMarketDataDto; where: Prisma.MarketDataWhereUniqueInput; }): Promise { const { data, where } = params; return this.prismaService.marketData.upsert({ where, create: { dataSource: where.dataSource_date_symbol.dataSource, date: where.dataSource_date_symbol.date, marketPrice: data.marketPrice, state: data.state, symbol: where.dataSource_date_symbol.symbol }, update: { marketPrice: data.marketPrice, state: data.state } }); } /** * Upsert market data by imitating missing upsertMany functionality * with $transaction */ public async updateMany({ data }: { data: Prisma.MarketDataUpdateInput[]; }): Promise { const upsertPromises = data.map( ({ dataSource, date, marketPrice, symbol, state }) => { return this.prismaService.marketData.upsert({ create: { dataSource: dataSource, date: date, marketPrice: marketPrice, state: state, symbol: symbol }, update: { marketPrice: marketPrice, state: state }, where: { dataSource_date_symbol: { dataSource: dataSource, date: date, symbol: symbol } } }); } ); return this.prismaService.$transaction(upsertPromises); } }