diff --git a/tools/kpi_tracker.py b/tools/kpi_tracker.py new file mode 100644 index 0000000..4447aba --- /dev/null +++ b/tools/kpi_tracker.py @@ -0,0 +1,133 @@ +import os +import csv +import time +import logging +from decimal import Decimal +from typing import Dict, Optional + +# Setup Logger +logger = logging.getLogger("KPI_TRACKER") +logger.setLevel(logging.INFO) +# Basic handler if not already handled by parent +if not logger.handlers: + ch = logging.StreamHandler() + formatter = logging.Formatter('%(asctime)s - KPI - %(message)s') + ch.setFormatter(formatter) + logger.addHandler(ch) + +KPI_FILE = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), 'logs', 'kpi_history.csv') + +def initialize_kpi_csv(): + """Creates the CSV with headers if it doesn't exist.""" + if not os.path.exists(os.path.dirname(KPI_FILE)): + os.makedirs(os.path.dirname(KPI_FILE)) + + if not os.path.exists(KPI_FILE): + with open(KPI_FILE, 'w', newline='') as f: + writer = csv.writer(f) + writer.writerow([ + "Timestamp", + "Date", + "NAV_Total_USD", + "Benchmark_HODL_USD", + "Alpha_USD", + "Uniswap_Val_USD", + "Uniswap_Fees_Claimed_USD", + "Uniswap_Fees_Unclaimed_USD", + "Hedge_Equity_USD", + "Hedge_PnL_Realized_USD", + "Hedge_Fees_Paid_USD", + "ETH_Price", + "Fee_Coverage_Ratio" + ]) + +def calculate_hodl_benchmark(initial_eth: Decimal, initial_usdc: Decimal, current_eth_price: Decimal) -> Decimal: + """Calculates value if assets were just held.""" + return (initial_eth * current_eth_price) + initial_usdc + +def log_kpi_snapshot( + snapshot_data: Dict[str, float] +): + """ + Logs a KPI snapshot to CSV. + Expected keys in snapshot_data: + - initial_eth, initial_usdc + - current_eth_price + - uniswap_pos_value_usd + - uniswap_fees_claimed_usd + - uniswap_fees_unclaimed_usd + - hedge_equity_usd + - hedge_pnl_realized_usd + - hedge_fees_paid_usd + - wallet_eth_bal, wallet_usdc_bal (Optional, for full NAV) + """ + try: + initialize_kpi_csv() + + # Convert all inputs to Decimal for precision + price = Decimal(str(snapshot_data.get('current_eth_price', 0))) + + # 1. Benchmark (HODL) + init_eth = Decimal(str(snapshot_data.get('initial_eth', 0))) + init_usdc = Decimal(str(snapshot_data.get('initial_usdc', 0))) + benchmark_val = calculate_hodl_benchmark(init_eth, init_usdc, price) + + # 2. Strategy NAV (Net Asset Value) + # NAV = Uni Pos + Uni Fees (Claimed+Unclaimed) + Hedge Equity + (Wallet Surplus - Initial Wallet Surplus?) + # For simplicity, we focus on the Strategy PnL components: + # Strategy Val = (Current Uni Pos) + (Claimed Fees) + (Unclaimed Fees) + (Hedge PnL Realized) + (Hedge Unrealized?) + # Note: Hedge Equity usually includes margin. We strictly want "Value Generated". + + uni_val = Decimal(str(snapshot_data.get('uniswap_pos_value_usd', 0))) + uni_fees_claimed = Decimal(str(snapshot_data.get('uniswap_fees_claimed_usd', 0))) + uni_fees_unclaimed = Decimal(str(snapshot_data.get('uniswap_fees_unclaimed_usd', 0))) + + # Hedge PnL (Realized + Unrealized) is better than Equity for PnL tracking, + # but Equity represents actual redeemable cash. Let's use Equity if provided, or PnL components. + hedge_equity = Decimal(str(snapshot_data.get('hedge_equity_usd', 0))) + hedge_fees = Decimal(str(snapshot_data.get('hedge_fees_paid_usd', 0))) + + # Simplified NAV for Strategy Comparison: + # We assume 'hedge_equity' is the Liquidation Value of the hedge account. + # But if we want strictly "Strategy Performance", we usually do: + # Current Value = Uni_Val + Unclaimed + Hedge_Equity + # (Assuming Hedge_Equity started at 0 or we track delta? No, usually Hedge Account has deposit). + + # Let's define NAV as Total Current Liquidation Value of Strategy Components + current_nav = uni_val + uni_fees_unclaimed + uni_fees_claimed + hedge_equity + + # Alpha + alpha = current_nav - benchmark_val + + # Coverage Ratio + total_hedge_cost = abs(hedge_fees) # + funding if available + total_uni_earnings = uni_fees_claimed + uni_fees_unclaimed + + if total_hedge_cost > 0: + coverage_ratio = total_uni_earnings / total_hedge_cost + else: + coverage_ratio = Decimal("999.0") # Infinite/Good + + # Write + with open(KPI_FILE, 'a', newline='') as f: + writer = csv.writer(f) + writer.writerow([ + int(time.time()), + time.strftime('%Y-%m-%d %H:%M:%S'), + f"{current_nav:.2f}", + f"{benchmark_val:.2f}", + f"{alpha:.2f}", + f"{uni_val:.2f}", + f"{uni_fees_claimed:.2f}", + f"{uni_fees_unclaimed:.2f}", + f"{hedge_equity:.2f}", + f"{snapshot_data.get('hedge_pnl_realized_usd', 0):.2f}", + f"{hedge_fees:.2f}", + f"{price:.2f}", + f"{coverage_ratio:.2f}" + ]) + + logger.info(f"📊 KPI Logged | NAV: ${current_nav:.2f} | Benchmark: ${benchmark_val:.2f} | Alpha: ${alpha:.2f}") + + except Exception as e: + logger.error(f"Failed to log KPI: {e}")