From 0e05028dddfd1fb68b446e2b969c2b6cea142dbd Mon Sep 17 00:00:00 2001 From: Vlad Marian Date: Sat, 24 Apr 2021 18:48:27 +0100 Subject: [PATCH] 2.1.8 - Use secondary API (key contingent) if first fails for Coins update. --- README.md | 3 ++ source/API.gs | 23 ++++++++++-- source/APICoinGecko.gs | 10 ++++-- source/APICryptoCompare.gs | 49 +++++++++++++++++++++++++- source/Cache.gs | 12 +++---- source/CoinsUpdate.gs | 72 ++++++++++++++++++++++++++------------ source/FiatValues.gs | 32 ++++++++--------- source/FluxUpdate.gs | 10 +++--- source/Import.gs | 16 ++++----- source/Init.gs | 8 ++--- source/Settings.gs | 21 +++++++++-- source/Util.gs | 2 +- 12 files changed, 187 insertions(+), 71 deletions(-) diff --git a/README.md b/README.md index ec4eb9e..209ff87 100644 --- a/README.md +++ b/README.md @@ -76,6 +76,9 @@ _Please do keep in mind at this point there are a lot of differences between thi - Made Flux multi-sheet by using active sheet. - Improved Flux update error messages. +- Version 2.1.8 + - Use secondary API (key contingent) if first fails for Coins update. + ## Planned updates - Automatically add coin when adding trade. diff --git a/source/API.gs b/source/API.gs index 32ebd0d..4b3b740 100644 --- a/source/API.gs +++ b/source/API.gs @@ -2,8 +2,17 @@ * Gets coins market data. *

