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>
This commit is contained in:
parent
fbf377f67f
commit
38ac3d387b
CHANGELOG.md
apps
api/src/app/admin
client/src/app/components/admin-market-data
libs/common/src/lib/interfaces
@ -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 currencyPairs = this.exchangeRateDataService.getCurrencyPairs();
|
||||
|
||||
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 [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…
x
Reference in New Issue
Block a user