Feature/extend market data endpoint by lastMarketPrice (#3752)

* Extend market data endpoint by lastMarketPrice

* Integrate last market price in admin market data

* Update changelog

---------

Co-authored-by: Thomas Kaul <4159106+dtslvr@users.noreply.github.com>
pull/3775/head
Nikolai 3 months ago committed by GitHub
parent fbf377f67f
commit 38ac3d387b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -9,6 +9,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Added the current market price column to the historical market data table of the admin control
- Introduced filters (`dataSource` and `symbol`) in the accounts endpoint
### Changed

@ -15,7 +15,11 @@ import {
PROPERTY_IS_READ_ONLY_MODE,
PROPERTY_IS_USER_SIGNUP_ENABLED
} from '@ghostfolio/common/config';
import { isCurrency, getCurrencyFromSymbol } from '@ghostfolio/common/helper';
import {
getAssetProfileIdentifier,
getCurrencyFromSymbol,
isCurrency
} from '@ghostfolio/common/helper';
import {
AdminData,
AdminMarketData,
@ -261,6 +265,37 @@ export class AdminService {
this.prismaService.symbolProfile.count({ where })
]);
const lastMarketPrices = await this.prismaService.marketData.findMany({
distinct: ['dataSource', 'symbol'],
orderBy: { date: 'desc' },
select: {
dataSource: true,
marketPrice: true,
symbol: true
},
where: {
dataSource: {
in: assetProfiles.map(({ dataSource }) => {
return dataSource;
})
},
symbol: {
in: assetProfiles.map(({ symbol }) => {
return symbol;
})
}
}
});
const lastMarketPriceMap = new Map<string, number>();
for (const { dataSource, marketPrice, symbol } of lastMarketPrices) {
lastMarketPriceMap.set(
getAssetProfileIdentifier({ dataSource, symbol }),
marketPrice
);
}
let marketData: AdminMarketDataItem[] = await Promise.all(
assetProfiles.map(
async ({
@ -281,6 +316,11 @@ export class AdminService {
const countriesCount = countries
? Object.keys(countries).length
: 0;
const lastMarketPrice = lastMarketPriceMap.get(
getAssetProfileIdentifier({ dataSource, symbol })
);
const marketDataItemCount =
marketDataItems.find((marketDataItem) => {
return (
@ -288,6 +328,7 @@ export class AdminService {
marketDataItem.symbol === symbol
);
})?._count ?? 0;
const sectorsCount = sectors ? Object.keys(sectors).length : 0;
return {
@ -298,6 +339,7 @@ export class AdminService {
countriesCount,
dataSource,
id,
lastMarketPrice,
name,
symbol,
marketDataItemCount,
@ -511,48 +553,86 @@ export class AdminService {
}
private async getMarketDataForCurrencies(): Promise<AdminMarketData> {
const marketDataItems = await this.prismaService.marketData.groupBy({
_count: true,
by: ['dataSource', 'symbol']
});
const marketDataPromise: Promise<AdminMarketDataItem>[] =
this.exchangeRateDataService
.getCurrencyPairs()
.map(async ({ dataSource, symbol }) => {
let activitiesCount: EnhancedSymbolProfile['activitiesCount'] = 0;
let currency: EnhancedSymbolProfile['currency'] = '-';
let dateOfFirstActivity: EnhancedSymbolProfile['dateOfFirstActivity'];
if (isCurrency(getCurrencyFromSymbol(symbol))) {
currency = getCurrencyFromSymbol(symbol);
({ activitiesCount, dateOfFirstActivity } =
await this.orderService.getStatisticsByCurrency(currency));
const currencyPairs = this.exchangeRateDataService.getCurrencyPairs();
const [lastMarketPrices, marketDataItems] = await Promise.all([
this.prismaService.marketData.findMany({
distinct: ['dataSource', 'symbol'],
orderBy: { date: 'desc' },
select: {
dataSource: true,
marketPrice: true,
symbol: true
},
where: {
dataSource: {
in: currencyPairs.map(({ dataSource }) => {
return dataSource;
})
},
symbol: {
in: currencyPairs.map(({ symbol }) => {
return symbol;
})
}
}
}),
this.prismaService.marketData.groupBy({
_count: true,
by: ['dataSource', 'symbol']
})
]);
const marketDataItemCount =
marketDataItems.find((marketDataItem) => {
return (
marketDataItem.dataSource === dataSource &&
marketDataItem.symbol === symbol
);
})?._count ?? 0;
const lastMarketPriceMap = new Map<string, number>();
return {
activitiesCount,
currency,
dataSource,
marketDataItemCount,
symbol,
assetClass: AssetClass.LIQUIDITY,
assetSubClass: AssetSubClass.CASH,
countriesCount: 0,
date: dateOfFirstActivity,
id: undefined,
name: symbol,
sectorsCount: 0
};
});
for (const { dataSource, marketPrice, symbol } of lastMarketPrices) {
lastMarketPriceMap.set(
getAssetProfileIdentifier({ dataSource, symbol }),
marketPrice
);
}
const marketDataPromise: Promise<AdminMarketDataItem>[] = currencyPairs.map(
async ({ dataSource, symbol }) => {
let activitiesCount: EnhancedSymbolProfile['activitiesCount'] = 0;
let currency: EnhancedSymbolProfile['currency'] = '-';
let dateOfFirstActivity: EnhancedSymbolProfile['dateOfFirstActivity'];
if (isCurrency(getCurrencyFromSymbol(symbol))) {
currency = getCurrencyFromSymbol(symbol);
({ activitiesCount, dateOfFirstActivity } =
await this.orderService.getStatisticsByCurrency(currency));
}
const lastMarketPrice = lastMarketPriceMap.get(
getAssetProfileIdentifier({ dataSource, symbol })
);
const marketDataItemCount =
marketDataItems.find((marketDataItem) => {
return (
marketDataItem.dataSource === dataSource &&
marketDataItem.symbol === symbol
);
})?._count ?? 0;
return {
activitiesCount,
currency,
dataSource,
lastMarketPrice,
marketDataItemCount,
symbol,
assetClass: AssetClass.LIQUIDITY,
assetSubClass: AssetSubClass.CASH,
countriesCount: 0,
date: dateOfFirstActivity,
id: undefined,
name: symbol,
sectorsCount: 0
};
}
);
const marketData = await Promise.all(marketDataPromise);
return { marketData, count: marketData.length };

@ -142,6 +142,7 @@ export class AdminMarketDataComponent
'dataSource',
'assetClass',
'assetSubClass',
'lastMarketPrice',
'date',
'activitiesCount',
'marketDataItemCount',

@ -99,6 +99,21 @@
</td>
</ng-container>
<ng-container matColumnDef="lastMarketPrice">
<th *matHeaderCellDef class="px-1" mat-header-cell>
<ng-container i18n>Market Price</ng-container>
</th>
<td *matCellDef="let element" class="px-1" mat-cell>
<div class="d-flex justify-content-end">
<gf-value
[isCurrency]="true"
[locale]="user?.settings?.locale"
[value]="element.lastMarketPrice ?? ''"
/>
</div>
</td>
</ng-container>
<ng-container matColumnDef="date">
<th *matHeaderCellDef class="px-1" mat-header-cell>
<ng-container i18n>First Activity</ng-container>

@ -1,6 +1,7 @@
import { GfSymbolModule } from '@ghostfolio/client/pipes/symbol/symbol.module';
import { GfActivitiesFilterComponent } from '@ghostfolio/ui/activities-filter';
import { GfPremiumIndicatorComponent } from '@ghostfolio/ui/premium-indicator';
import { GfValueComponent } from '@ghostfolio/ui/value';
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
@ -27,6 +28,7 @@ import { GfCreateAssetProfileDialogModule } from './create-asset-profile-dialog/
GfCreateAssetProfileDialogModule,
GfPremiumIndicatorComponent,
GfSymbolModule,
GfValueComponent,
MatButtonModule,
MatCheckboxModule,
MatMenuModule,

@ -16,6 +16,7 @@ export interface AdminMarketDataItem {
id: string;
isBenchmark?: boolean;
isUsedByUsersWithSubscription?: boolean;
lastMarketPrice: number;
marketDataItemCount: number;
name: string;
sectorsCount: number;

Loading…
Cancel
Save