|
|
@ -8,10 +8,11 @@ import time |
|
|
|
import csv |
|
|
|
import decimal |
|
|
|
from decimal import Decimal |
|
|
|
import concurrent.futures |
|
|
|
import traceback |
|
|
|
from typing import Sequence |
|
|
|
|
|
|
|
from aiorpcx.curio import timeout_after, TaskTimeout, TaskGroup |
|
|
|
|
|
|
|
from .bitcoin import COIN |
|
|
|
from .i18n import _ |
|
|
|
from .util import (PrintError, ThreadJob, make_dir, log_exceptions, |
|
|
@ -40,14 +41,18 @@ class ExchangeBase(PrintError): |
|
|
|
async def get_raw(self, site, get_string): |
|
|
|
# APIs must have https |
|
|
|
url = ''.join(['https://', site, get_string]) |
|
|
|
async with make_aiohttp_session(Network.get_instance().proxy) as session: |
|
|
|
network = Network.get_instance() |
|
|
|
proxy = network.proxy if network else None |
|
|
|
async with make_aiohttp_session(proxy) as session: |
|
|
|
async with session.get(url) as response: |
|
|
|
return await response.text() |
|
|
|
|
|
|
|
async def get_json(self, site, get_string): |
|
|
|
# APIs must have https |
|
|
|
url = ''.join(['https://', site, get_string]) |
|
|
|
async with make_aiohttp_session(Network.get_instance().proxy) as session: |
|
|
|
network = Network.get_instance() |
|
|
|
proxy = network.proxy if network else None |
|
|
|
async with make_aiohttp_session(proxy) as session: |
|
|
|
async with session.get(url) as response: |
|
|
|
# set content_type to None to disable checking MIME type |
|
|
|
return await response.json(content_type=None) |
|
|
@ -60,7 +65,6 @@ class ExchangeBase(PrintError): |
|
|
|
def name(self): |
|
|
|
return self.__class__.__name__ |
|
|
|
|
|
|
|
@log_exceptions |
|
|
|
async def update_safe(self, ccy): |
|
|
|
try: |
|
|
|
self.print_error("getting fx quotes for", ccy) |
|
|
@ -71,9 +75,6 @@ class ExchangeBase(PrintError): |
|
|
|
self.quotes = {} |
|
|
|
self.on_quotes() |
|
|
|
|
|
|
|
def update(self, ccy): |
|
|
|
asyncio.get_event_loop().create_task(self.update_safe(ccy)) |
|
|
|
|
|
|
|
def read_historical_rates(self, ccy, cache_dir): |
|
|
|
filename = os.path.join(cache_dir, self.name() + '_'+ ccy) |
|
|
|
if os.path.exists(filename): |
|
|
@ -123,8 +124,8 @@ class ExchangeBase(PrintError): |
|
|
|
def historical_rate(self, ccy, d_t): |
|
|
|
return self.history.get(ccy, {}).get(d_t.strftime('%Y-%m-%d'), 'NaN') |
|
|
|
|
|
|
|
def get_currencies(self): |
|
|
|
rates = self.get_rates('') |
|
|
|
async def get_currencies(self): |
|
|
|
rates = await self.get_rates('') |
|
|
|
return sorted([str(a) for (a, b) in rates.items() if b is not None and len(a)==3]) |
|
|
|
|
|
|
|
class BitcoinAverage(ExchangeBase): |
|
|
@ -229,20 +230,6 @@ class BlockchainInfo(ExchangeBase): |
|
|
|
return dict([(r, Decimal(json[r]['15m'])) for r in json]) |
|
|
|
|
|
|
|
|
|
|
|
class BTCChina(ExchangeBase): |
|
|
|
|
|
|
|
async def get_rates(self, ccy): |
|
|
|
json = await self.get_json('data.btcchina.com', '/data/ticker') |
|
|
|
return {'CNY': Decimal(json['ticker']['last'])} |
|
|
|
|
|
|
|
|
|
|
|
class BTCParalelo(ExchangeBase): |
|
|
|
|
|
|
|
async def get_rates(self, ccy): |
|
|
|
json = await self.get_json('btcparalelo.com', '/api/price') |
|
|
|
return {'VEF': Decimal(json['price'])} |
|
|
|
|
|
|
|
|
|
|
|
class Coinbase(ExchangeBase): |
|
|
|
|
|
|
|
async def get_rates(self, ccy): |
|
|
@ -280,20 +267,6 @@ class CoinDesk(ExchangeBase): |
|
|
|
return json['bpi'] |
|
|
|
|
|
|
|
|
|
|
|
class Coinsecure(ExchangeBase): |
|
|
|
|
|
|
|
async def get_rates(self, ccy): |
|
|
|
json = await self.get_json('api.coinsecure.in', '/v0/noauth/newticker') |
|
|
|
return {'INR': Decimal(json['lastprice'] / 100.0 )} |
|
|
|
|
|
|
|
|
|
|
|
class Foxbit(ExchangeBase): |
|
|
|
|
|
|
|
async def get_rates(self,ccy): |
|
|
|
json = await self.get_json('api.bitvalor.com', '/v1/ticker.json') |
|
|
|
return {'BRL': Decimal(json['ticker_1h']['exchanges']['FOX']['last'])} |
|
|
|
|
|
|
|
|
|
|
|
class itBit(ExchangeBase): |
|
|
|
|
|
|
|
async def get_rates(self, ccy): |
|
|
@ -344,23 +317,6 @@ class TheRockTrading(ExchangeBase): |
|
|
|
'/v1/funds/BTCEUR/ticker') |
|
|
|
return {'EUR': Decimal(json['last'])} |
|
|
|
|
|
|
|
class Unocoin(ExchangeBase): |
|
|
|
|
|
|
|
async def get_rates(self, ccy): |
|
|
|
json = await self.get_json('www.unocoin.com', 'trade?buy') |
|
|
|
return {'INR': Decimal(json)} |
|
|
|
|
|
|
|
|
|
|
|
class WEX(ExchangeBase): |
|
|
|
|
|
|
|
async def get_rates(self, ccy): |
|
|
|
json_eur = await self.get_json('wex.nz', '/api/3/ticker/btc_eur') |
|
|
|
json_rub = await self.get_json('wex.nz', '/api/3/ticker/btc_rur') |
|
|
|
json_usd = await self.get_json('wex.nz', '/api/3/ticker/btc_usd') |
|
|
|
return {'EUR': Decimal(json_eur['btc_eur']['last']), |
|
|
|
'RUB': Decimal(json_rub['btc_rur']['last']), |
|
|
|
'USD': Decimal(json_usd['btc_usd']['last'])} |
|
|
|
|
|
|
|
|
|
|
|
class Winkdex(ExchangeBase): |
|
|
|
|
|
|
@ -394,25 +350,39 @@ def dictinvert(d): |
|
|
|
return inv |
|
|
|
|
|
|
|
def get_exchanges_and_currencies(): |
|
|
|
# load currencies.json from disk |
|
|
|
path = resource_path('currencies.json') |
|
|
|
try: |
|
|
|
with open(path, 'r', encoding='utf-8') as f: |
|
|
|
return json.loads(f.read()) |
|
|
|
except: |
|
|
|
pass |
|
|
|
# or if not present, generate it now. |
|
|
|
print("cannot find currencies.json. will regenerate it now.") |
|
|
|
d = {} |
|
|
|
is_exchange = lambda obj: (inspect.isclass(obj) |
|
|
|
and issubclass(obj, ExchangeBase) |
|
|
|
and obj != ExchangeBase) |
|
|
|
exchanges = dict(inspect.getmembers(sys.modules[__name__], is_exchange)) |
|
|
|
for name, klass in exchanges.items(): |
|
|
|
exchange = klass(None, None) |
|
|
|
|
|
|
|
async def get_currencies_safe(name, exchange): |
|
|
|
try: |
|
|
|
d[name] = exchange.get_currencies() |
|
|
|
d[name] = await exchange.get_currencies() |
|
|
|
print(name, "ok") |
|
|
|
except: |
|
|
|
print(name, "error") |
|
|
|
continue |
|
|
|
|
|
|
|
async def query_all_exchanges_for_their_ccys_over_network(): |
|
|
|
async with timeout_after(10): |
|
|
|
async with TaskGroup() as group: |
|
|
|
for name, klass in exchanges.items(): |
|
|
|
exchange = klass(None, None) |
|
|
|
await group.spawn(get_currencies_safe(name, exchange)) |
|
|
|
loop = asyncio.get_event_loop() |
|
|
|
try: |
|
|
|
loop.run_until_complete(query_all_exchanges_for_their_ccys_over_network()) |
|
|
|
except Exception as e: |
|
|
|
pass |
|
|
|
with open(path, 'w', encoding='utf-8') as f: |
|
|
|
f.write(json.dumps(d, indent=4, sort_keys=True)) |
|
|
|
return d |
|
|
@ -478,17 +448,18 @@ class FxThread(ThreadJob): |
|
|
|
|
|
|
|
async def run(self): |
|
|
|
while True: |
|
|
|
# approx. every 2.5 minutes, refresh spot price |
|
|
|
try: |
|
|
|
await asyncio.wait_for(self._trigger.wait(), 150) |
|
|
|
except concurrent.futures.TimeoutError: |
|
|
|
async with timeout_after(150): |
|
|
|
await self._trigger.wait() |
|
|
|
self._trigger.clear() |
|
|
|
# we were manually triggered, so get historical rates |
|
|
|
if self.is_enabled() and self.show_history(): |
|
|
|
self.exchange.get_historical_rates(self.ccy, self.cache_dir) |
|
|
|
except TaskTimeout: |
|
|
|
pass |
|
|
|
else: |
|
|
|
self._trigger.clear() |
|
|
|
if self.is_enabled(): |
|
|
|
if self.show_history(): |
|
|
|
self.exchange.get_historical_rates(self.ccy, self.cache_dir) |
|
|
|
if self.is_enabled(): |
|
|
|
self.exchange.update(self.ccy) |
|
|
|
await self.exchange.update_safe(self.ccy) |
|
|
|
|
|
|
|
def is_enabled(self): |
|
|
|
return bool(self.config.get('use_exchange_rate')) |
|
|
|