From bd89fb8f76f45a63c8f8c44ca133fe621656cb93 Mon Sep 17 00:00:00 2001 From: Andrea Bugeja <25248102+arogan178@users.noreply.github.com> Date: Mon, 19 Oct 2026 13:08:00 +0200 Subject: [PATCH 1/2] Group the asset profile identifiers by data source in the market data range query --- .../market-data/market-data.service.spec.ts | 22 ++++++++++++ .../market-data/market-data.service.ts | 36 ++++++++++++------- 2 files changed, 46 insertions(+), 12 deletions(-) diff --git a/apps/api/src/services/market-data/market-data.service.spec.ts b/apps/api/src/services/market-data/market-data.service.spec.ts index 73a7cd9297..587f717493 100644 --- a/apps/api/src/services/market-data/market-data.service.spec.ts +++ b/apps/api/src/services/market-data/market-data.service.spec.ts @@ -9,6 +9,7 @@ describe('MarketDataService', () => { marketData: { createMany: jest.Mock; deleteMany: jest.Mock; + findMany: jest.Mock; upsert: jest.Mock; }; }; @@ -19,6 +20,7 @@ describe('MarketDataService', () => { marketData: { createMany: jest.fn(), deleteMany: jest.fn(), + findMany: jest.fn().mockResolvedValue([]), upsert: jest.fn().mockResolvedValue({}) } }; @@ -26,6 +28,26 @@ describe('MarketDataService', () => { marketDataService = new MarketDataService(prismaService as any); }); + describe('getRange', () => { + it('queries the symbols grouped by data source', async () => { + await marketDataService.getRange({ + assetProfileIdentifiers: [ + { dataSource: 'YAHOO', symbol: 'AAPL' }, + { dataSource: 'COINGECKO', symbol: 'bitcoin' }, + { dataSource: 'YAHOO', symbol: 'MSFT' } + ], + dateQuery: { gte: parseDate('2026-01-01') } + }); + + expect( + prismaService.marketData.findMany.mock.calls[0][0].where.OR + ).toEqual([ + { dataSource: 'YAHOO', symbol: { in: ['AAPL', 'MSFT'] } }, + { dataSource: 'COINGECKO', symbol: { in: ['bitcoin'] } } + ]); + }); + }); + describe('updateMany', () => { it('does not drop isCarriedForward', async () => { prismaService.$transaction.mockImplementation((promises) => { diff --git a/apps/api/src/services/market-data/market-data.service.ts b/apps/api/src/services/market-data/market-data.service.ts index c1a31ce815..0f24260457 100644 --- a/apps/api/src/services/market-data/market-data.service.ts +++ b/apps/api/src/services/market-data/market-data.service.ts @@ -12,6 +12,7 @@ import { MarketDataState, Prisma } from '@prisma/client'; +import { groupBy } from 'lodash-es'; @Injectable() export class MarketDataService { @@ -95,12 +96,7 @@ export class MarketDataService { ], where: { date: dateQuery, - OR: assetProfileIdentifiers.map(({ dataSource, symbol }) => { - return { - dataSource, - symbol - }; - }) + OR: this.getAssetProfileIdentifiersCondition(assetProfileIdentifiers) } }); } @@ -115,12 +111,7 @@ export class MarketDataService { return this.prismaService.marketData.count({ where: { date: dateQuery, - OR: assetProfileIdentifiers.map(({ dataSource, symbol }) => { - return { - dataSource, - symbol - }; - }) + OR: this.getAssetProfileIdentifiersCondition(assetProfileIdentifiers) } }); } @@ -260,4 +251,25 @@ export class MarketDataService { return this.prismaService.$transaction(upsertPromises); } + + // One condition per data source with all its symbols, since a condition per + // asset profile identifier is slow to evaluate for many asset profiles + private getAssetProfileIdentifiersCondition( + aAssetProfileIdentifiers: AssetProfileIdentifier[] + ): Prisma.MarketDataWhereInput[] { + return Object.entries( + groupBy(aAssetProfileIdentifiers, ({ dataSource }) => { + return dataSource; + }) + ).map(([dataSource, assetProfileIdentifiers]) => { + return { + dataSource: dataSource as DataSource, + symbol: { + in: assetProfileIdentifiers.map(({ symbol }) => { + return symbol; + }) + } + }; + }); + } } From 7c96ae3d9a4984ef1056d4cbce2b08237558779b Mon Sep 17 00:00:00 2001 From: Andrea Bugeja <25248102+arogan178@users.noreply.github.com> Date: Wed, 21 Oct 2026 22:10:06 +0200 Subject: [PATCH 2/2] Update changelog --- CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4f13393d1a..4d705f60bc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## Unreleased + +### Changed + +- Improved the performance of the market data range query for portfolios with many holdings + ## 3.90.0 - 2026-10-21 ### Changed