Browse Source
Task/improve exchange rate and market data gathering robustness (#7119)
* Improve exchange rate robustness
* Improve market data gathering robustness
* Update changelog
pull/7120/head
Thomas Kaul
2 weeks ago
committed by
GitHub
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with
58 additions and
50 deletions
-
CHANGELOG.md
-
apps/api/src/app/activities/activities.service.ts
-
apps/api/src/app/import/import.service.ts
-
apps/api/src/app/portfolio/portfolio.service.ts
-
apps/api/src/services/market-data/market-data.service.ts
-
libs/common/src/lib/config.ts
|
|
|
@ -18,6 +18,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 |
|
|
|
- Fixed an issue where symbols with special characters caused API request failures by URL encoding the symbol |
|
|
|
- Fixed the disabled state of the delete action in the asset profiles actions menu of the historical market data table in the admin control panel |
|
|
|
- Fixed the persistence of an empty `locale` string in the scraper configuration |
|
|
|
- Fixed a transaction timeout that prevented gathering historical market data for symbols with a long history |
|
|
|
- Fixed an exception in various portfolio endpoints when historical exchange rate data is missing |
|
|
|
|
|
|
|
## 3.14.0 - 2026-06-22 |
|
|
|
|
|
|
|
|
|
|
|
@ -746,10 +746,10 @@ export class ActivitiesService { |
|
|
|
const value = new Big(order.quantity).mul(order.unitPrice).toNumber(); |
|
|
|
|
|
|
|
const [ |
|
|
|
feeInAssetProfileCurrency, |
|
|
|
feeInBaseCurrency, |
|
|
|
unitPriceInAssetProfileCurrency, |
|
|
|
valueInBaseCurrency |
|
|
|
feeInAssetProfileCurrency = 0, |
|
|
|
feeInBaseCurrency = 0, |
|
|
|
unitPriceInAssetProfileCurrency = 0, |
|
|
|
valueInBaseCurrency = 0 |
|
|
|
] = await Promise.all([ |
|
|
|
this.exchangeRateDataService.toCurrencyAtDate( |
|
|
|
order.fee, |
|
|
|
|
|
|
|
@ -592,18 +592,19 @@ export class ImportService { |
|
|
|
|
|
|
|
const value = new Big(quantity).mul(unitPrice).toNumber(); |
|
|
|
|
|
|
|
const valueInBaseCurrency = this.exchangeRateDataService.toCurrencyAtDate( |
|
|
|
const valueInBaseCurrency = |
|
|
|
(await this.exchangeRateDataService.toCurrencyAtDate( |
|
|
|
value, |
|
|
|
currency ?? assetProfile.currency, |
|
|
|
userCurrency, |
|
|
|
date |
|
|
|
); |
|
|
|
)) ?? 0; |
|
|
|
|
|
|
|
activities.push({ |
|
|
|
...order, |
|
|
|
error, |
|
|
|
value, |
|
|
|
valueInBaseCurrency: await valueInBaseCurrency, |
|
|
|
valueInBaseCurrency, |
|
|
|
// @ts-ignore
|
|
|
|
SymbolProfile: assetProfile |
|
|
|
}); |
|
|
|
|
|
|
|
@ -205,21 +205,21 @@ export class PortfolioService { |
|
|
|
switch (type) { |
|
|
|
case ActivityType.DIVIDEND: |
|
|
|
dividendInBaseCurrency += |
|
|
|
await this.exchangeRateDataService.toCurrencyAtDate( |
|
|
|
(await this.exchangeRateDataService.toCurrencyAtDate( |
|
|
|
new Big(quantity).mul(unitPrice).toNumber(), |
|
|
|
currency ?? SymbolProfile.currency, |
|
|
|
userCurrency, |
|
|
|
date |
|
|
|
); |
|
|
|
)) ?? 0; |
|
|
|
break; |
|
|
|
case ActivityType.INTEREST: |
|
|
|
interestInBaseCurrency += |
|
|
|
await this.exchangeRateDataService.toCurrencyAtDate( |
|
|
|
(await this.exchangeRateDataService.toCurrencyAtDate( |
|
|
|
unitPrice, |
|
|
|
currency ?? SymbolProfile.currency, |
|
|
|
userCurrency, |
|
|
|
date |
|
|
|
); |
|
|
|
)) ?? 0; |
|
|
|
break; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
@ -1,6 +1,7 @@ |
|
|
|
import { DateQuery } from '@ghostfolio/api/app/portfolio/interfaces/date-query.interface'; |
|
|
|
import { DataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces'; |
|
|
|
import { PrismaService } from '@ghostfolio/api/services/prisma/prisma.service'; |
|
|
|
import { DEFAULT_PROCESSOR_GATHER_HISTORICAL_MARKET_DATA_TIMEOUT } from '@ghostfolio/common/config'; |
|
|
|
import { UpdateMarketDataDto } from '@ghostfolio/common/dtos'; |
|
|
|
import { resetHours } from '@ghostfolio/common/helper'; |
|
|
|
import { AssetProfileIdentifier } from '@ghostfolio/common/interfaces'; |
|
|
|
@ -155,7 +156,8 @@ export class MarketDataService { |
|
|
|
dataSource, |
|
|
|
symbol |
|
|
|
}: AssetProfileIdentifier & { data: Prisma.MarketDataUpdateInput[] }) { |
|
|
|
await this.prismaService.$transaction(async (prisma) => { |
|
|
|
await this.prismaService.$transaction( |
|
|
|
async (prisma) => { |
|
|
|
if (data.length > 0) { |
|
|
|
let minTime = Infinity; |
|
|
|
let maxTime = -Infinity; |
|
|
|
@ -197,7 +199,9 @@ export class MarketDataService { |
|
|
|
skipDuplicates: true |
|
|
|
}); |
|
|
|
} |
|
|
|
}); |
|
|
|
}, |
|
|
|
{ timeout: DEFAULT_PROCESSOR_GATHER_HISTORICAL_MARKET_DATA_TIMEOUT } |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
public async updateAssetProfileIdentifier( |
|
|
|
|
|
|
|
@ -90,6 +90,7 @@ export const DEFAULT_PAGE_SIZE = 50; |
|
|
|
export const DEFAULT_PORT = 3333; |
|
|
|
export const DEFAULT_PROCESSOR_GATHER_ASSET_PROFILE_CONCURRENCY = 1; |
|
|
|
export const DEFAULT_PROCESSOR_GATHER_HISTORICAL_MARKET_DATA_CONCURRENCY = 1; |
|
|
|
export const DEFAULT_PROCESSOR_GATHER_HISTORICAL_MARKET_DATA_TIMEOUT = 60000; |
|
|
|
export const DEFAULT_PROCESSOR_GATHER_STATISTICS_CONCURRENCY = 1; |
|
|
|
export const DEFAULT_PROCESSOR_PORTFOLIO_SNAPSHOT_COMPUTATION_CONCURRENCY = 1; |
|
|
|
export const DEFAULT_PROCESSOR_PORTFOLIO_SNAPSHOT_COMPUTATION_TIMEOUT = 30000; |
|
|
|
|