2 Commits 8f9c4ba48c ... 84e8ea65d0

Author SHA1 Message Date
  The MMGen Project 84e8ea65d0 mmnode-ticker: display percent change columns in terms of non-USD assets 1 month ago
  The MMGen Project 2647fa1fe3 mmnode-ticker: minor cleanups 1 month ago

+ 43 - 16
mmgen_node_tools/Ticker.py

@@ -43,10 +43,10 @@ percent_cols = {
 
 
 sp = namedtuple('sort_parameter', ['key', 'sort_dfl', 'desc'])
 sp = namedtuple('sort_parameter', ['key', 'sort_dfl', 'desc'])
 sort_params = {
 sort_params = {
-	'd': sp('percent_change_24h', 0.0,        '1-day % change'),
-	'w': sp('percent_change_7d',  0.0,        '1-week % change'),
-	'm': sp('percent_change_30d', 0.0,        '1-month % change'),
-	'y': sp('percent_change_1y',  0.0,        '1-year % change'),
+	'd': sp('percent_change_24h', 0.0,        '1-day percent change'),
+	'w': sp('percent_change_7d',  0.0,        '1-week percent change'),
+	'm': sp('percent_change_30d', 0.0,        '1-month percent change'),
+	'y': sp('percent_change_1y',  0.0,        '1-year percent change'),
 	'p': sp('price_usd',          Decimal(0), 'asset price'),
 	'p': sp('price_usd',          Decimal(0), 'asset price'),
 	'c': sp('market_cap',         0,          'market cap')}
 	'c': sp('market_cap',         0,          'market cap')}
 
 
@@ -535,6 +535,10 @@ def gen_data(data):
 		'name': 'US Dollar',
 		'name': 'US Dollar',
 		'price_usd': Decimal(1),
 		'price_usd': Decimal(1),
 		'price_btc': Decimal(1) / btcusd,
 		'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})
 		'last_updated': None})
 
 
 def cache_data(data_src, no_overwrite=False):
 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()})
 			{k: tuple(parse_asset_id(e) for e in v) for k, v in cfg_in.cfg['assets'].items()})
 		for hdr, data in (
 		for hdr, data in (
 				('user_uniq', get_usr_assets()),
 				('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 data:
 				if uniq_data := tuple(gen_uniq(data, 'symbol', preload=rows)):
 				if uniq_data := tuple(gen_uniq(data, 'symbol', preload=rows)):
 					rows[hdr] = uniq_data
 					rows[hdr] = uniq_data
@@ -791,6 +796,7 @@ def make_cfg(gcfg_arg):
 		'portfolio',
 		'portfolio',
 		'sort',
 		'sort',
 		'percent_cols',
 		'percent_cols',
+		'pchg_unit',
 		'asset_limit',
 		'asset_limit',
 		'cached_data',
 		'cached_data',
 		'elapsed',
 		'elapsed',
@@ -833,6 +839,9 @@ def make_cfg(gcfg_arg):
 	if portfolio and asset_range:
 	if portfolio and asset_range:
 		die(1, '--portfolio not supported in market cap view')
 		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(
 	cfg = cfg_tuple(
 		rows        = create_rows(),
 		rows        = create_rows(),
 		usr_rows    = usr_rows,
 		usr_rows    = usr_rows,
@@ -849,6 +858,7 @@ def make_cfg(gcfg_arg):
 		portfolio   = portfolio,
 		portfolio   = portfolio,
 		sort        = get_sort_opt(),
 		sort        = get_sort_opt(),
 		percent_cols    = parse_percent_cols(get_cfg_var('percent_cols')),
 		percent_cols    = parse_percent_cols(get_cfg_var('percent_cols')),
+		pchg_unit       = pchg_unit,
 		asset_limit     = get_cfg_var('asset_limit'),
 		asset_limit     = get_cfg_var('asset_limit'),
 		cached_data     = get_cfg_var('cached_data'),
 		cached_data     = get_cfg_var('cached_data'),
 		elapsed         = get_cfg_var('elapsed'),
 		elapsed         = get_cfg_var('elapsed'),
@@ -890,7 +900,7 @@ class Ticker:
 
 
 		offer = None
 		offer = None
 		to_asset = None
 		to_asset = None
-		hidden_groups = ('extra',)
+		hidden_groups = ('extra', 'pchg_unit_uniq')
 
 
 		def __init__(self, data):
 		def __init__(self, data):
 
 
@@ -925,6 +935,14 @@ class Ticker:
 					cfg = cfg._replace(
 					cfg = cfg._replace(
 						portfolio = sorted(cfg.portfolio, key=pf_sort_func, reverse=reverse))
 						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.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 = {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')
 			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]')
 				text = sort_params[cfg.sort[0]].desc + ('' if cfg.sort[1] else ' [reversed]')
 				yield f'Sort order: {pink(text.upper())}'
 				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:
 			for asset in self.usr_col_assets:
 				if asset.symbol != 'USD':
 				if asset.symbol != 'USD':
 					usdprice = self.data[asset.id]['price_usd']
 					usdprice = self.data[asset.id]['price_usd']
@@ -1025,8 +1048,8 @@ class Ticker:
 			if cfg.asset_range:
 			if cfg.asset_range:
 				yield from process_rows(self.rows['asset_list'])
 				yield from process_rows(self.rows['asset_list'])
 			else:
 			else:
-				for rows in self.rows.values():
-					if rows:
+				for group, rows in self.rows.items():
+					if rows and group not in self.hidden_groups:
 						yield from process_rows(rows)
 						yield from process_rows(rows)
 
 
 			yield '-' * self.hl_wid
 			yield '-' * self.hl_wid
@@ -1082,8 +1105,12 @@ class Ticker:
 
 
 		def fmt_row(self, d, amt=None, amt_fmt=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}')
+			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']]
 			p = self.prices[d['id']]
 
 
@@ -1096,10 +1123,10 @@ class Ticker:
 				idx = int(d['rank']) if cfg.asset_range else None,
 				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,
 				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'],
 				lbl = self.create_label(d['id']) if cfg.name_labels else d['symbol'],
-				pc1 = fmt_pct(d.get('percent_change_7d')),
-				pc2 = fmt_pct(d.get('percent_change_24h')),
-				pc3 = fmt_pct(d.get('percent_change_1y')),
-				pc4 = fmt_pct(d.get('percent_change_30d')),
+				pc1 = fmt_pct(d, 'percent_change_7d'),
+				pc2 = fmt_pct(d, 'percent_change_24h'),
+				pc3 = fmt_pct(d, 'percent_change_1y', wid=8),
+				pc4 = fmt_pct(d, 'percent_change_30d'),
 				upd = d.get('last_updated_fmt'),
 				upd = d.get('last_updated_fmt'),
 				amt = amt_fmt,
 				amt = amt_fmt,
 				**{k.replace('-', '_'): v * (1 if amt is None else amt) for k, v in p.items()})
 				**{k.replace('-', '_'): v * (1 if amt is None else amt) for k, v in p.items()})
@@ -1117,7 +1144,7 @@ class Ticker:
 
 
 			col_fs_data = {
 			col_fs_data = {
 				'label':       fd(f'{{lbl:{self.col1_wid}}}', f'{{lbl:{self.col1_wid}}}', self.col1_wid),
 				'label':       fd(f'{{lbl:{self.col1_wid}}}', f'{{lbl:{self.col1_wid}}}', self.col1_wid),
-				'pct1y':       fd(' {pc3:7}', ' {pc3:7}', 8),
+				'pct1y':       fd(' {pc3:8}', ' {pc3:8}', 9),
 				'pct1m':       fd(' {pc4:7}', ' {pc4:7}', 8),
 				'pct1m':       fd(' {pc4:7}', ' {pc4:7}', 8),
 				'pct1w':       fd(' {pc1:7}', ' {pc1:7}', 8),
 				'pct1w':       fd(' {pc1:7}', ' {pc1:7}', 8),
 				'pct1d':       fd(' {pc2:7}', ' {pc2:7}', 8),
 				'pct1d':       fd(' {pc2:7}', ' {pc2:7}', 8),
@@ -1169,7 +1196,7 @@ class Ticker:
 				mcap = 'MarketCap(B)',
 				mcap = 'MarketCap(B)',
 				pc1 = ' CHG_7d',
 				pc1 = ' CHG_7d',
 				pc2 = 'CHG_24h',
 				pc2 = 'CHG_24h',
-				pc3 = 'CHG_1y',
+				pc3 = '  CHG_1y',
 				pc4 = 'CHG_30d',
 				pc4 = 'CHG_30d',
 				upd = 'UPDATED',
 				upd = 'UPDATED',
 				amt = '         AMOUNT',
 				amt = '         AMOUNT',

+ 1 - 1
mmgen_node_tools/data/version

@@ -1 +1 @@
-3.6.dev8
+3.6.dev9

+ 4 - 2
mmgen_node_tools/main_ticker.py

@@ -40,10 +40,10 @@ opts_data = {
                       used to supply a USD exchange rate for missing assets.
                       used to supply a USD exchange rate for missing assets.
 -C, --cached-data     Use cached data from previous network query instead of
 -C, --cached-data     Use cached data from previous network query instead of
                       live data from server
                       live data from server
--D, --cachedir=D      Read and write cached JSON data to directory ‘D’
-                      instead of ‘~/{dfl_cachedir}’
 -d, --download=D      Retrieve and cache asset data ‘D’ from network (valid
 -d, --download=D      Retrieve and cache asset data ‘D’ from network (valid
                       options: {ds})
                       options: {ds})
+-D, --cachedir=D      Read and write cached JSON data to directory ‘D’
+                      instead of ‘~/{dfl_cachedir}’
 -e, --add-precision=N Add ‘N’ digits of precision to columns
 -e, --add-precision=N Add ‘N’ digits of precision to columns
 -E, --elapsed         Show elapsed time in UPDATED column (see --update-time)
 -E, --elapsed         Show elapsed time in UPDATED column (see --update-time)
 -F, --portfolio       Display portfolio data
 -F, --portfolio       Display portfolio data
@@ -63,6 +63,8 @@ opts_data = {
 -t, --testing         Print command(s) to be executed to stdout and exit
 -t, --testing         Print command(s) to be executed to stdout and exit
 -T, --thousands-comma Use comma as a thousands separator
 -T, --thousands-comma Use comma as a thousands separator
 -u, --update-time     Include UPDATED (last update time) column
 -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
 -v, --verbose         Be more verbose
 -w, --wide            Display most optional columns (same as -unT -p d,w)
 -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)
 -W, --widest          Display all optional columns (same as -unT -p d,w,m,y)

+ 40 - 0
test/cmdtest_d/misc.py

@@ -98,6 +98,9 @@ class CmdTestScripts(CmdTestBase):
 		('ticker27', 'ticker [--sort=rp -r algo,ada]'),
 		('ticker27', 'ticker [--sort=rp -r algo,ada]'),
 		('ticker28', 'ticker [--sort=d -r algo,ada]'),
 		('ticker28', 'ticker [--sort=d -r algo,ada]'),
 		('ticker29', 'ticker [--sort=y -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]'),
 	)
 	)
 	}
 	}
 
 
@@ -133,6 +136,7 @@ class CmdTestScripts(CmdTestBase):
 			cached_data = True,
 			cached_data = True,
 			add_opts    = [],
 			add_opts    = [],
 			use_proxy   = True,
 			use_proxy   = True,
+			no_msg      = False,
 			exit_val    = None):
 			exit_val    = None):
 		t = self.spawn(
 		t = self.spawn(
 			'mmnode-ticker',
 			'mmnode-ticker',
@@ -141,6 +145,7 @@ class CmdTestScripts(CmdTestBase):
 			+ (['--proxy=http://asdfzxcv:32459'] if use_proxy else [])
 			+ (['--proxy=http://asdfzxcv:32459'] if use_proxy else [])
 			+ add_opts
 			+ add_opts
 			+ args,
 			+ args,
+			no_msg = no_msg,
 			exit_val = exit_val)
 			exit_val = exit_val)
 		if expect_list:
 		if expect_list:
 			t.match_expect_list(expect_list)
 			t.match_expect_list(expect_list)
@@ -436,3 +441,38 @@ class CmdTestScripts(CmdTestBase):
 			[],
 			[],
 			['ETHEREUM', 'BITCOIN', 'MONERO', 'S&P', 'DOW', 'NASDAQ', 'CARDANO', 'ALGORAND'],
 			['ETHEREUM', 'BITCOIN', 'MONERO', 'S&P', 'DOW', 'NASDAQ', 'CARDANO', 'ALGORAND'],
 			add_opts = ['--widest', '-s', 'y', '-r', 'ada,algo'])
 			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'])

+ 14 - 0
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