From 84e8ea65d0b86cab2896125e1f1022d4848742a8 Mon Sep 17 00:00:00 2001 From: The MMGen Project Date: Mon, 20 Oct 2025 09:14:34 +0000 Subject: [PATCH] mmnode-ticker: display percent change columns in terms of non-USD assets Any crypto or finance asset may be specified. Examples: # Display percentage changes in relation to Bitcoin: $ mmnode-ticker --widest --pchg-unit=btc # In relation to Gold: $ mmnode-ticker --widest --pchg-unit=gc=f # In relation to Euros: $ mmnode-ticker --widest --pchg-unit=eurusd=x # In relation to the Nasdaq Index: $ mmnode-ticker --widest --pchg-unit=^ixic --- mmgen_node_tools/Ticker.py | 29 +++++++++++++++-- mmgen_node_tools/data/version | 2 +- mmgen_node_tools/main_ticker.py | 2 ++ test/cmdtest_d/misc.py | 38 +++++++++++++++++++++++ test/ref/ticker/ticker-cfg-sort-pchg.yaml | 14 +++++++++ 5 files changed, 82 insertions(+), 3 deletions(-) create mode 100644 test/ref/ticker/ticker-cfg-sort-pchg.yaml diff --git a/mmgen_node_tools/Ticker.py b/mmgen_node_tools/Ticker.py index 7bcb567..4b49b44 100755 --- a/mmgen_node_tools/Ticker.py +++ b/mmgen_node_tools/Ticker.py @@ -535,6 +535,10 @@ def gen_data(data): 'name': 'US Dollar', 'price_usd': Decimal(1), 'price_btc': Decimal(1) / btcusd, + 'percent_change_24h': 0.0, + 'percent_change_7d': 0.0, + 'percent_change_30d': 0.0, + 'percent_change_1y': 0.0, 'last_updated': None}) def cache_data(data_src, no_overwrite=False): @@ -742,7 +746,8 @@ def make_cfg(gcfg_arg): {k: tuple(parse_asset_id(e) for e in v) for k, v in cfg_in.cfg['assets'].items()}) for hdr, data in ( ('user_uniq', get_usr_assets()), - ('portfolio_uniq', get_portfolio_assets())): + ('portfolio_uniq', get_portfolio_assets()), + ('pchg_unit_uniq', [pchg_unit] if pchg_unit else None)): if data: if uniq_data := tuple(gen_uniq(data, 'symbol', preload=rows)): rows[hdr] = uniq_data @@ -791,6 +796,7 @@ def make_cfg(gcfg_arg): 'portfolio', 'sort', 'percent_cols', + 'pchg_unit', 'asset_limit', 'cached_data', 'elapsed', @@ -833,6 +839,9 @@ def make_cfg(gcfg_arg): if portfolio and asset_range: die(1, '--portfolio not supported in market cap view') + pchg_unit = (lambda s: parse_asset_id(s, require_label=False) if s else None)( + get_cfg_var('pchg_unit')) + cfg = cfg_tuple( rows = create_rows(), usr_rows = usr_rows, @@ -849,6 +858,7 @@ def make_cfg(gcfg_arg): portfolio = portfolio, sort = get_sort_opt(), percent_cols = parse_percent_cols(get_cfg_var('percent_cols')), + pchg_unit = pchg_unit, asset_limit = get_cfg_var('asset_limit'), cached_data = get_cfg_var('cached_data'), elapsed = get_cfg_var('elapsed'), @@ -890,7 +900,7 @@ class Ticker: offer = None to_asset = None - hidden_groups = ('extra',) + hidden_groups = ('extra', 'pchg_unit_uniq') def __init__(self, data): @@ -925,6 +935,14 @@ class Ticker: cfg = cfg._replace( portfolio = sorted(cfg.portfolio, key=pf_sort_func, reverse=reverse)) + if cfg.pchg_unit: + self.pchg_data = self.data[self.get_id(cfg.pchg_unit)] + self.pchg_factors = {k: (self.pchg_data[k] / 100) + 1 for k in ( + 'percent_change_24h', + 'percent_change_7d', + 'percent_change_30d', + 'percent_change_1y')} + 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') @@ -995,6 +1013,11 @@ class Ticker: text = sort_params[cfg.sort[0]].desc + ('' if cfg.sort[1] else ' [reversed]') yield f'Sort order: {pink(text.upper())}' + if cfg.pchg_unit: + yield 'Percent change unit: {}'.format(orange('{} ({})'.format( + self.pchg_data['symbol'], + self.pchg_data['name'].upper()))) + for asset in self.usr_col_assets: if asset.symbol != 'USD': usdprice = self.data[asset.id]['price_usd'] @@ -1085,6 +1108,8 @@ class Ticker: def fmt_pct(d, key, wid=7): if (n := d.get(key)) is None: return gray(' --') + if cfg.pchg_unit: + n = ((((n / 100) + 1) / self.pchg_factors[key]) - 1) * 100 return (red, green)[n>=0](f'{n:+{wid}.2f}') p = self.prices[d['id']] diff --git a/mmgen_node_tools/data/version b/mmgen_node_tools/data/version index 6eb528d..47203f7 100644 --- a/mmgen_node_tools/data/version +++ b/mmgen_node_tools/data/version @@ -1 +1 @@ -3.6.dev8 +3.6.dev9 diff --git a/mmgen_node_tools/main_ticker.py b/mmgen_node_tools/main_ticker.py index d4c0055..cfca2ac 100755 --- a/mmgen_node_tools/main_ticker.py +++ b/mmgen_node_tools/main_ticker.py @@ -63,6 +63,8 @@ opts_data = { -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 +-U, --pchg-unit=A Use asset ‘A’ as unit of reference for percentage + change columns (default: USD) -v, --verbose Be more verbose -w, --wide Display most optional columns (same as -unT -p d,w) -W, --widest Display all optional columns (same as -unT -p d,w,m,y) diff --git a/test/cmdtest_d/misc.py b/test/cmdtest_d/misc.py index c2a4188..9112f48 100755 --- a/test/cmdtest_d/misc.py +++ b/test/cmdtest_d/misc.py @@ -98,6 +98,9 @@ class CmdTestScripts(CmdTestBase): ('ticker27', 'ticker [--sort=rp -r algo,ada]'), ('ticker28', 'ticker [--sort=d -r algo,ada]'), ('ticker29', 'ticker [--sort=y -r algo,ada]'), + ('ticker30', 'ticker [--cached-data --wide --pchg-unit=btc --sort=d] (cf with config file)'), + ('ticker31', 'ticker [--cached-data --wide --pchg-unit=usd] (cf with no USD)'), + ('ticker32', 'ticker [--cached-data --wide --pchg-unit=gc=f]'), ) } @@ -438,3 +441,38 @@ class CmdTestScripts(CmdTestBase): [], ['ETHEREUM', 'BITCOIN', 'MONERO', 'S&P', 'DOW', 'NASDAQ', 'CARDANO', 'ALGORAND'], add_opts = ['--widest', '-s', 'y', '-r', 'ada,algo']) + + def ticker30(self): + self.copy_file('ticker-cfg-sort-pchg.yaml', 'ticker-cfg.yaml') + t = self.ticker(add_opts=['--wide']) + chk1 = '\n'.join(t.read().splitlines()[5:-2]) + self.rm_file('ticker-cfg.yaml') + + self.copy_file('ticker-cfg-bad.yaml', 'ticker-cfg.yaml') + t = self.ticker(add_opts=['--wide', '--pchg-unit=btc', '--sort=d'], no_msg=True) + chk2 = '\n'.join(t.read().splitlines()[5:-2]) + self.rm_file('ticker-cfg.yaml') + + assert chk1 == chk2, f'\nOUTPUT 1\n{chk1}\n!= OUTPUT 2\n{chk2}\n' + return t + + def ticker31(self): + t = self.ticker(add_opts=['--wide']) + chk1 = '\n'.join(t.read().splitlines()[5:-2]) + + t = self.ticker(add_opts=['--wide', '--pchg-unit=usd'], no_msg=True) + chk2 = '\n'.join(t.read().splitlines()[6:-2]) + + assert chk1 == chk2, f'\nOUTPUT 1\n{chk1}\n!= OUTPUT 2\n{chk2}\n' + return t + + def ticker32(self): + return self.ticker( + [], + [ + 'BITCOIN', r'\+10.99', r'\+7.06', '-1.18', r'\+1.05', + 'ETHEREUM', + 'GOLD', r'\+0.00', r'\+0.00', r'\+0.00', r'\+0.00', + 'SILVER' + ], + add_opts = ['--widest', '--pchg-unit=gc=f']) diff --git a/test/ref/ticker/ticker-cfg-sort-pchg.yaml b/test/ref/ticker/ticker-cfg-sort-pchg.yaml new file mode 100644 index 0000000..4660ae4 --- /dev/null +++ b/test/ref/ticker/ticker-cfg-sort-pchg.yaml @@ -0,0 +1,14 @@ +sort: d +pchg_unit: btc + +assets: + coin1: + - btc-bitcoin + - ltc-litecoin + - eth-ethereum + - xmr-monero + - bad-badcoin + commodity: + - gc=f + - si=f + - bz=f