2022-06-23 15:42:42 +02:00
import * as fs from 'fs' ;
2023-02-15 16:05:14 +09:00
import path from 'path' ;
2022-06-23 15:42:42 +02:00
import config from '../config' ;
import logger from '../logger' ;
2023-03-04 10:51:13 +09:00
import PricesRepository , { ApiPrice , MAX_PRICES } from '../repositories/PricesRepository' ;
2022-06-23 15:42:42 +02:00
import BitfinexApi from './price-feeds/bitfinex-api' ;
import BitflyerApi from './price-feeds/bitflyer-api' ;
import CoinbaseApi from './price-feeds/coinbase-api' ;
import GeminiApi from './price-feeds/gemini-api' ;
import KrakenApi from './price-feeds/kraken-api' ;
2024-03-07 10:48:32 +01:00
import FreeCurrencyApi from './price-feeds/free-currency-api' ;
2022-06-23 15:42:42 +02:00
export interface PriceFeed {
name : string ;
url : string ;
urlHist : string ;
currencies : string [ ] ;
$fetchPrice ( currency ) : Promise < number > ;
2022-07-11 23:16:48 +02:00
$fetchRecentPrice ( currencies : string [ ] , type : string ) : Promise < PriceHistory > ;
2022-06-23 15:42:42 +02:00
}
export interface PriceHistory {
2023-03-04 10:51:13 +09:00
[ timestamp : number ] : ApiPrice ;
2022-06-23 15:42:42 +02:00
}
2024-03-07 10:48:32 +01:00
export interface ConversionFeed {
$getQuota ( ) : Promise < any > ;
2024-03-11 15:27:43 +01:00
$fetchLatestConversionRates ( ) : Promise < ConversionRates > ;
$fetchConversionRates ( date : string ) : Promise < ConversionRates > ;
2024-03-07 10:48:32 +01:00
}
export interface ConversionRates {
[ currency : string ] : number
}
2024-01-08 22:33:11 +00:00
function getMedian ( arr : number [ ] ) : number {
const sortedArr = arr . slice ( ) . sort ( ( a , b ) = > a - b ) ;
const mid = Math . floor ( sortedArr . length / 2 ) ;
return sortedArr . length % 2 !== 0
? sortedArr [ mid ]
: ( sortedArr [ mid - 1 ] + sortedArr [ mid ] ) / 2 ;
}
2022-06-23 15:42:42 +02:00
class PriceUpdater {
2022-07-16 09:22:45 +02:00
public historyInserted = false ;
2024-03-07 10:48:32 +01:00
private additionalCurrenciesHistoryInserted = false ;
private additionalCurrenciesHistoryRunning = false ;
2024-03-11 15:27:43 +01:00
private lastFailedHistoricalRun = 0 ;
2023-08-04 13:26:19 +09:00
private timeBetweenUpdatesMs = 360 _0000 / config . MEMPOOL . PRICE_UPDATES_PER_HOUR ;
2023-08-03 17:42:41 +09:00
private cyclePosition = - 1 ;
private firstRun = true ;
2023-08-04 13:23:09 +09:00
private lastTime = - 1 ;
2023-03-04 10:51:13 +09:00
private lastHistoricalRun = 0 ;
private running = false ;
private feeds : PriceFeed [ ] = [ ] ;
private currencies : string [ ] = [ 'USD' , 'EUR' , 'GBP' , 'CAD' , 'CHF' , 'AUD' , 'JPY' ] ;
private latestPrices : ApiPrice ;
2024-03-07 10:48:32 +01:00
private currencyConversionFeed : ConversionFeed | undefined ;
private newCurrencies : string [ ] = [ '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' ] ;
private lastTimeConversionsRatesFetched : number = 0 ;
private latestConversionsRatesFromFeed : ConversionRates = { } ;
2023-03-04 10:51:13 +09:00
private ratesChangedCallback : ( ( rates : ApiPrice ) = > void ) | undefined ;
2022-06-23 15:42:42 +02:00
constructor ( ) {
this . latestPrices = this . getEmptyPricesObj ( ) ;
this . feeds . push ( new BitflyerApi ( ) ) ; // Does not have historical endpoint
this . feeds . push ( new KrakenApi ( ) ) ;
this . feeds . push ( new CoinbaseApi ( ) ) ;
this . feeds . push ( new BitfinexApi ( ) ) ;
this . feeds . push ( new GeminiApi ( ) ) ;
2023-08-03 17:42:41 +09:00
2024-03-10 17:12:19 +01:00
this . currencyConversionFeed = new FreeCurrencyApi ( config . FIAT_PRICE . API_KEY ) ;
2023-08-03 17:42:41 +09:00
this . setCyclePosition ( ) ;
2022-06-23 15:42:42 +02:00
}
2023-03-04 10:51:13 +09:00
public getLatestPrices ( ) : ApiPrice {
return this . latestPrices ;
}
public getEmptyPricesObj ( ) : ApiPrice {
2022-06-23 15:42:42 +02:00
return {
2023-03-04 10:51:13 +09:00
time : 0 ,
2023-02-23 09:50:34 +09:00
USD : - 1 ,
EUR : - 1 ,
GBP : - 1 ,
CAD : - 1 ,
CHF : - 1 ,
AUD : - 1 ,
JPY : - 1 ,
2024-03-07 10:48:32 +01:00
BGN : - 1 ,
BRL : - 1 ,
CNY : - 1 ,
CZK : - 1 ,
DKK : - 1 ,
HKD : - 1 ,
HRK : - 1 ,
HUF : - 1 ,
IDR : - 1 ,
ILS : - 1 ,
INR : - 1 ,
ISK : - 1 ,
KRW : - 1 ,
MXN : - 1 ,
MYR : - 1 ,
NOK : - 1 ,
NZD : - 1 ,
PHP : - 1 ,
PLN : - 1 ,
RON : - 1 ,
RUB : - 1 ,
SEK : - 1 ,
SGD : - 1 ,
THB : - 1 ,
TRY : - 1 ,
ZAR : - 1 ,
2022-06-23 15:42:42 +02:00
} ;
}
2023-03-04 10:51:13 +09:00
public setRatesChangedCallback ( fn : ( rates : ApiPrice ) = > void ) : void {
2023-02-15 16:05:14 +09:00
this . ratesChangedCallback = fn ;
}
/ * *
* We execute this function before the websocket initialization since
* the websocket init is not done asyncronously
* /
public async $initializeLatestPriceWithDb ( ) : Promise < void > {
this . latestPrices = await PricesRepository . $getLatestConversionRates ( ) ;
}
2023-08-03 17:42:41 +09:00
public async $run ( ) : Promise < void > {
2023-03-14 15:39:15 +09:00
if ( config . MEMPOOL . NETWORK === 'signet' || config . MEMPOOL . NETWORK === 'testnet' ) {
// Coins have no value on testnet/signet, so we want to always show 0
return ;
}
2022-06-23 15:42:42 +02:00
if ( this . running === true ) {
return ;
}
this . running = true ;
if ( ( Math . round ( new Date ( ) . getTime ( ) / 1000 ) - this . lastHistoricalRun ) > 3600 * 24 ) {
// Once a day, look for missing prices (could happen due to network connectivity issues)
this . historyInserted = false ;
2024-03-07 10:48:32 +01:00
this . additionalCurrenciesHistoryInserted = false ;
}
2024-03-11 15:27:43 +01:00
if ( this . lastFailedHistoricalRun > 0 && ( Math . round ( new Date ( ) . getTime ( ) / 1000 ) - this . lastFailedHistoricalRun ) > 60 ) {
// If the last attempt to insert missing prices failed, we try again after 60 seconds
this . additionalCurrenciesHistoryInserted = false ;
}
2024-03-10 17:12:19 +01:00
if ( config . FIAT_PRICE . API_KEY && this . currencyConversionFeed && ( Math . round ( new Date ( ) . getTime ( ) / 1000 ) - this . lastTimeConversionsRatesFetched ) > 3600 * 24 ) {
2024-03-07 10:48:32 +01:00
// Once a day, fetch conversion rates from api: we don't need more granularity for fiat currencies and have a limited number of requests
try {
this . latestConversionsRatesFromFeed = await this . currencyConversionFeed . $fetchLatestConversionRates ( ) ;
this . lastTimeConversionsRatesFetched = Math . round ( new Date ( ) . getTime ( ) / 1000 ) ;
logger . debug ( ` Fetched currencies conversion rates from external API: ${ JSON . stringify ( this . latestConversionsRatesFromFeed ) } ` ) ;
} catch ( e ) {
logger . err ( ` Cannot fetch conversion rates from the API. Reason: ${ ( e instanceof Error ? e.message : e ) } ` ) ;
}
2022-06-23 15:42:42 +02:00
}
try {
2023-08-03 17:42:41 +09:00
await this . $updatePrice ( ) ;
2022-06-23 15:42:42 +02:00
if ( this . historyInserted === false && config . DATABASE . ENABLED === true ) {
await this . $insertHistoricalPrices ( ) ;
}
2024-03-10 17:12:19 +01:00
if ( this . additionalCurrenciesHistoryInserted === false && config . DATABASE . ENABLED === true && config . FIAT_PRICE . API_KEY && ! this . additionalCurrenciesHistoryRunning ) {
2024-03-07 10:48:32 +01:00
await this . $insertMissingAdditionalPrices ( ) ;
}
2024-03-11 15:27:43 +01:00
2023-03-14 15:39:15 +09:00
} catch ( e : any ) {
2022-12-01 15:52:06 +01:00
logger . err ( ` Cannot save BTC prices in db. Reason: ${ e instanceof Error ? e.message : e } ` , logger . tags . mining ) ;
2022-06-23 15:42:42 +02:00
}
this . running = false ;
}
2023-08-03 17:42:41 +09:00
private getMillisecondsSinceBeginningOfHour ( ) : number {
const now = new Date ( ) ;
const beginningOfHour = new Date ( now ) ;
beginningOfHour . setMinutes ( 0 , 0 , 0 ) ;
return now . getTime ( ) - beginningOfHour . getTime ( ) ;
}
private setCyclePosition ( ) : void {
const millisecondsSinceBeginningOfHour = this . getMillisecondsSinceBeginningOfHour ( ) ;
for ( let i = 0 ; i < config . MEMPOOL . PRICE_UPDATES_PER_HOUR ; i ++ ) {
if ( this . timeBetweenUpdatesMs * i > millisecondsSinceBeginningOfHour ) {
this . cyclePosition = i ;
return ;
}
}
this . cyclePosition = config . MEMPOOL . PRICE_UPDATES_PER_HOUR ;
}
2022-06-23 15:42:42 +02:00
/ * *
* Fetch last BTC price from exchanges , average them , and save it in the database once every hour
* /
2023-08-03 17:42:41 +09:00
private async $updatePrice ( ) : Promise < void > {
let forceUpdate = false ;
if ( this . firstRun === true && config . DATABASE . ENABLED === true ) {
const lastUpdate = await PricesRepository . $getLatestPriceTime ( ) ;
if ( new Date ( ) . getTime ( ) / 1000 - lastUpdate > this . timeBetweenUpdatesMs / 1000 ) {
forceUpdate = true ;
}
this . firstRun = false ;
2022-06-23 15:42:42 +02:00
}
2023-08-03 17:42:41 +09:00
const millisecondsSinceBeginningOfHour = this . getMillisecondsSinceBeginningOfHour ( ) ;
2023-08-04 13:23:09 +09:00
// Reset the cycle on new hour
if ( this . lastTime > millisecondsSinceBeginningOfHour ) {
this . cyclePosition = 0 ;
}
this . lastTime = millisecondsSinceBeginningOfHour ;
if ( millisecondsSinceBeginningOfHour < this . timeBetweenUpdatesMs * this . cyclePosition && ! forceUpdate && this . cyclePosition !== 0 ) {
2022-06-23 15:42:42 +02:00
return ;
}
for ( const currency of this . currencies ) {
let prices : number [ ] = [ ] ;
for ( const feed of this . feeds ) {
// Fetch prices from API which supports `currency`
if ( feed . currencies . includes ( currency ) ) {
try {
const price = await feed . $fetchPrice ( currency ) ;
2023-02-23 09:50:34 +09:00
if ( price > - 1 && price < MAX_PRICES [ currency ] ) {
2022-06-23 15:42:42 +02:00
prices . push ( price ) ;
}
2022-12-01 15:52:06 +01:00
logger . debug ( ` ${ feed . name } BTC/ ${ currency } price: ${ price } ` , logger . tags . mining ) ;
2022-06-23 15:42:42 +02:00
} catch ( e ) {
2022-12-01 15:52:06 +01:00
logger . debug ( ` Could not fetch BTC/ ${ currency } price at ${ feed . name } . Reason: ${ ( e instanceof Error ? e.message : e ) } ` , logger . tags . mining ) ;
2022-06-23 15:42:42 +02:00
}
}
}
if ( prices . length === 1 ) {
2022-12-01 15:52:06 +01:00
logger . debug ( ` Only ${ prices . length } feed available for BTC/ ${ currency } price ` , logger . tags . mining ) ;
2022-06-23 15:42:42 +02:00
}
// Compute average price, non weighted
prices = prices . filter ( price = > price > 0 ) ;
2022-12-01 12:05:23 +01:00
if ( prices . length === 0 ) {
this . latestPrices [ currency ] = - 1 ;
} else {
2024-01-08 22:33:11 +00:00
this . latestPrices [ currency ] = Math . round ( getMedian ( prices ) ) ;
2022-12-01 12:05:23 +01:00
}
2022-06-23 15:42:42 +02:00
}
2024-03-10 17:12:19 +01:00
if ( config . FIAT_PRICE . API_KEY && this . latestPrices . USD > 0 && Object . keys ( this . latestConversionsRatesFromFeed ) . length > 0 ) {
2024-03-07 10:48:32 +01:00
for ( const conversionCurrency of this . newCurrencies ) {
if ( this . latestConversionsRatesFromFeed [ conversionCurrency ] > 0 && this . latestPrices . USD * this . latestConversionsRatesFromFeed [ conversionCurrency ] < MAX_PRICES [ conversionCurrency ] ) {
this . latestPrices [ conversionCurrency ] = Math . round ( this . latestPrices . USD * this . latestConversionsRatesFromFeed [ conversionCurrency ] ) ;
}
}
}
2023-08-03 17:42:41 +09:00
if ( config . DATABASE . ENABLED === true && this . cyclePosition === 0 ) {
2022-06-23 15:42:42 +02:00
// Save everything in db
try {
const p = 60 * 60 * 1000 ; // milliseconds in an hour
const nowRounded = new Date ( Math . round ( new Date ( ) . getTime ( ) / p ) * p ) ; // https://stackoverflow.com/a/28037042
await PricesRepository . $savePrices ( nowRounded . getTime ( ) / 1000 , this . latestPrices ) ;
} catch ( e ) {
logger . err ( ` Cannot save latest prices into db. Trying again in 5 minutes. Reason: ${ ( e instanceof Error ? e.message : e ) } ` ) ;
}
}
2023-07-29 19:27:19 +09:00
this . latestPrices . time = Math . round ( new Date ( ) . getTime ( ) / 1000 ) ;
logger . info ( ` Latest BTC fiat averaged price: ${ JSON . stringify ( this . latestPrices ) } ` ) ;
2023-02-15 16:05:14 +09:00
if ( this . ratesChangedCallback ) {
this . ratesChangedCallback ( this . latestPrices ) ;
}
2023-08-03 17:42:41 +09:00
if ( ! forceUpdate ) {
this . cyclePosition ++ ;
}
2023-03-04 10:51:13 +09:00
if ( this . latestPrices . USD === - 1 ) {
this . latestPrices = await PricesRepository . $getLatestConversionRates ( ) ;
}
2022-06-23 15:42:42 +02:00
}
/ * *
* Called once by the database migration to initialize historical prices data ( weekly )
* We use MtGox weekly price from July 19 , 2010 to September 30 , 2013
* We use Kraken weekly price from October 3 , 2013 up to last month
* We use Kraken hourly price for the past month
* /
private async $insertHistoricalPrices ( ) : Promise < void > {
const existingPriceTimes = await PricesRepository . $getPricesTimes ( ) ;
// Insert MtGox weekly prices
2022-07-13 12:44:31 +02:00
const pricesJson : any [ ] = JSON . parse ( fs . readFileSync ( path . join ( __dirname , 'mtgox-weekly.json' ) ) . toString ( ) ) ;
2022-06-23 15:42:42 +02:00
const prices = this . getEmptyPricesObj ( ) ;
let insertedCount : number = 0 ;
for ( const price of pricesJson ) {
if ( existingPriceTimes . includes ( price [ 'ct' ] ) ) {
continue ;
}
// From 1380758400 we will use Kraken price as it follows closely MtGox, but was not affected as much
// by the MtGox exchange collapse a few months later
if ( price [ 'ct' ] > 1380758400 ) {
break ;
}
prices . USD = price [ 'c' ] ;
await PricesRepository . $savePrices ( price [ 'ct' ] , prices ) ;
++ insertedCount ;
}
if ( insertedCount > 0 ) {
2022-12-01 15:52:06 +01:00
logger . notice ( ` Inserted ${ insertedCount } MtGox USD weekly price history into db ` , logger . tags . mining ) ;
2022-07-11 10:55:25 +02:00
} else {
2022-12-01 15:52:06 +01:00
logger . debug ( ` Inserted ${ insertedCount } MtGox USD weekly price history into db ` , logger . tags . mining ) ;
2022-06-23 15:42:42 +02:00
}
// Insert Kraken weekly prices
await new KrakenApi ( ) . $insertHistoricalPrice ( ) ;
// Insert missing recent hourly prices
2022-07-11 23:16:48 +02:00
await this . $insertMissingRecentPrices ( 'day' ) ;
await this . $insertMissingRecentPrices ( 'hour' ) ;
2022-06-23 15:42:42 +02:00
this . historyInserted = true ;
2024-03-11 14:28:06 +01:00
this . lastHistoricalRun = Math . round ( new Date ( ) . getTime ( ) / 1000 ) ;
2022-06-23 15:42:42 +02:00
}
/ * *
* Find missing hourly prices and insert them in the database
* It has a limited backward range and it depends on which API are available
* /
2022-07-11 23:16:48 +02:00
private async $insertMissingRecentPrices ( type : 'hour' | 'day' ) : Promise < void > {
2022-06-23 15:42:42 +02:00
const existingPriceTimes = await PricesRepository . $getPricesTimes ( ) ;
2023-03-14 13:53:52 +09:00
logger . debug ( ` Fetching ${ type === 'day' ? 'dai' : 'hour' } ly price history from exchanges and saving missing ones into the database ` , logger . tags . mining ) ;
2022-06-23 15:42:42 +02:00
const historicalPrices : PriceHistory [ ] = [ ] ;
// Fetch all historical hourly prices
for ( const feed of this . feeds ) {
try {
2022-07-11 23:16:48 +02:00
historicalPrices . push ( await feed . $fetchRecentPrice ( this . currencies , type ) ) ;
2022-06-23 15:42:42 +02:00
} catch ( e ) {
2022-12-01 15:52:06 +01:00
logger . err ( ` Cannot fetch hourly historical price from ${ feed . name } . Ignoring this feed. Reason: ${ e instanceof Error ? e.message : e } ` , logger . tags . mining ) ;
2022-06-23 15:42:42 +02:00
}
}
// Group them by timestamp and currency, for example
// grouped[123456789]['USD'] = [1, 2, 3, 4];
2023-03-04 10:51:13 +09:00
const grouped = { } ;
2022-06-23 15:42:42 +02:00
for ( const historicalEntry of historicalPrices ) {
for ( const time in historicalEntry ) {
if ( existingPriceTimes . includes ( parseInt ( time , 10 ) ) ) {
continue ;
}
2022-06-26 13:49:39 +02:00
if ( grouped [ time ] === undefined ) {
2022-06-23 15:42:42 +02:00
grouped [ time ] = {
USD : [ ] , EUR : [ ] , GBP : [ ] , CAD : [ ] , CHF : [ ] , AUD : [ ] , JPY : [ ]
2022-06-26 13:49:39 +02:00
} ;
2022-06-23 15:42:42 +02:00
}
for ( const currency of this . currencies ) {
const price = historicalEntry [ time ] [ currency ] ;
2023-02-23 09:50:34 +09:00
if ( price > - 1 && price < MAX_PRICES [ currency ] ) {
2023-02-15 16:05:14 +09:00
grouped [ time ] [ currency ] . push ( typeof price === 'string' ? parseInt ( price , 10 ) : price ) ;
2022-06-23 15:42:42 +02:00
}
}
}
}
// Average prices and insert everything into the db
let totalInserted = 0 ;
for ( const time in grouped ) {
2023-03-04 10:51:13 +09:00
const prices : ApiPrice = this . getEmptyPricesObj ( ) ;
2022-06-23 15:42:42 +02:00
for ( const currency in grouped [ time ] ) {
2022-06-26 13:49:39 +02:00
if ( grouped [ time ] [ currency ] . length === 0 ) {
continue ;
}
2024-01-08 22:33:11 +00:00
prices [ currency ] = Math . round ( getMedian ( grouped [ time ] [ currency ] ) ) ;
2022-06-23 15:42:42 +02:00
}
await PricesRepository . $savePrices ( parseInt ( time , 10 ) , prices ) ;
++ totalInserted ;
}
2022-06-26 13:49:39 +02:00
if ( totalInserted > 0 ) {
2022-12-01 15:52:06 +01:00
logger . notice ( ` Inserted ${ totalInserted } ${ type === 'day' ? 'dai' : 'hour' } ly historical prices into the db ` , logger . tags . mining ) ;
2022-07-11 10:55:25 +02:00
} else {
2022-12-01 15:52:06 +01:00
logger . debug ( ` Inserted ${ totalInserted } ${ type === 'day' ? 'dai' : 'hour' } ly historical prices into the db ` , logger . tags . mining ) ;
2022-06-26 13:49:39 +02:00
}
2022-06-23 15:42:42 +02:00
}
2024-03-07 10:48:32 +01:00
/ * *
* Find missing prices for additional currencies and insert them in the database
* We calculate the additional prices from the USD price and the conversion rates
* /
private async $insertMissingAdditionalPrices ( ) : Promise < void > {
2024-03-11 15:27:43 +01:00
this . lastFailedHistoricalRun = 0 ;
2024-03-07 10:48:32 +01:00
const priceTimesToFill = await PricesRepository . $getPricesTimesWithMissingFields ( ) ;
if ( priceTimesToFill . length === 0 ) {
return ;
}
2024-03-11 15:27:43 +01:00
try {
const remainingQuota = await this . currencyConversionFeed ? . $getQuota ( ) ;
if ( remainingQuota [ 'month' ] [ 'remaining' ] < 500 ) { // We need some calls left for the daily updates
logger . debug ( ` Not enough currency API credit to insert missing prices in ${ priceTimesToFill . length } rows ( ${ remainingQuota [ 'month' ] [ 'remaining' ] } calls left). ` , logger . tags . mining ) ;
this . additionalCurrenciesHistoryInserted = true ; // Do not try again until next day
return ;
}
} catch ( e ) {
logger . err ( ` Cannot fetch currency API credit, insertion of missing prices aborted. Reason: ${ ( e instanceof Error ? e.message : e ) } ` ) ;
return ;
}
this . additionalCurrenciesHistoryRunning = true ;
2024-03-07 10:48:32 +01:00
logger . debug ( ` Fetching missing conversion rates from external API to fill ${ priceTimesToFill . length } rows ` , logger . tags . mining ) ;
let conversionRates : { [ timestamp : number ] : ConversionRates } = { } ;
let totalInserted = 0 ;
2024-03-11 15:27:43 +01:00
for ( let i = 0 ; i < priceTimesToFill . length ; i ++ ) {
const priceTime = priceTimesToFill [ i ] ;
2024-03-07 10:48:32 +01:00
const missingLegacyCurrencies = this . getMissingLegacyCurrencies ( priceTime ) ; // In the case a legacy currency (EUR, GBP, CAD, CHF, AUD, JPY)
const year = new Date ( priceTime . time * 1000 ) . getFullYear ( ) ; // is missing, we use the same process as for the new currencies
2024-03-11 15:27:43 +01:00
const month = new Date ( priceTime . time * 1000 ) . getMonth ( ) ;
const yearMonthTimestamp = new Date ( year , month , 1 ) . getTime ( ) / 1000 ;
if ( conversionRates [ yearMonthTimestamp ] === undefined ) {
conversionRates [ yearMonthTimestamp ] = await this . currencyConversionFeed ? . $fetchConversionRates ( ` ${ year } - ${ month + 1 < 10 ? ` 0 ${ month + 1 } ` : ` ${ month + 1 } ` } -01 ` ) || { USD : - 1 } ;
if ( conversionRates [ yearMonthTimestamp ] [ 'USD' ] < 0 ) {
logger . err ( ` Cannot fetch conversion rates from the API for ${ year } - ${ month + 1 < 10 ? ` 0 ${ month + 1 } ` : ` ${ month + 1 } ` } -01. Aborting insertion of missing prices. ` , logger . tags . mining ) ;
this . lastFailedHistoricalRun = Math . round ( new Date ( ) . getTime ( ) / 1000 ) ;
break ;
2024-03-07 10:48:32 +01:00
}
}
const prices : ApiPrice = this . getEmptyPricesObj ( ) ;
let willInsert = false ;
for ( const conversionCurrency of this . newCurrencies . concat ( missingLegacyCurrencies ) ) {
2024-03-11 15:27:43 +01:00
if ( conversionRates [ yearMonthTimestamp ] [ conversionCurrency ] > 0 && priceTime . USD * conversionRates [ yearMonthTimestamp ] [ conversionCurrency ] < MAX_PRICES [ conversionCurrency ] ) {
prices [ conversionCurrency ] = year >= 2013 ? Math . round ( priceTime . USD * conversionRates [ yearMonthTimestamp ] [ conversionCurrency ] ) : Math . round ( priceTime . USD * conversionRates [ yearMonthTimestamp ] [ conversionCurrency ] * 100 ) / 100 ;
2024-03-07 10:48:32 +01:00
willInsert = true ;
2024-03-11 15:27:43 +01:00
} else {
prices [ conversionCurrency ] = 0 ;
2024-03-07 10:48:32 +01:00
}
}
if ( willInsert ) {
await PricesRepository . $saveAdditionalCurrencyPrices ( priceTime . time , prices , missingLegacyCurrencies ) ;
++ totalInserted ;
}
}
2024-03-11 15:27:43 +01:00
2024-03-07 10:48:32 +01:00
logger . debug ( ` Inserted ${ totalInserted } missing additional currency prices into the db ` , logger . tags . mining ) ;
this . additionalCurrenciesHistoryInserted = true ;
this . additionalCurrenciesHistoryRunning = false ;
}
// Helper function to get legacy missing currencies in a row (EUR, GBP, CAD, CHF, AUD, JPY)
private getMissingLegacyCurrencies ( priceTime : any ) : string [ ] {
const missingCurrencies : string [ ] = [ ] ;
[ 'eur' , 'gbp' , 'cad' , 'chf' , 'aud' , 'jpy' ] . forEach ( currency = > {
if ( priceTime [ ` ${ currency } _missing ` ] ) {
missingCurrencies . push ( currency . toUpperCase ( ) ) ;
}
} ) ;
return missingCurrencies ;
}
2022-06-23 15:42:42 +02:00
}
export default new PriceUpdater ( ) ;