Add more fiat currencies using fx rates from FreeCurrencyAPI

This commit is contained in:
natsoni
2024-03-07 10:48:32 +01:00
parent bfaddfc345
commit f121d16544
7 changed files with 727 additions and 38 deletions

View File

@@ -1,5 +1,6 @@
import DB from '../database';
import logger from '../logger';
import config from '../config';
import priceUpdater from '../tasks/price-updater';
export interface ApiPrice {
@@ -11,17 +12,81 @@ export interface ApiPrice {
CHF: number,
AUD: number,
JPY: number,
BGN: number,
BRL: number,
CNY: number,
CZK: number,
DKK: number,
HKD: number,
HRK: number,
HUF: number,
IDR: number,
ILS: number,
INR: number,
ISK: number,
KRW: number,
MXN: number,
MYR: number,
NOK: number,
NZD: number,
PHP: number,
PLN: number,
RON: number,
RUB: number,
SEK: number,
SGD: number,
THB: number,
TRY: number,
ZAR: number,
}
const ApiPriceFields = `
UNIX_TIMESTAMP(time) as time,
USD,
EUR,
GBP,
CAD,
CHF,
AUD,
JPY
`;
const ApiPriceFields = config.MEMPOOL.CURRENCY_API_KEY ?
`
UNIX_TIMESTAMP(time) as time,
USD,
EUR,
GBP,
CAD,
CHF,
AUD,
JPY,
BGN,
BRL,
CNY,
CZK,
DKK,
HKD,
HRK,
HUF,
IDR,
ILS,
INR,
ISK,
KRW,
MXN,
MYR,
NOK,
NZD,
PHP,
PLN,
RON,
RUB,
SEK,
SGD,
THB,
TRY,
ZAR
`:
`
UNIX_TIMESTAMP(time) as time,
USD,
EUR,
GBP,
CAD,
CHF,
AUD,
JPY
`;
export interface ExchangeRates {
USDEUR: number,
@@ -30,6 +95,32 @@ export interface ExchangeRates {
USDCHF: number,
USDAUD: number,
USDJPY: number,
USDBGN?: number,
USDBRL?: number,
USDCNY?: number,
USDCZK?: number,
USDDKK?: number,
USDHKD?: number,
USDHRK?: number,
USDHUF?: number,
USDIDR?: number,
USDILS?: number,
USDINR?: number,
USDISK?: number,
USDKRW?: number,
USDMXN?: number,
USDMYR?: number,
USDNOK?: number,
USDNZD?: number,
USDPHP?: number,
USDPLN?: number,
USDRON?: number,
USDRUB?: number,
USDSEK?: number,
USDSGD?: number,
USDTHB?: number,
USDTRY?: number,
USDZAR?: number,
}
export interface Conversion {
@@ -45,6 +136,32 @@ export const MAX_PRICES = {
CHF: 100000000,
AUD: 100000000,
JPY: 10000000000,
BGN: 1000000000,
BRL: 1000000000,
CNY: 1000000000,
CZK: 10000000000,
DKK: 1000000000,
HKD: 1000000000,
HRK: 1000000000,
HUF: 10000000000,
IDR: 100000000000,
ILS: 1000000000,
INR: 10000000000,
ISK: 10000000000,
KRW: 100000000000,
MXN: 1000000000,
MYR: 1000000000,
NOK: 1000000000,
NZD: 1000000000,
PHP: 10000000000,
PLN: 1000000000,
RON: 1000000000,
RUB: 10000000000,
SEK: 1000000000,
SGD: 100000000,
THB: 10000000000,
TRY: 10000000000,
ZAR: 10000000000,
};
class PricesRepository {
@@ -64,17 +181,49 @@ class PricesRepository {
}
try {
await DB.query(`
INSERT INTO prices(time, USD, EUR, GBP, CAD, CHF, AUD, JPY)
VALUE (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ? )`,
[time, prices.USD, prices.EUR, prices.GBP, prices.CAD, prices.CHF, prices.AUD, prices.JPY]
);
if (!config.MEMPOOL.CURRENCY_API_KEY) { // Store only the 7 main currencies
await DB.query(`
INSERT INTO prices(time, USD, EUR, GBP, CAD, CHF, AUD, JPY)
VALUE (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ? )`,
[time, prices.USD, prices.EUR, prices.GBP, prices.CAD, prices.CHF, prices.AUD, prices.JPY]
);
} else { // Store all 7 main currencies + all the currencies obtained with the external API
await DB.query(`
INSERT INTO prices(time, USD, EUR, GBP, CAD, CHF, AUD, JPY, BGN, BRL, CNY, CZK, DKK, HKD, HRK, HUF, IDR, ILS, INR, ISK, KRW, MXN, MYR, NOK, NZD, PHP, PLN, RON, RUB, SEK, SGD, THB, TRY, ZAR)
VALUE (FROM_UNIXTIME(?), ?, ?, ?, ?, ?, ?, ?, ? , ?, ?, ?, ?, ?, ?, ?, ? , ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ? , ? )`,
[time, prices.USD, prices.EUR, prices.GBP, prices.CAD, prices.CHF, prices.AUD, prices.JPY, prices.BGN, prices.BRL, prices.CNY, prices.CZK, prices.DKK,
prices.HKD, prices.HRK, prices.HUF, prices.IDR, prices.ILS, prices.INR, prices.ISK, prices.KRW, prices.MXN, prices.MYR, prices.NOK, prices.NZD,
prices.PHP, prices.PLN, prices.RON, prices.RUB, prices.SEK, prices.SGD, prices.THB, prices.TRY, prices.ZAR]
);
}
} catch (e) {
logger.err(`Cannot save exchange rate into db. Reason: ` + (e instanceof Error ? e.message : e));
throw e;
}
}
public async $saveAdditionalCurrencyPrices(time: number, prices: ApiPrice, legacyCurrencies: string[]): Promise<void> {
try {
await DB.query(`
UPDATE prices
SET BGN = ?, BRL = ?, CNY = ?, CZK = ?, DKK = ?, HKD = ?, HRK = ?, HUF = ?, IDR = ?, ILS = ?, INR = ?, ISK = ?, KRW = ?, MXN = ?, MYR = ?, NOK = ?, NZD = ?, PHP = ?, PLN = ?, RON = ?, RUB = ?, SEK = ?, SGD = ?, THB = ?, TRY = ?, ZAR = ?
WHERE UNIX_TIMESTAMP(time) = ?`,
[prices.BGN, prices.BRL, prices.CNY, prices.CZK, prices.DKK, prices.HKD, prices.HRK, prices.HUF, prices.IDR, prices.ILS, prices.INR, prices.ISK, prices.KRW, prices.MXN, prices.MYR, prices.NOK, prices.NZD, prices.PHP, prices.PLN, prices.RON, prices.RUB, prices.SEK, prices.SGD, prices.THB, prices.TRY, prices.ZAR, time]
);
for (const currency of legacyCurrencies) {
await DB.query(`
UPDATE prices
SET ${currency} = ?
WHERE UNIX_TIMESTAMP(time) = ?`,
[prices[currency], time]
);
}
} catch (e) {
logger.err(`Cannot update exchange rate into db. Reason: ` + (e instanceof Error ? e.message : e));
throw e;
}
}
public async $getOldestPriceTime(): Promise<number> {
const [oldestRow] = await DB.query(`
SELECT UNIX_TIMESTAMP(time) AS time
@@ -118,6 +267,28 @@ class PricesRepository {
return times.map(time => time.time);
}
public async $getPricesTimesWithMissingFields(): Promise<{time: number, USD: number, eur_missing: boolean, gbp_missing: boolean, cad_missing: boolean, chf_missing: boolean, aud_missing: boolean, jpy_missing: boolean}[]> {
const [times] = await DB.query(`
SELECT UNIX_TIMESTAMP(time) AS time,
USD,
CASE WHEN EUR = -1 THEN TRUE ELSE FALSE END AS eur_missing,
CASE WHEN GBP = -1 THEN TRUE ELSE FALSE END AS gbp_missing,
CASE WHEN CAD = -1 THEN TRUE ELSE FALSE END AS cad_missing,
CASE WHEN CHF = -1 THEN TRUE ELSE FALSE END AS chf_missing,
CASE WHEN AUD = -1 THEN TRUE ELSE FALSE END AS aud_missing,
CASE WHEN JPY = -1 THEN TRUE ELSE FALSE END AS jpy_missing
FROM prices
WHERE USD != -1
AND -1 IN (EUR, GBP, CAD, CHF, AUD, JPY, BGN, BRL, CNY, CZK, DKK, HKD, HRK, HUF, IDR, ILS, INR, ISK, KRW,
MXN, MYR, NOK, NZD, PHP, PLN, RON, RUB, SEK, SGD, THB, TRY, ZAR)
ORDER BY time
`);
if (!Array.isArray(times)) {
return [];
}
return times as {time: number, USD: number, eur_missing: boolean, gbp_missing: boolean, cad_missing: boolean, chf_missing: boolean, aud_missing: boolean, jpy_missing: boolean}[];
}
public async $getPricesTimesAndId(): Promise<{time: number, id: number, USD: number}[]> {
const [times] = await DB.query(`
SELECT
@@ -167,14 +338,48 @@ class PricesRepository {
const computeFx = (usd: number, other: number): number =>
Math.round(Math.max(other, 0) / Math.max(usd, 1) * 100) / 100;
const exchangeRates: ExchangeRates = {
USDEUR: computeFx(latestPrice.USD, latestPrice.EUR),
USDGBP: computeFx(latestPrice.USD, latestPrice.GBP),
USDCAD: computeFx(latestPrice.USD, latestPrice.CAD),
USDCHF: computeFx(latestPrice.USD, latestPrice.CHF),
USDAUD: computeFx(latestPrice.USD, latestPrice.AUD),
USDJPY: computeFx(latestPrice.USD, latestPrice.JPY),
};
const exchangeRates: ExchangeRates = config.MEMPOOL.CURRENCY_API_KEY ?
{
USDEUR: computeFx(latestPrice.USD, latestPrice.EUR),
USDGBP: computeFx(latestPrice.USD, latestPrice.GBP),
USDCAD: computeFx(latestPrice.USD, latestPrice.CAD),
USDCHF: computeFx(latestPrice.USD, latestPrice.CHF),
USDAUD: computeFx(latestPrice.USD, latestPrice.AUD),
USDJPY: computeFx(latestPrice.USD, latestPrice.JPY),
USDBGN: computeFx(latestPrice.USD, latestPrice.BGN),
USDBRL: computeFx(latestPrice.USD, latestPrice.BRL),
USDCNY: computeFx(latestPrice.USD, latestPrice.CNY),
USDCZK: computeFx(latestPrice.USD, latestPrice.CZK),
USDDKK: computeFx(latestPrice.USD, latestPrice.DKK),
USDHKD: computeFx(latestPrice.USD, latestPrice.HKD),
USDHRK: computeFx(latestPrice.USD, latestPrice.HRK),
USDHUF: computeFx(latestPrice.USD, latestPrice.HUF),
USDIDR: computeFx(latestPrice.USD, latestPrice.IDR),
USDILS: computeFx(latestPrice.USD, latestPrice.ILS),
USDINR: computeFx(latestPrice.USD, latestPrice.INR),
USDISK: computeFx(latestPrice.USD, latestPrice.ISK),
USDKRW: computeFx(latestPrice.USD, latestPrice.KRW),
USDMXN: computeFx(latestPrice.USD, latestPrice.MXN),
USDMYR: computeFx(latestPrice.USD, latestPrice.MYR),
USDNOK: computeFx(latestPrice.USD, latestPrice.NOK),
USDNZD: computeFx(latestPrice.USD, latestPrice.NZD),
USDPHP: computeFx(latestPrice.USD, latestPrice.PHP),
USDPLN: computeFx(latestPrice.USD, latestPrice.PLN),
USDRON: computeFx(latestPrice.USD, latestPrice.RON),
USDRUB: computeFx(latestPrice.USD, latestPrice.RUB),
USDSEK: computeFx(latestPrice.USD, latestPrice.SEK),
USDSGD: computeFx(latestPrice.USD, latestPrice.SGD),
USDTHB: computeFx(latestPrice.USD, latestPrice.THB),
USDTRY: computeFx(latestPrice.USD, latestPrice.TRY),
USDZAR: computeFx(latestPrice.USD, latestPrice.ZAR),
} : {
USDEUR: computeFx(latestPrice.USD, latestPrice.EUR),
USDGBP: computeFx(latestPrice.USD, latestPrice.GBP),
USDCAD: computeFx(latestPrice.USD, latestPrice.CAD),
USDCHF: computeFx(latestPrice.USD, latestPrice.CHF),
USDAUD: computeFx(latestPrice.USD, latestPrice.AUD),
USDJPY: computeFx(latestPrice.USD, latestPrice.JPY),
};
return {
prices: rates as ApiPrice[],
@@ -206,14 +411,48 @@ class PricesRepository {
const computeFx = (usd: number, other: number): number =>
Math.round(Math.max(other, 0) / Math.max(usd, 1) * 100) / 100;
const exchangeRates: ExchangeRates = {
USDEUR: computeFx(latestPrice.USD, latestPrice.EUR),
USDGBP: computeFx(latestPrice.USD, latestPrice.GBP),
USDCAD: computeFx(latestPrice.USD, latestPrice.CAD),
USDCHF: computeFx(latestPrice.USD, latestPrice.CHF),
USDAUD: computeFx(latestPrice.USD, latestPrice.AUD),
USDJPY: computeFx(latestPrice.USD, latestPrice.JPY),
};
const exchangeRates: ExchangeRates = config.MEMPOOL.CURRENCY_API_KEY ?
{
USDEUR: computeFx(latestPrice.USD, latestPrice.EUR),
USDGBP: computeFx(latestPrice.USD, latestPrice.GBP),
USDCAD: computeFx(latestPrice.USD, latestPrice.CAD),
USDCHF: computeFx(latestPrice.USD, latestPrice.CHF),
USDAUD: computeFx(latestPrice.USD, latestPrice.AUD),
USDJPY: computeFx(latestPrice.USD, latestPrice.JPY),
USDBGN: computeFx(latestPrice.USD, latestPrice.BGN),
USDBRL: computeFx(latestPrice.USD, latestPrice.BRL),
USDCNY: computeFx(latestPrice.USD, latestPrice.CNY),
USDCZK: computeFx(latestPrice.USD, latestPrice.CZK),
USDDKK: computeFx(latestPrice.USD, latestPrice.DKK),
USDHKD: computeFx(latestPrice.USD, latestPrice.HKD),
USDHRK: computeFx(latestPrice.USD, latestPrice.HRK),
USDHUF: computeFx(latestPrice.USD, latestPrice.HUF),
USDIDR: computeFx(latestPrice.USD, latestPrice.IDR),
USDILS: computeFx(latestPrice.USD, latestPrice.ILS),
USDINR: computeFx(latestPrice.USD, latestPrice.INR),
USDISK: computeFx(latestPrice.USD, latestPrice.ISK),
USDKRW: computeFx(latestPrice.USD, latestPrice.KRW),
USDMXN: computeFx(latestPrice.USD, latestPrice.MXN),
USDMYR: computeFx(latestPrice.USD, latestPrice.MYR),
USDNOK: computeFx(latestPrice.USD, latestPrice.NOK),
USDNZD: computeFx(latestPrice.USD, latestPrice.NZD),
USDPHP: computeFx(latestPrice.USD, latestPrice.PHP),
USDPLN: computeFx(latestPrice.USD, latestPrice.PLN),
USDRON: computeFx(latestPrice.USD, latestPrice.RON),
USDRUB: computeFx(latestPrice.USD, latestPrice.RUB),
USDSEK: computeFx(latestPrice.USD, latestPrice.SEK),
USDSGD: computeFx(latestPrice.USD, latestPrice.SGD),
USDTHB: computeFx(latestPrice.USD, latestPrice.THB),
USDTRY: computeFx(latestPrice.USD, latestPrice.TRY),
USDZAR: computeFx(latestPrice.USD, latestPrice.ZAR),
} : {
USDEUR: computeFx(latestPrice.USD, latestPrice.EUR),
USDGBP: computeFx(latestPrice.USD, latestPrice.GBP),
USDCAD: computeFx(latestPrice.USD, latestPrice.CAD),
USDCHF: computeFx(latestPrice.USD, latestPrice.CHF),
USDAUD: computeFx(latestPrice.USD, latestPrice.AUD),
USDJPY: computeFx(latestPrice.USD, latestPrice.JPY),
};
return {
prices: rates as ApiPrice[],