1035 lines
48 KiB
Python
1035 lines
48 KiB
Python
"""
|
|
Shared lockdrop calculation functions and utilities.
|
|
|
|
This module contains the reference calculation logic for lockdrop token distribution,
|
|
penalty systems, bonus pools, and final allocations. It can be used by both
|
|
the simulation notebook and the experimental notebook.
|
|
"""
|
|
|
|
from decimal import Decimal, ROUND_DOWN, getcontext
|
|
import pandas as pd
|
|
import matplotlib.pyplot as plt
|
|
import seaborn as sns
|
|
import json
|
|
import os
|
|
from collections import defaultdict
|
|
from tabulate import tabulate
|
|
|
|
# Configure decimal precision
|
|
getcontext().prec = 28
|
|
getcontext().rounding = ROUND_DOWN
|
|
|
|
# Constants
|
|
TOKEN_PRECISION = Decimal('0.00000001') # 8 decimal precision
|
|
TOTAL_SUPPLY = 4294967296 # 1 $Z per Urbit ID
|
|
LOCKDROP_ALLOCATION_PERCENT = Decimal('0.3')
|
|
LOCKDROP_ALLOCATION = TOTAL_SUPPLY * LOCKDROP_ALLOCATION_PERCENT
|
|
|
|
# Points
|
|
NUM_GALAXIES = pow(2, 8)
|
|
NUM_STARS = pow(2, 16) - pow(2, 8)
|
|
NUM_PLANETS = pow(2, 32) - pow(2, 16)
|
|
|
|
# Allocation distribution
|
|
STAR_ALLOCATION_PERCENT = Decimal(NUM_STARS / pow(2, 16))
|
|
GALAXY_ALLOCATION_PERCENT = 1 - STAR_ALLOCATION_PERCENT
|
|
|
|
# Lockdrop duration (5 years including 1 leap year)
|
|
LOCKDROP_DURATION_SECONDS = 5 * 365.25 * 24 * 60 * 60
|
|
BLOCK_DURATION_SECONDS = 2
|
|
LOCKDROP_DURATION_BLOCKS = int(LOCKDROP_DURATION_SECONDS / BLOCK_DURATION_SECONDS)
|
|
|
|
# Penalty rates
|
|
PENALTY_RATES = {
|
|
5: Decimal('0'),
|
|
4: Decimal('0.2'),
|
|
3: Decimal('0.4'),
|
|
2: Decimal('0.6'),
|
|
1: Decimal('0.8')
|
|
}
|
|
|
|
|
|
def print_table_with_borders(df, title=""):
|
|
"""Print DataFrame with borders using tabulate."""
|
|
if title:
|
|
print(f"\n{title}")
|
|
print(tabulate(df, headers='keys', tablefmt='grid', showindex=False))
|
|
|
|
|
|
def configure_matplotlib():
|
|
"""Configure matplotlib settings for consistent plots."""
|
|
plt.style.use('seaborn-v0_8')
|
|
sns.set_palette("husl")
|
|
|
|
plt.rcParams['figure.figsize'] = (12, 8)
|
|
plt.rcParams['font.size'] = 11
|
|
plt.rcParams['axes.titlesize'] = 14
|
|
plt.rcParams['axes.labelsize'] = 12
|
|
plt.rcParams['xtick.labelsize'] = 10
|
|
plt.rcParams['ytick.labelsize'] = 10
|
|
plt.rcParams['legend.fontsize'] = 10
|
|
plt.rcParams['axes.unicode_minus'] = False
|
|
|
|
try:
|
|
plt.rcParams['font.family'] = 'DejaVu Sans'
|
|
except:
|
|
plt.rcParams['font.family'] = 'sans-serif'
|
|
plt.rcParams['font.sans-serif'] = ['DejaVu Sans', 'Liberation Sans', 'Arial', 'Helvetica']
|
|
|
|
|
|
def print_constants_summary():
|
|
"""Display core constants as formatted tables."""
|
|
print("=" * 80)
|
|
print("📊 $Z LOCKDROP DISTRIBUTION - CORE CONSTANTS")
|
|
print("=" * 80)
|
|
|
|
# Lockdrop Allocation Table
|
|
lockdrop_df = pd.DataFrame({
|
|
'Parameter': ['Total Supply (1 $Z per Urbit ID)', 'Lockdrop Allocation %', 'Lockdrop Allocation ($Z)'],
|
|
'Value': [f"{TOTAL_SUPPLY:,}", f"{LOCKDROP_ALLOCATION_PERCENT:.1%}", f"{LOCKDROP_ALLOCATION:,.1f}"]
|
|
})
|
|
print_table_with_borders(lockdrop_df, "🔒 LOCKDROP ALLOCATION")
|
|
|
|
# Points Distribution Table
|
|
points_df = pd.DataFrame({
|
|
'Point Type': ['Galaxies', 'Stars', 'Planets'],
|
|
'Count': [f"{NUM_GALAXIES:,}", f"{NUM_STARS:,}", f"{NUM_PLANETS:,}"],
|
|
'Allocation %': ['0.39%', '99.61%', '0%']
|
|
})
|
|
print_table_with_borders(points_df, "⭐ URBIT POINTS DISTRIBUTION")
|
|
|
|
# Lockdrop Parameters Table
|
|
params_df = pd.DataFrame({
|
|
'Parameter': ['Block Duration', 'Max Point Lock Duration (5 yrs)', 'Total Blocks', 'Star Allocation %', 'Galaxy Allocation %'],
|
|
'Value': [f"{BLOCK_DURATION_SECONDS} seconds", f"{LOCKDROP_DURATION_SECONDS:,.0f} seconds",
|
|
f"{LOCKDROP_DURATION_BLOCKS:,}", f"{STAR_ALLOCATION_PERCENT:.6%}", f"{GALAXY_ALLOCATION_PERCENT:.6%}"]
|
|
})
|
|
print_table_with_borders(params_df, "⏱️ LOCKDROP PARAMETERS")
|
|
print("\n" + "="*80)
|
|
|
|
|
|
def calculate_base_allocations():
|
|
"""Calculate base allocation amounts and quanta."""
|
|
lockdrop_allocation_stars = LOCKDROP_ALLOCATION * STAR_ALLOCATION_PERCENT
|
|
lockdrop_allocation_galaxies = LOCKDROP_ALLOCATION * GALAXY_ALLOCATION_PERCENT
|
|
|
|
assert (lockdrop_allocation_stars + lockdrop_allocation_galaxies) == LOCKDROP_ALLOCATION, "point allocation doesn't add up"
|
|
|
|
# Max allocation per point (theoretical)
|
|
max_allocation_per_star = lockdrop_allocation_stars / NUM_STARS
|
|
max_allocation_per_galaxy = lockdrop_allocation_galaxies / NUM_GALAXIES
|
|
|
|
# Quanta calculation
|
|
z_available_per_star_per_block = max_allocation_per_star / LOCKDROP_DURATION_BLOCKS
|
|
z_available_per_galaxy_per_block = max_allocation_per_galaxy / LOCKDROP_DURATION_BLOCKS
|
|
|
|
# Round down to 6 decimals
|
|
adjusted_z_per_star_per_block = z_available_per_star_per_block.quantize(Decimal('0.000001'), rounding=ROUND_DOWN)
|
|
adjusted_z_per_galaxy_per_block = z_available_per_galaxy_per_block.quantize(Decimal('0.000001'), rounding=ROUND_DOWN)
|
|
|
|
# Adjusted max allocation
|
|
adjusted_max_allocation_per_star = adjusted_z_per_star_per_block * LOCKDROP_DURATION_BLOCKS
|
|
adjusted_max_allocation_per_galaxy = adjusted_z_per_galaxy_per_block * LOCKDROP_DURATION_BLOCKS
|
|
|
|
return {
|
|
'lockdrop_allocation_stars': lockdrop_allocation_stars,
|
|
'lockdrop_allocation_galaxies': lockdrop_allocation_galaxies,
|
|
'max_allocation_per_star': max_allocation_per_star,
|
|
'max_allocation_per_galaxy': max_allocation_per_galaxy,
|
|
'adjusted_max_allocation_per_star': adjusted_max_allocation_per_star,
|
|
'adjusted_max_allocation_per_galaxy': adjusted_max_allocation_per_galaxy,
|
|
'adjusted_z_per_star_per_block': adjusted_z_per_star_per_block,
|
|
'adjusted_z_per_galaxy_per_block': adjusted_z_per_galaxy_per_block
|
|
}
|
|
|
|
|
|
def calculate_dynamic_allocations(participation_counts):
|
|
"""
|
|
Calculate allocations based on actual participation counts.
|
|
|
|
Args:
|
|
participation_counts: Dict with keys like 'stars_1_years', 'stars_2_years', etc.
|
|
"""
|
|
# Extract participation data
|
|
stars_counts = {year: Decimal(participation_counts[f'stars_{year}_years']) for year in range(1, 6)}
|
|
galaxies_counts = {year: Decimal(participation_counts[f'galaxies_{year}_years']) for year in range(1, 6)}
|
|
|
|
total_stars_locked = sum(stars_counts.values())
|
|
total_galaxies_locked = sum(galaxies_counts.values())
|
|
|
|
lockdrop_allocation_stars = LOCKDROP_ALLOCATION * STAR_ALLOCATION_PERCENT
|
|
lockdrop_allocation_galaxies = LOCKDROP_ALLOCATION * GALAXY_ALLOCATION_PERCENT
|
|
|
|
# Dynamic allocations based on actual participation
|
|
max_allocation_per_star = lockdrop_allocation_stars / total_stars_locked if total_stars_locked > 0 else Decimal('0')
|
|
max_allocation_per_galaxy = lockdrop_allocation_galaxies / total_galaxies_locked if total_galaxies_locked > 0 else Decimal('0')
|
|
|
|
# Quanta calculation
|
|
z_available_per_star_per_block = max_allocation_per_star / LOCKDROP_DURATION_BLOCKS
|
|
z_available_per_galaxy_per_block = max_allocation_per_galaxy / LOCKDROP_DURATION_BLOCKS
|
|
|
|
# Round down to 6 decimals
|
|
adjusted_z_per_star_per_block = z_available_per_star_per_block.quantize(Decimal('0.000001'), rounding=ROUND_DOWN)
|
|
adjusted_z_per_galaxy_per_block = z_available_per_galaxy_per_block.quantize(Decimal('0.000001'), rounding=ROUND_DOWN)
|
|
|
|
# Adjusted max allocation
|
|
adjusted_max_allocation_per_star = adjusted_z_per_star_per_block * LOCKDROP_DURATION_BLOCKS
|
|
adjusted_max_allocation_per_galaxy = adjusted_z_per_galaxy_per_block * LOCKDROP_DURATION_BLOCKS
|
|
|
|
# Rounding errors
|
|
rounding_error_per_star = max_allocation_per_star - adjusted_max_allocation_per_star
|
|
rounding_error_per_galaxy = max_allocation_per_galaxy - adjusted_max_allocation_per_galaxy
|
|
|
|
total_rounding_error_stars = lockdrop_allocation_stars - (adjusted_max_allocation_per_star * total_stars_locked)
|
|
total_rounding_error_galaxies = lockdrop_allocation_galaxies - (adjusted_max_allocation_per_galaxy * total_galaxies_locked)
|
|
|
|
return {
|
|
'stars_counts': stars_counts,
|
|
'galaxies_counts': galaxies_counts,
|
|
'total_stars_locked': total_stars_locked,
|
|
'total_galaxies_locked': total_galaxies_locked,
|
|
'adjusted_max_allocation_per_star': adjusted_max_allocation_per_star,
|
|
'adjusted_max_allocation_per_galaxy': adjusted_max_allocation_per_galaxy,
|
|
'total_rounding_error_stars': total_rounding_error_stars,
|
|
'total_rounding_error_galaxies': total_rounding_error_galaxies,
|
|
'adjusted_z_per_star_per_block': adjusted_z_per_star_per_block,
|
|
'adjusted_z_per_galaxy_per_block': adjusted_z_per_galaxy_per_block
|
|
}
|
|
|
|
|
|
def calculate_bonus_pools(allocation_data):
|
|
"""Calculate bonus pools from penalties and rounding errors."""
|
|
stars_counts = allocation_data['stars_counts']
|
|
galaxies_counts = allocation_data['galaxies_counts']
|
|
adjusted_max_allocation_per_star = allocation_data['adjusted_max_allocation_per_star']
|
|
adjusted_max_allocation_per_galaxy = allocation_data['adjusted_max_allocation_per_galaxy']
|
|
total_rounding_error_stars = allocation_data['total_rounding_error_stars']
|
|
total_rounding_error_galaxies = allocation_data['total_rounding_error_galaxies']
|
|
|
|
# Calculate penalty pools
|
|
star_penalty_pool = Decimal('0')
|
|
for years in [1, 2, 3, 4]:
|
|
penalty = PENALTY_RATES[years]
|
|
star_penalty_pool += adjusted_max_allocation_per_star * stars_counts[years] * penalty
|
|
|
|
galaxy_penalty_pool = Decimal('0')
|
|
for years in [1, 2, 3, 4]:
|
|
penalty = PENALTY_RATES[years]
|
|
galaxy_penalty_pool += adjusted_max_allocation_per_galaxy * galaxies_counts[years] * penalty
|
|
|
|
star_penalty_pool = star_penalty_pool.quantize(TOKEN_PRECISION, rounding=ROUND_DOWN)
|
|
galaxy_penalty_pool = galaxy_penalty_pool.quantize(TOKEN_PRECISION, rounding=ROUND_DOWN)
|
|
|
|
# Add rounding errors to bonus pools
|
|
star_bonus_pool_total = star_penalty_pool + total_rounding_error_stars
|
|
galaxy_bonus_pool_total = galaxy_penalty_pool + total_rounding_error_galaxies
|
|
|
|
# Calculate bonus per 5-year participant
|
|
stars_5_years = stars_counts[5]
|
|
galaxies_5_years = galaxies_counts[5]
|
|
|
|
bonus_per_star_5_years = star_bonus_pool_total / stars_5_years if stars_5_years > 0 else Decimal('0')
|
|
bonus_per_galaxy_5_years = galaxy_bonus_pool_total / galaxies_5_years if galaxies_5_years > 0 else Decimal('0')
|
|
|
|
return {
|
|
'star_penalty_pool': star_penalty_pool,
|
|
'galaxy_penalty_pool': galaxy_penalty_pool,
|
|
'star_bonus_pool_total': star_bonus_pool_total,
|
|
'galaxy_bonus_pool_total': galaxy_bonus_pool_total,
|
|
'bonus_per_star_5_years': bonus_per_star_5_years,
|
|
'bonus_per_galaxy_5_years': bonus_per_galaxy_5_years
|
|
}
|
|
|
|
|
|
def calculate_final_allocations(allocation_data, bonus_data):
|
|
"""Calculate final allocations for all lock periods."""
|
|
adjusted_max_allocation_per_star = allocation_data['adjusted_max_allocation_per_star']
|
|
adjusted_max_allocation_per_galaxy = allocation_data['adjusted_max_allocation_per_galaxy']
|
|
bonus_per_star_5_years = bonus_data['bonus_per_star_5_years']
|
|
bonus_per_galaxy_5_years = bonus_data['bonus_per_galaxy_5_years']
|
|
stars_counts = allocation_data['stars_counts']
|
|
galaxies_counts = allocation_data['galaxies_counts']
|
|
|
|
# Calculate final allocations
|
|
final_star_allocations = {}
|
|
final_galaxy_allocations = {}
|
|
|
|
for years in range(1, 6):
|
|
penalty = PENALTY_RATES[years]
|
|
|
|
if years == 5:
|
|
# 5-year participants get base + bonus
|
|
final_star_allocations[years] = (adjusted_max_allocation_per_star + bonus_per_star_5_years).quantize(TOKEN_PRECISION, rounding=ROUND_DOWN)
|
|
final_galaxy_allocations[years] = (adjusted_max_allocation_per_galaxy + bonus_per_galaxy_5_years).quantize(TOKEN_PRECISION, rounding=ROUND_DOWN)
|
|
else:
|
|
# Other years get penalized amounts
|
|
final_star_allocations[years] = (adjusted_max_allocation_per_star * (1 - penalty)).quantize(TOKEN_PRECISION, rounding=ROUND_DOWN)
|
|
final_galaxy_allocations[years] = (adjusted_max_allocation_per_galaxy * (1 - penalty)).quantize(TOKEN_PRECISION, rounding=ROUND_DOWN)
|
|
|
|
# Calculate Z per block for each lock period
|
|
star_z_per_block = {}
|
|
galaxy_z_per_block = {}
|
|
|
|
for years in range(1, 6):
|
|
lock_duration_fraction = Decimal(years) / Decimal('5') # Fraction of full 5-year period
|
|
effective_blocks = LOCKDROP_DURATION_BLOCKS * lock_duration_fraction
|
|
|
|
star_z_per_block[years] = (final_star_allocations[years] / effective_blocks).quantize(TOKEN_PRECISION, rounding=ROUND_DOWN)
|
|
galaxy_z_per_block[years] = (final_galaxy_allocations[years] / effective_blocks).quantize(TOKEN_PRECISION, rounding=ROUND_DOWN)
|
|
|
|
# Calculate total allocations for verification
|
|
total_stars_allocation = sum(final_star_allocations[year] * stars_counts[year] for year in range(1, 6))
|
|
total_galaxies_allocation = sum(final_galaxy_allocations[year] * galaxies_counts[year] for year in range(1, 6))
|
|
|
|
return {
|
|
'final_star_allocations': final_star_allocations,
|
|
'final_galaxy_allocations': final_galaxy_allocations,
|
|
'star_z_per_block': star_z_per_block,
|
|
'galaxy_z_per_block': galaxy_z_per_block,
|
|
'total_stars_allocation': total_stars_allocation,
|
|
'total_galaxies_allocation': total_galaxies_allocation
|
|
}
|
|
|
|
|
|
def generate_test_output(final_data):
|
|
"""Generate JSON output for test validation."""
|
|
final_star_allocations = final_data['final_star_allocations']
|
|
final_galaxy_allocations = final_data['final_galaxy_allocations']
|
|
total_allocation = final_data['total_stars_allocation'] + final_data['total_galaxies_allocation']
|
|
|
|
# Convert to $sZ units (multiply by 10^8)
|
|
output = {
|
|
"stars": {
|
|
f"{year}_years": int(final_star_allocations[year] * Decimal('1e8'))
|
|
for year in range(1, 6)
|
|
},
|
|
"galaxies": {
|
|
f"{year}_years": int(final_galaxy_allocations[year] * Decimal('1e8'))
|
|
for year in range(1, 6)
|
|
},
|
|
"total": int(total_allocation * Decimal('1e8'))
|
|
}
|
|
|
|
return output
|
|
|
|
|
|
def print_allocation_calculations(allocation_data):
|
|
"""Print basic allocation calculation tables."""
|
|
lockdrop_allocation_stars = LOCKDROP_ALLOCATION * STAR_ALLOCATION_PERCENT
|
|
lockdrop_allocation_galaxies = LOCKDROP_ALLOCATION * GALAXY_ALLOCATION_PERCENT
|
|
|
|
total_stars_locked = allocation_data['total_stars_locked']
|
|
total_galaxies_locked = allocation_data['total_galaxies_locked']
|
|
adjusted_max_allocation_per_star = allocation_data['adjusted_max_allocation_per_star']
|
|
adjusted_max_allocation_per_galaxy = allocation_data['adjusted_max_allocation_per_galaxy']
|
|
adjusted_z_per_star_per_block = allocation_data['adjusted_z_per_star_per_block']
|
|
adjusted_z_per_galaxy_per_block = allocation_data['adjusted_z_per_galaxy_per_block']
|
|
total_rounding_error_stars = allocation_data['total_rounding_error_stars']
|
|
total_rounding_error_galaxies = allocation_data['total_rounding_error_galaxies']
|
|
|
|
print("=" * 80)
|
|
print("🔢 ALLOCATION CALCULATIONS")
|
|
print("=" * 80)
|
|
|
|
# Raw Allocations Table
|
|
raw_allocation_per_star = lockdrop_allocation_stars / total_stars_locked if total_stars_locked > 0 else Decimal('0')
|
|
raw_allocation_per_galaxy = lockdrop_allocation_galaxies / total_galaxies_locked if total_galaxies_locked > 0 else Decimal('0')
|
|
|
|
raw_allocations_df = pd.DataFrame({
|
|
'Point Type': ['Stars', 'Galaxies'],
|
|
'Total Allocation ($Z)': [f"{lockdrop_allocation_stars:,.1f}", f"{lockdrop_allocation_galaxies:,.1f}"],
|
|
'Max Per Point ($Z)': [f"{raw_allocation_per_star:,.6f}", f"{raw_allocation_per_galaxy:,.6f}"]
|
|
})
|
|
print_table_with_borders(raw_allocations_df, "💰 RAW PARTICIPANT ALLOCATIONS")
|
|
|
|
# Quanta Calculations Table
|
|
raw_z_per_star_per_block = raw_allocation_per_star / LOCKDROP_DURATION_BLOCKS if total_stars_locked > 0 else Decimal('0')
|
|
raw_z_per_galaxy_per_block = raw_allocation_per_galaxy / LOCKDROP_DURATION_BLOCKS if total_galaxies_locked > 0 else Decimal('0')
|
|
|
|
quanta_df = pd.DataFrame({
|
|
'Point Type': ['Stars', 'Galaxies'],
|
|
'Raw Z per Block': [f"{raw_z_per_star_per_block:.15f}", f"{raw_z_per_galaxy_per_block:.15f}"],
|
|
'Adjusted Z per Block (q)': [f"{adjusted_z_per_star_per_block:.6f}", f"{adjusted_z_per_galaxy_per_block:.6f}"]
|
|
})
|
|
print_table_with_borders(quanta_df, "⚙️ QUANTA CALCULATION")
|
|
|
|
# Adjusted Allocations Table (after quanta calculation)
|
|
rounding_error_per_star = raw_allocation_per_star - adjusted_max_allocation_per_star
|
|
rounding_error_per_galaxy = raw_allocation_per_galaxy - adjusted_max_allocation_per_galaxy
|
|
percentage_rounding_error_stars = total_rounding_error_stars / TOTAL_SUPPLY * 100 if total_stars_locked > 0 else Decimal('0')
|
|
percentage_rounding_error_galaxies = total_rounding_error_galaxies / TOTAL_SUPPLY * 100 if total_galaxies_locked > 0 else Decimal('0')
|
|
|
|
# Separate tables for better readability
|
|
star_adjusted_df = pd.DataFrame({
|
|
'Metric': ['Adjusted Max per Star', 'Rounding Error per Star', 'Total Rounding Error', 'Rounding Error %'],
|
|
'Value': [f"{adjusted_max_allocation_per_star:,.6f} $Z", f"{rounding_error_per_star:.9f} $Z",
|
|
f"{total_rounding_error_stars:,.6f} $Z", f"{percentage_rounding_error_stars:.8f}%"],
|
|
'Note': ['Before bonus', 'Per star loss', 'Goes to bonus pool', 'Of total supply']
|
|
})
|
|
|
|
galaxy_adjusted_df = pd.DataFrame({
|
|
'Metric': ['Adjusted Max per Galaxy', 'Rounding Error per Galaxy', 'Total Rounding Error', 'Rounding Error %'],
|
|
'Value': [f"{adjusted_max_allocation_per_galaxy:,.6f} $Z", f"{rounding_error_per_galaxy:.9f} $Z",
|
|
f"{total_rounding_error_galaxies:,.6f} $Z", f"{percentage_rounding_error_galaxies:.8f}%"],
|
|
'Note': ['Before bonus', 'Per galaxy loss', 'Goes to bonus pool', 'Of total supply']
|
|
})
|
|
|
|
print("\n🎯 QUANTA ADJUSTED PARTICIPANT ALLOCATIONS")
|
|
print_table_with_borders(star_adjusted_df, "⭐ Star Adjustments")
|
|
print_table_with_borders(galaxy_adjusted_df, "🌌 Galaxy Adjustments")
|
|
print("\n" + "="*80)
|
|
|
|
|
|
def print_penalty_analysis(allocation_data):
|
|
"""Print penalty system analysis using values before bonus calculations."""
|
|
print("=" * 80)
|
|
print("⚖️ PENALTY SYSTEM ANALYSIS")
|
|
print("=" * 80)
|
|
|
|
# Use adjusted allocations (after penalties, before bonus distribution)
|
|
adjusted_max_allocation_per_star = allocation_data['adjusted_max_allocation_per_star']
|
|
adjusted_max_allocation_per_galaxy = allocation_data['adjusted_max_allocation_per_galaxy']
|
|
|
|
penalty_analysis_df = pd.DataFrame({
|
|
'Lock Period': ['5 Years', '4 Years', '3 Years', '2 Years', '1 Year'],
|
|
'Penalty Rate': [f"{PENALTY_RATES[year]:.1%}" for year in [5, 4, 3, 2, 1]],
|
|
'Star Allocation ($Z)': [f"{adjusted_max_allocation_per_star * (1 - PENALTY_RATES[year]):,.6f}" for year in [5, 4, 3, 2, 1]],
|
|
'Galaxy Allocation ($Z)': [f"{adjusted_max_allocation_per_galaxy * (1 - PENALTY_RATES[year]):,.6f}" for year in [5, 4, 3, 2, 1]],
|
|
'vs Max Allocation': ['100%', '80%', '60%', '40%', '20%']
|
|
})
|
|
|
|
print_table_with_borders(penalty_analysis_df, "📊 PENALTY ADJUSTED ALLOCATIONS (Before Bonus Distribution)")
|
|
print("\n" + "="*80)
|
|
|
|
|
|
def print_participation_summary(allocation_data):
|
|
"""Print participation distribution summary."""
|
|
stars_counts = allocation_data['stars_counts']
|
|
galaxies_counts = allocation_data['galaxies_counts']
|
|
total_stars_locked = allocation_data['total_stars_locked']
|
|
total_galaxies_locked = allocation_data['total_galaxies_locked']
|
|
|
|
# Consolidated participation and lock period distribution
|
|
participation_df = pd.DataFrame({
|
|
'Lock Period': ['1 Year', '2 Years', '3 Years', '4 Years', '5 Years', 'Total'],
|
|
'Stars': [f"{stars_counts[year]:,}" for year in [1, 2, 3, 4, 5]] + [f"{total_stars_locked:,}"],
|
|
'% of Total Stars': [f"{stars_counts[year]/NUM_STARS:.2%}" for year in [1, 2, 3, 4, 5]] + [f"{total_stars_locked/NUM_STARS:.1%}"],
|
|
'Galaxies': [f"{galaxies_counts[year]:,}" for year in [1, 2, 3, 4, 5]] + [f"{total_galaxies_locked:,}"],
|
|
'% of Total Galaxies': [f"{galaxies_counts[year]/NUM_GALAXIES:.2%}" for year in [1, 2, 3, 4, 5]] + [f"{total_galaxies_locked/NUM_GALAXIES:.1%}"],
|
|
'Total': [f"{stars_counts[year] + galaxies_counts[year]:,}" for year in [1, 2, 3, 4, 5]] + [f"{total_stars_locked + total_galaxies_locked:,}"],
|
|
})
|
|
|
|
print("=" * 80)
|
|
print_table_with_borders(participation_df, "🎯 PARTICIPANTS LOCK PERIOD DISTRIBUTION")
|
|
print("\n" + "="*80)
|
|
|
|
|
|
def print_bonus_pool_calculations(allocation_data, bonus_data):
|
|
"""Print detailed bonus pool calculations."""
|
|
stars_counts = allocation_data['stars_counts']
|
|
galaxies_counts = allocation_data['galaxies_counts']
|
|
adjusted_max_allocation_per_star = allocation_data['adjusted_max_allocation_per_star']
|
|
adjusted_max_allocation_per_galaxy = allocation_data['adjusted_max_allocation_per_galaxy']
|
|
total_rounding_error_stars = allocation_data['total_rounding_error_stars']
|
|
total_rounding_error_galaxies = allocation_data['total_rounding_error_galaxies']
|
|
|
|
print("=" * 80)
|
|
print("🎁 BONUS POOL CALCULATIONS")
|
|
print("=" * 80)
|
|
|
|
# Star Bonus Pool Analysis
|
|
|
|
star_bonus_df = pd.DataFrame({
|
|
'Component': ['Penalty Pool', 'Rounding Error Bonus', 'Total Star Bonus Pool',
|
|
'Recipients (5Y Stars)', 'Bonus per 5Y Star', 'Final 5Y Star Allocation'],
|
|
'Value': [f"{bonus_data['star_penalty_pool']:,.6f} $Z", f"{total_rounding_error_stars:,.6f} $Z",
|
|
f"{bonus_data['star_bonus_pool_total']:,.6f} $Z", f"{stars_counts[5]:,}",
|
|
f"{bonus_data['bonus_per_star_5_years']:,.6f} $Z", f"{adjusted_max_allocation_per_star + bonus_data['bonus_per_star_5_years']:,.6f} $Z"]
|
|
})
|
|
print_table_with_borders(star_bonus_df, "⭐ STAR BONUS POOL ANALYSIS")
|
|
|
|
# Galaxy Bonus Pool Analysis
|
|
|
|
galaxy_bonus_df = pd.DataFrame({
|
|
'Component': ['Penalty Pool', 'Rounding Error Bonus', 'Total Galaxy Bonus Pool',
|
|
'Recipients (5Y Galaxies)', 'Bonus per 5Y Galaxy', 'Final 5Y Galaxy Allocation'],
|
|
'Value': [f"{bonus_data['galaxy_penalty_pool']:,.6f} $Z", f"{total_rounding_error_galaxies:,.6f} $Z",
|
|
f"{bonus_data['galaxy_bonus_pool_total']:,.6f} $Z", f"{galaxies_counts[5]:,}",
|
|
f"{bonus_data['bonus_per_galaxy_5_years']:,.6f} $Z", f"{adjusted_max_allocation_per_galaxy + bonus_data['bonus_per_galaxy_5_years']:,.6f} $Z"]
|
|
})
|
|
print_table_with_borders(galaxy_bonus_df, "🌌 GALAXY BONUS POOL ANALYSIS")
|
|
|
|
print("\n" + "="*80)
|
|
|
|
|
|
def print_final_allocations_and_verification(allocation_data, final_data):
|
|
"""Print final allocations and verification tables."""
|
|
stars_counts = allocation_data['stars_counts']
|
|
galaxies_counts = allocation_data['galaxies_counts']
|
|
final_star_allocations = final_data['final_star_allocations']
|
|
final_galaxy_allocations = final_data['final_galaxy_allocations']
|
|
star_z_per_block = final_data['star_z_per_block']
|
|
galaxy_z_per_block = final_data['galaxy_z_per_block']
|
|
total_stars_allocation = final_data['total_stars_allocation']
|
|
total_galaxies_allocation = final_data['total_galaxies_allocation']
|
|
|
|
print("=" * 80)
|
|
print("✅ FINAL ALLOCATIONS & VERIFICATION")
|
|
print("=" * 80)
|
|
|
|
# Star Final Allocations Table
|
|
star_final_df = pd.DataFrame({
|
|
'Lock Period': ['5 Years', '4 Years', '3 Years', '2 Years', '1 Year'],
|
|
'Penalty': [f"{PENALTY_RATES[year]:.1%}" for year in [5, 4, 3, 2, 1]],
|
|
'Final Allocation ($Z)': [f"{final_star_allocations[year]:,.8f}" for year in [5, 4, 3, 2, 1]],
|
|
'Z per Block': [f"{star_z_per_block[year]:,.8f}" for year in [5, 4, 3, 2, 1]],
|
|
'Participants': [f"{stars_counts[year]:,}" for year in [5, 4, 3, 2, 1]]
|
|
})
|
|
print_table_with_borders(star_final_df, "⭐ FINAL STAR ALLOCATIONS")
|
|
|
|
# Galaxy Final Allocations Table
|
|
galaxy_final_df = pd.DataFrame({
|
|
'Lock Period': ['5 Years', '4 Years', '3 Years', '2 Years', '1 Year'],
|
|
'Penalty': [f"{PENALTY_RATES[year]:.1%}" for year in [5, 4, 3, 2, 1]],
|
|
'Final Allocation ($Z)': [f"{final_galaxy_allocations[year]:,.8f}" for year in [5, 4, 3, 2, 1]],
|
|
'Z per Block': [f"{galaxy_z_per_block[year]:,.8f}" for year in [5, 4, 3, 2, 1]],
|
|
'Participants': [f"{galaxies_counts[year]:,}" for year in [5, 4, 3, 2, 1]]
|
|
})
|
|
print_table_with_borders(galaxy_final_df, "🌌 FINAL GALAXY ALLOCATIONS")
|
|
|
|
# Verification and Rounding Error Analysis
|
|
lockdrop_allocation_stars = LOCKDROP_ALLOCATION * STAR_ALLOCATION_PERCENT
|
|
lockdrop_allocation_galaxies = LOCKDROP_ALLOCATION * GALAXY_ALLOCATION_PERCENT
|
|
final_rounding_error_stars = lockdrop_allocation_stars - total_stars_allocation
|
|
final_rounding_error_galaxies = lockdrop_allocation_galaxies - total_galaxies_allocation
|
|
final_rounding_error = final_rounding_error_stars + final_rounding_error_galaxies
|
|
|
|
verification_df = pd.DataFrame({
|
|
'Category': ['Star Allocations', 'Galaxy Allocations', 'Combined'],
|
|
'Calculated Total': [f"{total_stars_allocation:.8f} $Z", f"{total_galaxies_allocation:.8f} $Z",
|
|
f"{total_stars_allocation + total_galaxies_allocation:.8f} $Z"],
|
|
'Expected Total': [f"{lockdrop_allocation_stars:.8f} $Z", f"{lockdrop_allocation_galaxies:.8f} $Z",
|
|
f"{LOCKDROP_ALLOCATION:.8f} $Z"],
|
|
'Rounding Error': [f"{final_rounding_error_stars:.8f} $Z", f"{final_rounding_error_galaxies:.8f} $Z",
|
|
f"{final_rounding_error:.8f} $Z"]
|
|
})
|
|
print_table_with_borders(verification_df, "🔍 ALLOCATION VERIFICATION")
|
|
print("\n📝 NOTE: Final rounding errors go to Zenith Foundation")
|
|
print("\n" + "="*80)
|
|
|
|
|
|
def print_analysis_tables(allocation_data, bonus_data, final_data):
|
|
"""Print comprehensive analysis tables - calls all the detailed sections."""
|
|
print_participation_summary(allocation_data)
|
|
print_allocation_calculations(allocation_data)
|
|
print_penalty_analysis(allocation_data)
|
|
print_bonus_pool_calculations(allocation_data, bonus_data)
|
|
print_final_allocations_and_verification(allocation_data, final_data)
|
|
|
|
|
|
def create_visualization(allocation_data, final_data):
|
|
"""Create comprehensive visualization plots."""
|
|
stars_counts = allocation_data['stars_counts']
|
|
galaxies_counts = allocation_data['galaxies_counts']
|
|
final_star_allocations = final_data['final_star_allocations']
|
|
final_galaxy_allocations = final_data['final_galaxy_allocations']
|
|
|
|
fig = plt.figure(figsize=(20, 10))
|
|
gs = fig.add_gridspec(2, 3, hspace=0.35, wspace=0.35)
|
|
|
|
fig.suptitle('Lockdrop Analysis', fontsize=18, fontweight='bold', y=0.98)
|
|
|
|
# 1. Star Participation Distribution
|
|
ax1 = fig.add_subplot(gs[0, 0])
|
|
star_years = [1, 2, 3, 4, 5]
|
|
star_counts = [stars_counts[year] for year in star_years]
|
|
|
|
bars1 = ax1.bar(range(len(star_years)), star_counts,
|
|
color=['#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728'], alpha=0.8)
|
|
ax1.set_xlabel('Lock Period (Years)')
|
|
ax1.set_ylabel('Number of Stars')
|
|
ax1.set_title('Star Participation by Lock Period', fontweight='bold')
|
|
ax1.set_xticks(range(len(star_years)))
|
|
ax1.set_xticklabels(star_years)
|
|
|
|
for i, count in enumerate(star_counts):
|
|
height = bars1[i].get_height()
|
|
ax1.text(bars1[i].get_x() + bars1[i].get_width()/2., height + height*0.01,
|
|
f'{count:,}', ha='center', va='bottom', fontweight='bold', fontsize=9)
|
|
|
|
# 2. Galaxy Participation Distribution
|
|
ax2 = fig.add_subplot(gs[0, 1])
|
|
galaxy_counts = [galaxies_counts[year] for year in star_years]
|
|
|
|
bars2 = ax2.bar(range(len(star_years)), galaxy_counts,
|
|
color=['#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2'], alpha=0.8)
|
|
ax2.set_xlabel('Lock Period (Years)')
|
|
ax2.set_ylabel('Number of Galaxies')
|
|
ax2.set_title('Galaxy Participation by Lock Period', fontweight='bold')
|
|
ax2.set_xticks(range(len(star_years)))
|
|
ax2.set_xticklabels(star_years)
|
|
|
|
for i, count in enumerate(galaxy_counts):
|
|
height = bars2[i].get_height()
|
|
ax2.text(bars2[i].get_x() + bars2[i].get_width()/2., height + height*0.01,
|
|
f'{count:,}', ha='center', va='bottom', fontweight='bold', fontsize=9)
|
|
|
|
# 3. Star Allocations
|
|
ax3 = fig.add_subplot(gs[0, 2])
|
|
star_alloc_values = [float(final_star_allocations[year]) for year in star_years]
|
|
|
|
bars3 = ax3.bar(range(len(star_years)), star_alloc_values,
|
|
color=['#ff7f0e', '#ffbb78', '#2ca02c', '#98df8a', '#d62728'], alpha=0.8)
|
|
ax3.set_xlabel('Lock Period (Years)')
|
|
ax3.set_ylabel('Allocation per Star ($Z)')
|
|
ax3.set_title('Star Allocations by Lock Period', fontweight='bold')
|
|
ax3.set_xticks(range(len(star_years)))
|
|
ax3.set_xticklabels(star_years)
|
|
|
|
for i, allocation in enumerate(star_alloc_values):
|
|
height = bars3[i].get_height()
|
|
ax3.text(bars3[i].get_x() + bars3[i].get_width()/2., height + height*0.01,
|
|
f'{allocation:,.0f}', ha='center', va='bottom', fontweight='bold', fontsize=9)
|
|
|
|
# 4. Galaxy Allocations
|
|
ax4 = fig.add_subplot(gs[1, 0])
|
|
galaxy_alloc_values = [float(final_galaxy_allocations[year]) for year in star_years]
|
|
|
|
bars4 = ax4.bar(range(len(star_years)), galaxy_alloc_values,
|
|
color=['#9467bd', '#c5b0d5', '#8c564b', '#c49c94', '#e377c2'], alpha=0.8)
|
|
ax4.set_xlabel('Lock Period (Years)')
|
|
ax4.set_ylabel('Allocation per Galaxy ($Z)')
|
|
ax4.set_title('Galaxy Allocations by Lock Period', fontweight='bold')
|
|
ax4.set_xticks(range(len(star_years)))
|
|
ax4.set_xticklabels(star_years)
|
|
|
|
for i, allocation in enumerate(galaxy_alloc_values):
|
|
height = bars4[i].get_height()
|
|
ax4.text(bars4[i].get_x() + bars4[i].get_width()/2., height + height*0.01,
|
|
f'{allocation:,.0f}', ha='center', va='bottom', fontweight='bold', fontsize=9)
|
|
|
|
# Hide remaining subplots
|
|
ax5 = fig.add_subplot(gs[1, 1])
|
|
ax5.axis('off')
|
|
ax6 = fig.add_subplot(gs[1, 2])
|
|
ax6.axis('off')
|
|
|
|
plt.subplots_adjust(bottom=0.08, top=0.88, left=0.05, right=0.98)
|
|
plt.show()
|
|
|
|
|
|
def load_watcher_events_data(generated_dir='generated'):
|
|
"""Load participation data from watcher events file."""
|
|
import urbitob
|
|
|
|
watcher_events_path = os.path.join(generated_dir, 'watcher-events.json')
|
|
|
|
try:
|
|
with open(watcher_events_path, 'r') as f:
|
|
data = json.load(f)
|
|
events = data['data']['eventsInRange']
|
|
|
|
# Analyze events
|
|
lock_duration_counts = {
|
|
'star': defaultdict(int),
|
|
'galaxy': defaultdict(int)
|
|
}
|
|
|
|
for event_data in events:
|
|
if event_data['event']['__typename'] == 'PointLockedEvent':
|
|
point = event_data['event']['point']
|
|
lock_period = event_data['event']['lock_period']
|
|
|
|
point_num = urbitob.patp_to_num(point)
|
|
point_type = "galaxy" if point_num < NUM_GALAXIES else "star"
|
|
lock_duration_counts[point_type][lock_period] += 1
|
|
|
|
# Extract counts
|
|
result = {}
|
|
for years in [1, 2, 3, 4, 5]:
|
|
result[f'stars_{years}_years'] = lock_duration_counts['star'][years]
|
|
result[f'galaxies_{years}_years'] = lock_duration_counts['galaxy'][years]
|
|
|
|
return result, True
|
|
|
|
except FileNotFoundError:
|
|
print(f"⚠️ Watcher events file not found at {watcher_events_path}")
|
|
return None, False
|
|
except Exception as e:
|
|
print(f"⚠️ Error loading watcher events: {e}")
|
|
return None, False
|
|
|
|
|
|
# Experimental notebook widget and interaction functions
|
|
def create_experiment_widgets():
|
|
"""Create input widgets for the experimental notebook."""
|
|
try:
|
|
import ipywidgets as widgets
|
|
except ImportError:
|
|
raise ImportError("ipywidgets is required for the experimental interface. Install with: pip install ipywidgets")
|
|
|
|
# Define reasonable default values
|
|
default_participants = {
|
|
'stars_1_years': 8000,
|
|
'stars_2_years': 8000,
|
|
'stars_3_years': 8000,
|
|
'stars_4_years': 8000,
|
|
'stars_5_years': 8000,
|
|
'galaxies_1_years': 40,
|
|
'galaxies_2_years': 40,
|
|
'galaxies_3_years': 40,
|
|
'galaxies_4_years': 40,
|
|
'galaxies_5_years': 40
|
|
}
|
|
|
|
# Create input widgets
|
|
print("="*50)
|
|
|
|
# Star participation widgets
|
|
stars_1_years = widgets.IntText(value=default_participants['stars_1_years'], description='1 Year:', style={'description_width': '80px'})
|
|
stars_2_years = widgets.IntText(value=default_participants['stars_2_years'], description='2 Years:', style={'description_width': '80px'})
|
|
stars_3_years = widgets.IntText(value=default_participants['stars_3_years'], description='3 Years:', style={'description_width': '80px'})
|
|
stars_4_years = widgets.IntText(value=default_participants['stars_4_years'], description='4 Years:', style={'description_width': '80px'})
|
|
stars_5_years = widgets.IntText(value=default_participants['stars_5_years'], description='5 Years:', style={'description_width': '80px'})
|
|
|
|
star_controls = widgets.VBox([stars_1_years, stars_2_years, stars_3_years, stars_4_years, stars_5_years])
|
|
|
|
galaxies_1_years = widgets.IntText(value=default_participants['galaxies_1_years'], description='1 Year:', style={'description_width': '80px'})
|
|
galaxies_2_years = widgets.IntText(value=default_participants['galaxies_2_years'], description='2 Years:', style={'description_width': '80px'})
|
|
galaxies_3_years = widgets.IntText(value=default_participants['galaxies_3_years'], description='3 Years:', style={'description_width': '80px'})
|
|
galaxies_4_years = widgets.IntText(value=default_participants['galaxies_4_years'], description='4 Years:', style={'description_width': '80px'})
|
|
galaxies_5_years = widgets.IntText(value=default_participants['galaxies_5_years'], description='5 Years:', style={'description_width': '80px'})
|
|
|
|
galaxy_controls = widgets.VBox([galaxies_1_years, galaxies_2_years, galaxies_3_years, galaxies_4_years, galaxies_5_years])
|
|
|
|
# Preset scenario dropdown
|
|
scenario_dropdown = widgets.Dropdown(
|
|
options=[('Select Preset', '')] + [(v['description'], k) for k, v in SCENARIOS.items()],
|
|
description='Preset:',
|
|
style={'description_width': '60px'},
|
|
layout={'width': '400px'}
|
|
)
|
|
|
|
# Calculate button
|
|
calculate_button = widgets.Button(description='🔄 Calculate Allocations', button_style='success', layout={'width': '200px', 'margin': '10px 5px'})
|
|
|
|
# Export button
|
|
export_button = widgets.Button(description='💾 Export Results', button_style='info', layout={'width': '180px', 'margin': '10px 5px'})
|
|
|
|
# Output area
|
|
output_area = widgets.Output()
|
|
|
|
# Set up preset selection handler
|
|
def on_preset_change(change):
|
|
if change['new'] and change['new'] != '':
|
|
scenario = SCENARIOS[change['new']]
|
|
params = scenario['params']
|
|
|
|
# Update widget values
|
|
stars_1_years.value = params['stars_1_years']
|
|
stars_2_years.value = params['stars_2_years']
|
|
stars_3_years.value = params['stars_3_years']
|
|
stars_4_years.value = params['stars_4_years']
|
|
stars_5_years.value = params['stars_5_years']
|
|
galaxies_1_years.value = params['galaxies_1_years']
|
|
galaxies_2_years.value = params['galaxies_2_years']
|
|
galaxies_3_years.value = params['galaxies_3_years']
|
|
galaxies_4_years.value = params['galaxies_4_years']
|
|
galaxies_5_years.value = params['galaxies_5_years']
|
|
|
|
scenario_dropdown.observe(on_preset_change, names='value')
|
|
|
|
# Set up export button handler
|
|
def on_export_click(button=None):
|
|
"""Handle export button click with timestamp."""
|
|
try:
|
|
from datetime import datetime
|
|
from IPython.display import clear_output
|
|
except ImportError:
|
|
print("❌ Required libraries not available")
|
|
return
|
|
|
|
# Generate timestamp for filename
|
|
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
|
|
filename = f'lockdrop_calculations_result_{timestamp}.json'
|
|
|
|
# Get current values from widgets
|
|
participation_counts = {
|
|
'stars_1_years': stars_1_years.value,
|
|
'stars_2_years': stars_2_years.value,
|
|
'stars_3_years': stars_3_years.value,
|
|
'stars_4_years': stars_4_years.value,
|
|
'stars_5_years': stars_5_years.value,
|
|
'galaxies_1_years': galaxies_1_years.value,
|
|
'galaxies_2_years': galaxies_2_years.value,
|
|
'galaxies_3_years': galaxies_3_years.value,
|
|
'galaxies_4_years': galaxies_4_years.value,
|
|
'galaxies_5_years': galaxies_5_years.value
|
|
}
|
|
|
|
try:
|
|
# Calculate allocations
|
|
allocation_data = calculate_dynamic_allocations(participation_counts)
|
|
bonus_data = calculate_bonus_pools(allocation_data)
|
|
final_data = calculate_final_allocations(allocation_data, bonus_data)
|
|
|
|
# Generate test output
|
|
test_output = generate_test_output(final_data)
|
|
|
|
# Add metadata
|
|
export_data = {
|
|
'metadata': {
|
|
'source': 'lockdrop-calculations.ipynb',
|
|
'timestamp': timestamp,
|
|
'participation_counts': participation_counts
|
|
},
|
|
'allocations': test_output
|
|
}
|
|
|
|
# Save to file
|
|
with open(filename, 'w') as f:
|
|
json.dump(export_data, f, indent=2)
|
|
|
|
print(f"✅ Results exported to: {filename}")
|
|
|
|
except Exception as e:
|
|
print(f"❌ Export failed: {str(e)}")
|
|
|
|
export_button.on_click(on_export_click)
|
|
|
|
# Return all widgets as a dictionary
|
|
return {
|
|
'star_controls': star_controls,
|
|
'galaxy_controls': galaxy_controls,
|
|
'scenario_dropdown': scenario_dropdown,
|
|
'calculate_button': calculate_button,
|
|
'export_button': export_button,
|
|
'output_area': output_area,
|
|
'widgets': {
|
|
'stars_1_years': stars_1_years,
|
|
'stars_2_years': stars_2_years,
|
|
'stars_3_years': stars_3_years,
|
|
'stars_4_years': stars_4_years,
|
|
'stars_5_years': stars_5_years,
|
|
'galaxies_1_years': galaxies_1_years,
|
|
'galaxies_2_years': galaxies_2_years,
|
|
'galaxies_3_years': galaxies_3_years,
|
|
'galaxies_4_years': galaxies_4_years,
|
|
'galaxies_5_years': galaxies_5_years
|
|
}
|
|
}
|
|
|
|
|
|
def create_calculate_function(widget_dict):
|
|
"""Create the calculation and display function for the experimental notebook."""
|
|
def calculate_and_display(button=None):
|
|
"""Calculate allocations and display results based on input parameters."""
|
|
try:
|
|
from IPython.display import clear_output
|
|
except ImportError:
|
|
raise ImportError("IPython is required for the experimental interface")
|
|
|
|
widgets_map = widget_dict['widgets']
|
|
output_area = widget_dict['output_area']
|
|
|
|
with output_area:
|
|
clear_output(wait=True)
|
|
|
|
# Get current values from widgets
|
|
participation_counts = {
|
|
'stars_1_years': widgets_map['stars_1_years'].value,
|
|
'stars_2_years': widgets_map['stars_2_years'].value,
|
|
'stars_3_years': widgets_map['stars_3_years'].value,
|
|
'stars_4_years': widgets_map['stars_4_years'].value,
|
|
'stars_5_years': widgets_map['stars_5_years'].value,
|
|
'galaxies_1_years': widgets_map['galaxies_1_years'].value,
|
|
'galaxies_2_years': widgets_map['galaxies_2_years'].value,
|
|
'galaxies_3_years': widgets_map['galaxies_3_years'].value,
|
|
'galaxies_4_years': widgets_map['galaxies_4_years'].value,
|
|
'galaxies_5_years': widgets_map['galaxies_5_years'].value
|
|
}
|
|
|
|
# Validate inputs
|
|
total_stars = sum(participation_counts[key] for key in participation_counts if 'stars' in key)
|
|
total_galaxies = sum(participation_counts[key] for key in participation_counts if 'galaxies' in key)
|
|
|
|
if total_stars > NUM_STARS:
|
|
print(f"⚠️ ERROR: Total stars ({total_stars:,}) exceeds maximum available ({NUM_STARS:,})")
|
|
return
|
|
|
|
if total_galaxies > NUM_GALAXIES:
|
|
print(f"⚠️ ERROR: Total galaxies ({total_galaxies:,}) exceeds maximum available ({NUM_GALAXIES:,})")
|
|
return
|
|
|
|
if total_stars == 0 and total_galaxies == 0:
|
|
print("⚠️ ERROR: Must have at least some participants")
|
|
return
|
|
|
|
try:
|
|
# Perform calculations
|
|
print("🔄 Calculating allocations...\n")
|
|
|
|
allocation_data = calculate_dynamic_allocations(participation_counts)
|
|
bonus_data = calculate_bonus_pools(allocation_data)
|
|
final_data = calculate_final_allocations(allocation_data, bonus_data)
|
|
|
|
# Display results
|
|
print_analysis_tables(allocation_data, bonus_data, final_data)
|
|
|
|
# Create visualization
|
|
print("\n📊 Generating visualization...")
|
|
create_visualization(allocation_data, final_data)
|
|
|
|
# Calculate participation insights
|
|
star_participation_rate = Decimal(total_stars) / NUM_STARS
|
|
galaxy_participation_rate = Decimal(total_galaxies) / NUM_GALAXIES
|
|
|
|
print("\n" + "="*80)
|
|
print("🎯 EXPERIMENT INSIGHTS")
|
|
print("=" * 80)
|
|
|
|
insights_df = pd.DataFrame({
|
|
'Metric': [
|
|
'Total Participants',
|
|
'Star Participation Rate',
|
|
'Galaxy Participation Rate',
|
|
'5-Year Star Bonus',
|
|
'5-Year Galaxy Bonus',
|
|
'Highest Individual Allocation',
|
|
'Lowest Individual Allocation'
|
|
],
|
|
'Value': [
|
|
f"{total_stars + total_galaxies:,}",
|
|
f"{star_participation_rate:.1%}",
|
|
f"{galaxy_participation_rate:.1%}",
|
|
f"{float(bonus_data['bonus_per_star_5_years']):,.2f} $Z" if participation_counts['stars_5_years'] > 0 else "N/A (no 5Y stars)",
|
|
f"{float(bonus_data['bonus_per_galaxy_5_years']):,.2f} $Z" if participation_counts['galaxies_5_years'] > 0 else "N/A (no 5Y galaxies)",
|
|
f"{max(float(final_data['final_star_allocations'][5]), float(final_data['final_galaxy_allocations'][5])):,.2f} $Z",
|
|
f"{min(float(final_data['final_star_allocations'][1]), float(final_data['final_galaxy_allocations'][1])):,.2f} $Z"
|
|
]
|
|
})
|
|
print_table_with_borders(insights_df, "🎯 EXPERIMENT INSIGHTS")
|
|
print("\n" + "="*80)
|
|
|
|
except Exception as e:
|
|
print(f"❌ Error during calculation: {str(e)}")
|
|
import traceback
|
|
traceback.print_exc()
|
|
|
|
return calculate_and_display
|
|
|
|
|
|
# Preset scenario definitions for experimentation
|
|
SCENARIOS = {
|
|
'balanced': {
|
|
'description': 'Balanced distribution across all lock periods',
|
|
'params': {
|
|
'stars_1_years': 8000, 'stars_2_years': 8000, 'stars_3_years': 8000, 'stars_4_years': 8000, 'stars_5_years': 8000,
|
|
'galaxies_1_years': 40, 'galaxies_2_years': 40, 'galaxies_3_years': 40, 'galaxies_4_years': 40, 'galaxies_5_years': 40
|
|
}
|
|
},
|
|
'five_year_focused': {
|
|
'description': 'Most participants choose 5-year lock (maximum bonus scenario)',
|
|
'params': {
|
|
'stars_1_years': 2000, 'stars_2_years': 2000, 'stars_3_years': 3000, 'stars_4_years': 5000, 'stars_5_years': 28000,
|
|
'galaxies_1_years': 10, 'galaxies_2_years': 10, 'galaxies_3_years': 20, 'galaxies_4_years': 30, 'galaxies_5_years': 130
|
|
}
|
|
},
|
|
'short_term_focused': {
|
|
'description': 'Most participants choose shorter locks (high penalty scenario)',
|
|
'params': {
|
|
'stars_1_years': 20000, 'stars_2_years': 15000, 'stars_3_years': 4000, 'stars_4_years': 800, 'stars_5_years': 200,
|
|
'galaxies_1_years': 100, 'galaxies_2_years': 80, 'galaxies_3_years': 15, 'galaxies_4_years': 3, 'galaxies_5_years': 2
|
|
}
|
|
},
|
|
'low_participation': {
|
|
'description': 'Low overall participation scenario',
|
|
'params': {
|
|
'stars_1_years': 1000, 'stars_2_years': 800, 'stars_3_years': 600, 'stars_4_years': 400, 'stars_5_years': 200,
|
|
'galaxies_1_years': 8, 'galaxies_2_years': 6, 'galaxies_3_years': 4, 'galaxies_4_years': 2, 'galaxies_5_years': 5
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
def create_scenario_loader(widget_dict):
|
|
"""Create scenario loading function for the experimental notebook."""
|
|
def load_scenario(scenario_name):
|
|
"""Load a preset scenario into the input widgets."""
|
|
if scenario_name not in SCENARIOS:
|
|
print(f"❌ Unknown scenario: {scenario_name}")
|
|
return
|
|
|
|
scenario = SCENARIOS[scenario_name]
|
|
params = scenario['params']
|
|
widgets_map = widget_dict['widgets']
|
|
|
|
# Update widget values
|
|
widgets_map['stars_1_years'].value = params['stars_1_years']
|
|
widgets_map['stars_2_years'].value = params['stars_2_years']
|
|
widgets_map['stars_3_years'].value = params['stars_3_years']
|
|
widgets_map['stars_4_years'].value = params['stars_4_years']
|
|
widgets_map['stars_5_years'].value = params['stars_5_years']
|
|
widgets_map['galaxies_1_years'].value = params['galaxies_1_years']
|
|
widgets_map['galaxies_2_years'].value = params['galaxies_2_years']
|
|
widgets_map['galaxies_3_years'].value = params['galaxies_3_years']
|
|
widgets_map['galaxies_4_years'].value = params['galaxies_4_years']
|
|
widgets_map['galaxies_5_years'].value = params['galaxies_5_years']
|
|
|
|
print(f"✅ Loaded scenario: {scenario_name}")
|
|
print(f"📝 Description: {scenario['description']}")
|
|
print("\n🔄 Click 'Calculate Allocations' button above to see results!")
|
|
|
|
return load_scenario
|
|
|
|
|
|
def create_export_function(widget_dict):
|
|
"""Create export function for the experimental notebook."""
|
|
def export_current_scenario(filename='lockdrop_allocations_experiment.json'):
|
|
"""Export current scenario results to JSON file."""
|
|
widgets_map = widget_dict['widgets']
|
|
|
|
# Get current values from widgets
|
|
participation_counts = {
|
|
'stars_1_years': widgets_map['stars_1_years'].value,
|
|
'stars_2_years': widgets_map['stars_2_years'].value,
|
|
'stars_3_years': widgets_map['stars_3_years'].value,
|
|
'stars_4_years': widgets_map['stars_4_years'].value,
|
|
'stars_5_years': widgets_map['stars_5_years'].value,
|
|
'galaxies_1_years': widgets_map['galaxies_1_years'].value,
|
|
'galaxies_2_years': widgets_map['galaxies_2_years'].value,
|
|
'galaxies_3_years': widgets_map['galaxies_3_years'].value,
|
|
'galaxies_4_years': widgets_map['galaxies_4_years'].value,
|
|
'galaxies_5_years': widgets_map['galaxies_5_years'].value
|
|
}
|
|
|
|
try:
|
|
# Calculate allocations
|
|
allocation_data = calculate_dynamic_allocations(participation_counts)
|
|
bonus_data = calculate_bonus_pools(allocation_data)
|
|
final_data = calculate_final_allocations(allocation_data, bonus_data)
|
|
|
|
# Generate test output
|
|
test_output = generate_test_output(final_data)
|
|
|
|
# Add metadata
|
|
export_data = {
|
|
'metadata': {
|
|
'source': 'lockdrop-calculations.ipynb',
|
|
'participation_counts': participation_counts
|
|
},
|
|
'allocations': test_output
|
|
}
|
|
|
|
# Save to file
|
|
with open(filename, 'w') as f:
|
|
json.dump(export_data, f, indent=2)
|
|
|
|
print(f"✅ Results exported to: {filename}")
|
|
print(f"📊 Total participants: {sum(participation_counts.values()):,}")
|
|
|
|
except Exception as e:
|
|
print(f"❌ Export failed: {str(e)}")
|
|
|
|
return export_current_scenario
|