Browse Source

Import dividend

pull/1560/head
Thomas 3 years ago
parent
commit
ab7e06a8df
  1. 22
      apps/api/src/app/symbol/symbol.controller.ts
  2. 54
      apps/api/src/app/symbol/symbol.service.ts
  3. 6
      apps/api/src/services/data-provider/data-provider.module.ts
  4. 66
      apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts
  5. 11
      apps/client/src/app/pages/portfolio/activities/activities-page.component.ts
  6. 1
      apps/client/src/app/pages/portfolio/activities/activities-page.html
  7. 7
      apps/client/src/app/services/data.service.ts
  8. 12
      libs/ui/src/lib/activities-table/activities-table.component.html
  9. 5
      libs/ui/src/lib/activities-table/activities-table.component.ts

22
apps/api/src/app/symbol/symbol.controller.ts

@ -1,6 +1,7 @@
import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request.interceptor'; import { TransformDataSourceInRequestInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-request.interceptor';
import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response.interceptor'; import { TransformDataSourceInResponseInterceptor } from '@ghostfolio/api/interceptors/transform-data-source-in-response.interceptor';
import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces'; import { IDataProviderHistoricalResponse } from '@ghostfolio/api/services/interfaces/interfaces';
import { ImportResponse } from '@ghostfolio/common/interfaces';
import { import {
Controller, Controller,
Get, Get,
@ -75,6 +76,27 @@ export class SymbolController {
return result; return result;
} }
@Get(':dataSource/:symbol/dividends')
@UseGuards(AuthGuard('jwt'))
public async gatherDividends(
@Param('dataSource') dataSource: DataSource,
@Param('symbol') symbol: string
): Promise<ImportResponse> {
const result = await this.symbolService.getDividends({
dataSource,
symbol
});
if (!result || isEmpty(result)) {
throw new HttpException(
getReasonPhrase(StatusCodes.NOT_FOUND),
StatusCodes.NOT_FOUND
);
}
return result;
}
@Get(':dataSource/:symbol/:dateString') @Get(':dataSource/:symbol/:dateString')
@UseGuards(AuthGuard('jwt')) @UseGuards(AuthGuard('jwt'))
public async gatherSymbolForDate( public async gatherSymbolForDate(

54
apps/api/src/app/symbol/symbol.service.ts

@ -1,13 +1,17 @@
import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service'; import { DataProviderService } from '@ghostfolio/api/services/data-provider/data-provider.service';
import { YahooFinanceService } from '@ghostfolio/api/services/data-provider/yahoo-finance/yahoo-finance.service';
import { import {
IDataGatheringItem, IDataGatheringItem,
IDataProviderHistoricalResponse IDataProviderHistoricalResponse
} from '@ghostfolio/api/services/interfaces/interfaces'; } from '@ghostfolio/api/services/interfaces/interfaces';
import { MarketDataService } from '@ghostfolio/api/services/market-data.service'; import { MarketDataService } from '@ghostfolio/api/services/market-data.service';
import { DATE_FORMAT } from '@ghostfolio/common/helper'; import { DATE_FORMAT, parseDate } from '@ghostfolio/common/helper';
import { HistoricalDataItem } from '@ghostfolio/common/interfaces'; import {
HistoricalDataItem,
ImportResponse
} from '@ghostfolio/common/interfaces';
import { Injectable, Logger } from '@nestjs/common'; import { Injectable, Logger } from '@nestjs/common';
import { format, subDays } from 'date-fns'; import { format, subDays, subYears } from 'date-fns';
import { LookupItem } from './interfaces/lookup-item.interface'; import { LookupItem } from './interfaces/lookup-item.interface';
import { SymbolItem } from './interfaces/symbol-item.interface'; import { SymbolItem } from './interfaces/symbol-item.interface';
@ -16,7 +20,8 @@ import { SymbolItem } from './interfaces/symbol-item.interface';
export class SymbolService { export class SymbolService {
public constructor( public constructor(
private readonly dataProviderService: DataProviderService, private readonly dataProviderService: DataProviderService,
private readonly marketDataService: MarketDataService private readonly marketDataService: MarketDataService,
private readonly yahooFinanceService: YahooFinanceService
) {} ) {}
public async get({ public async get({
@ -62,6 +67,47 @@ export class SymbolService {
return undefined; return undefined;
} }
public async getDividends({
dataSource,
symbol
}: IDataGatheringItem): Promise<ImportResponse> {
const date = new Date();
// TODO: Use DataProviderService
const historicalData = await this.yahooFinanceService.getDividends(
symbol,
'day',
subYears(date, 5),
date
);
return {
activities: Object.entries(historicalData[symbol]).map(
([dateString, historicalDataItem]) => {
return {
accountId: undefined,
accountUserId: undefined,
comment: undefined,
createdAt: undefined,
date: parseDate(dateString),
fee: 0,
feeInBaseCurrency: 0,
id: undefined,
isDraft: false,
quantity: 0,
symbolProfileId: undefined,
type: 'DIVIDEND',
unitPrice: historicalDataItem.marketPrice,
updatedAt: undefined,
userId: undefined,
value: 0,
valueInBaseCurrency: 0
};
}
)
};
}
public async getForDate({ public async getForDate({
dataSource, dataSource,
date = new Date(), date = new Date(),

6
apps/api/src/services/data-provider/data-provider.module.ts

@ -59,6 +59,10 @@ import { DataProviderService } from './data-provider.service';
] ]
} }
], ],
exports: [DataProviderService, GhostfolioScraperApiService] exports: [
DataProviderService,
GhostfolioScraperApiService,
YahooFinanceService
]
}) })
export class DataProviderModule {} export class DataProviderModule {}

66
apps/api/src/services/data-provider/yahoo-finance/yahoo-finance.service.ts

@ -160,6 +160,72 @@ export class YahooFinanceService implements DataProviderInterface {
return response; return response;
} }
public async getDividends(
aSymbol: string,
aGranularity: Granularity = 'day',
from: Date,
to: Date
): Promise<{
[symbol: string]: { [date: string]: IDataProviderHistoricalResponse };
}> {
// TODO: Call getHistorical()
if (isSameDay(from, to)) {
to = addDays(to, 1);
}
const yahooFinanceSymbol = this.convertToYahooFinanceSymbol(aSymbol);
try {
const historicalResult = await yahooFinance.historical(
yahooFinanceSymbol,
{
events: 'dividends',
interval: '1d',
period1: format(from, DATE_FORMAT),
period2: format(to, DATE_FORMAT)
}
);
const response: {
[symbol: string]: { [date: string]: IDataProviderHistoricalResponse };
} = {};
// Convert symbol back
const symbol = this.convertFromYahooFinanceSymbol(yahooFinanceSymbol);
response[symbol] = {};
for (const historicalItem of historicalResult) {
let marketPrice = historicalItem.dividends;
if (symbol === `${this.baseCurrency}GBp`) {
// Convert GPB to GBp (pence)
marketPrice = new Big(marketPrice).mul(100).toNumber();
} else if (symbol === `${this.baseCurrency}ILA`) {
// Convert ILS to ILA
marketPrice = new Big(marketPrice).mul(100).toNumber();
} else if (symbol === `${this.baseCurrency}ZAc`) {
// Convert ZAR to ZAc (cents)
marketPrice = new Big(marketPrice).mul(100).toNumber();
}
response[symbol][format(historicalItem.date, DATE_FORMAT)] = {
marketPrice
};
}
return response;
} catch (error) {
throw new Error(
`Could not get historical market data for ${aSymbol} (${this.getName()}) from ${format(
from,
DATE_FORMAT
)} to ${format(to, DATE_FORMAT)}: [${error.name}] ${error.message}`
);
}
}
public async getHistorical( public async getHistorical(
aSymbol: string, aSymbol: string,
aGranularity: Granularity = 'day', aGranularity: Granularity = 'day',

11
apps/client/src/app/pages/portfolio/activities/activities-page.component.ts

@ -11,7 +11,7 @@ import { IcsService } from '@ghostfolio/client/services/ics/ics.service';
import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service'; import { ImpersonationStorageService } from '@ghostfolio/client/services/impersonation-storage.service';
import { UserService } from '@ghostfolio/client/services/user/user.service'; import { UserService } from '@ghostfolio/client/services/user/user.service';
import { downloadAsFile } from '@ghostfolio/common/helper'; import { downloadAsFile } from '@ghostfolio/common/helper';
import { User } from '@ghostfolio/common/interfaces'; import { UniqueAsset, User } from '@ghostfolio/common/interfaces';
import { hasPermission, permissions } from '@ghostfolio/common/permissions'; import { hasPermission, permissions } from '@ghostfolio/common/permissions';
import { DataSource, Order as OrderModel } from '@prisma/client'; import { DataSource, Order as OrderModel } from '@prisma/client';
import { format, parseISO } from 'date-fns'; import { format, parseISO } from 'date-fns';
@ -198,6 +198,15 @@ export class ActivitiesPageComponent implements OnDestroy, OnInit {
}); });
} }
public onImportDividends({ dataSource, symbol }: UniqueAsset) {
this.dataService
.fetchDividendsImport({ dataSource, symbol })
.pipe(takeUntil(this.unsubscribeSubject))
.subscribe(({ activities }) => {
console.log(activities);
});
}
public onUpdateActivity(aActivity: OrderModel) { public onUpdateActivity(aActivity: OrderModel) {
this.router.navigate([], { this.router.navigate([], {
queryParams: { activityId: aActivity.id, editDialog: true } queryParams: { activityId: aActivity.id, editDialog: true }

1
apps/client/src/app/pages/portfolio/activities/activities-page.html

@ -17,6 +17,7 @@
(export)="onExport($event)" (export)="onExport($event)"
(exportDrafts)="onExportDrafts($event)" (exportDrafts)="onExportDrafts($event)"
(import)="onImport()" (import)="onImport()"
(importDividends)="onImportDividends($event)"
></gf-activities-table> ></gf-activities-table>
</div> </div>
</div> </div>

7
apps/client/src/app/services/data.service.ts

@ -24,6 +24,7 @@ import {
BenchmarkResponse, BenchmarkResponse,
Export, Export,
Filter, Filter,
ImportResponse,
InfoItem, InfoItem,
OAuthResponse, OAuthResponse,
PortfolioDetails, PortfolioDetails,
@ -119,6 +120,12 @@ export class DataService {
}); });
} }
public fetchDividendsImport({ dataSource, symbol }: UniqueAsset) {
return this.http.get<ImportResponse>(
`/api/v1/symbol/${dataSource}/${symbol}/dividends`
);
}
public fetchExchangeRateForDate({ public fetchExchangeRateForDate({
date, date,
symbol symbol

12
libs/ui/src/lib/activities-table/activities-table.component.html

@ -437,6 +437,18 @@
<ion-icon class="mr-2" name="calendar-clear-outline"></ion-icon> <ion-icon class="mr-2" name="calendar-clear-outline"></ion-icon>
<span i18n>Export Draft as ICS</span> <span i18n>Export Draft as ICS</span>
</button> </button>
<button
mat-menu-item
(click)="
onImportDividends({
dataSource: element.SymbolProfile.dataSource,
symbol: element.SymbolProfile.symbol
})
"
>
<ion-icon class="mr-2" name="cloud-upload-outline"></ion-icon>
<span i18n>Import Dividend</span>
</button>
<button mat-menu-item (click)="onDeleteActivity(element.id)"> <button mat-menu-item (click)="onDeleteActivity(element.id)">
<ion-icon class="mr-2" name="trash-outline"></ion-icon> <ion-icon class="mr-2" name="trash-outline"></ion-icon>
<span i18n>Delete</span> <span i18n>Delete</span>

5
libs/ui/src/lib/activities-table/activities-table.component.ts

@ -51,6 +51,7 @@ export class ActivitiesTableComponent implements OnChanges, OnDestroy {
@Output() export = new EventEmitter<string[]>(); @Output() export = new EventEmitter<string[]>();
@Output() exportDrafts = new EventEmitter<string[]>(); @Output() exportDrafts = new EventEmitter<string[]>();
@Output() import = new EventEmitter<void>(); @Output() import = new EventEmitter<void>();
@Output() importDividends = new EventEmitter<UniqueAsset>();
@Output() selectedActivities = new EventEmitter<Activity[]>(); @Output() selectedActivities = new EventEmitter<Activity[]>();
@ViewChild(MatPaginator) paginator: MatPaginator; @ViewChild(MatPaginator) paginator: MatPaginator;
@ -233,6 +234,10 @@ export class ActivitiesTableComponent implements OnChanges, OnDestroy {
this.import.emit(); this.import.emit();
} }
public onImportDividends({ dataSource, symbol }: UniqueAsset) {
this.importDividends.emit({ dataSource, symbol });
}
public onOpenComment(aComment: string) { public onOpenComment(aComment: string) {
alert(aComment); alert(aComment);
} }

Loading…
Cancel
Save