Browse Source

Add data provider info

pull/1730/head
Thomas 3 years ago
parent
commit
e049aa70af
  1. 14
      apps/api/src/app/portfolio/current-rate.service.mock.ts
  2. 11
      apps/api/src/app/portfolio/current-rate.service.spec.ts
  3. 20
      apps/api/src/app/portfolio/current-rate.service.ts
  4. 2
      apps/api/src/app/portfolio/interfaces/portfolio-position-detail.interface.ts
  5. 24
      apps/api/src/app/portfolio/portfolio-calculator.ts
  6. 3
      apps/api/src/app/portfolio/portfolio.service.ts
  7. 3
      apps/api/src/services/interfaces/interfaces.ts
  8. 4
      apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.component.ts
  9. 10
      apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html
  10. 2
      apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.module.ts
  11. 4
      libs/common/src/lib/interfaces/data-provider-info.interface.ts
  12. 2
      libs/common/src/lib/interfaces/index.ts
  13. 9
      libs/ui/src/lib/data-provider-credits/data-provider-credits.component.html
  14. 7
      libs/ui/src/lib/data-provider-credits/data-provider-credits.component.scss
  15. 14
      libs/ui/src/lib/data-provider-credits/data-provider-credits.component.ts
  16. 12
      libs/ui/src/lib/data-provider-credits/data-provider-credits.module.ts
  17. 1
      libs/ui/src/lib/data-provider-credits/index.ts

14
apps/api/src/app/portfolio/current-rate.service.mock.ts

