Skip to content

Commit 88a4459

Browse files
authored
Merge pull request #569 from opentensor/feat/thewhaleking/current-s-price
Subnets Price --current + improvements
2 parents 158acb5 + 6ac4232 commit 88a4459

File tree

3 files changed

+176
-32
lines changed

3 files changed

+176
-32
lines changed

bittensor_cli/cli.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1079,11 +1079,11 @@ def initialize_chain(
10791079

10801080
self.subtensor = SubtensorInterface(network_)
10811081
elif self.config["network"]:
1082-
self.subtensor = SubtensorInterface(self.config["network"])
10831082
console.print(
10841083
f"Using the specified network [{COLORS.G.LINKS}]{self.config['network']}"
10851084
f"[/{COLORS.G.LINKS}] from config"
10861085
)
1086+
self.subtensor = SubtensorInterface(self.config["network"])
10871087
else:
10881088
self.subtensor = SubtensorInterface(defaults.subtensor.network)
10891089
return self.subtensor
@@ -5026,6 +5026,11 @@ def subnets_price(
50265026
"--log",
50275027
help="Show the price in log scale.",
50285028
),
5029+
current_only: bool = typer.Option(
5030+
False,
5031+
"--current",
5032+
help="Show only the current data, and no historical data.",
5033+
),
50295034
html_output: bool = Options.html_output,
50305035
quiet: bool = Options.quiet,
50315036
verbose: bool = Options.verbose,
@@ -5048,9 +5053,31 @@ def subnets_price(
50485053
[green]$[/green] btcli subnets price --netuids 1,2,3,4 --html
50495054
"""
50505055
if json_output and html_output:
5051-
print_error("Cannot specify both `--json-output` and `--html`")
5056+
print_error(
5057+
f"Cannot specify both [{COLORS.G.ARG}]--json-output[/{COLORS.G.ARG}] "
5058+
f"and [{COLORS.G.ARG}]--html[/{COLORS.G.ARG}]"
5059+
)
5060+
return
5061+
if current_only and html_output:
5062+
print_error(
5063+
f"Cannot specify both [{COLORS.G.ARG}]--current[/{COLORS.G.ARG}] "
5064+
f"and [{COLORS.G.ARG}]--html[/{COLORS.G.ARG}]"
5065+
)
50525066
return
50535067
self.verbosity_handler(quiet=quiet, verbose=verbose, json_output=json_output)
5068+
5069+
subtensor = self.initialize_chain(network)
5070+
non_archives = ["finney", "latent-lite", "subvortex"]
5071+
if not current_only and subtensor.network in non_archives + [
5072+
Constants.network_map[x] for x in non_archives
5073+
]:
5074+
err_console.print(
5075+
f"[red]Error[/red] Running this command without [{COLORS.G.ARG}]--current[/{COLORS.G.ARG}] requires "
5076+
"use of an archive node. "
5077+
f"Try running again with the [{COLORS.G.ARG}]--network archive[/{COLORS.G.ARG}] flag."
5078+
)
5079+
return False
5080+
50545081
if netuids:
50555082
netuids = parse_to_list(
50565083
netuids,
@@ -5080,10 +5107,11 @@ def subnets_price(
50805107

50815108
return self._run_command(
50825109
price.price(
5083-
self.initialize_chain(network),
5110+
subtensor,
50845111
netuids,
50855112
all_netuids,
50865113
interval_hours,
5114+
current_only,
50875115
html_output,
50885116
log_scale,
50895117
json_output,

bittensor_cli/src/commands/subnets/price.py

Lines changed: 125 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
import plotly.graph_objects as go
1111

1212
from bittensor_cli.src import COLOR_PALETTE
13+
from bittensor_cli.src.bittensor.chain_data import DynamicInfo
1314
from bittensor_cli.src.bittensor.utils import (
1415
console,
1516
err_console,
@@ -28,6 +29,7 @@ async def price(
2829
netuids: list[int],
2930
all_netuids: bool = False,
3031
interval_hours: int = 4,
32+
current_only: bool = False,
3133
html_output: bool = False,
3234
log_scale: bool = False,
3335
json_output: bool = False,
@@ -41,45 +43,96 @@ async def price(
4143
blocks_per_hour = int(3600 / 12) # ~300 blocks per hour
4244
total_blocks = blocks_per_hour * interval_hours
4345

44-
with console.status(":chart_increasing: Fetching historical price data..."):
45-
current_block_hash = await subtensor.substrate.get_chain_head()
46-
current_block = await subtensor.substrate.get_block_number(current_block_hash)
46+
if not current_only:
47+
with console.status(":chart_increasing: Fetching historical price data..."):
48+
current_block_hash = await subtensor.substrate.get_chain_head()
49+
current_block = await subtensor.substrate.get_block_number(
50+
current_block_hash
51+
)
4752

48-
step = 300
49-
start_block = max(0, current_block - total_blocks)
50-
block_numbers = list(range(start_block, current_block + 1, step))
53+
step = 300
54+
start_block = max(0, current_block - total_blocks)
55+
block_numbers = list(range(start_block, current_block + 1, step))
5156

52-
# Block hashes
53-
block_hash_cors = [
54-
subtensor.substrate.get_block_hash(bn) for bn in block_numbers
55-
]
56-
block_hashes = await asyncio.gather(*block_hash_cors)
57+
# Block hashes
58+
block_hash_cors = [
59+
subtensor.substrate.get_block_hash(bn) for bn in block_numbers
60+
]
61+
block_hashes = await asyncio.gather(*block_hash_cors)
5762

58-
# We fetch all subnets when there is more than one netuid
59-
if all_netuids or len(netuids) > 1:
60-
subnet_info_cors = [subtensor.all_subnets(bh) for bh in block_hashes]
61-
else:
62-
# If there is only one netuid, we fetch the subnet info for that netuid
63-
netuid = netuids[0]
64-
subnet_info_cors = [subtensor.subnet(netuid, bh) for bh in block_hashes]
65-
all_subnet_infos = await asyncio.gather(*subnet_info_cors)
63+
# We fetch all subnets when there is more than one netuid
64+
if all_netuids or len(netuids) > 1:
65+
subnet_info_cors = [subtensor.all_subnets(bh) for bh in block_hashes]
66+
else:
67+
# If there is only one netuid, we fetch the subnet info for that netuid
68+
netuid = netuids[0]
69+
subnet_info_cors = [subtensor.subnet(netuid, bh) for bh in block_hashes]
70+
all_subnet_infos = await asyncio.gather(*subnet_info_cors)
6671

6772
subnet_data = _process_subnet_data(
6873
block_numbers, all_subnet_infos, netuids, all_netuids
6974
)
75+
if not subnet_data:
76+
err_console.print("[red]No valid price data found for any subnet[/red]")
77+
return
7078

71-
if not subnet_data:
72-
err_console.print("[red]No valid price data found for any subnet[/red]")
73-
return
74-
75-
if html_output:
76-
await _generate_html_output(
77-
subnet_data, block_numbers, interval_hours, log_scale
79+
if html_output:
80+
await _generate_html_output(
81+
subnet_data, block_numbers, interval_hours, log_scale
82+
)
83+
elif json_output:
84+
json_console.print(json.dumps(_generate_json_output(subnet_data)))
85+
else:
86+
_generate_cli_output(subnet_data, block_numbers, interval_hours, log_scale)
87+
else:
88+
with console.status("Fetching current price data..."):
89+
if all_netuids or len(netuids) > 1:
90+
all_subnet_info = await subtensor.all_subnets()
91+
else:
92+
all_subnet_info = [await subtensor.subnet(netuid=netuids[0])]
93+
subnet_data = _process_current_subnet_data(
94+
all_subnet_info, netuids, all_netuids
7895
)
79-
elif json_output:
80-
json_console.print(json.dumps(_generate_json_output(subnet_data)))
96+
if json_output:
97+
json_console.print(json.dumps(_generate_json_output(subnet_data)))
98+
else:
99+
_generate_cli_output_current(subnet_data)
100+
101+
102+
def _process_current_subnet_data(subnet_infos: list[DynamicInfo], netuids, all_netuids):
103+
subnet_data = {}
104+
if all_netuids or len(netuids) > 1:
105+
# Most recent data for statistics
106+
for subnet_info in subnet_infos:
107+
stats = {
108+
"current_price": subnet_info.price,
109+
"supply": subnet_info.alpha_in.tao + subnet_info.alpha_out.tao,
110+
"market_cap": subnet_info.price.tao
111+
* (subnet_info.alpha_in.tao + subnet_info.alpha_out.tao),
112+
"emission": subnet_info.emission.tao,
113+
"stake": subnet_info.alpha_out.tao,
114+
"symbol": subnet_info.symbol,
115+
"name": get_subnet_name(subnet_info),
116+
}
117+
subnet_data[subnet_info.netuid] = {
118+
"stats": stats,
119+
}
81120
else:
82-
_generate_cli_output(subnet_data, block_numbers, interval_hours, log_scale)
121+
subnet_info = subnet_infos[0]
122+
stats = {
123+
"current_price": subnet_info.price.tao,
124+
"supply": subnet_info.alpha_in.tao + subnet_info.alpha_out.tao,
125+
"market_cap": subnet_info.price.tao
126+
* (subnet_info.alpha_in.tao + subnet_info.alpha_out.tao),
127+
"emission": subnet_info.emission.tao,
128+
"stake": subnet_info.alpha_out.tao,
129+
"symbol": subnet_info.symbol,
130+
"name": get_subnet_name(subnet_info),
131+
}
132+
subnet_data[subnet_info.netuid] = {
133+
"stats": stats,
134+
}
135+
return subnet_data
83136

84137

85138
def _process_subnet_data(block_numbers, all_subnet_infos, netuids, all_netuids):
@@ -626,3 +679,46 @@ def color_label(text):
626679
)
627680

628681
console.print(stats_text)
682+
683+
684+
def _generate_cli_output_current(subnet_data):
685+
for netuid, data in subnet_data.items():
686+
stats = data["stats"]
687+
688+
if netuid != 0:
689+
console.print(
690+
f"\n[{COLOR_PALETTE.G.SYM}]Subnet {netuid} - {stats['symbol']} "
691+
f"[cyan]{stats['name']}[/cyan][/{COLOR_PALETTE.G.SYM}]\n"
692+
f"Current: [blue]{stats['current_price']:.6f}{stats['symbol']}[/blue]\n"
693+
)
694+
else:
695+
console.print(
696+
f"\n[{COLOR_PALETTE.G.SYM}]Subnet {netuid} - {stats['symbol']} "
697+
f"[cyan]{stats['name']}[/cyan][/{COLOR_PALETTE.G.SYM}]\n"
698+
f"Current: [blue]{stats['symbol']} {stats['current_price']:.6f}[/blue]\n"
699+
)
700+
701+
if netuid != 0:
702+
stats_text = (
703+
"\nLatest stats:\n"
704+
f"Supply: [{COLOR_PALETTE.P.ALPHA_IN}]"
705+
f"{stats['supply']:,.2f} {stats['symbol']}[/{COLOR_PALETTE.P.ALPHA_IN}]\n"
706+
f"Market Cap: [steel_blue3]{stats['market_cap']:,.2f} {stats['symbol']} / 21M[/steel_blue3]\n"
707+
f"Emission: [{COLOR_PALETTE.P.EMISSION}]"
708+
f"{stats['emission']:,.2f} {stats['symbol']}[/{COLOR_PALETTE.P.EMISSION}]\n"
709+
f"Stake: [{COLOR_PALETTE.S.TAO}]"
710+
f"{stats['stake']:,.2f} {stats['symbol']}[/{COLOR_PALETTE.S.TAO}]"
711+
)
712+
else:
713+
stats_text = (
714+
"\nLatest stats:\n"
715+
f"Supply: [{COLOR_PALETTE.P.ALPHA_IN}]"
716+
f"{stats['symbol']} {stats['supply']:,.2f}[/{COLOR_PALETTE.P.ALPHA_IN}]\n"
717+
f"Market Cap: [steel_blue3]{stats['symbol']} {stats['market_cap']:,.2f} / 21M[/steel_blue3]\n"
718+
f"Emission: [{COLOR_PALETTE.P.EMISSION}]"
719+
f"{stats['symbol']} {stats['emission']:,.2f}[/{COLOR_PALETTE.P.EMISSION}]\n"
720+
f"Stake: [{COLOR_PALETTE.S.TAO}]"
721+
f"{stats['symbol']} {stats['stake']:,.2f}[/{COLOR_PALETTE.S.TAO}]"
722+
)
723+
724+
console.print(stats_text)

tests/e2e_tests/test_staking_sudo.py

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
* btcli subnets set-identity
1212
* btcli subnets get-identity
1313
* btcli subnets register
14+
* btcli subnets price
1415
* btcli stake add
1516
* btcli stake remove
1617
* btcli stake show
@@ -234,6 +235,25 @@ def test_staking(local_chain, wallet_setup):
234235
assert get_identity_output["logo_url"] == sn_logo_url
235236
assert get_identity_output["additional"] == sn_add_info
236237

238+
get_s_price = exec_command_alice(
239+
"subnets",
240+
"price",
241+
extra_args=[
242+
"--chain",
243+
"ws://127.0.0.1:9945",
244+
"--netuid",
245+
netuid,
246+
"--current",
247+
"--json-output",
248+
],
249+
)
250+
get_s_price_output = json.loads(get_s_price.stdout)
251+
assert str(netuid) in get_s_price_output.keys()
252+
stats = get_s_price_output[str(netuid)]["stats"]
253+
assert stats["name"] == sn_name
254+
assert stats["current_price"] == 0.0
255+
assert stats["market_cap"] == 0.0
256+
237257
# Start emissions on SNs
238258
for netuid_ in multiple_netuids:
239259
start_subnet_emissions = exec_command_alice(

0 commit comments

Comments
 (0)