Feature/refactor exchange rate service (#289)

* Refactor exchange rate service

* Update changelog
pull/290/head
Thomas 3 years ago committed by GitHub
parent 3330ae70b6
commit b898c0678d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -5,6 +5,12 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## Unreleased
### Changed
- Refactored the exchange rate service
## 1.37.0 - 13.08.2021
### Added

@ -1,6 +1,7 @@
import { DataSource } from '@prisma/client';
import { Currency, DataSource } from '@prisma/client';
export interface LookupItem {
currency: Currency;
dataSource: DataSource;
name: string;
symbol: string;

@ -39,6 +39,7 @@ export class SymbolService {
const ghostfolioSymbolProfiles =
await this.prismaService.symbolProfile.findMany({
select: {
currency: true,
dataSource: true,
name: true,
symbol: true

@ -1,3 +1,4 @@
import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface';
import {
DATE_FORMAT,
isGhostfolioScraperApiSymbol,
@ -166,10 +167,19 @@ export class DataProviderService {
return result;
}
public async search(aSymbol: string) {
return this.getDataProvider(
public async search(aSymbol: string): Promise<{ items: LookupItem[] }> {
const { items } = await this.getDataProvider(
<DataSource>this.configurationService.get('DATA_SOURCES')[0]
).search(aSymbol);
const filteredItems = items.filter((item) => {
// Only allow symbols with supported currency
return item.currency ? true : false;
});
return {
items: filteredItems
};
}
private getDataProvider(providerName: DataSource) {

@ -1,3 +1,4 @@
import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface';
import {
DATE_FORMAT,
getYesterday,
@ -143,7 +144,7 @@ export class GhostfolioScraperApiService implements DataProviderInterface {
return [];
}
public async search(aSymbol: string) {
public async search(aSymbol: string): Promise<{ items: LookupItem[] }> {
return { items: [] };
}

@ -1,3 +1,4 @@
import { LookupItem } from '@ghostfolio/api/app/symbol/interfaces/lookup-item.interface';
import {
DATE_FORMAT,
getToday,
@ -129,7 +130,7 @@ export class RakutenRapidApiService implements DataProviderInterface {
return {};
}
public async search(aSymbol: string) {
public async search(aSymbol: string): Promise<{ items: LookupItem[] }> {
return { items: [] };
}

@ -137,7 +137,7 @@ export class YahooFinanceService implements DataProviderInterface {
}
public async search(aSymbol: string): Promise<{ items: LookupItem[] }> {
let items = [];
let items: LookupItem[] = [];
try {
const get = bent(
@ -180,15 +180,11 @@ export class YahooFinanceService implements DataProviderInterface {
return symbol.includes(Currency.USD);
}
if (!marketData[symbol]?.currency) {
// Only allow symbols with supported currency
return false;
}
return true;
})
.map(({ longname, shortname, symbol }) => {
return {
currency: marketData[symbol]?.currency,
dataSource: DataSource.YAHOO,
name: longname || shortname,
symbol: convertFromYahooSymbol(symbol)

@ -1,3 +1,4 @@
import { currencyPairs } from '@ghostfolio/common/config';
import { DATE_FORMAT, getYesterday } from '@ghostfolio/common/helper';
import { Injectable } from '@nestjs/common';
import { Currency } from '@prisma/client';
@ -8,29 +9,27 @@ import { DataProviderService } from './data-provider.service';
@Injectable()
export class ExchangeRateDataService {
private currencies = {};
private pairs: string[] = [];
private currencyPairs: string[] = [];
private exchangeRates: { [currencyPair: string]: number } = {};
public constructor(private dataProviderService: DataProviderService) {
this.initialize();
}
public async initialize() {
this.pairs = [];
this.currencyPairs = [];
this.exchangeRates = {};
this.addPairs(Currency.CHF, Currency.EUR);
this.addPairs(Currency.CHF, Currency.GBP);
this.addPairs(Currency.CHF, Currency.USD);
this.addPairs(Currency.EUR, Currency.GBP);
this.addPairs(Currency.EUR, Currency.USD);
this.addPairs(Currency.GBP, Currency.USD);
for (const { currency1, currency2 } of currencyPairs) {
this.addCurrencyPairs(currency1, currency2);
}
await this.loadCurrencies();
}
public async loadCurrencies() {
const result = await this.dataProviderService.getHistorical(
this.pairs,
this.currencyPairs,
'day',
getYesterday(),
getYesterday()
@ -50,20 +49,21 @@ export class ExchangeRateDataService {
};
});
this.pairs.forEach((pair) => {
this.currencyPairs.forEach((pair) => {
const [currency1, currency2] = pair.match(/.{1,3}/g);
const date = format(getYesterday(), DATE_FORMAT);
this.currencies[pair] = resultExtended[pair]?.[date]?.marketPrice;
this.exchangeRates[pair] = resultExtended[pair]?.[date]?.marketPrice;
if (!this.currencies[pair]) {
if (!this.exchangeRates[pair]) {
// Not found, calculate indirectly via USD
this.currencies[pair] =
this.exchangeRates[pair] =
resultExtended[`${currency1}${Currency.USD}`]?.[date]?.marketPrice *
resultExtended[`${Currency.USD}${currency2}`]?.[date]?.marketPrice;
// Calculate the opposite direction
this.currencies[`${currency2}${currency1}`] = 1 / this.currencies[pair];
this.exchangeRates[`${currency2}${currency1}`] =
1 / this.exchangeRates[pair];
}
});
}
@ -73,7 +73,7 @@ export class ExchangeRateDataService {
aFromCurrency: Currency,
aToCurrency: Currency
) {
if (isNaN(this.currencies[`${Currency.USD}${Currency.CHF}`])) {
if (isNaN(this.exchangeRates[`${Currency.USD}${Currency.CHF}`])) {
// Reinitialize if data is not loaded correctly
this.initialize();
}
@ -81,7 +81,17 @@ export class ExchangeRateDataService {
let factor = 1;
if (aFromCurrency !== aToCurrency) {
factor = this.currencies[`${aFromCurrency}${aToCurrency}`];
if (this.exchangeRates[`${aFromCurrency}${aToCurrency}`]) {
factor = this.exchangeRates[`${aFromCurrency}${aToCurrency}`];
} else {
// Calculate indirectly via USD
const factor1 = this.exchangeRates[`${aFromCurrency}${Currency.USD}`];
const factor2 = this.exchangeRates[`${Currency.USD}${aToCurrency}`];
factor = factor1 * factor2;
this.exchangeRates[`${aFromCurrency}${aToCurrency}`] = factor;
}
}
if (isNumber(factor)) {
@ -95,8 +105,8 @@ export class ExchangeRateDataService {
return aValue;
}
private addPairs(aCurrency1: Currency, aCurrency2: Currency) {
this.pairs.push(`${aCurrency1}${aCurrency2}`);
this.pairs.push(`${aCurrency2}${aCurrency1}`);
private addCurrencyPairs(aCurrency1: Currency, aCurrency2: Currency) {
this.currencyPairs.push(`${aCurrency1}${aCurrency2}`);
this.currencyPairs.push(`${aCurrency2}${aCurrency1}`);
}
}

@ -8,11 +8,23 @@ export const benchmarks: Partial<IDataGatheringItem>[] = [
{ dataSource: DataSource.YAHOO, symbol: 'VOO' }
];
export const currencyPairs: Partial<IDataGatheringItem>[] = [
{ dataSource: DataSource.YAHOO, symbol: `${Currency.USD}${Currency.EUR}` },
{ dataSource: DataSource.YAHOO, symbol: `${Currency.USD}${Currency.GBP}` },
{ dataSource: DataSource.YAHOO, symbol: `${Currency.USD}${Currency.CHF}` }
];
export const currencyPairs: Partial<
IDataGatheringItem & {
currency1: Currency;
currency2: Currency;
}
>[] = Object.keys(Currency)
.filter((currency) => {
return currency !== Currency.USD;
})
.map((currency) => {
return {
currency1: Currency.USD,
currency2: Currency[currency],
dataSource: DataSource.YAHOO,
symbol: `${Currency.USD}${Currency[currency]}`
};
});
export const ghostfolioScraperApiSymbolPrefix = '_GF_';
export const ghostfolioCashSymbol = `${ghostfolioScraperApiSymbolPrefix}CASH`;

Loading…
Cancel
Save