Skip to content

Commit

Permalink
contrib: add clboss-earnings-history and clboss-recent-earnings scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
ksedgwic committed Aug 19, 2024
1 parent 8acbc2b commit 3dc88a1
Show file tree
Hide file tree
Showing 5 changed files with 340 additions and 1 deletion.
2 changes: 1 addition & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
clboss
/clboss
create-tarball
dev-boltz-api
dev-boltz
Expand Down
125 changes: 125 additions & 0 deletions contrib/clboss-earnings-history
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
#!/usr/bin/env python3

import subprocess
import argparse
import json
from datetime import datetime
from tabulate import tabulate

def run_lightning_cli_command(network_option, command, *args):
try:
result = subprocess.run(['lightning-cli', network_option, command, *args], capture_output=True, text=True, check=True)
return json.loads(result.stdout)
except subprocess.CalledProcessError as e:
print(f"Command '{command}' failed with error: {e}")
except json.JSONDecodeError as e:
print(f"Failed to parse JSON from command '{command}': {e}")
return None

def format_bucket_time(bucket_time):
if bucket_time == 0:
return "Legacy"
else:
return datetime.utcfromtimestamp(bucket_time).strftime('%Y-%m-%d')

def main():
parser = argparse.ArgumentParser(description="Run lightning-cli with specified network")
parser.add_argument('--mainnet', action='store_true', help='Run on mainnet')
parser.add_argument('--testnet', action='store_true', help='Run on testnet')
parser.add_argument('nodeid', nargs='?', help='The node ID to pass to clboss-earnings-history (optional)')

args = parser.parse_args()

if args.testnet:
network_option = '--testnet'
else:
network_option = '--mainnet' # Default to mainnet if no option is specified

if args.nodeid:
earnings_data = run_lightning_cli_command(network_option, 'clboss-earnings-history', args.nodeid)
else:
earnings_data = run_lightning_cli_command(network_option, 'clboss-earnings-history')

# Initialize totals
total_forwarded = 0
total_earnings = 0
total_rebalanced = 0
total_expense = 0
total_net_earnings = 0

# Process and format data
rows = []
for entry in earnings_data['history']:
if args.nodeid:
# Sum "in" and "out" values
earnings = entry['in_earnings'] + entry['out_earnings']
forwarded = entry['in_forwarded'] + entry['out_forwarded']
expense = entry['in_expenditures'] + entry['out_expenditures']
rebalanced = entry['in_rebalanced'] + entry['out_rebalanced']
else:
# Just use the in values, they are symetrical
earnings = entry['in_earnings']
forwarded = entry['in_forwarded']
expense = entry['in_expenditures']
rebalanced = entry['in_rebalanced']

# Calculate rates with checks for division by zero
forwarded_rate = (earnings / forwarded) * 1_000_000 if forwarded != 0 else 0
rebalance_rate = (expense / rebalanced) * 1_000_000 if rebalanced != 0 else 0
net_earnings = earnings - expense

# Update totals
total_forwarded += forwarded
total_earnings += earnings
total_rebalanced += rebalanced
total_expense += expense
total_net_earnings += net_earnings

rows.append([
format_bucket_time(entry['bucket_time']),
f"{forwarded:,}".replace(',', '_'),
f"{forwarded_rate:,.0f}",
f"{earnings:,}".replace(',', '_'),
f"{rebalanced:,}".replace(',', '_'),
f"{rebalance_rate:,.0f}",
f"{expense:,}".replace(',', '_'),
f"{int(net_earnings):,}".replace(',', '_')
])

# Calculate total rates
total_forwarded_rate = (total_earnings / total_forwarded) * 1_000_000 if total_forwarded != 0 else 0
total_rebalance_rate = (total_expense / total_rebalanced) * 1_000_000 if total_rebalanced != 0 else 0

# Add a separator row
separator_row = ["-" * len(header) for header in ["Date", "Forwarded", "Rate", "Earnings", "Rebalanced", "Rate", "Expense", "Net Earnings"]]
rows.append(separator_row)