@ -1,4 +1,5 @@
import { parseDate, resetHours } from '@ghostfolio/common/helper';
import { DataProviderInfo } from '@ghostfolio/common/interfaces';
import { addDays, endOfDay, isBefore, isSameDay } from 'date-fns';
import { GetValueObject } from './interfaces/get-value-object.interface';
@ -48,8 +49,11 @@ export const CurrentRateServiceMock = {
getValues: ({
dataGatheringItems,
dateQuery
}: GetValuesParams): Promise<GetValueObject[]> => {
const result: GetValueObject[] = [];
}: GetValuesParams): Promise<{
dataProviderInfos: DataProviderInfo[];
values: GetValueObject[];
}> => {
const values: GetValueObject[] = [];
if (dateQuery.lt) {
for (
let date = resetHours(dateQuery.gte);
@ -57,7 +61,7 @@ export const CurrentRateServiceMock = {
date = addDays(date, 1)
) {
for (const dataGatheringItem of dataGatheringItems) {
result.push({
values.push({
date,
marketPriceInBaseCurrency: mockGetValue(
dataGatheringItem.symbol,
@ -70,7 +74,7 @@ export const CurrentRateServiceMock = {
} else {
for (const date of dateQuery.in) {
for (const dataGatheringItem of dataGatheringItems) {
result.push({
values.push({
date,
marketPriceInBaseCurrency: mockGetValue(
dataGatheringItem.symbol,
@ -81,6 +85,6 @@ export const CurrentRateServiceMock = {
}
}
}
return Promise.resolve(result);
return Promise.resolve({ values, dataProviderInfos: [] });
}
};

11
apps/api/src/app/portfolio/current-rate.service.spec.ts

@ -1,6 +1,7 @@
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
import { MarketDataService } from '@ghostfolio/api/services/market-data.service';
import { DataProviderInfo } from '@ghostfolio/common/interfaces';
import { DataSource, MarketData } from '@prisma/client';
import { CurrentRateService } from './current-rate.service';
@ -103,7 +104,12 @@ describe('CurrentRateService', () => {
},
userCurrency: 'CHF'
})
).toMatchObject<GetValueObject[]>([
).toMatchObject<{
dataProviderInfos: DataProviderInfo[];
values: GetValueObject[];
}>({
dataProviderInfos: [],
values: [
{
date: undefined,
marketPriceInBaseCurrency: 1841.823902,
@ -114,6 +120,7 @@ describe('CurrentRateService', () => {
marketPriceInBaseCurrency: 1847.839966,
symbol: 'AMZN'
}
]);
]
});
});
});

20
apps/api/src/app/portfolio/current-rate.service.ts

@ -2,6 +2,7 @@ import { DataProviderService } from '@ghostfolio/api/services/data-provider/data
import { ExchangeRateDataService } from '@ghostfolio/api/services/exchange-rate-data.service';
import { MarketDataService } from '@ghostfolio/api/services/market-data.service';
import { resetHours } from '@ghostfolio/common/helper';
import { DataProviderInfo } from '@ghostfolio/common/interfaces';
import { Injectable } from '@nestjs/common';
import { isBefore, isToday } from 'date-fns';
import { flatten } from 'lodash';
@ -22,7 +23,11 @@ export class CurrentRateService {
dataGatheringItems,
dateQuery,
userCurrency
}: GetValuesParams): Promise<GetValueObject[]> {
}: GetValuesParams): Promise<{
dataProviderInfos: DataProviderInfo[];
values: GetValueObject[];
}> {
const dataProviderInfos: DataProviderInfo[] = [];
const includeToday =
(!dateQuery.lt || isBefore(new Date(), dateQuery.lt)) &&
(!dateQuery.gte || isBefore(dateQuery.gte, new Date())) &&
@ -38,6 +43,14 @@ export class CurrentRateService {
.then((dataResultProvider) => {
const result: GetValueObject[] = [];
for (const dataGatheringItem of dataGatheringItems) {
if (
dataResultProvider?.[dataGatheringItem.symbol]?.dataProviderInfo
) {
dataProviderInfos.push(
dataResultProvider[dataGatheringItem.symbol].dataProviderInfo
);
}
result.push({
date: today,
marketPriceInBaseCurrency:
@ -81,7 +94,10 @@ export class CurrentRateService {
})
);
return flatten(await Promise.all(promises));
return {
dataProviderInfos,
values: flatten(await Promise.all(promises))
};
}
private containsToday(dates: Date[]): boolean {

2
apps/api/src/app/portfolio/interfaces/portfolio-position-detail.interface.ts

@ -1,4 +1,5 @@
import {
DataProviderInfo,
EnhancedSymbolProfile,
HistoricalDataItem
} from '@ghostfolio/common/interfaces';
@ -7,6 +8,7 @@ import { Tag } from '@prisma/client';
export interface PortfolioPositionDetail {
averagePrice: number;
dataProviderInfo: DataProviderInfo;
dividendInBaseCurrency: number;
feeInBaseCurrency: number;
firstBuyDate: string;

24
apps/api/src/app/portfolio/portfolio-calculator.ts

@ -1,7 +1,11 @@
import { TimelineInfoInterface } from '@ghostfolio/api/app/portfolio/interfaces/timeline-info.interface';
import { IDataGatheringItem } from '@ghostfolio/api/services/interfaces/interfaces';
import { DATE_FORMAT, parseDate, resetHours } from '@ghostfolio/common/helper';
import { ResponseError, TimelinePosition } from '@ghostfolio/common/interfaces';
import {
DataProviderInfo,
ResponseError,
TimelinePosition
} from '@ghostfolio/common/interfaces';
import { GroupBy } from '@ghostfolio/common/types';
import { Logger } from '@nestjs/common';
import { Type as TypeOfOrder } from '@prisma/client';
@ -45,6 +49,7 @@ export class PortfolioCalculator {
private currency: string;
private currentRateService: CurrentRateService;
private dataProviderInfos: DataProviderInfo[];
private orders: PortfolioOrder[];
private transactionPoints: TransactionPoint[];
@ -202,7 +207,8 @@ export class PortfolioCalculator {
symbols[item.symbol] = true;
}
const marketSymbols = await this.currentRateService.getValues({
const { dataProviderInfos, values: marketSymbols } =
await this.currentRateService.getValues({
currencies,
dataGatheringItems,
dateQuery: {
@ -211,6 +217,8 @@ export class PortfolioCalculator {
userCurrency: this.currency
});
this.dataProviderInfos = dataProviderInfos;
const marketSymbolMap: {
[date: string]: { [symbol: string]: Big };
} = {};
@ -368,7 +376,8 @@ export class PortfolioCalculator {
dates.push(resetHours(end));
const marketSymbols = await this.currentRateService.getValues({
const { dataProviderInfos, values: marketSymbols } =
await this.currentRateService.getValues({
currencies,
dataGatheringItems,
dateQuery: {
@ -377,6 +386,8 @@ export class PortfolioCalculator {
userCurrency: this.currency
});
this.dataProviderInfos = dataProviderInfos;
const marketSymbolMap: {
[date: string]: { [symbol: string]: Big };
} = {};
@ -463,6 +474,10 @@ export class PortfolioCalculator {
};
}
public getDataProviderInfos() {
return this.dataProviderInfos;
}
public getInvestments(): { date: string; investment: Big }[] {
if (this.transactionPoints.length === 0) {
return [];
@ -748,7 +763,7 @@ export class PortfolioCalculator {
let marketSymbols: GetValueObject[] = [];
if (dataGatheringItems.length > 0) {
try {
marketSymbols = await this.currentRateService.getValues({
const { values } = await this.currentRateService.getValues({
currencies,
dataGatheringItems,
dateQuery: {
@ -757,6 +772,7 @@ export class PortfolioCalculator {
},
userCurrency: this.currency
});
marketSymbols = values;
} catch (error) {
Logger.error(
`Failed to fetch info for date ${startDate} with exception`,

3
apps/api/src/app/portfolio/portfolio.service.ts

@ -678,6 +678,7 @@ export class PortfolioService {
return {
tags,
averagePrice: undefined,
dataProviderInfo: undefined,
dividendInBaseCurrency: undefined,
feeInBaseCurrency: undefined,
firstBuyDate: undefined,
@ -849,6 +850,7 @@ export class PortfolioService {
tags,
transactionCount,
averagePrice: averagePrice.toNumber(),
dataProviderInfo: portfolioCalculator.getDataProviderInfos()?.[0],
dividendInBaseCurrency: dividendInBaseCurrency.toNumber(),
feeInBaseCurrency: this.exchangeRateDataService.toCurrency(
fee.toNumber(),
@ -911,6 +913,7 @@ export class PortfolioService {
SymbolProfile,
tags,
averagePrice: 0,
dataProviderInfo: undefined,
dividendInBaseCurrency: 0,
feeInBaseCurrency: 0,
firstBuyDate: undefined,

3
apps/api/src/services/interfaces/interfaces.ts

@ -1,4 +1,4 @@
import { UniqueAsset } from '@ghostfolio/common/interfaces';
import { DataProviderInfo, UniqueAsset } from '@ghostfolio/common/interfaces';
import { MarketState } from '@ghostfolio/common/types';
import {
Account,
@ -28,6 +28,7 @@ export interface IDataProviderHistoricalResponse {
export interface IDataProviderResponse {
currency: string;
dataProviderInfo?: DataProviderInfo;
dataSource: DataSource;
marketPrice: number;
marketState: MarketState;

4
apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.component.ts

@ -13,6 +13,7 @@ import {
import { DataService } from '@ghostfolio/client/services/data.service';
import { DATE_FORMAT, downloadAsFile } from '@ghostfolio/common/helper';
import {
DataProviderInfo,
EnhancedSymbolProfile,
LineChartItem
} from '@ghostfolio/common/interfaces';
@ -40,6 +41,7 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
public countries: {
[code: string]: { name: string; value: number };
};
public dataProviderInfo: DataProviderInfo;
public dividendInBaseCurrency: number;
public feeInBaseCurrency: number;
public firstBuyDate: string;
@ -83,6 +85,7 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
.subscribe(
({
averagePrice,
dataProviderInfo,
dividendInBaseCurrency,
feeInBaseCurrency,
firstBuyDate,
@ -105,6 +108,7 @@ export class PositionDetailDialog implements OnDestroy, OnInit {
this.averagePrice = averagePrice;
this.benchmarkDataItems = [];
this.countries = {};
this.dataProviderInfo = dataProviderInfo;
this.dividendInBaseCurrency = dividendInBaseCurrency;
this.feeInBaseCurrency = feeInBaseCurrency;
this.firstBuyDate = firstBuyDate;

10
apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.html

@ -249,7 +249,7 @@
</div>
<div *ngIf="tags?.length > 0" class="row">
<div class="col mb-3">
<div class="col">
<div class="h5" i18n>Tags</div>
<mat-chip-list>
<mat-chip *ngFor="let tag of tags">{{ tag.name }}</mat-chip>
@ -261,7 +261,7 @@
*ngIf="data.hasPermissionToReportDataGlitch === true && orders?.length > 0"
class="row"
>
<div class="col mb-3">
<div class="col">
<hr />
<a color="warn" mat-stroked-button [href]="reportDataGlitchMail"
><ion-icon class="mr-1" name="flag-outline"></ion-icon
@ -269,6 +269,12 @@
>
</div>
</div>
<div *ngIf="dataProviderInfo" class="text-center">
<hr />
<gf-data-provider-credits [dataProviderInfos]="[dataProviderInfo]">
</gf-data-provider-credits>
</div>
</div>
</div>

2
apps/client/src/app/components/position/position-detail-dialog/position-detail-dialog.module.ts

@ -6,6 +6,7 @@ import { MatLegacyDialogModule as MatDialogModule } from '@angular/material/lega
import { GfDialogFooterModule } from '@ghostfolio/client/components/dialog-footer/dialog-footer.module';
import { GfDialogHeaderModule } from '@ghostfolio/client/components/dialog-header/dialog-header.module';
import { GfActivitiesTableModule } from '@ghostfolio/ui/activities-table/activities-table.module';
import { GfDataProviderCreditsModule } from '@ghostfolio/ui/data-provider-credits/data-provider-credits.module';
import { GfLineChartModule } from '@ghostfolio/ui/line-chart/line-chart.module';
import { GfPortfolioProportionChartModule } from '@ghostfolio/ui/portfolio-proportion-chart/portfolio-proportion-chart.module';
import { GfValueModule } from '@ghostfolio/ui/value';
@ -18,6 +19,7 @@ import { PositionDetailDialog } from './position-detail-dialog.component';
imports: [
CommonModule,
GfActivitiesTableModule,
GfDataProviderCreditsModule,
GfDialogFooterModule,
GfDialogHeaderModule,
GfLineChartModule,

4
libs/common/src/lib/interfaces/data-provider-info.interface.ts

@ -0,0 +1,4 @@
export interface DataProviderInfo {
name: string;
url: string;
}

2
libs/common/src/lib/interfaces/index.ts

@ -10,6 +10,7 @@ import {
import { BenchmarkMarketDataDetails } from './benchmark-market-data-details.interface';
import { Benchmark } from './benchmark.interface';
import { Coupon } from './coupon.interface';
import { DataProviderInfo } from './data-provider-info.interface';
import { EnhancedSymbolProfile } from './enhanced-symbol-profile.interface';
import { Export } from './export.interface';
import { FilterGroup } from './filter-group.interface';
@ -54,6 +55,7 @@ export {
BenchmarkMarketDataDetails,
BenchmarkResponse,
Coupon,
DataProviderInfo,
EnhancedSymbolProfile,
Export,
Filter,

9
libs/ui/src/lib/data-provider-credits/data-provider-credits.component.html

@ -0,0 +1,9 @@
<small class="text-muted">
<ng-container i18n>Data provided by</ng-container>&nbsp;<ng-container
*ngFor="let dataProviderInfo of dataProviderInfos; let last = last"
><a target="_blank" [href]="dataProviderInfo.url">{{
dataProviderInfo.name
}}</a
><ng-container *ngIf="!last">, </ng-container></ng-container
>.
</small>

7
libs/ui/src/lib/data-provider-credits/data-provider-credits.component.scss

@ -0,0 +1,7 @@
:host {
display: block;
a {
color: rgba(var(--palette-primary-500), 1);
}
}

14
libs/ui/src/lib/data-provider-credits/data-provider-credits.component.ts

@ -0,0 +1,14 @@
import { ChangeDetectionStrategy, Component, Input } from '@angular/core';
import { DataProviderInfo } from '@ghostfolio/common/interfaces';
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'gf-data-provider-credits',
styleUrls: ['./data-provider-credits.component.scss'],
templateUrl: './data-provider-credits.component.html'
})
export class DataProviderCreditsComponent {
@Input() dataProviderInfos: DataProviderInfo[];
public constructor() {}
}

12
libs/ui/src/lib/data-provider-credits/data-provider-credits.module.ts

@ -0,0 +1,12 @@
import { CommonModule } from '@angular/common';
import { CUSTOM_ELEMENTS_SCHEMA, NgModule } from '@angular/core';
import { DataProviderCreditsComponent } from './data-provider-credits.component';
@NgModule({
declarations: [DataProviderCreditsComponent],
exports: [DataProviderCreditsComponent],
imports: [CommonModule],
schemas: [CUSTOM_ELEMENTS_SCHEMA]
})
export class GfDataProviderCreditsModule {}

1
libs/ui/src/lib/data-provider-credits/index.ts

@ -0,0 +1 @@
export * from './data-provider-credits.module';
Loading…
Cancel
Save