This is an implementation wrapper for API change capability. */ -function apiMarkets(fiat, ids) { - return geckoMarkets(fiat, ids); +function apiMarkets(fiat, ids, tickers) { + var market = geckoMarkets(fiat, ids); + + if(Object.keys(market).length == 0) { + var key = getCryptoCompareKey(); + if(key != "") { + market = cryptoMarkets(key, fiat, tickers); + } + } + + return market; } /** @@ -12,7 +21,7 @@ function apiMarkets(fiat, ids) { */ function apiRate(fiat, coin, date) { var key = getCryptoCompareKey(); - if (key != "") { + if(key != "") { return cryptoRate(key, fiat, coin, date); } return geckoRate(fiat, coin, date); @@ -24,3 +33,11 @@ function apiRate(fiat, coin, date) { function apiFlux(fiat, coin, days, interval) { return geckoFlux(fiat, coin, days, interval); } + +/** + * Debug. + */ +function apiMarketsDebug() { + disableCache(); + return apiMarkets("usd", "bitcoin,ethereum,litecoin", "btc,eth,ltc"); +} diff --git a/source/APICoinGecko.gs b/source/APICoinGecko.gs index 8989b46..b8db263 100644 --- a/source/APICoinGecko.gs +++ b/source/APICoinGecko.gs @@ -5,9 +5,11 @@ function geckoMarkets(fiat, ids) { var market = {}; + enableCache(); var json = importJson("https://api.coingecko.com/api/v3/coins/markets?vs_currency=" + fiat + "&ids=" + ids); - if (typeof (json) === "object") { - for (i = 0; i < json.length; i++) { + + if(typeof(json) === "object") { + for(i = 0; i < json.length; i++) { market[json[i][1]["symbol"].toLowerCase()] = json[i][1]; } } @@ -21,6 +23,8 @@ function geckoMarkets(fiat, ids) { function geckoRate(fiat, coin, date) { var coins = getCoins(); date = date.getDate().padLeft(2) + "-" + (date.getMonth() + 1).padLeft(2) + "-" + date.getFullYear(); + + enableCache(); return importJson("https://api.coingecko.com/api/v3/coins/" + coins[coin] + "/history?date=" + date, "market_data.current_price." + fiat); } @@ -30,5 +34,7 @@ function geckoRate(fiat, coin, date) { function geckoFlux(fiat, coin, days, interval) { var coins = getCoins(); days = interval == "hourly" ? Math.ceil(days / 24) : days; + + enableCache(); return importJson("https://api.coingecko.com/api/v3/coins/" + coins[coin] + "/market_chart?vs_currency=" + fiat + "&days=" + days + "&interval=" + interval); } diff --git a/source/APICryptoCompare.gs b/source/APICryptoCompare.gs index a25e183..a08bb90 100644 --- a/source/APICryptoCompare.gs +++ b/source/APICryptoCompare.gs @@ -1,8 +1,49 @@ +/** + * Gets coins market data from CoinGecko API versus given fiat. + *

Documentation: https://www.coingecko.com/api/documentations/v3 + */ +function cryptoMarkets(key, fiat, tickers) { + var market = {}; + + disableCache(); + var json = importJson("https://min-api.cryptocompare.com/data/pricemultifull?api_key=" + key + "&tsyms=" + fiat + "&fsyms=" + tickers + "&relaxedValidation=true"); + + if(typeof(json) === "object" && json.length > 1 && json[0].length > 1) { + var key, data, ticker; + for(key in json[0][1]) { + data = json[0][1][key][fiat.toUpperCase()]; + ticker = key.toLowerCase(); + + market[ticker] = []; + if(data.hasOwnProperty("MKTCAP")) { + market[ticker]["market_cap"] = data["MKTCAP"]; + } + if(data.hasOwnProperty("SUPPLY")) { + market[ticker]["circulating_supply"] = data["SUPPLY"]; + } + if(data.hasOwnProperty("LOW24HOUR")) { + market[ticker]["low_24h"] = data["LOW24HOUR"]; + } + if(data.hasOwnProperty("HIGH24HOUR")) { + market[ticker]["high_24h"] = data["HIGH24HOUR"]; + } + if(data.hasOwnProperty("CHANGE24HOUR")) { + market[ticker]["price_change_24h"] = data["CHANGE24HOUR"]; + } + if(data.hasOwnProperty("PRICE")) { + market[ticker]["current_price"] = data["PRICE"]; + } + } + } + + return market; +} /** * Gets coin vs fiat exchnage rate from CryptoCompare API for given date. */ function cryptoRate(key, fiat, coin, date) { + enableCache(); var rate = importJson("https://min-api.cryptocompare.com/data/v2/histohour?api_key=" + key + "&fsym=" + coin + "&tsym=" + fiat + "&toTs=" + (date.getTime() / 1000) + "&limit=1","Data.Data.0.close"); if(typeof(rate) === "number") { return rate; @@ -14,8 +55,14 @@ function cryptoRate(key, fiat, coin, date) { /** * Debug. */ +function cryptoMarketsDebug() { + disableCache(); + var key = getCryptoCompareKey(); + Logger.log(cryptoMarkets(key, "usd", "btc,eth,ltc")); +} + function cryptoRateDebug() { disableCache(); var key = getCryptoCompareKey(); Logger.log(cryptoRate(key, "usd", "btc", new Date("04/10/2021"))); -} \ No newline at end of file +} diff --git a/source/Cache.gs b/source/Cache.gs index a66cf7d..eb0bb21 100644 --- a/source/Cache.gs +++ b/source/Cache.gs @@ -31,9 +31,9 @@ function enableCache() { * @param duration Retention duration. */ function setCache(key, value, duration) { - if (isCache()) { + if(isCache()) { duration = parseInt(duration); - if (duration == 0 || isNaN(duration)) { + if(duration == 0 || isNaN(duration)) { duration = 600; } var cacheService = CacheService.getUserCache(); @@ -53,13 +53,13 @@ function setCache(key, value, duration) { * @param key Cache key. */ function getCache(key) { - if (isCache()) { + if(isCache()) { var cacheService = CacheService.getUserCache(); key = key.length > 250 ? key.substring(0, 250) : key; var items = cacheService.get(key); - if (!items) { + if(!items) { return null; } @@ -71,7 +71,7 @@ function getCache(key) { * Delete cache entry. */ function deleteCache(key) { - if (isCache()) { + if(isCache()) { var cacheService = CacheService.getUserCache(); key = key.length > 250 ? key.substring(0, 250) : key; @@ -83,7 +83,7 @@ function deleteCache(key) { * Is cache enabled. */ function isCache() { - if (cacheDisabled == true) { + if(cacheDisabled == true) { Logger.log("isCache:: Is disabled"); return false; } diff --git a/source/CoinsUpdate.gs b/source/CoinsUpdate.gs index 6f46b81..3c54959 100644 --- a/source/CoinsUpdate.gs +++ b/source/CoinsUpdate.gs @@ -3,36 +3,64 @@ *

Fetch coins data from CoinGecko API. */ function updateCoins() { - var ids = getCoinNames(); - var fiat = getFiat(); - var stable = getStableCoins(); - Logger.log("updateCoins:: Coins list: " + ids); + var market = apiMarkets(getFiat(), getCoinNames(), getTickers()); + if(Object.keys(market).length > 0) { + var coins = getCoins(); + var stable = getStableCoins(); - var coins = apiMarkets(fiat, ids); - if (Object.keys(coins).length > 0) { var active = SpreadsheetApp.getActive(); var sheet = active.getSheetByName("Coins"); var rows = sheet.getRange("A3:A").getValues(); - for (i = 0; i < rows.length; i++) { - if (rows[i] != undefined && rows[i][0].length > 0) { + for(i = 0; i < rows.length; i++) { + if(rows[i] != undefined && rows[i][0].length > 0) { var ticker = rows[i][0].toLowerCase(); - if (coins.hasOwnProperty(ticker)) { - if (stable.indexOf(ticker) != -1) { - coins[ticker]["current_price"] = 1; - } - var payload = [[ - coins[ticker]["market_cap_rank"], coins[ticker]["total_volume"], - coins[ticker]["market_cap"], coins[ticker]["fully_diluted_valuation"], - coins[ticker]["circulating_supply"], coins[ticker]["total_supply"], - coins[ticker]["low_24h"], coins[ticker]["high_24h"], coins[ticker]["ath"], - coins[ticker]["price_change_24h"], coins[ticker]["current_price"] - ]]; - Logger.log("updateCoins:: Row data: " + JSON.stringify([i, ticker, payload[0]])); + if(market.hasOwnProperty(ticker)) { + // Skip fiat + if(coins[ticker] == "fiat") { + continue; + } + // Set stablecoins to 1 + if(stable.indexOf(ticker) != -1) { + market[ticker]["current_price"] = 1; + } var r = i + 3; - var range = sheet.getRange("C" + r + ":M" + r); - range.setValues(payload); + if(market[ticker].hasOwnProperty("market_cap_rank")) { + sheet.getRange("C" + r).setValue(market[ticker]["market_cap_rank"]); + } + if(market[ticker].hasOwnProperty("total_volume")) { + sheet.getRange("D" + r).setValue(market[ticker]["total_volume"]); + } + if(market[ticker].hasOwnProperty("market_cap")) { + sheet.getRange("E" + r).setValue(market[ticker]["market_cap"]); + } + if(market[ticker].hasOwnProperty("fully_diluted_valuation")) { + sheet.getRange("F" + r).setValue(market[ticker]["fully_diluted_valuation"]); + } + if(market[ticker].hasOwnProperty("circulating_supply")) { + sheet.getRange("G" + r).setValue(market[ticker]["circulating_supply"]); + } + if(market[ticker].hasOwnProperty("total_supply")) { + sheet.getRange("H" + r).setValue(market[ticker]["total_supply"]); + } + if(market[ticker].hasOwnProperty("low_24h")) { + sheet.getRange("I" + r).setValue(market[ticker]["low_24h"]); + } + if(market[ticker].hasOwnProperty("high_24h")) { + sheet.getRange("J" + r).setValue(market[ticker]["high_24h"]); + } + if(market[ticker].hasOwnProperty("ath")) { + sheet.getRange("K" + r).setValue(market[ticker]["ath"]); + } + if(market[ticker].hasOwnProperty("price_change_24h")) { + sheet.getRange("L" + r).setValue(market[ticker]["price_change_24h"]); + } + if(market[ticker].hasOwnProperty("current_price")) { + sheet.getRange("M" + r).setValue(market[ticker]["current_price"]); + } + + Logger.log("updateCoins:: Row data: " + JSON.stringify([i, r, ticker, market[ticker]])); } } } diff --git a/source/FiatValues.gs b/source/FiatValues.gs index 15ee7e6..94d1f6b 100644 --- a/source/FiatValues.gs +++ b/source/FiatValues.gs @@ -9,7 +9,7 @@ function addFiatValues() { var fiat = getFiat(); var coins = getCoins(); var stable = getStableCoins(); - for (var i = 1; i < trades.length; i++) { + for(var i = 1; i < trades.length; i++) { var date = new Date(trades[i][0]); var buy_count = trades[i][2]; @@ -26,17 +26,17 @@ function addFiatValues() { var isSell = sell_count > 0 && sell_coin != "" && sell_fiat === "" && coins.hasOwnProperty(sell_coin); var isFee = fee_count > 0 && fee_coin != "" && fee_fiat === "" && coins.hasOwnProperty(fee_coin); - if (isBuy && isSell) { + if(isBuy && isSell) { setEqualFiat(sheet, coins, fiat, stable, date, buy_count, buy_coin, sell_count, sell_coin, i + 1); - } else if (isBuy) { + } else if(isBuy) { setFiat(sheet, coins, fiat, stable, date, buy_count, buy_coin, "C", "D", "E", i + 1); - } else if (isSell) { + } else if(isSell) { setFiat(sheet, coins, fiat, stable, date, sell_count, sell_coin, "F", "G", "H", i + 1); } - if (isFee) { + if(isFee) { setFiat(sheet, coins, fiat, stable, date, fee_count, fee_coin, "I", "J", "K", i + 1); } } @@ -52,21 +52,21 @@ function setEqualFiat(sheet, coins, fiat, stable, date, buy_count, buy_coin, sel var coin = sell_coin; var coinName = sellCoinName; - if ((buyCoinName == "fiat" && sellCoinName != "fiat") || stable.indexOf(buy_coin) != -1) { + if((buyCoinName == "fiat" && sellCoinName != "fiat") || stable.indexOf(buy_coin) != -1) { coin = buy_coin; coinName = buyCoinName; } - if (coinName == "fiat" && stable.indexOf(coin) == -1) { - if (coinName == buyCoinName) { + if(coinName == "fiat" && stable.indexOf(coin) == -1) { + if(coinName == buyCoinName) { sheet.getRange("E" + row).setValue("=INDEX(GOOGLEFINANCE(CONCAT(\"CURRENCY:\", CONCAT(D" + row + ", \"" + fiat.toUpperCase() + "\")), \"price\", TO_DATE(A" + row + ")), 2, 2) * C" + row); sheet.getRange("H" + row).setValue("=E" + row); } else { sheet.getRange("E" + row).setValue("=H" + row); sheet.getRange("H" + row).setValue("=INDEX(GOOGLEFINANCE(CONCAT(\"CURRENCY:\", CONCAT(G" + row + ", \"" + fiat.toUpperCase() + "\")), \"price\", TO_DATE(A" + row + ")), 2, 2) * F" + row); } - } else if (stable.indexOf(coin) != -1) { - if (coinName == buyCoinName) { + } else if(stable.indexOf(coin) != -1) { + if(coinName == buyCoinName) { sheet.getRange("E" + row).setValue("=C" + row); sheet.getRange("H" + row).setValue("=E" + row); } else { @@ -74,11 +74,11 @@ function setEqualFiat(sheet, coins, fiat, stable, date, buy_count, buy_coin, sel sheet.getRange("H" + row).setValue("=F" + row); } } else { - if (coinName == buyCoinName) { + if(coinName == buyCoinName) { var rate = apiRate(fiat, buy_coin, date); Logger.log("setEqualFiat:: " + [date, buy_coin, rate * buy_count]); - if (rate != undefined) { + if(rate != undefined) { sheet.getRange("E" + row).setValue(rate * buy_count); sheet.getRange("H" + row).setValue("=E" + row); } @@ -86,7 +86,7 @@ function setEqualFiat(sheet, coins, fiat, stable, date, buy_count, buy_coin, sel var rate = apiRate(fiat, sell_coin, date); Logger.log("setEqualFiat:: " + [date, sell_coin, rate * sell_count]); - if (rate != undefined) { + if(rate != undefined) { sheet.getRange("E" + row).setValue("=H" + row); sheet.getRange("H" + row).setValue(rate * sell_count); } @@ -99,8 +99,8 @@ function setEqualFiat(sheet, coins, fiat, stable, date, buy_count, buy_coin, sel */ function setFiat(sheet, coins, fiat, stable, date, count, coin, columnValue, columnTicker, columnFiat, row) { var coinName = coins[coin]; - if (coinName.toLowerCase() == "fiat" || stable.indexOf(coin) != -1) { - if (coin == fiat || stable.indexOf(coin) != -1) { + if(coinName.toLowerCase() == "fiat" || stable.indexOf(coin) != -1) { + if(coin == fiat || stable.indexOf(coin) != -1) { sheet.getRange(columnFiat + row).setValue("=" + columnValue + row); } else { sheet.getRange(columnFiat + row).setValue("=INDEX(GOOGLEFINANCE(CONCAT(\"CURRENCY:\", CONCAT(" + columnTicker + row + ", \"" + fiat.toUpperCase() + "\")), \"price\", TO_DATE(A" + row + ")), 2, 2) * " + columnValue + row); @@ -109,7 +109,7 @@ function setFiat(sheet, coins, fiat, stable, date, count, coin, columnValue, col var rate = apiRate(fiat, coin, date); Logger.log("setFiat:: " + [date, coin, rate * count]); - if (rate != undefined) { + if(rate != undefined) { sheet.getRange(columnFiat + row).setValue(rate * count); } } diff --git a/source/FluxUpdate.gs b/source/FluxUpdate.gs index dd211b6..167ef77 100644 --- a/source/FluxUpdate.gs +++ b/source/FluxUpdate.gs @@ -8,7 +8,7 @@ function updateFlux(ui) { var sheet = SpreadsheetApp.getActive(); var name = sheet.getSheetName(); - if (name.indexOf("Flux") == -1) { + if(name.indexOf("Flux") == -1) { ui.alert('Incorrect active sheet!'); return; } @@ -20,12 +20,12 @@ function updateFlux(ui) { Logger.log("updateFlux:: Request: " + [days, coin, interval]); var date; - if (coins.hasOwnProperty(coin) && isNumeric(days) && (interval == "hourly" || interval == "daily")) { + if(coins.hasOwnProperty(coin) && isNumeric(days) && (interval == "hourly" || interval == "daily")) { var chart = apiFlux(fiat, coin, days, interval); - if (chart != undefined) { + if(chart != undefined) { var r = 2; - for (var i = chart[0][1].length -1; i >= 0; i--) { + for(var i = chart[0][1].length -1; i >= 0; i--) { r++; date = new Date(chart[0][1][i][0]); @@ -36,7 +36,7 @@ function updateFlux(ui) { sheet.getRange("D" + r).setValue(chart[2][1][i][1]); Logger.log("updateFlux:: Row: " + [date, chart[0][1][i][1], chart[2][1][i][1]]); - if ((r - 2) == days) return true; + if((r - 2) == days) return true; } return; diff --git a/source/Import.gs b/source/Import.gs index 3526e87..f5ac02a 100644 --- a/source/Import.gs +++ b/source/Import.gs @@ -15,27 +15,27 @@ function importJson(url, xpath) { var patharray = xpath.split("."); Logger.log("importJson:: Path: " + patharray); - for (var i = 0; i < patharray.length; i++) { + for(var i = 0; i < patharray.length; i++) { json = json[patharray[i]]; } } - Logger.log("importJson:: Data type: " + typeof (json)); + Logger.log("importJson:: Data type: " + typeof(json)); if(typeof(json) === "undefined") { return "No node"; - } else if (typeof (json) === "object") { + } else if(typeof(json) === "object") { var tempArr = []; - for (var obj in json) { + for(var obj in json) { tempArr.push([obj, json[obj]]); } return tempArr; - } else if (typeof (json) !== "object") { + } else if(typeof(json) !== "object") { return json; } - } catch (err) { + } catch(err) { Logger.log("importJson:: " + err); return; } @@ -49,7 +49,7 @@ function importJson(url, xpath) { */ function importUrl(url) { var cached = getCache(url); - if (cached != null) { + if(cached != null) { Logger.log("importUrl:: Cache found for: " + url); return cached; } @@ -68,6 +68,6 @@ function importUrl(url) { * Debug method. */ function importJsonDebug() { - enableCache(); + disableCache(); Logger.log(importJson("https://api.coingecko.com/api/v3/coins/markets?vs_currency=usd&ids=bitcoin", "0.total_volume")); } diff --git a/source/Init.gs b/source/Init.gs index 969f6bf..cd2d07b 100644 --- a/source/Init.gs +++ b/source/Init.gs @@ -25,9 +25,9 @@ function menuUpdateCoins() { 'Do you want to update Coins sheet?', ui.ButtonSet.OK_CANCEL); - if (result == ui.Button.OK) { + if(result == ui.Button.OK) { var ok = updateCoins(); - if (!ok) { + if(!ok) { ui.alert('Error updating coins!'); } } @@ -43,7 +43,7 @@ function menuAddFiatValues() { 'Do you want to populate FIAT values in Trades sheet?', ui.ButtonSet.OK_CANCEL); - if (result == ui.Button.OK) { + if(result == ui.Button.OK) { addFiatValues(); } } @@ -58,7 +58,7 @@ function menuUpdateFlux() { 'Do you want to update current Flux sheet?', ui.ButtonSet.OK_CANCEL); - if (result == ui.Button.OK) { + if(result == ui.Button.OK) { updateFlux(ui); } } \ No newline at end of file diff --git a/source/Settings.gs b/source/Settings.gs index 1aa5019..04d64f7 100644 --- a/source/Settings.gs +++ b/source/Settings.gs @@ -14,6 +14,21 @@ function getCoinNames() { return coins.toLowerCase().replace(/\s/g, "-"); } +/** + * Gets tickers. + */ +function getTickers() { + var active = SpreadsheetApp.getActive(); + var sheet = active.getSheetByName("Coins"); + var coins = sheet + .getRange("A3:A") + .getValues() + .filter(String) + .join(","); + + return coins.toLowerCase(); +} + /** * Gets coin tickers and names. *

Will lowercase and hyphenate spaces for CoinGeko request. @@ -28,11 +43,11 @@ function getCoins() { var coins = {}; - for (i = 0; i < rows.length; i++) { + for(i = 0; i < rows.length; i++) { var ticker = rows[i][0].toLowerCase(); var name = rows[i][1].toLowerCase().replace(/\s/g, "-"); - if (ticker !== "" && name !== "") { + if(ticker !== "" && name !== "") { coins[ticker] = name; } } @@ -63,7 +78,7 @@ function getStableCoins() { .filter(String); var fiat = getFiat(); - if (coins.indexOf(fiat) === -1) { + if(coins.indexOf(fiat) === -1) { coins.push(fiat); } diff --git a/source/Util.gs b/source/Util.gs index 8709707..6216431 100644 --- a/source/Util.gs +++ b/source/Util.gs @@ -1,7 +1,7 @@ /** * Pad left Number prototype. */ -Number.prototype.padLeft = function (n, str) { +Number.prototype.padLeft = function(n, str) { return Array(n - String(this).length + 1).join(str || '0') + this; }