|
|
@@ -24,7 +24,7 @@ from subprocess import run, PIPE, CalledProcessError
|
|
|
from decimal import Decimal
|
|
|
from collections import namedtuple
|
|
|
|
|
|
-from mmgen.color import red, yellow, green, blue, orange, gray
|
|
|
+from mmgen.color import red, yellow, green, blue, orange, gray, cyan, pink
|
|
|
from mmgen.util import msg, msg_r, rmsg, Msg, Msg_r, die, fmt, fmt_list, fmt_dict, list_gen, suf, is_int
|
|
|
from mmgen.ui import do_pager
|
|
|
|
|
|
@@ -39,8 +39,16 @@ percent_cols = {
|
|
|
'd': 'day',
|
|
|
'w': 'week',
|
|
|
'm': 'month',
|
|
|
- 'y': 'year',
|
|
|
-}
|
|
|
+ 'y': 'year'}
|
|
|
+
|
|
|
+sp = namedtuple('sort_parameter', ['key', 'desc'])
|
|
|
+sort_params = {
|
|
|
+ 'd': sp('percent_change_24h', '1-day % change'),
|
|
|
+ 'w': sp('percent_change_7d', '1-week % change'),
|
|
|
+ 'm': sp('percent_change_30d', '1-month % change'),
|
|
|
+ 'y': sp('percent_change_1y', '1-year % change'),
|
|
|
+ 'p': sp('price_usd', 'asset price'),
|
|
|
+ 'c': sp('market_cap', 'market cap')}
|
|
|
|
|
|
class RowDict(dict):
|
|
|
|
|
|
@@ -275,6 +283,7 @@ class DataSource:
|
|
|
'percent_change_30d': data['pct_chg_4wks'],
|
|
|
'percent_change_7d': data['pct_chg_1wk'],
|
|
|
'percent_change_24h': data['regularMarketChangePercent']['raw'] * 100,
|
|
|
+ 'market_cap': 0, # dummy - required for sorting
|
|
|
'last_updated': data['regularMarketTime']}
|
|
|
|
|
|
def rate_limit_errmsg(self, elapsed):
|
|
|
@@ -713,8 +722,8 @@ def make_cfg(gcfg_arg):
|
|
|
return ()
|
|
|
|
|
|
def get_portfolio():
|
|
|
- return {k: Decimal(v) for k, v in cfg_in.portfolio.items()
|
|
|
- if (not gcfg.btc) or k == 'btc-bitcoin'}
|
|
|
+ return tuple((k, Decimal(v)) for k, v in cfg_in.portfolio.items()
|
|
|
+ if (not gcfg.btc) or k == 'btc-bitcoin')
|
|
|
|
|
|
def parse_add_precision(arg):
|
|
|
if not arg:
|
|
|
@@ -753,6 +762,19 @@ def make_cfg(gcfg_arg):
|
|
|
'' if proxy == '' else 'none' if (proxy and proxy.lower() == 'none')
|
|
|
else (proxy or cfg_in.cfg.get(name)))
|
|
|
|
|
|
+ def get_sort_opt():
|
|
|
+ match get_cfg_var('sort'):
|
|
|
+ case None:
|
|
|
+ return None
|
|
|
+ case s if s in sort_params:
|
|
|
+ return (s, True)
|
|
|
+ case s if s in ['r' + ch for ch in sort_params]:
|
|
|
+ return (s[1], False)
|
|
|
+ case s:
|
|
|
+ die(1,
|
|
|
+ f'{s!r}: invalid parameter for --sort option (must be one of {fmt_list(sort_params)})'
|
|
|
+ '\nTo reverse the sort, prefix the code letter with ‘r’')
|
|
|
+
|
|
|
cfg_tuple = namedtuple('global_cfg',[
|
|
|
'rows',
|
|
|
'usr_rows',
|
|
|
@@ -767,6 +789,7 @@ def make_cfg(gcfg_arg):
|
|
|
'proxy',
|
|
|
'proxy2',
|
|
|
'portfolio',
|
|
|
+ 'sort',
|
|
|
'percent_cols',
|
|
|
'asset_limit',
|
|
|
'cached_data',
|
|
|
@@ -824,6 +847,7 @@ def make_cfg(gcfg_arg):
|
|
|
proxy = proxy,
|
|
|
proxy2 = None if proxy2 == 'none' else '' if proxy2 == '' else (proxy2 or proxy),
|
|
|
portfolio = portfolio,
|
|
|
+ sort = get_sort_opt(),
|
|
|
percent_cols = parse_percent_cols(get_cfg_var('percent_cols')),
|
|
|
asset_limit = get_cfg_var('asset_limit'),
|
|
|
cached_data = get_cfg_var('cached_data'),
|
|
|
@@ -866,9 +890,12 @@ class Ticker:
|
|
|
|
|
|
offer = None
|
|
|
to_asset = None
|
|
|
+ hidden_groups = ('extra',)
|
|
|
|
|
|
def __init__(self, data):
|
|
|
|
|
|
+ global cfg
|
|
|
+
|
|
|
self.comma = ',' if cfg.thousands_comma else ''
|
|
|
|
|
|
self.col1_wid = max(len('TOTAL'), (
|
|
|
@@ -877,6 +904,26 @@ class Ticker:
|
|
|
|
|
|
self.rows = RowDict(
|
|
|
{k: tuple(row._replace(id=self.get_id(row)) for row in v) for k, v in cfg.rows.items()})
|
|
|
+
|
|
|
+ if cfg.asset_range:
|
|
|
+ self.max_rank = 0
|
|
|
+ for group, rows in self.rows.items():
|
|
|
+ if group not in self.hidden_groups:
|
|
|
+ for row in rows:
|
|
|
+ self.max_rank = max(self.max_rank, int(data[row.id]['rank']))
|
|
|
+
|
|
|
+ if cfg.sort:
|
|
|
+ code, reverse = cfg.sort
|
|
|
+ key = sort_params[code].key
|
|
|
+ sort_func = lambda row: data[row.id][key]
|
|
|
+ pf_sort_func = lambda row: data[row[0]][key]
|
|
|
+ for group in self.rows.keys():
|
|
|
+ if group not in self.hidden_groups:
|
|
|
+ self.rows[group] = sorted(self.rows[group], key=sort_func, reverse=reverse)
|
|
|
+ if cfg.portfolio:
|
|
|
+ cfg = cfg._replace(
|
|
|
+ portfolio = sorted(cfg.portfolio, key=pf_sort_func, reverse=reverse))
|
|
|
+
|
|
|
self.col_usd_prices = {k: self.data[k]['price_usd'] for k in self.col_ids}
|
|
|
self.prices = {row.id: self.get_row_prices(row.id) for row in self.rows if row.id in data}
|
|
|
self.prices['usd-us-dollar'] = self.get_row_prices('usd-us-dollar')
|
|
|
@@ -932,7 +979,20 @@ class Ticker:
|
|
|
return self.data[id]['name'].upper()
|
|
|
|
|
|
def gen_output(self):
|
|
|
- yield 'Current time: {} UTC'.format(time.strftime('%F %X', time.gmtime(now)))
|
|
|
+
|
|
|
+ def process_rows(rows):
|
|
|
+ yield '-' * self.hl_wid
|
|
|
+ for row in rows:
|
|
|
+ try:
|
|
|
+ yield self.fmt_row(self.data[row.id])
|
|
|
+ except KeyError:
|
|
|
+ yield gray(f'(no data for {row.id})')
|
|
|
+
|
|
|
+ yield 'Current time: {}'.format(cyan(time.strftime('%F %X', time.gmtime(now)) + ' UTC'))
|
|
|
+
|
|
|
+ if cfg.sort:
|
|
|
+ text = sort_params[cfg.sort[0]].desc + ('' if cfg.sort[1] else ' [reversed]')
|
|
|
+ yield f'Sort order: {pink(text.upper())}'
|
|
|
|
|
|
for asset in self.usr_col_assets:
|
|
|
if asset.symbol != 'USD':
|
|
|
@@ -962,21 +1022,11 @@ class Ticker:
|
|
|
yield self.table_hdr
|
|
|
|
|
|
if cfg.asset_range:
|
|
|
- yield '-' * self.hl_wid
|
|
|
- for n, row in enumerate(self.rows['asset_list'], cfg.asset_range[0]):
|
|
|
- try:
|
|
|
- yield self.fmt_row(self.data[row.id], idx=n)
|
|
|
- except KeyError:
|
|
|
- yield gray(f'(no data for {row.id})')
|
|
|
+ yield from process_rows(self.rows['asset_list'])
|
|
|
else:
|
|
|
for rows in self.rows.values():
|
|
|
if rows:
|
|
|
- yield '-' * self.hl_wid
|
|
|
- for row in rows:
|
|
|
- try:
|
|
|
- yield self.fmt_row(self.data[row.id])
|
|
|
- except KeyError:
|
|
|
- yield gray(f'(no data for {row.id})')
|
|
|
+ yield from process_rows(rows)
|
|
|
|
|
|
yield '-' * self.hl_wid
|
|
|
|
|
|
@@ -987,7 +1037,7 @@ class Ticker:
|
|
|
yield blue('PORTFOLIO')
|
|
|
yield self.table_hdr
|
|
|
yield '-' * self.hl_wid
|
|
|
- for sym, amt in cfg.portfolio.items():
|
|
|
+ for sym, amt in cfg.portfolio:
|
|
|
try:
|
|
|
yield self.fmt_row(self.data[sym], amt=amt)
|
|
|
except KeyError:
|
|
|
@@ -1005,16 +1055,17 @@ class Ticker:
|
|
|
self.adjust = cfg.adjust
|
|
|
self.show_adj = self.adjust != 1
|
|
|
self.usr_col_assets = [asset._replace(id=self.get_id(asset)) for asset in cfg.usr_columns]
|
|
|
- self.col_ids = ('usd-us-dollar',) + tuple(a.id for a in self.usr_col_assets) + ('btc-bitcoin',)
|
|
|
+ self.col_ids = ('usd-us-dollar', 'btc-bitcoin') + tuple(a.id for a in self.usr_col_assets)
|
|
|
|
|
|
super().__init__(data)
|
|
|
|
|
|
self.format_last_updated_col()
|
|
|
|
|
|
if cfg.portfolio:
|
|
|
- self.prices['total'] = {col_id: sum(self.prices[row.id][col_id] * cfg.portfolio[row.id]
|
|
|
+ pf_dict = dict(cfg.portfolio)
|
|
|
+ self.prices['total'] = {col_id: sum(self.prices[row.id][col_id] * pf_dict[row.id]
|
|
|
for row in self.rows
|
|
|
- if row.id in cfg.portfolio and row.id in data)
|
|
|
+ if row.id in pf_dict and row.id in data)
|
|
|
for col_id in self.col_ids}
|
|
|
|
|
|
self.init_prec()
|
|
|
@@ -1028,7 +1079,7 @@ class Ticker:
|
|
|
d['price_usd'] / self.col_usd_prices[k]
|
|
|
) * self.adjust for k in self.col_ids}
|
|
|
|
|
|
- def fmt_row(self, d, amt=None, amt_fmt=None, idx=None):
|
|
|
+ def fmt_row(self, d, amt=None, amt_fmt=None):
|
|
|
|
|
|
def fmt_pct(n):
|
|
|
return gray(' --') if n is None else (red, green)[n>=0](f'{n:+7.2f}')
|
|
|
@@ -1041,7 +1092,7 @@ class Ticker:
|
|
|
amt_fmt = amt_fmt.rstrip('0').rstrip('.')
|
|
|
|
|
|
return self.fs_num.format(
|
|
|
- idx = idx,
|
|
|
+ idx = int(d['rank']) if cfg.asset_range else None,
|
|
|
mcap = d.get('market_cap') / 1_000_000_000 if cfg.asset_range else None,
|
|
|
lbl = self.create_label(d['id']) if cfg.name_labels else d['symbol'],
|
|
|
pc1 = fmt_pct(d.get('percent_change_7d')),
|
|
|
@@ -1091,7 +1142,7 @@ class Ticker:
|
|
|
if b])
|
|
|
|
|
|
if cfg.asset_range:
|
|
|
- num_w = len(str(len(cfg.rows['asset_list'])))
|
|
|
+ num_w = len(str(self.max_rank))
|
|
|
col_fs_data.update({
|
|
|
'idx': fd(' ' * (num_w + 2), f'{{idx:{num_w}}}) ', num_w + 2),
|
|
|
'mcap': fd('{mcap:>12}', '{mcap:12.5f}', 12)})
|
|
|
@@ -1186,7 +1237,7 @@ class Ticker:
|
|
|
self.fs_str += ' {upd}'
|
|
|
self.hl_wid += self.upd_w + 2
|
|
|
|
|
|
- def fmt_row(self, d, idx=None):
|
|
|
+ def fmt_row(self, d):
|
|
|
id = d['id']
|
|
|
p = self.prices[id][self.asset.id] * self.asset.amount
|
|
|
p_spot = '{:{}{}.{}f}'.format(p, self.max_wid, self.comma, 8+cfg.add_prec)
|