# Append the total row
rows.append([
"TOTAL",
f"{total_forwarded:,}".replace(',', '_'),
# misleading because legacy: f"{total_forwarded_rate:,.0f}",
f"",
f"{total_earnings:,}".replace(',', '_'),
f"{total_rebalanced:,}".replace(',', '_'),
# misleading because legacy: f"{total_rebalance_rate:,.0f}",
f"",
f"{total_expense:,}".replace(',', '_'),
f"{int(total_net_earnings):,}".replace(',', '_')
])

headers = [
"Date",
"Forwarded",
"Rate",
"Earnings",
"Rebalanced",
"Rate",
"Expense",
"Net Earnings"
]

print(tabulate(rows, headers=headers, tablefmt="pretty", stralign="right", numalign="right"))

if __name__ == "__main__":
main()
172 changes: 172 additions & 0 deletions contrib/clboss-recent-earnings
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
#!/usr/bin/env python3

import os
import subprocess
import argparse
import json
from tabulate import tabulate
from clboss.alias_cache import lookup_alias

def run_lightning_cli_command(network_option, command, *args):
try:
result = subprocess.run(['lightning-cli', network_option, command, *args], capture_output=True, text=True, check=True)
return json.loads(result.stdout)
except subprocess.CalledProcessError as e:
print(f"Command '{command}' failed with error: {e}")
except json.JSONDecodeError as e:
print(f"Failed to parse JSON from command '{command}': {e}")
return None

def calculate_net_earnings(data, network_option):
rows = []

# Initialize totals
total_net_earnings = 0
total_in_earnings = 0
total_in_forwarded = 0
total_in_expenditures = 0
total_in_rebalanced = 0
total_out_earnings = 0
total_out_forwarded = 0
total_out_expenditures = 0
total_out_rebalanced = 0

for node, stats in data['recent'].items():
in_earnings = stats['in_earnings']
in_forwarded = stats['in_forwarded']
in_expenditures = stats['in_expenditures']
in_rebalanced = stats['in_rebalanced']

out_earnings = stats['out_earnings']
out_forwarded = stats['out_forwarded']
out_expenditures = stats['out_expenditures']
out_rebalanced = stats['out_rebalanced']

# Skip rows where all values are zero
if (
in_earnings == 0 and in_forwarded == 0 and in_expenditures == 0 and in_rebalanced == 0 and
out_earnings == 0 and out_forwarded == 0 and out_expenditures == 0 and out_rebalanced == 0
):
continue
alias = lookup_alias(run_lightning_cli_command, network_option, node)
in_rate = (in_earnings / in_forwarded) * 1_000_000 if in_forwarded != 0 else 0
in_rebalance_rate = (in_expenditures / in_rebalanced) * 1_000_000 if in_rebalanced != 0 else 0
out_rate = (out_earnings / out_forwarded) * 1_000_000 if out_forwarded != 0 else 0
out_rebalance_rate = (out_expenditures / out_rebalanced) * 1_000_000 if out_rebalanced != 0 else 0

net_earnings = in_earnings - in_expenditures + out_earnings - out_expenditures

# Update totals
total_net_earnings += net_earnings
total_in_earnings += in_earnings
total_in_forwarded += in_forwarded
total_in_expenditures += in_expenditures
total_in_rebalanced += in_rebalanced
total_out_earnings += out_earnings
total_out_forwarded += out_forwarded
total_out_expenditures += out_expenditures
total_out_rebalanced += out_rebalanced

avg_in_earnings_rate = (total_in_earnings / total_in_forwarded) * 1_000_000 if total_in_forwarded != 0 else 0
avg_out_earnings_rate = (total_out_earnings / total_out_forwarded) * 1_000_000 if total_out_forwarded != 0 else 0
avg_in_expenditures_rate = (total_in_expenditures / total_in_rebalanced) * 1_000_000 if total_in_rebalanced != 0 else 0
avg_out_expenditures_rate = (total_out_expenditures / total_out_rebalanced) * 1_000_000 if total_out_rebalanced != 0 else 0

rows.append([
alias,
f"{in_forwarded:,}".replace(',', '_'),
f"{in_rate:,.0f}",
f"{in_earnings:,}".replace(',', '_'),
f"{out_forwarded:,}".replace(',', '_'),
f"{out_rate:,.0f}",
f"{out_earnings:,}".replace(',', '_'),
f"{in_rebalanced:,}".replace(',', '_'),
f"{in_rebalance_rate:,.0f}",
f"{in_expenditures:,}".replace(',', '_'),
f"{out_rebalanced:,}".replace(',', '_'),
f"{out_rebalance_rate:,.0f}",
f"{out_expenditures:,}".replace(',', '_'),
f"{net_earnings:,}".replace(',', '_'),
])

