You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
ghostfolio/apps/api/src/services/exchange-rate-data.service.ts

113 lines
3.3 KiB

import { currencyPairs } from '@ghostfolio/common/config';
3 years ago
import { DATE_FORMAT, getYesterday } from '@ghostfolio/common/helper';
3 years ago
import { Injectable } from '@nestjs/common';
import { Currency } from '@prisma/client';
import { format } from 'date-fns';
import { isNumber } from 'lodash';
3 years ago
import { DataProviderService } from './data-provider/data-provider.service';
3 years ago
@Injectable()
export class ExchangeRateDataService {
private currencyPairs: string[] = [];
private exchangeRates: { [currencyPair: string]: number } = {};
3 years ago
public constructor(private dataProviderService: DataProviderService) {
this.initialize();
}
public async initialize() {
this.currencyPairs = [];
this.exchangeRates = {};
for (const { currency1, currency2 } of currencyPairs) {
this.addCurrencyPairs(currency1, currency2);
}
3 years ago
await this.loadCurrencies();
}
public async loadCurrencies() {
const result = await this.dataProviderService.getHistorical(
this.currencyPairs,
3 years ago
'day',
getYesterday(),
getYesterday()
);
const resultExtended = result;
Object.keys(result).forEach((pair) => {
const [currency1, currency2] = pair.match(/.{1,3}/g);
const [date] = Object.keys(result[pair]);
// Calculate the opposite direction
resultExtended[`${currency2}${currency1}`] = {
[date]: {
marketPrice: 1 / result[pair][date].marketPrice
}
};
});
this.currencyPairs.forEach((pair) => {
const [currency1, currency2] = pair.match(/.{1,3}/g);
3 years ago
const date = format(getYesterday(), DATE_FORMAT);
3 years ago
this.exchangeRates[pair] = resultExtended[pair]?.[date]?.marketPrice;
3 years ago
if (!this.exchangeRates[pair]) {
// Not found, calculate indirectly via USD
this.exchangeRates[pair] =
resultExtended[`${currency1}${Currency.USD}`]?.[date]?.marketPrice *
resultExtended[`${Currency.USD}${currency2}`]?.[date]?.marketPrice;
// Calculate the opposite direction
this.exchangeRates[`${currency2}${currency1}`] =
1 / this.exchangeRates[pair];
3 years ago
}
});
}
public toCurrency(
aValue: number,
aFromCurrency: Currency,
aToCurrency: Currency
) {
if (isNaN(this.exchangeRates[`${Currency.USD}${Currency.CHF}`])) {
// Reinitialize if data is not loaded correctly
this.initialize();
}
3 years ago
let factor = 1;
if (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;
}
3 years ago
}
if (isNumber(factor)) {
return factor * aValue;
}
// Fallback with error, if currencies are not available
console.error(
`No exchange rate has been found for ${aFromCurrency}${aToCurrency}`
);
return aValue;
3 years ago
}
private addCurrencyPairs(aCurrency1: Currency, aCurrency2: Currency) {
this.currencyPairs.push(`${aCurrency1}${aCurrency2}`);
this.currencyPairs.push(`${aCurrency2}${aCurrency1}`);
}
3 years ago
}