mirror of
https://git.wownero.com/wowlet/wowlet-backend.git
synced 2024-08-15 01:03:13 +00:00
114 lines
3.6 KiB
Python
114 lines
3.6 KiB
Python
# SPDX-License-Identifier: BSD-3-Clause
|
|
# Copyright (c) 2020, The Monero Project.
|
|
# Copyright (c) 2020, dsc@xmr.pm
|
|
|
|
import asyncio
|
|
import os
|
|
import json
|
|
from typing import List, Union
|
|
from datetime import datetime
|
|
|
|
import aiofiles
|
|
|
|
import settings
|
|
from wowlet_backend.utils import httpget
|
|
from wowlet_backend.tasks import FeatherTask
|
|
|
|
|
|
class HistoricalPriceTask(FeatherTask):
|
|
"""
|
|
This class manages a historical price (USD) database, saved in a
|
|
textfile at `self._path`. A Feather wallet instance will ask
|
|
for the historical fiat price database on startup (but only
|
|
in chunks of a month for anti-fingerprinting reasons).
|
|
|
|
The task in this class simply keeps the fiat database
|
|
up-to-date locally.
|
|
"""
|
|
def __init__(self, interval: int = 43200):
|
|
super(HistoricalPriceTask, self).__init__(interval)
|
|
|
|
self._cache_key = f"historical_fiat"
|
|
self._path = f"data/historical_prices_{settings.COIN_SYMBOL}.json"
|
|
self._http_endpoint = f"https://www.coingecko.com/price_charts/{settings.COIN_NAME}/usd/max.json"
|
|
|
|
self._year_genesis = int(settings.COIN_GENESIS_DATE[:4])
|
|
|
|
asyncio.create_task(self._load())
|
|
|
|
async def task(self) -> Union[dict, None]:
|
|
content = await httpget(self._http_endpoint, json=True, raise_for_status=False)
|
|
if "stats" not in content:
|
|
raise Exception()
|
|
|
|
stats: List[List] = content.get('stats', []) # [[timestamp,USD],]
|
|
if not stats:
|
|
return
|
|
|
|
data = {
|
|
year: {
|
|
month: {} for month in range(1, 13)
|
|
} for year in range(self._year_genesis, datetime.now().year + 1)
|
|
}
|
|
|
|
# timestamp:USD
|
|
daily_price_blob = {day[0]: day[1] for day in stats}
|
|
|
|
# normalize
|
|
for timestamp, usd in daily_price_blob.items():
|
|
_date = datetime.fromtimestamp(timestamp / 1000)
|
|
data[_date.year].setdefault(_date.month, {})
|
|
data[_date.year][_date.month][_date.day] = usd
|
|
|
|
# update local database
|
|
await self._write(data)
|
|
return data
|
|
|
|
async def _load(self) -> None:
|
|
if not os.path.exists(self._path):
|
|
return
|
|
|
|
async with aiofiles.open(self._path, mode="r") as f:
|
|
content = await f.read()
|
|
blob = json.loads(content)
|
|
|
|
# ¯\_(ツ)_/¯
|
|
blob = {int(k): {
|
|
int(_k): {
|
|
int(__k): __v for __k, __v in _v.items()
|
|
} for _k, _v in v.items()
|
|
} for k, v in blob.items()}
|
|
|
|
await self.cache_set(self._cache_key, blob)
|
|
|
|
async def _write(self, blob: dict) -> None:
|
|
data = json.dumps(blob, sort_keys=True, indent=4)
|
|
async with aiofiles.open(self._path, mode="w") as f:
|
|
await f.write(data)
|
|
|
|
@staticmethod
|
|
async def get(year: int, month: int = None) -> Union[dict, None]:
|
|
"""This function is called when a Feather wallet client asks
|
|
for (a range of) historical fiat information. It returns the
|
|
data filtered by the parameters."""
|
|
from wowlet_backend.factory import cache
|
|
|
|
blob = await cache.get("historical_fiat")
|
|
blob = json.loads(blob)
|
|
if year not in blob:
|
|
return
|
|
|
|
rtn = {}
|
|
if not month:
|
|
for _m, days in blob[year].items():
|
|
for day, price in days.items():
|
|
rtn[datetime(year, _m, day).strftime('%Y%m%d')] = price
|
|
return rtn
|
|
|
|
if month not in blob[year]:
|
|
return
|
|
|
|
for day, price in blob[year][month].items():
|
|
rtn[datetime(year, month, day).strftime('%Y%m%d')] = price
|
|
|
|
return rtn
|