Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 31 additions & 3 deletions bittensor_cli/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -1079,11 +1079,11 @@ def initialize_chain(

self.subtensor = SubtensorInterface(network_)
elif self.config["network"]:
self.subtensor = SubtensorInterface(self.config["network"])
console.print(
f"Using the specified network [{COLORS.G.LINKS}]{self.config['network']}"
f"[/{COLORS.G.LINKS}] from config"
)
self.subtensor = SubtensorInterface(self.config["network"])
else:
self.subtensor = SubtensorInterface(defaults.subtensor.network)
return self.subtensor
Expand Down Expand Up @@ -5026,6 +5026,11 @@ def subnets_price(
"--log",
help="Show the price in log scale.",
),
current_only: bool = typer.Option(
False,
"--current",
help="Show only the current data, and no historical data.",
),
html_output: bool = Options.html_output,
quiet: bool = Options.quiet,
verbose: bool = Options.verbose,
Expand All @@ -5048,9 +5053,31 @@ def subnets_price(
[green]$[/green] btcli subnets price --netuids 1,2,3,4 --html
"""
if json_output and html_output:
print_error("Cannot specify both `--json-output` and `--html`")
print_error(
f"Cannot specify both [{COLORS.G.ARG}]--json-output[/{COLORS.G.ARG}] "
f"and [{COLORS.G.ARG}]--html[/{COLORS.G.ARG}]"
)
return
if current_only and html_output:
print_error(
f"Cannot specify both [{COLORS.G.ARG}]--current[/{COLORS.G.ARG}] "
f"and [{COLORS.G.ARG}]--html[/{COLORS.G.ARG}]"
)
return
self.verbosity_handler(quiet=quiet, verbose=verbose, json_output=json_output)

subtensor = self.initialize_chain(network)
non_archives = ["finney", "latent-lite", "subvortex"]
if not current_only and subtensor.network in non_archives + [
Constants.network_map[x] for x in non_archives
]:
err_console.print(
f"[red]Error[/red] Running this command without [{COLORS.G.ARG}]--current[/{COLORS.G.ARG}] requires "
"use of an archive node. "
f"Try running again with the [{COLORS.G.ARG}]--network archive[/{COLORS.G.ARG}] flag."
)
return False

if netuids:
netuids = parse_to_list(
netuids,
Expand Down Expand Up @@ -5080,10 +5107,11 @@ def subnets_price(

return self._run_command(
price.price(
self.initialize_chain(network),
subtensor,
netuids,
all_netuids,
interval_hours,
current_only,
html_output,
log_scale,
json_output,
Expand Down
154 changes: 125 additions & 29 deletions bittensor_cli/src/commands/subnets/price.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import plotly.graph_objects as go

from bittensor_cli.src import COLOR_PALETTE
from bittensor_cli.src.bittensor.chain_data import DynamicInfo
from bittensor_cli.src.bittensor.utils import (
console,
err_console,
Expand All @@ -28,6 +29,7 @@ async def price(
netuids: list[int],
all_netuids: bool = False,
interval_hours: int = 4,
current_only: bool = False,
html_output: bool = False,
log_scale: bool = False,
json_output: bool = False,
Expand All @@ -41,45 +43,96 @@ async def price(
blocks_per_hour = int(3600 / 12) # ~300 blocks per hour
total_blocks = blocks_per_hour * interval_hours

with console.status(":chart_increasing: Fetching historical price data..."):
current_block_hash = await subtensor.substrate.get_chain_head()
current_block = await subtensor.substrate.get_block_number(current_block_hash)
if not current_only:
with console.status(":chart_increasing: Fetching historical price data..."):
current_block_hash = await subtensor.substrate.get_chain_head()
current_block = await subtensor.substrate.get_block_number(
current_block_hash
)

step = 300
start_block = max(0, current_block - total_blocks)
block_numbers = list(range(start_block, current_block + 1, step))
step = 300
start_block = max(0, current_block - total_blocks)
block_numbers = list(range(start_block, current_block + 1, step))

# Block hashes
block_hash_cors = [
subtensor.substrate.get_block_hash(bn) for bn in block_numbers
]
block_hashes = await asyncio.gather(*block_hash_cors)
# Block hashes
block_hash_cors = [
subtensor.substrate.get_block_hash(bn) for bn in block_numbers
]
block_hashes = await asyncio.gather(*block_hash_cors)

# We fetch all subnets when there is more than one netuid
if all_netuids or len(netuids) > 1:
subnet_info_cors = [subtensor.all_subnets(bh) for bh in block_hashes]
else:
# If there is only one netuid, we fetch the subnet info for that netuid
netuid = netuids[0]
subnet_info_cors = [subtensor.subnet(netuid, bh) for bh in block_hashes]
all_subnet_infos = await asyncio.gather(*subnet_info_cors)
# We fetch all subnets when there is more than one netuid
if all_netuids or len(netuids) > 1:
subnet_info_cors = [subtensor.all_subnets(bh) for bh in block_hashes]
else:
# If there is only one netuid, we fetch the subnet info for that netuid
netuid = netuids[0]
subnet_info_cors = [subtensor.subnet(netuid, bh) for bh in block_hashes]
all_subnet_infos = await asyncio.gather(*subnet_info_cors)

subnet_data = _process_subnet_data(
block_numbers, all_subnet_infos, netuids, all_netuids
)
if not subnet_data:
err_console.print("[red]No valid price data found for any subnet[/red]")
return

if not subnet_data:
err_console.print("[red]No valid price data found for any subnet[/red]")
return

if html_output:
await _generate_html_output(
subnet_data, block_numbers, interval_hours, log_scale
if html_output:
await _generate_html_output(
subnet_data, block_numbers, interval_hours, log_scale
)
elif json_output:
json_console.print(json.dumps(_generate_json_output(subnet_data)))
else:
_generate_cli_output(subnet_data, block_numbers, interval_hours, log_scale)
else:
with console.status("Fetching current price data..."):
if all_netuids or len(netuids) > 1:
all_subnet_info = await subtensor.all_subnets()
else:
all_subnet_info = [await subtensor.subnet(netuid=netuids[0])]
subnet_data = _process_current_subnet_data(
all_subnet_info, netuids, all_netuids
)
elif json_output:
json_console.print(json.dumps(_generate_json_output(subnet_data)))
if json_output:
json_console.print(json.dumps(_generate_json_output(subnet_data)))
else:
_generate_cli_output_current(subnet_data)


def _process_current_subnet_data(subnet_infos: list[DynamicInfo], netuids, all_netuids):
subnet_data = {}
if all_netuids or len(netuids) > 1:
# Most recent data for statistics
for subnet_info in subnet_infos:
stats = {
"current_price": subnet_info.price,
"supply": subnet_info.alpha_in.tao + subnet_info.alpha_out.tao,
"market_cap": subnet_info.price.tao
* (subnet_info.alpha_in.tao + subnet_info.alpha_out.tao),
"emission": subnet_info.emission.tao,
"stake": subnet_info.alpha_out.tao,
"symbol": subnet_info.symbol,
"name": get_subnet_name(subnet_info),
}
subnet_data[subnet_info.netuid] = {
"stats": stats,
}
else:
_generate_cli_output(subnet_data, block_numbers, interval_hours, log_scale)
subnet_info = subnet_infos[0]
stats = {
"current_price": subnet_info.price.tao,
"supply": subnet_info.alpha_in.tao + subnet_info.alpha_out.tao,
"market_cap": subnet_info.price.tao
* (subnet_info.alpha_in.tao + subnet_info.alpha_out.tao),
"emission": subnet_info.emission.tao,
"stake": subnet_info.alpha_out.tao,
"symbol": subnet_info.symbol,
"name": get_subnet_name(subnet_info),
}
subnet_data[subnet_info.netuid] = {
"stats": stats,
}
return subnet_data


def _process_subnet_data(block_numbers, all_subnet_infos, netuids, all_netuids):
Expand Down Expand Up @@ -626,3 +679,46 @@ def color_label(text):
)

console.print(stats_text)


def _generate_cli_output_current(subnet_data):
for netuid, data in subnet_data.items():
stats = data["stats"]

if netuid != 0:
console.print(
f"\n[{COLOR_PALETTE.G.SYM}]Subnet {netuid} - {stats['symbol']} "
f"[cyan]{stats['name']}[/cyan][/{COLOR_PALETTE.G.SYM}]\n"
f"Current: [blue]{stats['current_price']:.6f}{stats['symbol']}[/blue]\n"
)
else:
console.print(
f"\n[{COLOR_PALETTE.G.SYM}]Subnet {netuid} - {stats['symbol']} "
f"[cyan]{stats['name']}[/cyan][/{COLOR_PALETTE.G.SYM}]\n"
f"Current: [blue]{stats['symbol']} {stats['current_price']:.6f}[/blue]\n"
)

if netuid != 0:
stats_text = (
"\nLatest stats:\n"
f"Supply: [{COLOR_PALETTE.P.ALPHA_IN}]"
f"{stats['supply']:,.2f} {stats['symbol']}[/{COLOR_PALETTE.P.ALPHA_IN}]\n"
f"Market Cap: [steel_blue3]{stats['market_cap']:,.2f} {stats['symbol']} / 21M[/steel_blue3]\n"
f"Emission: [{COLOR_PALETTE.P.EMISSION}]"
f"{stats['emission']:,.2f} {stats['symbol']}[/{COLOR_PALETTE.P.EMISSION}]\n"
f"Stake: [{COLOR_PALETTE.S.TAO}]"
f"{stats['stake']:,.2f} {stats['symbol']}[/{COLOR_PALETTE.S.TAO}]"
)
else:
stats_text = (
"\nLatest stats:\n"
f"Supply: [{COLOR_PALETTE.P.ALPHA_IN}]"
f"{stats['symbol']} {stats['supply']:,.2f}[/{COLOR_PALETTE.P.ALPHA_IN}]\n"
f"Market Cap: [steel_blue3]{stats['symbol']} {stats['market_cap']:,.2f} / 21M[/steel_blue3]\n"
f"Emission: [{COLOR_PALETTE.P.EMISSION}]"
f"{stats['symbol']} {stats['emission']:,.2f}[/{COLOR_PALETTE.P.EMISSION}]\n"
f"Stake: [{COLOR_PALETTE.S.TAO}]"
f"{stats['symbol']} {stats['stake']:,.2f}[/{COLOR_PALETTE.S.TAO}]"
)

console.print(stats_text)
20 changes: 20 additions & 0 deletions tests/e2e_tests/test_staking_sudo.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
* btcli subnets set-identity
* btcli subnets get-identity
* btcli subnets register
* btcli subnets price
* btcli stake add
* btcli stake remove
* btcli stake show
Expand Down Expand Up @@ -234,6 +235,25 @@ def test_staking(local_chain, wallet_setup):
assert get_identity_output["logo_url"] == sn_logo_url
assert get_identity_output["additional"] == sn_add_info

get_s_price = exec_command_alice(
"subnets",
"price",
extra_args=[
"--chain",
"ws://127.0.0.1:9945",
"--netuid",
netuid,
"--current",
"--json-output",
],
)
get_s_price_output = json.loads(get_s_price.stdout)
assert str(netuid) in get_s_price_output.keys()
stats = get_s_price_output[str(netuid)]["stats"]
assert stats["name"] == sn_name
assert stats["current_price"] == 0.0
assert stats["market_cap"] == 0.0

# Start emissions on SNs
for netuid_ in multiple_netuids:
start_subnet_emissions = exec_command_alice(
Expand Down
Loading