From d1704615df3e9fc6ec71c6df1e44fdcc58f3aede Mon Sep 17 00:00:00 2001 From: Chris Berkhout Date: Sat, 17 Jul 2021 18:39:59 +0200 Subject: [PATCH] Improve coinmarketcap error handling and add tests. --- src/pricehist/exceptions.py | 10 +- src/pricehist/sources/coinmarketcap.py | 130 +++++- tests/pricehist/sources/test_coinmarketcap.py | 430 ++++++++++++++++++ .../test_coinmarketcap/crypto-partial.json | 34 ++ .../test_coinmarketcap/fiat-partial.json | 30 ++ .../long-btc-aud-partial.json | 255 +++++++++++ .../test_coinmarketcap/recent-btc-aud.json | 136 ++++++ .../test_coinmarketcap/recent-btc-id2782.json | 136 ++++++ .../test_coinmarketcap/recent-id1-aud.json | 136 ++++++ .../test_coinmarketcap/recent-id1-id2782.json | 136 ++++++ 10 files changed, 1406 insertions(+), 27 deletions(-) create mode 100644 tests/pricehist/sources/test_coinmarketcap.py create mode 100644 tests/pricehist/sources/test_coinmarketcap/crypto-partial.json create mode 100644 tests/pricehist/sources/test_coinmarketcap/fiat-partial.json create mode 100644 tests/pricehist/sources/test_coinmarketcap/long-btc-aud-partial.json create mode 100644 tests/pricehist/sources/test_coinmarketcap/recent-btc-aud.json create mode 100644 tests/pricehist/sources/test_coinmarketcap/recent-btc-id2782.json create mode 100644 tests/pricehist/sources/test_coinmarketcap/recent-id1-aud.json create mode 100644 tests/pricehist/sources/test_coinmarketcap/recent-id1-id2782.json diff --git a/src/pricehist/exceptions.py b/src/pricehist/exceptions.py index e393b97..1e0f0eb 100644 --- a/src/pricehist/exceptions.py +++ b/src/pricehist/exceptions.py @@ -5,17 +5,19 @@ class SourceError(Exception): class InvalidPair(SourceError, ValueError): """An invalid pair was requested.""" - def __init__(self, base, quote, source): + def __init__(self, base, quote, source, message=None): self.base = base self.quote = quote self.source = source pair = "/".join([base, quote]) - message = ( - f"Invalid pair '{pair}'. " + insert = message + " " if message else "" + + full_message = ( + f"Invalid pair '{pair}'. {insert}" f"Run 'pricehist source {source.id()} --symbols' " f"for information about valid pairs." ) - super(InvalidPair, self).__init__(message) + super(InvalidPair, self).__init__(full_message) class InvalidType(SourceError, ValueError): diff --git a/src/pricehist/sources/coinmarketcap.py b/src/pricehist/sources/coinmarketcap.py index 2d6692b..313c493 100644 --- a/src/pricehist/sources/coinmarketcap.py +++ b/src/pricehist/sources/coinmarketcap.py @@ -5,6 +5,7 @@ from decimal import Decimal import requests +from pricehist import exceptions from pricehist.price import Price from .basesource import BaseSource @@ -47,10 +48,13 @@ class CoinMarketCap(BaseSource): return list(zip(ids, descriptions)) def fetch(self, series): + if series.base == "ID=" or not series.quote or series.quote == "ID=": + raise exceptions.InvalidPair(series.base, series.quote, self) + data = self._data(series) prices = [] - for item in data["data"]["quotes"]: + for item in data.get("quotes", []): d = item["time_open"][0:10] amount = self._amount(next(iter(item["quote"].values())), series.type) prices.append(Price(d, amount)) @@ -82,19 +86,73 @@ class CoinMarketCap(BaseSource): .replace(tzinfo=timezone.utc) .timestamp() ) + - 24 * 60 * 60 + # Start one period earlier since the start is exclusive. ) - params["time_end"] = ( - int( - datetime.strptime(series.end, "%Y-%m-%d") - .replace(tzinfo=timezone.utc) - .timestamp() + params["time_end"] = int( + datetime.strptime(series.end, "%Y-%m-%d") + .replace(tzinfo=timezone.utc) + .timestamp() + ) # Don't round up since it's inclusive of the period covering the end time. + + try: + response = self.log_curl(requests.get(url, params=params)) + except Exception as e: + raise exceptions.RequestError(str(e)) from e + + code = response.status_code + text = response.text + + if code == 400 and "No items found." in text: + raise exceptions.InvalidPair( + series.base, series.quote, self, "Bad base ID." ) - + 24 * 60 * 60 - ) # round up to include the last day - response = self.log_curl(requests.get(url, params=params)) + elif code == 400 and 'Invalid value for \\"convert_id\\"' in text: + raise exceptions.InvalidPair( + series.base, series.quote, self, "Bad quote ID." + ) - return json.loads(response.content) + elif code == 400 and 'Invalid value for \\"convert\\"' in text: + raise exceptions.InvalidPair( + series.base, series.quote, self, "Bad quote symbol." + ) + + elif code == 400 and "must be older than" in text: + if series.start <= series.end: + raise exceptions.BadResponse("The start date must be in the past.") + else: + raise exceptions.BadResponse( + "The start date must preceed or match the end date." + ) + + elif ( + code == 400 + and "must be a valid ISO 8601 timestamp or unix time" in text + and series.start < "2001-09-11" + ): + raise exceptions.BadResponse("The start date can't preceed 2001-09-11.") + + try: + response.raise_for_status() + except Exception as e: + raise exceptions.BadResponse(str(e)) from e + + try: + parsed = json.loads(response.content) + except Exception as e: + raise exceptions.ResponseParsingError(str(e)) from e + + if type(parsed) != dict or "data" not in parsed: + raise exceptions.ResponseParsingError("Unexpected content.") + + elif len(parsed["data"]) == 0: + raise exceptions.ResponseParsingError( + "The data section was empty. This can happen when the quote " + "currency symbol can't be found, and potentially for other reasons." + ) + + return parsed["data"] def _amount(self, data, type): if type in ["mid"]: @@ -105,26 +163,52 @@ class CoinMarketCap(BaseSource): return Decimal(str(data[type])) def _output_pair(self, base, quote, data): - data_base = data["data"]["symbol"] - data_quote = next(iter(data["data"]["quotes"][0]["quote"].keys())) + data_base = data["symbol"] - lookup_quote = False + data_quote = None + if len(data["quotes"]) > 0: + data_quote = next(iter(data["quotes"][0]["quote"].keys())) + + lookup_quote = None if quote.startswith("ID="): symbols = {i["id"]: (i["symbol"] or i["code"]) for i in self._symbol_data()} lookup_quote = symbols[int(quote[3:])] output_base = data_base - output_quote = lookup_quote or data_quote + output_quote = lookup_quote or data_quote or quote return (output_base, output_quote) def _symbol_data(self): - fiat_url = "https://web-api.coinmarketcap.com/v1/fiat/map?include_metals=true" - fiat_res = self.log_curl(requests.get(fiat_url)) - fiat = json.loads(fiat_res.content) - crypto_url = ( - "https://web-api.coinmarketcap.com/v1/cryptocurrency/map?sort=cmc_rank" - ) - crypto_res = self.log_curl(requests.get(crypto_url)) - crypto = json.loads(crypto_res.content) - return crypto["data"] + fiat["data"] + base_url = "https://web-api.coinmarketcap.com/v1/" + fiat_url = f"{base_url}fiat/map?include_metals=true" + crypto_url = f"{base_url}cryptocurrency/map?sort=cmc_rank" + + fiat = self._get_json_data(fiat_url) + crypto = self._get_json_data(crypto_url) + + return crypto + fiat + + def _get_json_data(self, url, params={}): + try: + response = self.log_curl(requests.get(url, params=params)) + except Exception as e: + raise exceptions.RequestError(str(e)) from e + + try: + response.raise_for_status() + except Exception as e: + raise exceptions.BadResponse(str(e)) from e + + try: + parsed = json.loads(response.content) + except Exception as e: + raise exceptions.ResponseParsingError(str(e)) from e + + if type(parsed) != dict or "data" not in parsed: + raise exceptions.ResponseParsingError("Unexpected content.") + + elif len(parsed["data"]) == 0: + raise exceptions.ResponseParsingError("Empty data section.") + + return parsed["data"] diff --git a/tests/pricehist/sources/test_coinmarketcap.py b/tests/pricehist/sources/test_coinmarketcap.py new file mode 100644 index 0000000..6ae175c --- /dev/null +++ b/tests/pricehist/sources/test_coinmarketcap.py @@ -0,0 +1,430 @@ +import logging +import os +from datetime import datetime, timezone +from decimal import Decimal +from pathlib import Path + +import pytest +import requests +import responses + +from pricehist import exceptions +from pricehist.price import Price +from pricehist.series import Series +from pricehist.sources.coinmarketcap import CoinMarketCap + + +def timestamp(date): + return int( + datetime.strptime(date, "%Y-%m-%d").replace(tzinfo=timezone.utc).timestamp() + ) + + +@pytest.fixture +def src(): + return CoinMarketCap() + + +@pytest.fixture +def type(src): + return src.types()[0] + + +@pytest.fixture +def requests_mock(): + with responses.RequestsMock() as mock: + yield mock + + +crypto_url = "https://web-api.coinmarketcap.com/v1/cryptocurrency/map?sort=cmc_rank" +fiat_url = "https://web-api.coinmarketcap.com/v1/fiat/map?include_metals=true" +fetch_url = "https://web-api.coinmarketcap.com/v1/cryptocurrency/ohlcv/historical" + + +@pytest.fixture +def crypto_ok(requests_mock): + json = (Path(os.path.splitext(__file__)[0]) / "crypto-partial.json").read_text() + requests_mock.add(responses.GET, crypto_url, body=json, status=200) + yield requests_mock + + +@pytest.fixture +def fiat_ok(requests_mock): + json = (Path(os.path.splitext(__file__)[0]) / "fiat-partial.json").read_text() + requests_mock.add(responses.GET, fiat_url, body=json, status=200) + yield requests_mock + + +@pytest.fixture +def recent_id_id_ok(requests_mock): + json = (Path(os.path.splitext(__file__)[0]) / "recent-id1-id2782.json").read_text() + requests_mock.add(responses.GET, fetch_url, body=json, status=200) + yield requests_mock + + +@pytest.fixture +def recent_id_sym_ok(requests_mock): + json = (Path(os.path.splitext(__file__)[0]) / "recent-id1-aud.json").read_text() + requests_mock.add(responses.GET, fetch_url, body=json, status=200) + yield requests_mock + + +@pytest.fixture +def recent_sym_id_ok(requests_mock): + json = (Path(os.path.splitext(__file__)[0]) / "recent-btc-id2782.json").read_text() + requests_mock.add(responses.GET, fetch_url, body=json, status=200) + yield requests_mock + + +@pytest.fixture +def recent_sym_sym_ok(requests_mock): + json = (Path(os.path.splitext(__file__)[0]) / "recent-btc-aud.json").read_text() + requests_mock.add(responses.GET, fetch_url, body=json, status=200) + yield requests_mock + + +@pytest.fixture +def long_sym_sym_ok(requests_mock): + json = ( + Path(os.path.splitext(__file__)[0]) / "long-btc-aud-partial.json" + ).read_text() + requests_mock.add(responses.GET, fetch_url, body=json, status=200) + yield requests_mock + + +def test_normalizesymbol(src): + assert src.normalizesymbol("btc") == "BTC" + assert src.normalizesymbol("id=1") == "ID=1" + + +def test_metadata(src): + assert isinstance(src.id(), str) + assert len(src.id()) > 0 + + assert isinstance(src.name(), str) + assert len(src.name()) > 0 + + assert isinstance(src.description(), str) + assert len(src.description()) > 0 + + assert isinstance(src.source_url(), str) + assert src.source_url().startswith("http") + + assert datetime.strptime(src.start(), "%Y-%m-%d") + + assert isinstance(src.types(), list) + assert len(src.types()) > 0 + assert isinstance(src.types()[0], str) + assert len(src.types()[0]) > 0 + + assert isinstance(src.notes(), str) + + +def test_symbols(src, crypto_ok, fiat_ok): + syms = src.symbols() + assert ("id=1", "BTC Bitcoin") in syms + assert ("id=2782", "AUD Australian Dollar") in syms + assert len(syms) > 2 + + +def test_symbols_requests_logged(src, crypto_ok, fiat_ok, caplog): + with caplog.at_level(logging.DEBUG): + src.symbols() + logged_requests = 0 + for r in caplog.records: + if r.levelname == "DEBUG" and " curl " in r.message: + logged_requests += 1 + assert logged_requests == 2 + + +def test_symbols_fiat_not_found(src, requests_mock): + requests_mock.add(responses.GET, fiat_url, body="{}", status=200) + with pytest.raises(exceptions.ResponseParsingError) as e: + src.symbols() + assert "Unexpected content" in str(e.value) + + +def test_symbols_fiat_network_issue(src, requests_mock): + requests_mock.add( + responses.GET, + fiat_url, + body=requests.exceptions.ConnectionError("Network issue"), + ) + with pytest.raises(exceptions.RequestError) as e: + src.symbols() + assert "Network issue" in str(e.value) + + +def test_symbols_fiat_bad_status(src, requests_mock): + requests_mock.add(responses.GET, fiat_url, status=500) + with pytest.raises(exceptions.BadResponse) as e: + src.symbols() + assert "Server Error" in str(e.value) + + +def test_symbols_fiat_parsing_error(src, requests_mock): + requests_mock.add(responses.GET, fiat_url, body="NOT JSON") + with pytest.raises(exceptions.ResponseParsingError) as e: + src.symbols() + assert "while parsing data" in str(e.value) + + +def test_symbols_crypto_not_found(src, requests_mock, fiat_ok): + requests_mock.add(responses.GET, crypto_url, body="{}", status=200) + with pytest.raises(exceptions.ResponseParsingError) as e: + src.symbols() + assert "Unexpected content" in str(e.value) + + +def test_symbols_crypto_network_issue(src, requests_mock, fiat_ok): + requests_mock.add( + responses.GET, + crypto_url, + body=requests.exceptions.ConnectionError("Network issue"), + ) + with pytest.raises(exceptions.RequestError) as e: + src.symbols() + assert "Network issue" in str(e.value) + + +def test_symbols_crypto_bad_status(src, requests_mock, fiat_ok): + requests_mock.add(responses.GET, crypto_url, status=500) + with pytest.raises(exceptions.BadResponse) as e: + src.symbols() + assert "Server Error" in str(e.value) + + +def test_symbols_crypto_parsing_error(src, requests_mock, fiat_ok): + requests_mock.add(responses.GET, crypto_url, body="NOT JSON") + with pytest.raises(exceptions.ResponseParsingError) as e: + src.symbols() + assert "while parsing data" in str(e.value) + + +def test_fetch_known_pair_id_id(src, type, recent_id_id_ok, crypto_ok, fiat_ok): + series = src.fetch(Series("ID=1", "ID=2782", type, "2021-01-01", "2021-01-07")) + req = recent_id_id_ok.calls[0].request + assert req.params["id"] == "1" + assert req.params["convert_id"] == "2782" + assert (series.base, series.quote) == ("BTC", "AUD") + assert len(series.prices) == 7 + + +def test_fetch_known_pair_id_sym(src, type, recent_id_sym_ok): + series = src.fetch(Series("ID=1", "AUD", type, "2021-01-01", "2021-01-07")) + req = recent_id_sym_ok.calls[0].request + assert req.params["id"] == "1" + assert req.params["convert"] == "AUD" + assert (series.base, series.quote) == ("BTC", "AUD") + assert len(series.prices) == 7 + + +def test_fetch_known_pair_sym_id(src, type, recent_sym_id_ok, crypto_ok, fiat_ok): + series = src.fetch(Series("BTC", "ID=2782", type, "2021-01-01", "2021-01-07")) + req = recent_sym_id_ok.calls[0].request + assert req.params["symbol"] == "BTC" + assert req.params["convert_id"] == "2782" + assert (series.base, series.quote) == ("BTC", "AUD") + assert len(series.prices) == 7 + + +def test_fetch_known_pair_sym_sym(src, type, recent_sym_sym_ok): + series = src.fetch(Series("BTC", "AUD", type, "2021-01-01", "2021-01-07")) + req = recent_sym_sym_ok.calls[0].request + assert req.params["symbol"] == "BTC" + assert req.params["convert"] == "AUD" + assert len(series.prices) == 7 + + +def test_fetch_requests_and_receives_correct_times( + src, type, recent_id_id_ok, crypto_ok, fiat_ok +): + series = src.fetch(Series("ID=1", "ID=2782", type, "2021-01-01", "2021-01-07")) + req = recent_id_id_ok.calls[0].request + assert req.params["time_start"] == str(timestamp("2020-12-31")) # back one period + assert req.params["time_end"] == str(timestamp("2021-01-07")) + assert series.prices[0] == Price("2021-01-01", Decimal("37914.350602379853")) + assert series.prices[-1] == Price("2021-01-07", Decimal("49370.064689585612")) + + +def test_fetch_requests_logged(src, type, recent_sym_sym_ok, caplog): + with caplog.at_level(logging.DEBUG): + src.fetch(Series("BTC", "AUD", type, "2021-01-01", "2021-01-07")) + assert any( + ["DEBUG" == r.levelname and " curl " in r.message for r in caplog.records] + ) + + +def test_fetch_types_all_available(src, recent_sym_sym_ok): + mid = src.fetch(Series("BTC", "AUD", "mid", "2021-01-01", "2021-01-07")) + opn = src.fetch(Series("BTC", "AUD", "open", "2021-01-01", "2021-01-07")) + hgh = src.fetch(Series("BTC", "AUD", "high", "2021-01-01", "2021-01-07")) + low = src.fetch(Series("BTC", "AUD", "low", "2021-01-01", "2021-01-07")) + cls = src.fetch(Series("BTC", "AUD", "close", "2021-01-01", "2021-01-07")) + assert mid.prices[0].amount == Decimal("37914.350602379853") + assert opn.prices[0].amount == Decimal("37658.83948707033") + assert hgh.prices[0].amount == Decimal("38417.9137031205") + assert low.prices[0].amount == Decimal("37410.787501639206") + assert cls.prices[0].amount == Decimal("38181.99133300758") + + +def test_fetch_type_mid_is_mean_of_low_and_high(src, recent_sym_sym_ok): + mid = src.fetch(Series("BTC", "AUD", "mid", "2021-01-01", "2021-01-07")).prices + low = src.fetch(Series("BTC", "AUD", "low", "2021-01-01", "2021-01-07")).prices + hgh = src.fetch(Series("BTC", "AUD", "high", "2021-01-01", "2021-01-07")).prices + assert all( + [ + mid[i].amount == (sum([low[i].amount, hgh[i].amount]) / 2) + for i in range(0, 7) + ] + ) + + +def test_fetch_long_hist_from_start(src, type, long_sym_sym_ok): + series = src.fetch(Series("BTC", "AUD", type, src.start(), "2021-01-07")) + assert series.prices[0] == Price("2013-04-28", Decimal("130.45956234123247")) + assert series.prices[-1] == Price("2021-01-07", Decimal("49370.064689585612")) + assert len(series.prices) > 13 + + +def test_fetch_from_before_start(src, type, requests_mock): + requests_mock.add( + responses.GET, + fetch_url, + status=400, + body="""{ "status": { "error_code": 400, "error_message": + "\\"time_start\\" must be a valid ISO 8601 timestamp or unix time value", + } }""", + ) + with pytest.raises(exceptions.BadResponse) as e: + src.fetch(Series("BTC", "AUD", type, "2001-09-10", "2001-10-01")) + assert "start date can't preceed" in str(e.value) + + +def test_fetch_to_future(src, type, recent_sym_sym_ok): + series = src.fetch(Series("BTC", "AUD", type, "2021-01-01", "2100-01-01")) + assert len(series.prices) > 0 + + +def test_fetch_in_future(src, type, requests_mock): + requests_mock.add( + responses.GET, + fetch_url, + status=400, + body="""{ + "status": { + "error_code": 400, + "error_message": "\\"time_start\\" must be older than \\"time_end\\"." + } + }""", + ) + with pytest.raises(exceptions.BadResponse) as e: + src.fetch(Series("BTC", "AUD", type, "2030-01-01", "2030-01-07")) + assert "start date must be in the past" in str(e.value) + + +def test_fetch_empty(src, type, requests_mock): + requests_mock.add( + responses.GET, + fetch_url, + body="""{ + "status": { + "error_code": 0, + "error_message": null + }, + "data": { + "id": 1, + "name": "Bitcoin", + "symbol": "BTC", + "quotes": [] + } + }""", + ) + series = src.fetch(Series("BTC", "AUD", type, "2010-01-01", "2010-01-07")) + assert len(series.prices) == 0 + + +def test_fetch_bad_base_sym(src, type, requests_mock): + requests_mock.add(responses.GET, fetch_url, body='{"data":{}}') + with pytest.raises(exceptions.ResponseParsingError) as e: + src.fetch(Series("NOTABASE", "USD", type, "2021-01-01", "2021-01-07")) + assert "quote currency symbol can't be found" in str(e.value) + assert "other reasons" in str(e.value) + + +def test_fetch_bad_quote_sym(src, type, requests_mock): + requests_mock.add( + responses.GET, + fetch_url, + status=400, + body="""{ + "status": { + "error_code": 400, + "error_message": "Invalid value for \\"convert\\": \\"NOTAQUOTE\\"" + } + }""", + ) + with pytest.raises(exceptions.InvalidPair) as e: + src.fetch(Series("BTC", "NOTAQUOTE", type, "2021-01-01", "2021-01-07")) + assert "Bad quote symbol" in str(e.value) + + +def test_fetch_bad_base_id(src, type, requests_mock): + requests_mock.add( + responses.GET, + fetch_url, + status=400, + body="""{ + "status": { + "error_code": 400, + "error_message": "No items found." + } + }""", + ) + with pytest.raises(exceptions.InvalidPair) as e: + src.fetch(Series("ID=20000", "USD", type, "2021-01-01", "2021-01-07")) + assert "Bad base ID" in str(e.value) + + +def test_fetch_bad_quote_id(src, type, requests_mock): + requests_mock.add( + responses.GET, + fetch_url, + status=400, + body="""{ + "status": { + "error_code": 400, + "error_message": "Invalid value for \\"convert_id\\": \\"20000\\"" + } + }""", + ) + with pytest.raises(exceptions.InvalidPair) as e: + src.fetch(Series("BTC", "ID=20000", type, "2021-01-01", "2021-01-07")) + assert "Bad quote ID" in str(e.value) + + +def test_fetch_no_quote(src, type): + with pytest.raises(exceptions.InvalidPair): + src.fetch(Series("BTC", "", type, "2021-01-01", "2021-01-07")) + + +def test_fetch_network_issue(src, type, requests_mock): + body = requests.exceptions.ConnectionError("Network issue") + requests_mock.add(responses.GET, fetch_url, body=body) + with pytest.raises(exceptions.RequestError) as e: + src.fetch(Series("BTC", "AUD", type, "2021-01-01", "2021-01-07")) + assert "Network issue" in str(e.value) + + +def test_fetch_bad_status(src, type, requests_mock): + requests_mock.add(responses.GET, fetch_url, status=500, body="Some other reason") + with pytest.raises(exceptions.BadResponse) as e: + src.fetch(Series("BTC", "AUD", type, "2021-01-01", "2021-01-07")) + assert "Internal Server Error" in str(e.value) + + +def test_fetch_parsing_error(src, type, requests_mock): + requests_mock.add(responses.GET, fetch_url, body="NOT JSON") + with pytest.raises(exceptions.ResponseParsingError) as e: + src.fetch(Series("BTC", "AUD", type, "2021-01-01", "2021-01-07")) + assert "while parsing data" in str(e.value) diff --git a/tests/pricehist/sources/test_coinmarketcap/crypto-partial.json b/tests/pricehist/sources/test_coinmarketcap/crypto-partial.json new file mode 100644 index 0000000..c1a27b5 --- /dev/null +++ b/tests/pricehist/sources/test_coinmarketcap/crypto-partial.json @@ -0,0 +1,34 @@ +{ + "status": { + "timestamp": "2021-07-16T10:08:28.938Z", + "error_code": 0, + "error_message": null, + "elapsed": 18, + "credit_count": 0, + "notice": null + }, + "data": [ + { + "id": 1, + "name": "Bitcoin", + "symbol": "BTC", + "slug": "bitcoin", + "rank": 1, + "is_active": 1, + "first_historical_data": "2013-04-28T18:47:21.000Z", + "last_historical_data": "2021-07-16T09:59:03.000Z", + "platform": null + }, + { + "id": 1027, + "name": "Ethereum", + "symbol": "ETH", + "slug": "ethereum", + "rank": 2, + "is_active": 1, + "first_historical_data": "2015-08-07T14:49:30.000Z", + "last_historical_data": "2021-07-16T09:59:04.000Z", + "platform": null + } + ] +} diff --git a/tests/pricehist/sources/test_coinmarketcap/fiat-partial.json b/tests/pricehist/sources/test_coinmarketcap/fiat-partial.json new file mode 100644 index 0000000..781824b --- /dev/null +++ b/tests/pricehist/sources/test_coinmarketcap/fiat-partial.json @@ -0,0 +1,30 @@ +{ + "status": { + "timestamp": "2021-07-16T10:08:13.272Z", + "error_code": 0, + "error_message": null, + "elapsed": 1, + "credit_count": 0, + "notice": null + }, + "data": [ + { + "id": 2781, + "name": "United States Dollar", + "sign": "$", + "symbol": "USD" + }, + { + "id": 2782, + "name": "Australian Dollar", + "sign": "$", + "symbol": "AUD" + }, + { + "id": 3575, + "name": "Gold Troy Ounce", + "symbol": "", + "code": "XAU" + } + ] +} diff --git a/tests/pricehist/sources/test_coinmarketcap/long-btc-aud-partial.json b/tests/pricehist/sources/test_coinmarketcap/long-btc-aud-partial.json new file mode 100644 index 0000000..0b11696 --- /dev/null +++ b/tests/pricehist/sources/test_coinmarketcap/long-btc-aud-partial.json @@ -0,0 +1,255 @@ +{ + "status": { + "timestamp": "2021-07-17T16:16:11.926Z", + "error_code": 0, + "error_message": null, + "elapsed": 2262, + "credit_count": 0, + "notice": null + }, + "data": { + "id": 1, + "name": "Bitcoin", + "symbol": "BTC", + "quotes": [ + { + "time_open": "2013-04-28T00:00:00.000Z", + "time_close": "2013-04-28T23:59:59.999Z", + "time_high": "2013-04-28T18:50:02.000Z", + "time_low": "2013-04-28T20:15:02.000Z", + "quote": { + "AUD": { + "open": null, + "high": 132.39216797540558, + "low": 128.52695670705936, + "close": 130.52908647526473, + "volume": 0, + "market_cap": 1447740447.626921, + "timestamp": "2013-04-28T23:59:00.000Z" + } + } + }, + { + "time_open": "2013-04-29T00:00:00.000Z", + "time_close": "2013-04-29T23:59:59.999Z", + "time_high": "2013-04-29T13:15:01.000Z", + "time_low": "2013-04-29T05:20:01.000Z", + "quote": { + "AUD": { + "open": 130.75666236543535, + "high": 142.67970067891736, + "low": 129.9456943366951, + "close": 139.77370978254794, + "volume": 0, + "market_cap": 1550883729.329852, + "timestamp": "2013-04-29T23:59:00.000Z" + } + } + }, + { + "time_open": "2013-04-30T00:00:00.000Z", + "time_close": "2013-04-30T23:59:59.999Z", + "time_high": "2013-04-30T08:25:02.000Z", + "time_low": "2013-04-30T18:55:01.000Z", + "quote": { + "AUD": { + "open": 139.2515230635335, + "high": 141.93391873626476, + "low": 129.37940647790543, + "close": 134.06635802469137, + "volume": 0, + "market_cap": 1488052782.6003087, + "timestamp": "2013-04-30T23:59:00.000Z" + } + } + }, + { + "time_open": "2013-05-01T00:00:00.000Z", + "time_close": "2013-05-01T23:59:59.999Z", + "time_high": "2013-05-01T00:15:01.000Z", + "time_low": "2013-05-01T19:55:01.000Z", + "quote": { + "AUD": { + "open": 134.06635802469137, + "high": 134.88573849160971, + "low": 104.93911468163968, + "close": 113.79243056489595, + "volume": 0, + "market_cap": 1263451603.6864119, + "timestamp": "2013-05-01T23:59:00.000Z" + } + } + }, + { + "time_open": "2013-05-02T00:00:00.000Z", + "time_close": "2013-05-02T23:59:59.999Z", + "time_high": "2013-05-02T14:25:01.000Z", + "time_low": "2013-05-02T14:30:02.000Z", + "quote": { + "AUD": { + "open": 113.19910247390133, + "high": 122.60835462135991, + "low": 90.08385249759387, + "close": 102.63388848353591, + "volume": 0, + "market_cap": 1139905858.2089553, + "timestamp": "2013-05-02T23:59:00.000Z" + } + } + }, + { + "time_open": "2013-05-03T00:00:00.000Z", + "time_close": "2013-05-03T23:59:59.999Z", + "time_high": "2013-05-03T05:30:02.000Z", + "time_low": "2013-05-03T03:05:01.000Z", + "quote": { + "AUD": { + "open": 103.64842454394694, + "high": 105.43929629649027, + "low": 77.03544845551335, + "close": 94.77409346519293, + "volume": 0, + "market_cap": 1052933070.3412836, + "timestamp": "2013-05-03T23:59:00.000Z" + } + } + }, + { + "time_open": "2013-05-04T00:00:00.000Z", + "time_close": "2013-05-04T23:59:59.999Z", + "time_high": "2013-05-04T07:15:01.000Z", + "time_low": "2013-05-04T06:50:01.000Z", + "quote": { + "AUD": { + "open": 95.11343656595025, + "high": 111.49893348846227, + "low": 89.68392476245879, + "close": 109.07504363001745, + "volume": 0, + "market_cap": 1212251854.2757416, + "timestamp": "2013-05-04T23:59:00.000Z" + } + } + }, + { + "time_open": "2021-01-01T00:00:00.000Z", + "time_close": "2021-01-01T23:59:59.999Z", + "time_high": "2021-01-01T12:38:43.000Z", + "time_low": "2021-01-01T00:16:43.000Z", + "quote": { + "AUD": { + "open": 37658.83948707033, + "high": 38417.9137031205, + "low": 37410.787501639206, + "close": 38181.99133300758, + "volume": 52943282221.028366, + "market_cap": 709720173049.5383, + "timestamp": "2021-01-01T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-02T00:00:00.000Z", + "time_close": "2021-01-02T23:59:59.999Z", + "time_high": "2021-01-02T19:49:42.000Z", + "time_low": "2021-01-02T00:31:44.000Z", + "quote": { + "AUD": { + "open": 38184.98611600682, + "high": 43096.681197423015, + "low": 37814.17187096531, + "close": 41760.62923079505, + "volume": 88214867181.97835, + "market_cap": 776278147177.8037, + "timestamp": "2021-01-02T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-03T00:00:00.000Z", + "time_close": "2021-01-03T23:59:59.999Z", + "time_high": "2021-01-03T07:47:38.000Z", + "time_low": "2021-01-03T00:20:45.000Z", + "quote": { + "AUD": { + "open": 41763.41015117659, + "high": 44985.93247585023, + "low": 41663.204350601605, + "close": 42511.10646879765, + "volume": 102011582370.28117, + "market_cap": 790270288834.0249, + "timestamp": "2021-01-03T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-04T00:00:00.000Z", + "time_close": "2021-01-04T23:59:59.999Z", + "time_high": "2021-01-04T04:07:42.000Z", + "time_low": "2021-01-04T10:19:42.000Z", + "quote": { + "AUD": { + "open": 42548.61349648768, + "high": 43360.96165147421, + "low": 37133.98436952697, + "close": 41686.38761359174, + "volume": 105824510346.65779, + "market_cap": 774984045201.7122, + "timestamp": "2021-01-04T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-05T00:00:00.000Z", + "time_close": "2021-01-05T23:59:59.999Z", + "time_high": "2021-01-05T22:44:35.000Z", + "time_low": "2021-01-05T06:16:41.000Z", + "quote": { + "AUD": { + "open": 41693.07321807638, + "high": 44403.79487147647, + "low": 39221.81167941294, + "close": 43790.067253370056, + "volume": 87016490203.50436, + "market_cap": 814135603090.2502, + "timestamp": "2021-01-05T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-06T00:00:00.000Z", + "time_close": "2021-01-06T23:59:59.999Z", + "time_high": "2021-01-06T23:57:36.000Z", + "time_low": "2021-01-06T00:25:38.000Z", + "quote": { + "AUD": { + "open": 43817.35864984641, + "high": 47186.65232598287, + "low": 43152.60281764236, + "close": 47115.85365360005, + "volume": 96330948324.8061, + "market_cap": 876019742889.9551, + "timestamp": "2021-01-06T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-07T00:00:00.000Z", + "time_close": "2021-01-07T23:59:59.999Z", + "time_high": "2021-01-07T18:17:42.000Z", + "time_low": "2021-01-07T08:25:51.000Z", + "quote": { + "AUD": { + "open": 47128.02139328098, + "high": 51833.478207775144, + "low": 46906.65117139608, + "close": 50686.90986207153, + "volume": 109124136558.20264, + "market_cap": 942469208700.134, + "timestamp": "2021-01-07T23:59:06.000Z" + } + } + } + ] + } +} diff --git a/tests/pricehist/sources/test_coinmarketcap/recent-btc-aud.json b/tests/pricehist/sources/test_coinmarketcap/recent-btc-aud.json new file mode 100644 index 0000000..342b824 --- /dev/null +++ b/tests/pricehist/sources/test_coinmarketcap/recent-btc-aud.json @@ -0,0 +1,136 @@ +{ + "status": { + "timestamp": "2021-07-16T10:42:32.013Z", + "error_code": 0, + "error_message": null, + "elapsed": 20, + "credit_count": 0, + "notice": null + }, + "data": { + "id": 1, + "name": "Bitcoin", + "symbol": "BTC", + "quotes": [ + { + "time_open": "2021-01-01T00:00:00.000Z", + "time_close": "2021-01-01T23:59:59.999Z", + "time_high": "2021-01-01T12:38:43.000Z", + "time_low": "2021-01-01T00:16:43.000Z", + "quote": { + "AUD": { + "open": 37658.83948707033, + "high": 38417.9137031205, + "low": 37410.787501639206, + "close": 38181.99133300758, + "volume": 52943282221.028366, + "market_cap": 709720173049.5383, + "timestamp": "2021-01-01T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-02T00:00:00.000Z", + "time_close": "2021-01-02T23:59:59.999Z", + "time_high": "2021-01-02T19:49:42.000Z", + "time_low": "2021-01-02T00:31:44.000Z", + "quote": { + "AUD": { + "open": 38184.98611600682, + "high": 43096.681197423015, + "low": 37814.17187096531, + "close": 41760.62923079505, + "volume": 88214867181.97835, + "market_cap": 776278147177.8037, + "timestamp": "2021-01-02T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-03T00:00:00.000Z", + "time_close": "2021-01-03T23:59:59.999Z", + "time_high": "2021-01-03T07:47:38.000Z", + "time_low": "2021-01-03T00:20:45.000Z", + "quote": { + "AUD": { + "open": 41763.41015117659, + "high": 44985.93247585023, + "low": 41663.204350601605, + "close": 42511.10646879765, + "volume": 102011582370.28117, + "market_cap": 790270288834.0249, + "timestamp": "2021-01-03T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-04T00:00:00.000Z", + "time_close": "2021-01-04T23:59:59.999Z", + "time_high": "2021-01-04T04:07:42.000Z", + "time_low": "2021-01-04T10:19:42.000Z", + "quote": { + "AUD": { + "open": 42548.61349648768, + "high": 43360.96165147421, + "low": 37133.98436952697, + "close": 41686.38761359174, + "volume": 105824510346.65779, + "market_cap": 774984045201.7122, + "timestamp": "2021-01-04T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-05T00:00:00.000Z", + "time_close": "2021-01-05T23:59:59.999Z", + "time_high": "2021-01-05T22:44:35.000Z", + "time_low": "2021-01-05T06:16:41.000Z", + "quote": { + "AUD": { + "open": 41693.07321807638, + "high": 44403.79487147647, + "low": 39221.81167941294, + "close": 43790.067253370056, + "volume": 87016490203.50436, + "market_cap": 814135603090.2502, + "timestamp": "2021-01-05T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-06T00:00:00.000Z", + "time_close": "2021-01-06T23:59:59.999Z", + "time_high": "2021-01-06T23:57:36.000Z", + "time_low": "2021-01-06T00:25:38.000Z", + "quote": { + "AUD": { + "open": 43817.35864984641, + "high": 47186.65232598287, + "low": 43152.60281764236, + "close": 47115.85365360005, + "volume": 96330948324.8061, + "market_cap": 876019742889.9551, + "timestamp": "2021-01-06T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-07T00:00:00.000Z", + "time_close": "2021-01-07T23:59:59.999Z", + "time_high": "2021-01-07T18:17:42.000Z", + "time_low": "2021-01-07T08:25:51.000Z", + "quote": { + "AUD": { + "open": 47128.02139328098, + "high": 51833.478207775144, + "low": 46906.65117139608, + "close": 50686.90986207153, + "volume": 109124136558.20264, + "market_cap": 942469208700.134, + "timestamp": "2021-01-07T23:59:06.000Z" + } + } + } + ] + } +} diff --git a/tests/pricehist/sources/test_coinmarketcap/recent-btc-id2782.json b/tests/pricehist/sources/test_coinmarketcap/recent-btc-id2782.json new file mode 100644 index 0000000..8614b07 --- /dev/null +++ b/tests/pricehist/sources/test_coinmarketcap/recent-btc-id2782.json @@ -0,0 +1,136 @@ +{ + "status": { + "timestamp": "2021-07-16T10:42:27.169Z", + "error_code": 0, + "error_message": null, + "elapsed": 19, + "credit_count": 0, + "notice": null + }, + "data": { + "id": 1, + "name": "Bitcoin", + "symbol": "BTC", + "quotes": [ + { + "time_open": "2021-01-01T00:00:00.000Z", + "time_close": "2021-01-01T23:59:59.999Z", + "time_high": "2021-01-01T12:38:43.000Z", + "time_low": "2021-01-01T00:16:43.000Z", + "quote": { + "2782": { + "open": 37658.83948707033, + "high": 38417.9137031205, + "low": 37410.787501639206, + "close": 38181.99133300758, + "volume": 52943282221.028366, + "market_cap": 709720173049.5383, + "timestamp": "2021-01-01T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-02T00:00:00.000Z", + "time_close": "2021-01-02T23:59:59.999Z", + "time_high": "2021-01-02T19:49:42.000Z", + "time_low": "2021-01-02T00:31:44.000Z", + "quote": { + "2782": { + "open": 38184.98611600682, + "high": 43096.681197423015, + "low": 37814.17187096531, + "close": 41760.62923079505, + "volume": 88214867181.97835, + "market_cap": 776278147177.8037, + "timestamp": "2021-01-02T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-03T00:00:00.000Z", + "time_close": "2021-01-03T23:59:59.999Z", + "time_high": "2021-01-03T07:47:38.000Z", + "time_low": "2021-01-03T00:20:45.000Z", + "quote": { + "2782": { + "open": 41763.41015117659, + "high": 44985.93247585023, + "low": 41663.204350601605, + "close": 42511.10646879765, + "volume": 102011582370.28117, + "market_cap": 790270288834.0249, + "timestamp": "2021-01-03T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-04T00:00:00.000Z", + "time_close": "2021-01-04T23:59:59.999Z", + "time_high": "2021-01-04T04:07:42.000Z", + "time_low": "2021-01-04T10:19:42.000Z", + "quote": { + "2782": { + "open": 42548.61349648768, + "high": 43360.96165147421, + "low": 37133.98436952697, + "close": 41686.38761359174, + "volume": 105824510346.65779, + "market_cap": 774984045201.7122, + "timestamp": "2021-01-04T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-05T00:00:00.000Z", + "time_close": "2021-01-05T23:59:59.999Z", + "time_high": "2021-01-05T22:44:35.000Z", + "time_low": "2021-01-05T06:16:41.000Z", + "quote": { + "2782": { + "open": 41693.07321807638, + "high": 44403.79487147647, + "low": 39221.81167941294, + "close": 43790.067253370056, + "volume": 87016490203.50436, + "market_cap": 814135603090.2502, + "timestamp": "2021-01-05T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-06T00:00:00.000Z", + "time_close": "2021-01-06T23:59:59.999Z", + "time_high": "2021-01-06T23:57:36.000Z", + "time_low": "2021-01-06T00:25:38.000Z", + "quote": { + "2782": { + "open": 43817.35864984641, + "high": 47186.65232598287, + "low": 43152.60281764236, + "close": 47115.85365360005, + "volume": 96330948324.8061, + "market_cap": 876019742889.9551, + "timestamp": "2021-01-06T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-07T00:00:00.000Z", + "time_close": "2021-01-07T23:59:59.999Z", + "time_high": "2021-01-07T18:17:42.000Z", + "time_low": "2021-01-07T08:25:51.000Z", + "quote": { + "2782": { + "open": 47128.02139328098, + "high": 51833.478207775144, + "low": 46906.65117139608, + "close": 50686.90986207153, + "volume": 109124136558.20264, + "market_cap": 942469208700.134, + "timestamp": "2021-01-07T23:59:06.000Z" + } + } + } + ] + } +} diff --git a/tests/pricehist/sources/test_coinmarketcap/recent-id1-aud.json b/tests/pricehist/sources/test_coinmarketcap/recent-id1-aud.json new file mode 100644 index 0000000..8b70b6a --- /dev/null +++ b/tests/pricehist/sources/test_coinmarketcap/recent-id1-aud.json @@ -0,0 +1,136 @@ +{ + "status": { + "timestamp": "2021-07-16T10:42:24.612Z", + "error_code": 0, + "error_message": null, + "elapsed": 57, + "credit_count": 0, + "notice": null + }, + "data": { + "id": 1, + "name": "Bitcoin", + "symbol": "BTC", + "quotes": [ + { + "time_open": "2021-01-01T00:00:00.000Z", + "time_close": "2021-01-01T23:59:59.999Z", + "time_high": "2021-01-01T12:38:43.000Z", + "time_low": "2021-01-01T00:16:43.000Z", + "quote": { + "AUD": { + "open": 37658.83948707033, + "high": 38417.9137031205, + "low": 37410.787501639206, + "close": 38181.99133300758, + "volume": 52943282221.028366, + "market_cap": 709720173049.5383, + "timestamp": "2021-01-01T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-02T00:00:00.000Z", + "time_close": "2021-01-02T23:59:59.999Z", + "time_high": "2021-01-02T19:49:42.000Z", + "time_low": "2021-01-02T00:31:44.000Z", + "quote": { + "AUD": { + "open": 38184.98611600682, + "high": 43096.681197423015, + "low": 37814.17187096531, + "close": 41760.62923079505, + "volume": 88214867181.97835, + "market_cap": 776278147177.8037, + "timestamp": "2021-01-02T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-03T00:00:00.000Z", + "time_close": "2021-01-03T23:59:59.999Z", + "time_high": "2021-01-03T07:47:38.000Z", + "time_low": "2021-01-03T00:20:45.000Z", + "quote": { + "AUD": { + "open": 41763.41015117659, + "high": 44985.93247585023, + "low": 41663.204350601605, + "close": 42511.10646879765, + "volume": 102011582370.28117, + "market_cap": 790270288834.0249, + "timestamp": "2021-01-03T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-04T00:00:00.000Z", + "time_close": "2021-01-04T23:59:59.999Z", + "time_high": "2021-01-04T04:07:42.000Z", + "time_low": "2021-01-04T10:19:42.000Z", + "quote": { + "AUD": { + "open": 42548.61349648768, + "high": 43360.96165147421, + "low": 37133.98436952697, + "close": 41686.38761359174, + "volume": 105824510346.65779, + "market_cap": 774984045201.7122, + "timestamp": "2021-01-04T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-05T00:00:00.000Z", + "time_close": "2021-01-05T23:59:59.999Z", + "time_high": "2021-01-05T22:44:35.000Z", + "time_low": "2021-01-05T06:16:41.000Z", + "quote": { + "AUD": { + "open": 41693.07321807638, + "high": 44403.79487147647, + "low": 39221.81167941294, + "close": 43790.067253370056, + "volume": 87016490203.50436, + "market_cap": 814135603090.2502, + "timestamp": "2021-01-05T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-06T00:00:00.000Z", + "time_close": "2021-01-06T23:59:59.999Z", + "time_high": "2021-01-06T23:57:36.000Z", + "time_low": "2021-01-06T00:25:38.000Z", + "quote": { + "AUD": { + "open": 43817.35864984641, + "high": 47186.65232598287, + "low": 43152.60281764236, + "close": 47115.85365360005, + "volume": 96330948324.8061, + "market_cap": 876019742889.9551, + "timestamp": "2021-01-06T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-07T00:00:00.000Z", + "time_close": "2021-01-07T23:59:59.999Z", + "time_high": "2021-01-07T18:17:42.000Z", + "time_low": "2021-01-07T08:25:51.000Z", + "quote": { + "AUD": { + "open": 47128.02139328098, + "high": 51833.478207775144, + "low": 46906.65117139608, + "close": 50686.90986207153, + "volume": 109124136558.20264, + "market_cap": 942469208700.134, + "timestamp": "2021-01-07T23:59:06.000Z" + } + } + } + ] + } +} diff --git a/tests/pricehist/sources/test_coinmarketcap/recent-id1-id2782.json b/tests/pricehist/sources/test_coinmarketcap/recent-id1-id2782.json new file mode 100644 index 0000000..d172453 --- /dev/null +++ b/tests/pricehist/sources/test_coinmarketcap/recent-id1-id2782.json @@ -0,0 +1,136 @@ +{ + "status": { + "timestamp": "2021-07-16T10:42:21.065Z", + "error_code": 0, + "error_message": null, + "elapsed": 17, + "credit_count": 0, + "notice": null + }, + "data": { + "id": 1, + "name": "Bitcoin", + "symbol": "BTC", + "quotes": [ + { + "time_open": "2021-01-01T00:00:00.000Z", + "time_close": "2021-01-01T23:59:59.999Z", + "time_high": "2021-01-01T12:38:43.000Z", + "time_low": "2021-01-01T00:16:43.000Z", + "quote": { + "2782": { + "open": 37658.83948707033, + "high": 38417.9137031205, + "low": 37410.787501639206, + "close": 38181.99133300758, + "volume": 52943282221.028366, + "market_cap": 709720173049.5383, + "timestamp": "2021-01-01T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-02T00:00:00.000Z", + "time_close": "2021-01-02T23:59:59.999Z", + "time_high": "2021-01-02T19:49:42.000Z", + "time_low": "2021-01-02T00:31:44.000Z", + "quote": { + "2782": { + "open": 38184.98611600682, + "high": 43096.681197423015, + "low": 37814.17187096531, + "close": 41760.62923079505, + "volume": 88214867181.97835, + "market_cap": 776278147177.8037, + "timestamp": "2021-01-02T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-03T00:00:00.000Z", + "time_close": "2021-01-03T23:59:59.999Z", + "time_high": "2021-01-03T07:47:38.000Z", + "time_low": "2021-01-03T00:20:45.000Z", + "quote": { + "2782": { + "open": 41763.41015117659, + "high": 44985.93247585023, + "low": 41663.204350601605, + "close": 42511.10646879765, + "volume": 102011582370.28117, + "market_cap": 790270288834.0249, + "timestamp": "2021-01-03T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-04T00:00:00.000Z", + "time_close": "2021-01-04T23:59:59.999Z", + "time_high": "2021-01-04T04:07:42.000Z", + "time_low": "2021-01-04T10:19:42.000Z", + "quote": { + "2782": { + "open": 42548.61349648768, + "high": 43360.96165147421, + "low": 37133.98436952697, + "close": 41686.38761359174, + "volume": 105824510346.65779, + "market_cap": 774984045201.7122, + "timestamp": "2021-01-04T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-05T00:00:00.000Z", + "time_close": "2021-01-05T23:59:59.999Z", + "time_high": "2021-01-05T22:44:35.000Z", + "time_low": "2021-01-05T06:16:41.000Z", + "quote": { + "2782": { + "open": 41693.07321807638, + "high": 44403.79487147647, + "low": 39221.81167941294, + "close": 43790.067253370056, + "volume": 87016490203.50436, + "market_cap": 814135603090.2502, + "timestamp": "2021-01-05T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-06T00:00:00.000Z", + "time_close": "2021-01-06T23:59:59.999Z", + "time_high": "2021-01-06T23:57:36.000Z", + "time_low": "2021-01-06T00:25:38.000Z", + "quote": { + "2782": { + "open": 43817.35864984641, + "high": 47186.65232598287, + "low": 43152.60281764236, + "close": 47115.85365360005, + "volume": 96330948324.8061, + "market_cap": 876019742889.9551, + "timestamp": "2021-01-06T23:59:06.000Z" + } + } + }, + { + "time_open": "2021-01-07T00:00:00.000Z", + "time_close": "2021-01-07T23:59:59.999Z", + "time_high": "2021-01-07T18:17:42.000Z", + "time_low": "2021-01-07T08:25:51.000Z", + "quote": { + "2782": { + "open": 47128.02139328098, + "high": 51833.478207775144, + "low": 46906.65117139608, + "close": 50686.90986207153, + "volume": 109124136558.20264, + "market_cap": 942469208700.134, + "timestamp": "2021-01-07T23:59:06.000Z" + } + } + } + ] + } +}