mmnode-ticker: sort output by various parameters

Supported parameters:

    d - 1-day % change
    w - 1-week % change
    m - 1-month % change
    y - 1-year % change
    p - asset price
    c - market cap

Examples:

    # Display top 50 assets by market cap, sorting by price change
    # in last 24 hours:
    $ mmnode-ticker --sort=d 50
This commit is contained in:
The MMGen Project 2025-10-16 17:09:15 +00:00
commit 0a953e3ca0
Signed by: mmgen
GPG key ID: 3F8B1861E32B7DA2
4 changed files with 118 additions and 3 deletions

View file

@ -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, cyan
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
@ -41,6 +41,15 @@ percent_cols = {
'm': 'month',
'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):
def __iter__(self):
@ -274,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):
@ -752,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',
@ -766,6 +789,7 @@ def make_cfg(gcfg_arg):
'proxy',
'proxy2',
'portfolio',
'sort',
'percent_cols',
'asset_limit',
'cached_data',
@ -823,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'),
@ -869,6 +894,8 @@ class Ticker:
def __init__(self, data):
global cfg
self.comma = ',' if cfg.thousands_comma else ''
self.col1_wid = max(len('TOTAL'), (
@ -885,6 +912,18 @@ class Ticker:
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')
@ -951,6 +990,10 @@ class Ticker:
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':
usdprice = self.data[asset.id]['price_usd']

View file

@ -1 +1 @@
3.6.dev6
3.6.dev7

View file

@ -57,6 +57,9 @@ opts_data = {
-r, --add-rows=LIST Add rows for asset specifiers in LIST (comma-separated,
see ASSET SPECIFIERS below). Can also be used to supply
a USD exchange rate for missing assets.
-s, --sort=P Sort output according to parameter P. Valid parameters
are {sp_codes}. See SORT PARAMETERS below.
To reverse the sort, prefix the parameter with r.
-t, --testing Print command(s) to be executed to stdout and exit
-T, --thousands-comma Use comma as a thousands separator
-u, --update-time Include UPDATED (last update time) column
@ -133,6 +136,10 @@ A TRADE_SPECIFIER is a single argument in the format:
a USD rate for the missing asset(s) must be supplied via the --add-columns
or --add-rows options.
SORT PARAMETERS:
{sp_fmt}
PROXY NOTE
@ -225,6 +232,7 @@ To add a portfolio, edit the file
dfl_cachedir = os.path.relpath(dfl_cachedir, start=homedir),
ds = fmt_dict(DataSource.get_sources(), fmt='equal_compact'),
al = DataSource.coinpaprika.dfl_asset_limit,
sp_codes = fmt_list(sort_params, fmt='fancy'),
pc = fmt_list(Ticker.percent_cols, fmt='fancy')),
'notes': lambda s: s.format(
assets = fmt_list(assets_list_gen(cfg_in), fmt='col', indent=' '),
@ -232,6 +240,7 @@ To add a portfolio, edit the file
pf_cfg = os.path.relpath(cfg_in.portfolio_file, start=homedir),
al = DataSource.coinpaprika.dfl_asset_limit,
cc = src_cls['cc'](),
sp_fmt = '\n '.join(f'{k}’ - {v.desc}' for k, v in sort_params.items()),
fi = src_cls['fi']())
}
}
@ -246,7 +255,7 @@ gcfg = Config(opts_data=opts_data, caller_post_init=True)
src_cls, cfg_in = Ticker.make_cfg(gcfg)
from .Ticker import dfl_cachedir, homedir, DataSource, assets_list_gen
from .Ticker import dfl_cachedir, homedir, DataSource, assets_list_gen, sort_params
gcfg._post_init()

View file

@ -90,6 +90,14 @@ class CmdTestScripts(CmdTestBase):
('ticker19', 'ticker [--cached-data 1-5]'),
('ticker20', 'ticker [--cached-data 2-5]'),
('ticker21', 'ticker [--cached-data 5-5]'),
('ticker22', 'ticker [--sort=rp]'),
('ticker23', 'ticker [--sort=rp xmr:10]'),
('ticker24', 'ticker [--sort=p]'),
('ticker25', 'ticker [--sort=p 200]'),
('ticker26', 'ticker [--sort=c -r algo,ada]'),
('ticker27', 'ticker [--sort=rp -r algo,ada]'),
('ticker28', 'ticker [--sort=d -r algo,ada]'),
('ticker29', 'ticker [--sort=y -r algo,ada]'),
)
}
@ -365,3 +373,58 @@ class CmdTestScripts(CmdTestBase):
r'8\) ADA 17.11161 0.51 0.4764 0.00002180',
],
add_opts = ['--add-columns=eurusd=x'])
def ticker22(self):
return self.ticker(
[],
['MONERO', 'ETHEREUM', 'BITCOIN', 'SILVER', 'BRENT', 'GOLD'],
add_opts = ['--name-labels', '--sort=rp'])
def ticker23(self):
return self.ticker(
[],
['MONERO', 'ETHEREUM', 'BITCOIN', 'SILVER', 'BRENT', 'GOLD'],
add_opts = ['--name-labels', '--sort=rp', 'xmr:10'])
def ticker24(self):
return self.ticker(
[],
['BITCOIN', 'ETHEREUM', 'MONERO', 'GOLD', 'BRENT', 'SILVER'],
add_opts = ['--name-labels', '--sort=p'])
def ticker25(self):
return self.ticker(
[],
[
r' 1\) BITCOIN',
r' 2\) ETHEREUM',
r'30\) MONERO',
r'23\) LITECOIN',
r' 8\) CARDANO',
r'33\) ALGORAND'
],
add_opts = ['--name-labels', '--sort=p', '200'])
def ticker26(self):
return self.ticker(
[],
['BITCOIN', 'ETHEREUM', 'MONERO', 'CARDANO', 'ALGORAND'],
add_opts = ['--name-labels', '--sort=c', '-r', 'ada,algo'])
def ticker27(self):
return self.ticker(
[],
['MONERO', 'ETHEREUM', 'BITCOIN', 'S&P', 'NASDAQ', 'DOW', 'ALGORAND', 'CARDANO'],
add_opts = ['--name-labels', '--sort=rp', '--add-rows=ada-cardano,algo-algorand'])
def ticker28(self):
return self.ticker(
[],
['ETHEREUM', 'MONERO', 'BITCOIN', 'NASDAQ', 'S&P', 'DOW', 'CARDANO', 'ALGORAND'],
add_opts = ['--widest', '--sort=d', '-r', 'ada,algo'])
def ticker29(self):
return self.ticker(
[],
['ETHEREUM', 'BITCOIN', 'MONERO', 'S&P', 'DOW', 'NASDAQ', 'CARDANO', 'ALGORAND'],
add_opts = ['--widest', '-s', 'y', '-r', 'ada,algo'])