# Divide the net earnings total by 2
total_net_earnings /= 2

# Add a separator row
separator_row = ["-" * len(header) for header in [
"Alias",
"In Forwarded",
"Rate",
"In Earn",
"Out Forwarded",
"Rate",
"Out Earn",
"In Rebal",
"Rate",
"In Exp",
"Out Rebal",
"Rate",
"Out Exp",
"Net Earn",
]]
rows.append(separator_row)

# Append the total row
rows.append([
"TOTAL",
f"{total_in_forwarded:,}".replace(',', '_'),
f"{avg_in_earnings_rate:,.0f}",
f"{total_in_earnings:,}".replace(',', '_'),
f"{total_out_forwarded:,}".replace(',', '_'),
f"{avg_out_earnings_rate:,.0f}",
f"{total_out_earnings:,}".replace(',', '_'),
f"{total_in_rebalanced:,}".replace(',', '_'),
f"{avg_in_expenditures_rate:,.0f}",
f"{total_in_expenditures:,}".replace(',', '_'),
f"{total_out_rebalanced:,}".replace(',', '_'),
f"{avg_out_expenditures_rate:,.0f}",
f"{total_out_expenditures:,}".replace(',', '_'),
f"{int(total_net_earnings):,}".replace(',', '_'),
])

return rows

def main():
parser = argparse.ArgumentParser(description="Run lightning-cli with specified network")
parser.add_argument('--mainnet', action='store_true', help='Run on mainnet')
parser.add_argument('--testnet', action='store_true', help='Run on testnet')
parser.add_argument('days', nargs='?', help='The number of days to pass to clboss-earnings-history (optional)')

args = parser.parse_args()

if args.testnet:
network_option = '--testnet'
else:
network_option = '--mainnet' # Default to mainnet if no option is specified

if args.days:
earnings_data = run_lightning_cli_command(network_option, 'clboss-recent-earnings', str(args.days))
else:
earnings_data = run_lightning_cli_command(network_option, 'clboss-recent-earnings')

if earnings_data:
rows = calculate_net_earnings(earnings_data, network_option)
print(tabulate(rows, headers=[
"Alias",
"In Forwarded",
"Rate",
"In Earn",
"Out Forwarded",
"Rate",
"Out Earn",
"In Rebal",
"Rate",
"In Exp",
"Out Rebal",
"Rate",
"Out Exp",
"Net Earn",
],tablefmt="pretty", stralign="right", numalign="right"))

if __name__ == "__main__":
main()
Empty file added contrib/clboss/__init__.py
Empty file.
42 changes: 42 additions & 0 deletions contrib/clboss/alias_cache.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import os
import json

# Define the cache directory and file path
CACHE_DIR = os.path.join(os.path.expanduser("~"), ".clboss")
CACHE_FILE = os.path.join(CACHE_DIR, "alias_cache.json")

def load_cache():
if os.path.exists(CACHE_FILE):
with open(CACHE_FILE, 'r') as f:
return json.load(f)
return {}

def save_cache(cache):
# Ensure the cache directory exists
if not os.path.exists(CACHE_DIR):
os.makedirs(CACHE_DIR)

with open(CACHE_FILE, 'w') as f:
json.dump(cache, f)

def lookup_alias(run_lightning_cli_command, network_option, peer_id):
# Load the cache
cache = load_cache()

# Check if the alias is already cached
if peer_id in cache:
return cache[peer_id]

# Perform the lookup
alias = peer_id # Default to peer_id if alias not found
listnodes_data = run_lightning_cli_command(network_option, 'listnodes', peer_id)
if listnodes_data:
nodes = listnodes_data.get("nodes", [])
for node in nodes:
alias = node.get("alias", peer_id) # Fallback to peer_id if alias not found

# Cache the result
cache[peer_id] = alias
save_cache(cache)

return alias

0 comments on commit 3dc88a1

Please sign in to comment.