Better logging of critical exceptions. Isolate tests and error handling for ECB.
This commit is contained in:
parent
2aa4319dbb
commit
c0c7e546a3
6 changed files with 604 additions and 66 deletions
|
@ -1,6 +1,9 @@
|
|||
import logging
|
||||
import sys
|
||||
from datetime import date, datetime, timedelta
|
||||
|
||||
from pricehist import exceptions
|
||||
|
||||
|
||||
def fetch(series, source, output, invert: bool, quantize: int, fmt) -> str:
|
||||
if series.start < source.start():
|
||||
|
@ -9,7 +12,12 @@ def fetch(series, source, output, invert: bool, quantize: int, fmt) -> str:
|
|||
f"source start date of {source.start()}."
|
||||
)
|
||||
|
||||
series = source.fetch(series)
|
||||
try:
|
||||
series = source.fetch(series)
|
||||
except exceptions.SourceError as e:
|
||||
logging.debug("Critical exception encountered", exc_info=e)
|
||||
logging.critical(str(e))
|
||||
sys.exit(1)
|
||||
|
||||
if len(series.prices) == 0:
|
||||
logging.warn(f"No data found for the interval [{series.start}--{series.end}].")
|
||||
|
|
|
@ -4,11 +4,18 @@ import sys
|
|||
|
||||
class Formatter(logging.Formatter):
|
||||
def format(self, record):
|
||||
message = record.msg % record.args if record.args else record.msg
|
||||
if record.levelno == logging.INFO:
|
||||
return message
|
||||
else:
|
||||
return f"{record.levelname} {message}"
|
||||
s = record.msg % record.args if record.args else record.msg
|
||||
|
||||
if record.exc_info:
|
||||
record.exc_text = self.formatException(record.exc_info)
|
||||
if s[-1:] != "\n":
|
||||
s = s + "\n"
|
||||
s = s + "\n".join([f" {line}" for line in record.exc_text.splitlines()])
|
||||
|
||||
if record.levelno != logging.INFO:
|
||||
s = "\n".join([f"{record.levelname} {line}" for line in s.splitlines()])
|
||||
|
||||
return s
|
||||
|
||||
|
||||
def init():
|
||||
|
|
|
@ -1,13 +1,11 @@
|
|||
import dataclasses
|
||||
import logging
|
||||
import sys
|
||||
from datetime import datetime, timedelta
|
||||
from decimal import Decimal
|
||||
|
||||
import requests
|
||||
from lxml import etree
|
||||
|
||||
from pricehist import isocurrencies
|
||||
from pricehist import exceptions, isocurrencies
|
||||
from pricehist.price import Price
|
||||
|
||||
from .basesource import BaseSource
|
||||
|
@ -36,19 +34,17 @@ class ECB(BaseSource):
|
|||
return ""
|
||||
|
||||
def symbols(self):
|
||||
root = self._data(more_than_90_days=True)
|
||||
nodes = root.cssselect("[currency]")
|
||||
currencies = sorted(set([n.attrib["currency"] for n in nodes]))
|
||||
quotes = self._quotes()
|
||||
iso = isocurrencies.by_code()
|
||||
return [(f"EUR/{c}", f"Euro against {iso[c].name}") for c in currencies]
|
||||
return [
|
||||
(f"EUR/{c}", f"Euro against {iso[c].name if c in iso else c}")
|
||||
for c in quotes
|
||||
]
|
||||
|
||||
def fetch(self, series):
|
||||
if series.base != "EUR": # EUR is the only valid base.
|
||||
logging.critical(
|
||||
f"Invalid pair '{'/'.join([series.base, series.quote])}'. "
|
||||
f"Run 'pricehist source {self.id()} --symbols' to list valid pairs."
|
||||
)
|
||||
sys.exit(1)
|
||||
if series.base != "EUR" or not series.quote: # EUR is the only valid base.
|
||||
raise exceptions.InvalidPair(series.base, series.quote, self)
|
||||
|
||||
almost_90_days_ago = (datetime.now().date() - timedelta(days=85)).isoformat()
|
||||
root = self._data(series.start < almost_90_days_ago)
|
||||
|
||||
|
@ -58,12 +54,24 @@ class ECB(BaseSource):
|
|||
for row in day.cssselect(f"[currency='{series.quote}']"):
|
||||
rate = Decimal(row.attrib["rate"])
|
||||
all_rows.insert(0, (date, rate))
|
||||
|
||||
if not all_rows and series.quote not in self._quotes():
|
||||
raise exceptions.InvalidPair(series.base, series.quote, self)
|
||||
|
||||
selected = [
|
||||
Price(d, r) for d, r in all_rows if d >= series.start and d <= series.end
|
||||
]
|
||||
|
||||
return dataclasses.replace(series, prices=selected)
|
||||
|
||||
def _quotes(self):
|
||||
root = self._data(more_than_90_days=True)
|
||||
nodes = root.cssselect("[currency]")
|
||||
quotes = sorted(set([n.attrib["currency"] for n in nodes]))
|
||||
if not quotes:
|
||||
raise exceptions.ResponseParsingError("Expected data not found")
|
||||
return quotes
|
||||
|
||||
def _data(self, more_than_90_days=False):
|
||||
url_base = "https://www.ecb.europa.eu/stats/eurofxref"
|
||||
if more_than_90_days:
|
||||
|
@ -71,6 +79,19 @@ class ECB(BaseSource):
|
|||
else:
|
||||
source_url = f"{url_base}/eurofxref-hist-90d.xml" # last 90 days
|
||||
|
||||
response = self.log_curl(requests.get(source_url))
|
||||
root = etree.fromstring(response.content)
|
||||
try:
|
||||
response = self.log_curl(requests.get(source_url))
|
||||
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:
|
||||
root = etree.fromstring(response.content)
|
||||
except Exception as e:
|
||||
raise exceptions.ResponseParsingError(str(e)) from e
|
||||
|
||||
return root
|
||||
|
|
|
@ -1,14 +1,18 @@
|
|||
import logging
|
||||
import os
|
||||
from datetime import datetime, timedelta
|
||||
from decimal import Decimal
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
import responses
|
||||
|
||||
from pricehist import exceptions, isocurrencies
|
||||
from pricehist.price import Price
|
||||
from pricehist.series import Series
|
||||
from pricehist.sources.ecb import ECB
|
||||
|
||||
# import responses
|
||||
# @responses.activate
|
||||
|
||||
|
||||
def in_log(caplog, levelname, substr):
|
||||
return any(
|
||||
|
@ -26,73 +30,204 @@ def type(src):
|
|||
return src.types()[0]
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def xml():
|
||||
dir = Path(os.path.splitext(__file__)[0])
|
||||
return (dir / "eurofxref-hist-partial.xml").read_text()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def empty_xml():
|
||||
dir = Path(os.path.splitext(__file__)[0])
|
||||
return (dir / "eurofxref-hist-empty.xml").read_text()
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def requests_mock():
|
||||
with responses.RequestsMock() as mock:
|
||||
yield mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def response_ok(requests_mock, xml):
|
||||
requests_mock.add(
|
||||
responses.GET,
|
||||
"https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.xml",
|
||||
body=xml,
|
||||
status=200,
|
||||
)
|
||||
yield requests_mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def response_ok_90d(requests_mock, xml):
|
||||
requests_mock.add(
|
||||
responses.GET,
|
||||
"https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist-90d.xml",
|
||||
body=xml,
|
||||
status=200,
|
||||
)
|
||||
yield requests_mock
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def response_empty_xml(requests_mock, empty_xml):
|
||||
requests_mock.add(
|
||||
responses.GET,
|
||||
"https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.xml",
|
||||
body=empty_xml,
|
||||
status=200,
|
||||
)
|
||||
yield requests_mock
|
||||
|
||||
|
||||
def test_normalizesymbol(src):
|
||||
assert src.normalizesymbol("eur") == "EUR"
|
||||
assert src.normalizesymbol("symbol") == "SYMBOL"
|
||||
|
||||
|
||||
@pytest.mark.live
|
||||
def test_known_pair(src, type):
|
||||
series = src.fetch(Series("EUR", "AUD", type, "2021-01-11", "2021-01-22"))
|
||||
assert series.prices[0] == Price("2021-01-11", Decimal("1.5783"))
|
||||
assert series.prices[-1] == Price("2021-01-22", Decimal("1.577"))
|
||||
assert len(series.prices) == 10
|
||||
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)
|
||||
|
||||
|
||||
@pytest.mark.live
|
||||
def test_long_hist_from_start(src, type):
|
||||
series = src.fetch(Series("EUR", "AUD", type, src.start(), "2021-07-01"))
|
||||
def test_symbols(src, response_ok):
|
||||
syms = src.symbols()
|
||||
assert ("EUR/AUD", "Euro against Australian Dollar") in syms
|
||||
assert len(syms) > 40
|
||||
|
||||
|
||||
def test_requests_logged_for_symbols(src, response_ok, caplog):
|
||||
with caplog.at_level(logging.DEBUG):
|
||||
src.symbols()
|
||||
assert in_log(caplog, "DEBUG", " curl ")
|
||||
|
||||
|
||||
def test_symbols_not_in_iso_data(src, response_ok, monkeypatch):
|
||||
iso = isocurrencies.by_code()
|
||||
del iso["AUD"]
|
||||
monkeypatch.setattr(isocurrencies, "by_code", lambda: iso)
|
||||
syms = src.symbols()
|
||||
assert ("EUR/AUD", "Euro against AUD") in syms
|
||||
|
||||
|
||||
def test_symbols_not_found(src, response_empty_xml):
|
||||
with pytest.raises(exceptions.ResponseParsingError) as e:
|
||||
src.symbols()
|
||||
assert "data not found" in str(e.value)
|
||||
|
||||
|
||||
def test_known_pair(src, type, response_ok):
|
||||
series = src.fetch(Series("EUR", "AUD", type, "2021-01-04", "2021-01-08"))
|
||||
assert series.prices[0] == Price("2021-01-04", Decimal("1.5928"))
|
||||
assert series.prices[-1] == Price("2021-01-08", Decimal("1.5758"))
|
||||
assert len(series.prices) == 5
|
||||
|
||||
|
||||
def test_requests_logged_for_fetch(src, response_ok, caplog):
|
||||
with caplog.at_level(logging.DEBUG):
|
||||
src.fetch(Series("EUR", "AUD", type, "2021-01-04", "2021-01-08"))
|
||||
assert in_log(caplog, "DEBUG", " curl ")
|
||||
|
||||
|
||||
def test_recent_interval_uses_90d_data(src, type, response_ok_90d):
|
||||
today = datetime.now().date()
|
||||
start = (today - timedelta(days=80)).isoformat()
|
||||
end = today.isoformat()
|
||||
src.fetch(Series("EUR", "AUD", type, start, end))
|
||||
assert len(response_ok_90d.calls) > 0
|
||||
|
||||
|
||||
def test_long_hist_from_start(src, type, response_ok):
|
||||
series = src.fetch(Series("EUR", "AUD", type, src.start(), "2021-01-08"))
|
||||
assert series.prices[0] == Price("1999-01-04", Decimal("1.91"))
|
||||
assert series.prices[-1] == Price("2021-07-01", Decimal("1.5836"))
|
||||
assert len(series.prices) == 5759
|
||||
assert series.prices[-1] == Price("2021-01-08", Decimal("1.5758"))
|
||||
assert len(series.prices) > 9
|
||||
|
||||
|
||||
@pytest.mark.live
|
||||
def test_from_before_start(src, type):
|
||||
def test_from_before_start(src, type, response_ok):
|
||||
series = src.fetch(Series("EUR", "AUD", type, "1998-12-01", "1999-01-10"))
|
||||
assert series.prices[0] == Price("1999-01-04", Decimal("1.91"))
|
||||
assert series.prices[-1] == Price("1999-01-08", Decimal("1.8406"))
|
||||
assert len(series.prices) == 5
|
||||
|
||||
|
||||
@pytest.mark.live
|
||||
def test_to_future(src, type):
|
||||
series = src.fetch(Series("EUR", "AUD", type, "2021-07-01", "2100-01-01"))
|
||||
def test_to_future(src, type, response_ok):
|
||||
series = src.fetch(Series("EUR", "AUD", type, "2021-01-04", "2100-01-01"))
|
||||
assert len(series.prices) > 0
|
||||
|
||||
|
||||
@pytest.mark.live
|
||||
def test_known_pair_no_data(src, type):
|
||||
series = src.fetch(Series("EUR", "ROL", type, "2020-01-01", "2021-01-01"))
|
||||
def test_known_pair_no_data(src, type, response_ok):
|
||||
series = src.fetch(Series("EUR", "ROL", type, "2021-01-04", "2021-02-08"))
|
||||
assert len(series.prices) == 0
|
||||
|
||||
|
||||
def test_non_eur_base(src, type, caplog):
|
||||
with pytest.raises(SystemExit) as e:
|
||||
src.fetch(Series("USD", "AUD", type, "2021-01-01", "2021-02-01"))
|
||||
assert e.value.code == 1
|
||||
assert in_log(caplog, "CRITICAL", "Invalid pair")
|
||||
def test_non_eur_base(src, type):
|
||||
with pytest.raises(exceptions.InvalidPair):
|
||||
src.fetch(Series("USD", "AUD", type, "2021-01-04", "2021-01-08"))
|
||||
|
||||
|
||||
@pytest.mark.xfail
|
||||
@pytest.mark.live
|
||||
def test_unknown_quote(src, type, caplog):
|
||||
with pytest.raises(SystemExit) as e:
|
||||
src.fetch(Series("EUR", "XZY", type, "2021-01-01", "2021-02-01"))
|
||||
assert e.value.code == 1
|
||||
assert in_log(caplog, "CRITICAL", "Invalid pair")
|
||||
def test_unknown_quote(src, type, response_ok):
|
||||
with pytest.raises(exceptions.InvalidPair):
|
||||
src.fetch(Series("EUR", "XZY", type, "2021-01-04", "2021-01-08"))
|
||||
|
||||
|
||||
@pytest.mark.xfail
|
||||
def test_no_quote(src, type, caplog):
|
||||
with pytest.raises(SystemExit) as e:
|
||||
src.fetch(Series("EUR", "", type, "2021-01-01", "2021-02-01"))
|
||||
assert e.value.code == 1
|
||||
assert in_log(caplog, "CRITICAL", "Invalid pair")
|
||||
def test_no_quote(src, type):
|
||||
with pytest.raises(exceptions.InvalidPair):
|
||||
src.fetch(Series("EUR", "", type, "2021-01-04", "2021-01-08"))
|
||||
|
||||
|
||||
def test_unknown_pair(src, type, caplog):
|
||||
with pytest.raises(SystemExit) as e:
|
||||
src.fetch(Series("ABC", "XZY", type, "2021-01-01", "2021-02-01"))
|
||||
assert e.value.code == 1
|
||||
assert in_log(caplog, "CRITICAL", "Invalid pair")
|
||||
def test_unknown_pair(src, type):
|
||||
with pytest.raises(exceptions.InvalidPair):
|
||||
src.fetch(Series("ABC", "XZY", type, "2021-01-04", "2021-01-08"))
|
||||
|
||||
|
||||
def test_network_issue(src, type, requests_mock):
|
||||
requests_mock.add(
|
||||
responses.GET,
|
||||
"https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.xml",
|
||||
body=requests.exceptions.ConnectionError("Network issue"),
|
||||
)
|
||||
with pytest.raises(exceptions.RequestError) as e:
|
||||
src.fetch(Series("EUR", "AUD", type, "2021-01-04", "2021-01-08"))
|
||||
assert "Network issue" in str(e.value)
|
||||
|
||||
|
||||
def test_bad_status(src, type, requests_mock):
|
||||
requests_mock.add(
|
||||
responses.GET,
|
||||
"https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.xml",
|
||||
status=500,
|
||||
)
|
||||
with pytest.raises(exceptions.BadResponse) as e:
|
||||
src.fetch(Series("EUR", "AUD", type, "2021-01-04", "2021-01-08"))
|
||||
assert "Server Error" in str(e.value)
|
||||
|
||||
|
||||
def test_parsing_error(src, type, requests_mock):
|
||||
requests_mock.add(
|
||||
responses.GET,
|
||||
"https://www.ecb.europa.eu/stats/eurofxref/eurofxref-hist.xml",
|
||||
body="NOT XML",
|
||||
)
|
||||
with pytest.raises(exceptions.ResponseParsingError) as e:
|
||||
src.fetch(Series("EUR", "AUD", type, "2021-01-04", "2021-01-08"))
|
||||
assert "while parsing data" in str(e.value)
|
||||
|
|
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gesmes:Envelope xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01" xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref">
|
||||
<gesmes:subject>Reference rates</gesmes:subject>
|
||||
<gesmes:Sender>
|
||||
<gesmes:name>European Central Bank</gesmes:name>
|
||||
</gesmes:Sender>
|
||||
<Cube>
|
||||
</Cube>
|
||||
</gesmes:Envelope>
|
358
tests/pricehist/sources/test_ecb/eurofxref-hist-partial.xml
Normal file
358
tests/pricehist/sources/test_ecb/eurofxref-hist-partial.xml
Normal file
|
@ -0,0 +1,358 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<gesmes:Envelope xmlns:gesmes="http://www.gesmes.org/xml/2002-08-01" xmlns="http://www.ecb.int/vocabulary/2002-08-01/eurofxref">
|
||||
<gesmes:subject>Reference rates</gesmes:subject>
|
||||
<gesmes:Sender>
|
||||
<gesmes:name>European Central Bank</gesmes:name>
|
||||
</gesmes:Sender>
|
||||
<Cube>
|
||||
<Cube time="2021-01-08">
|
||||
<Cube currency="USD" rate="1.225"/>
|
||||
<Cube currency="JPY" rate="127.26"/>
|
||||
<Cube currency="BGN" rate="1.9558"/>
|
||||
<Cube currency="CZK" rate="26.163"/>
|
||||
<Cube currency="DKK" rate="7.4369"/>
|
||||
<Cube currency="GBP" rate="0.90128"/>
|
||||
<Cube currency="HUF" rate="359.62"/>
|
||||
<Cube currency="PLN" rate="4.5113"/>
|
||||
<Cube currency="RON" rate="4.8708"/>
|
||||
<Cube currency="SEK" rate="10.051"/>
|
||||
<Cube currency="CHF" rate="1.0827"/>
|
||||
<Cube currency="ISK" rate="155.5"/>
|
||||
<Cube currency="NOK" rate="10.2863"/>
|
||||
<Cube currency="HRK" rate="7.569"/>
|
||||
<Cube currency="RUB" rate="90.8"/>
|
||||
<Cube currency="TRY" rate="9.0146"/>
|
||||
<Cube currency="AUD" rate="1.5758"/>
|
||||
<Cube currency="BRL" rate="6.5748"/>
|
||||
<Cube currency="CAD" rate="1.5543"/>
|
||||
<Cube currency="CNY" rate="7.9184"/>
|
||||
<Cube currency="HKD" rate="9.4982"/>
|
||||
<Cube currency="IDR" rate="17247.33"/>
|
||||
<Cube currency="ILS" rate="3.8981"/>
|
||||
<Cube currency="INR" rate="89.7975"/>
|
||||
<Cube currency="KRW" rate="1337.9"/>
|
||||
<Cube currency="MXN" rate="24.4718"/>
|
||||
<Cube currency="MYR" rate="4.9359"/>
|
||||
<Cube currency="NZD" rate="1.6883"/>
|
||||
<Cube currency="PHP" rate="58.947"/>
|
||||
<Cube currency="SGD" rate="1.6228"/>
|
||||
<Cube currency="THB" rate="36.848"/>
|
||||
<Cube currency="ZAR" rate="18.7212"/>
|
||||
</Cube>
|
||||
<Cube time="2021-01-07">
|
||||
<Cube currency="USD" rate="1.2276"/>
|
||||
<Cube currency="JPY" rate="127.13"/>
|
||||
<Cube currency="BGN" rate="1.9558"/>
|
||||
<Cube currency="CZK" rate="26.147"/>
|
||||
<Cube currency="DKK" rate="7.4392"/>
|
||||
<Cube currency="GBP" rate="0.9019"/>
|
||||
<Cube currency="HUF" rate="357.79"/>
|
||||
<Cube currency="PLN" rate="4.4998"/>
|
||||
<Cube currency="RON" rate="4.8712"/>
|
||||
<Cube currency="SEK" rate="10.0575"/>
|
||||
<Cube currency="CHF" rate="1.0833"/>
|
||||
<Cube currency="ISK" rate="155.3"/>
|
||||
<Cube currency="NOK" rate="10.3435"/>
|
||||
<Cube currency="HRK" rate="7.566"/>
|
||||
<Cube currency="RUB" rate="91.2"/>
|
||||
<Cube currency="TRY" rate="8.9987"/>
|
||||
<Cube currency="AUD" rate="1.5836"/>
|
||||
<Cube currency="BRL" rate="6.5172"/>
|
||||
<Cube currency="CAD" rate="1.5601"/>
|
||||
<Cube currency="CNY" rate="7.9392"/>
|
||||
<Cube currency="HKD" rate="9.5176"/>
|
||||
<Cube currency="IDR" rate="17259.99"/>
|
||||
<Cube currency="ILS" rate="3.9027"/>
|
||||
<Cube currency="INR" rate="90.0455"/>
|
||||
<Cube currency="KRW" rate="1342.29"/>
|
||||
<Cube currency="MXN" rate="24.2552"/>
|
||||
<Cube currency="MYR" rate="4.957"/>
|
||||
<Cube currency="NZD" rate="1.6907"/>
|
||||
<Cube currency="PHP" rate="59.043"/>
|
||||
<Cube currency="SGD" rate="1.6253"/>
|
||||
<Cube currency="THB" rate="36.859"/>
|
||||
<Cube currency="ZAR" rate="18.7919"/>
|
||||
</Cube>
|
||||
<Cube time="2021-01-06">
|
||||
<Cube currency="USD" rate="1.2338"/>
|
||||
<Cube currency="JPY" rate="127.03"/>
|
||||
<Cube currency="BGN" rate="1.9558"/>
|
||||
<Cube currency="CZK" rate="26.145"/>
|
||||
<Cube currency="DKK" rate="7.4393"/>
|
||||
<Cube currency="GBP" rate="0.90635"/>
|
||||
<Cube currency="HUF" rate="357.86"/>
|
||||
<Cube currency="PLN" rate="4.516"/>
|
||||
<Cube currency="RON" rate="4.872"/>
|
||||
<Cube currency="SEK" rate="10.0653"/>
|
||||
<Cube currency="CHF" rate="1.0821"/>
|
||||
<Cube currency="ISK" rate="156.3"/>
|
||||
<Cube currency="NOK" rate="10.381"/>
|
||||
<Cube currency="HRK" rate="7.5595"/>
|
||||
<Cube currency="RUB" rate="90.8175"/>
|
||||
<Cube currency="TRY" rate="9.0554"/>
|
||||
<Cube currency="AUD" rate="1.5824"/>
|
||||
<Cube currency="BRL" rate="6.5119"/>
|
||||
<Cube currency="CAD" rate="1.564"/>
|
||||
<Cube currency="CNY" rate="7.9653"/>
|
||||
<Cube currency="HKD" rate="9.5659"/>
|
||||
<Cube currency="IDR" rate="17168.2"/>
|
||||
<Cube currency="ILS" rate="3.9289"/>
|
||||
<Cube currency="INR" rate="90.204"/>
|
||||
<Cube currency="KRW" rate="1339.3"/>
|
||||
<Cube currency="MXN" rate="24.3543"/>
|
||||
<Cube currency="MYR" rate="4.9482"/>
|
||||
<Cube currency="NZD" rate="1.6916"/>
|
||||
<Cube currency="PHP" rate="59.296"/>
|
||||
<Cube currency="SGD" rate="1.6246"/>
|
||||
<Cube currency="THB" rate="36.921"/>
|
||||
<Cube currency="ZAR" rate="18.5123"/>
|
||||
</Cube>
|
||||
<Cube time="2021-01-05">
|
||||
<Cube currency="USD" rate="1.2271"/>
|
||||
<Cube currency="JPY" rate="126.25"/>
|
||||
<Cube currency="BGN" rate="1.9558"/>
|
||||
<Cube currency="CZK" rate="26.227"/>
|
||||
<Cube currency="DKK" rate="7.4387"/>
|
||||
<Cube currency="GBP" rate="0.90333"/>
|
||||
<Cube currency="HUF" rate="360.27"/>
|
||||
<Cube currency="PLN" rate="4.5473"/>
|
||||
<Cube currency="RON" rate="4.8721"/>
|
||||
<Cube currency="SEK" rate="10.057"/>
|
||||
<Cube currency="CHF" rate="1.0803"/>
|
||||
<Cube currency="ISK" rate="156.1"/>
|
||||
<Cube currency="NOK" rate="10.4713"/>
|
||||
<Cube currency="HRK" rate="7.5588"/>
|
||||
<Cube currency="RUB" rate="91.6715"/>
|
||||
<Cube currency="TRY" rate="9.0694"/>
|
||||
<Cube currency="AUD" rate="1.5927"/>
|
||||
<Cube currency="BRL" rate="6.5517"/>
|
||||
<Cube currency="CAD" rate="1.5651"/>
|
||||
<Cube currency="CNY" rate="7.9315"/>
|
||||
<Cube currency="HKD" rate="9.5136"/>
|
||||
<Cube currency="IDR" rate="17075.1"/>
|
||||
<Cube currency="ILS" rate="3.9277"/>
|
||||
<Cube currency="INR" rate="89.867"/>
|
||||
<Cube currency="KRW" rate="1335.85"/>
|
||||
<Cube currency="MXN" rate="24.586"/>
|
||||
<Cube currency="MYR" rate="4.9293"/>
|
||||
<Cube currency="NZD" rate="1.7036"/>
|
||||
<Cube currency="PHP" rate="59.02"/>
|
||||
<Cube currency="SGD" rate="1.618"/>
|
||||
<Cube currency="THB" rate="36.776"/>
|
||||
<Cube currency="ZAR" rate="18.4194"/>
|
||||
</Cube>
|
||||
<Cube time="2021-01-04">
|
||||
<Cube currency="USD" rate="1.2296"/>
|
||||
<Cube currency="JPY" rate="126.62"/>
|
||||
<Cube currency="BGN" rate="1.9558"/>
|
||||
<Cube currency="CZK" rate="26.141"/>
|
||||
<Cube currency="DKK" rate="7.4379"/>
|
||||
<Cube currency="GBP" rate="0.9016"/>
|
||||
<Cube currency="HUF" rate="361.32"/>
|
||||
<Cube currency="PLN" rate="4.5475"/>
|
||||
<Cube currency="RON" rate="4.8713"/>
|
||||
<Cube currency="SEK" rate="10.0895"/>
|
||||
<Cube currency="CHF" rate="1.0811"/>
|
||||
<Cube currency="ISK" rate="156.1"/>
|
||||
<Cube currency="NOK" rate="10.444"/>
|
||||
<Cube currency="HRK" rate="7.5565"/>
|
||||
<Cube currency="RUB" rate="90.342"/>
|
||||
<Cube currency="TRY" rate="9.0579"/>
|
||||
<Cube currency="AUD" rate="1.5928"/>
|
||||
<Cube currency="BRL" rate="6.3241"/>
|
||||
<Cube currency="CAD" rate="1.5621"/>
|
||||
<Cube currency="CNY" rate="7.9484"/>
|
||||
<Cube currency="HKD" rate="9.533"/>
|
||||
<Cube currency="IDR" rate="17062.67"/>
|
||||
<Cube currency="ILS" rate="3.943"/>
|
||||
<Cube currency="INR" rate="89.789"/>
|
||||
<Cube currency="KRW" rate="1332.03"/>
|
||||
<Cube currency="MXN" rate="24.3031"/>
|
||||
<Cube currency="MYR" rate="4.9264"/>
|
||||
<Cube currency="NZD" rate="1.7065"/>
|
||||
<Cube currency="PHP" rate="59.058"/>
|
||||
<Cube currency="SGD" rate="1.6198"/>
|
||||
<Cube currency="THB" rate="36.728"/>
|
||||
<Cube currency="ZAR" rate="17.9214"/>
|
||||
</Cube>
|
||||
<Cube time="2021-06-25">
|
||||
<Cube currency="USD" rate="1.195"/>
|
||||
<Cube currency="JPY" rate="132.27"/>
|
||||
<Cube currency="BGN" rate="1.9558"/>
|
||||
<Cube currency="CZK" rate="25.487"/>
|
||||
<Cube currency="DKK" rate="7.4363"/>
|
||||
<Cube currency="GBP" rate="0.8595"/>
|
||||
<Cube currency="HUF" rate="351.88"/>
|
||||
<Cube currency="PLN" rate="4.5132"/>
|
||||
<Cube currency="RON" rate="4.9263"/>
|
||||
<Cube currency="SEK" rate="10.1103"/>
|
||||
<Cube currency="CHF" rate="1.0956"/>
|
||||
<Cube currency="ISK" rate="147.1"/>
|
||||
<Cube currency="NOK" rate="10.136"/>
|
||||
<Cube currency="HRK" rate="7.4975"/>
|
||||
<Cube currency="RUB" rate="86.188"/>
|
||||
<Cube currency="TRY" rate="10.3887"/>
|
||||
<Cube currency="AUD" rate="1.5726"/>
|
||||
<Cube currency="BRL" rate="5.8635"/>
|
||||
<Cube currency="CAD" rate="1.4696"/>
|
||||
<Cube currency="CNY" rate="7.7139"/>
|
||||
<Cube currency="HKD" rate="9.2751"/>
|
||||
<Cube currency="IDR" rate="17245.4"/>
|
||||
<Cube currency="ILS" rate="3.8811"/>
|
||||
<Cube currency="INR" rate="88.6824"/>
|
||||
<Cube currency="KRW" rate="1346.35"/>
|
||||
<Cube currency="MXN" rate="23.6766"/>
|
||||
<Cube currency="MYR" rate="4.9664"/>
|
||||
<Cube currency="NZD" rate="1.6881"/>
|
||||
<Cube currency="PHP" rate="57.96"/>
|
||||
<Cube currency="SGD" rate="1.6035"/>
|
||||
<Cube currency="THB" rate="38.013"/>
|
||||
<Cube currency="ZAR" rate="16.8359"/>
|
||||
</Cube>
|
||||
<Cube time="1999-01-08">
|
||||
<Cube currency="USD" rate="1.1659"/>
|
||||
<Cube currency="JPY" rate="130.09"/>
|
||||
<Cube currency="CYP" rate="0.58187"/>
|
||||
<Cube currency="CZK" rate="34.938"/>
|
||||
<Cube currency="DKK" rate="7.4433"/>
|
||||
<Cube currency="EEK" rate="15.6466"/>
|
||||
<Cube currency="GBP" rate="0.7094"/>
|
||||
<Cube currency="HUF" rate="250.15"/>
|
||||
<Cube currency="LTL" rate="4.6643"/>
|
||||
<Cube currency="LVL" rate="0.6654"/>
|
||||
<Cube currency="MTL" rate="0.4419"/>
|
||||
<Cube currency="PLN" rate="4.0363"/>
|
||||
<Cube currency="ROL" rate="13143"/>
|
||||
<Cube currency="SEK" rate="9.165"/>
|
||||
<Cube currency="SIT" rate="188.84"/>
|
||||
<Cube currency="SKK" rate="42.56"/>
|
||||
<Cube currency="CHF" rate="1.6138"/>
|
||||
<Cube currency="ISK" rate="80.99"/>
|
||||
<Cube currency="NOK" rate="8.59"/>
|
||||
<Cube currency="TRL" rate="371830"/>
|
||||
<Cube currency="AUD" rate="1.8406"/>
|
||||
<Cube currency="CAD" rate="1.7643"/>
|
||||
<Cube currency="HKD" rate="9.0302"/>
|
||||
<Cube currency="KRW" rate="1366.73"/>
|
||||
<Cube currency="NZD" rate="2.1557"/>
|
||||
<Cube currency="SGD" rate="1.9537"/>
|
||||
<Cube currency="ZAR" rate="6.7855"/>
|
||||
</Cube>
|
||||
<Cube time="1999-01-07">
|
||||
<Cube currency="USD" rate="1.1632"/>
|
||||
<Cube currency="JPY" rate="129.43"/>
|
||||
<Cube currency="CYP" rate="0.58187"/>
|
||||
<Cube currency="CZK" rate="34.886"/>
|
||||
<Cube currency="DKK" rate="7.4431"/>
|
||||
<Cube currency="EEK" rate="15.6466"/>
|
||||
<Cube currency="GBP" rate="0.70585"/>
|
||||
<Cube currency="HUF" rate="250.09"/>
|
||||
<Cube currency="LTL" rate="4.6548"/>
|
||||
<Cube currency="LVL" rate="0.6627"/>
|
||||
<Cube currency="MTL" rate="0.4413"/>
|
||||
<Cube currency="PLN" rate="4.0165"/>
|
||||
<Cube currency="ROL" rate="13092"/>
|
||||
<Cube currency="SEK" rate="9.18"/>
|
||||
<Cube currency="SIT" rate="188.8"/>
|
||||
<Cube currency="SKK" rate="42.765"/>
|
||||
<Cube currency="CHF" rate="1.6165"/>
|
||||
<Cube currency="ISK" rate="81.06"/>
|
||||
<Cube currency="NOK" rate="8.6295"/>
|
||||
<Cube currency="TRL" rate="370147"/>
|
||||
<Cube currency="AUD" rate="1.8474"/>
|
||||
<Cube currency="CAD" rate="1.7602"/>
|
||||
<Cube currency="HKD" rate="9.0131"/>
|
||||
<Cube currency="KRW" rate="1337.16"/>
|
||||
<Cube currency="NZD" rate="2.1531"/>
|
||||
<Cube currency="SGD" rate="1.9436"/>
|
||||
<Cube currency="ZAR" rate="6.8283"/>
|
||||
</Cube>
|
||||
<Cube time="1999-01-06">
|
||||
<Cube currency="USD" rate="1.1743"/>
|
||||
<Cube currency="JPY" rate="131.42"/>
|
||||
<Cube currency="CYP" rate="0.582"/>
|
||||
<Cube currency="CZK" rate="34.85"/>
|
||||
<Cube currency="DKK" rate="7.4452"/>
|
||||
<Cube currency="EEK" rate="15.6466"/>
|
||||
<Cube currency="GBP" rate="0.7076"/>
|
||||
<Cube currency="HUF" rate="250.67"/>
|
||||
<Cube currency="LTL" rate="4.6994"/>
|
||||
<Cube currency="LVL" rate="0.6649"/>
|
||||
<Cube currency="MTL" rate="0.442"/>
|
||||
<Cube currency="PLN" rate="4.0065"/>
|
||||
<Cube currency="ROL" rate="13168"/>
|
||||
<Cube currency="SEK" rate="9.305"/>
|
||||
<Cube currency="SIT" rate="188.7"/>
|
||||
<Cube currency="SKK" rate="42.778"/>
|
||||
<Cube currency="CHF" rate="1.6116"/>
|
||||
<Cube currency="ISK" rate="81.54"/>
|
||||
<Cube currency="NOK" rate="8.7335"/>
|
||||
<Cube currency="TRL" rate="372188"/>
|
||||
<Cube currency="AUD" rate="1.882"/>
|
||||
<Cube currency="CAD" rate="1.7711"/>
|
||||
<Cube currency="HKD" rate="9.101"/>
|
||||
<Cube currency="KRW" rate="1359.54"/>
|
||||
<Cube currency="NZD" rate="2.189"/>
|
||||
<Cube currency="SGD" rate="1.9699"/>
|
||||
<Cube currency="ZAR" rate="6.7307"/>
|
||||
</Cube>
|
||||
<Cube time="1999-01-05">
|
||||
<Cube currency="USD" rate="1.179"/>
|
||||
<Cube currency="JPY" rate="130.96"/>
|
||||
<Cube currency="CYP" rate="0.5823"/>
|
||||
<Cube currency="CZK" rate="34.917"/>
|
||||
<Cube currency="DKK" rate="7.4495"/>
|
||||
<Cube currency="EEK" rate="15.6466"/>
|
||||
<Cube currency="GBP" rate="0.7122"/>
|
||||
<Cube currency="HUF" rate="250.8"/>
|
||||
<Cube currency="LTL" rate="4.7174"/>
|
||||
<Cube currency="LVL" rate="0.6657"/>
|
||||
<Cube currency="MTL" rate="0.4432"/>
|
||||
<Cube currency="PLN" rate="4.0245"/>
|
||||
<Cube currency="ROL" rate="13168"/>
|
||||
<Cube currency="SEK" rate="9.4025"/>
|
||||
<Cube currency="SIT" rate="188.775"/>
|
||||
<Cube currency="SKK" rate="42.848"/>
|
||||
<Cube currency="CHF" rate="1.6123"/>
|
||||
<Cube currency="ISK" rate="81.53"/>
|
||||
<Cube currency="NOK" rate="8.7745"/>
|
||||
<Cube currency="TRL" rate="372816"/>
|
||||
<Cube currency="AUD" rate="1.8944"/>
|
||||
<Cube currency="CAD" rate="1.7965"/>
|
||||
<Cube currency="HKD" rate="9.1341"/>
|
||||
<Cube currency="KRW" rate="1373.01"/>
|
||||
<Cube currency="NZD" rate="2.2011"/>
|
||||
<Cube currency="SGD" rate="1.9655"/>
|
||||
<Cube currency="ZAR" rate="6.7975"/>
|
||||
</Cube>
|
||||
<Cube time="1999-01-04">
|
||||
<Cube currency="USD" rate="1.1789"/>
|
||||
<Cube currency="JPY" rate="133.73"/>
|
||||
<Cube currency="CYP" rate="0.58231"/>
|
||||
<Cube currency="CZK" rate="35.107"/>
|
||||
<Cube currency="DKK" rate="7.4501"/>
|
||||
<Cube currency="EEK" rate="15.6466"/>
|
||||
<Cube currency="GBP" rate="0.7111"/>
|
||||
<Cube currency="HUF" rate="251.48"/>
|
||||
<Cube currency="LTL" rate="4.717"/>
|
||||
<Cube currency="LVL" rate="0.6668"/>
|
||||
<Cube currency="MTL" rate="0.4432"/>
|
||||
<Cube currency="PLN" rate="4.0712"/>
|
||||
<Cube currency="ROL" rate="13111"/>
|
||||
<Cube currency="SEK" rate="9.4696"/>
|
||||
<Cube currency="SIT" rate="189.045"/>
|
||||
<Cube currency="SKK" rate="42.991"/>
|
||||
<Cube currency="CHF" rate="1.6168"/>
|
||||
<Cube currency="ISK" rate="81.48"/>
|
||||
<Cube currency="NOK" rate="8.855"/>
|
||||
<Cube currency="TRL" rate="372274"/>
|
||||
<Cube currency="AUD" rate="1.91"/>
|
||||
<Cube currency="CAD" rate="1.8004"/>
|
||||
<Cube currency="HKD" rate="9.1332"/>
|
||||
<Cube currency="KRW" rate="1398.59"/>
|
||||
<Cube currency="NZD" rate="2.2229"/>
|
||||
<Cube currency="SGD" rate="1.9554"/>
|
||||
<Cube currency="ZAR" rate="6.9358"/>
|
||||
</Cube>
|
||||
</Cube>
|
||||
</gesmes:Envelope>
|
Loading…
Add table
Reference in a new issue