replit2 / ml_engine /pnl_tracker.py
Nhughes09
deploy: clean force push
c89a139
import pandas as pd
import numpy as np
from datetime import datetime, timedelta
from typing import Dict, List, Optional
class PnLTracker:
"""
Tracks hypothetical P&L for all predictions to demonstrate value.
Simulates a portfolio that invests based on model confidence.
"""
def __init__(self, initial_capital: float = 1_000_000.0):
self.initial_capital = initial_capital
self.current_capital = initial_capital
self.predictions = [] # Log of all predictions made
self.positions = [] # Active "investments"
self.closed_trades = [] # Completed trades
self.win_count = 0
self.loss_count = 0
# Performance metrics
self.total_pnl = 0.0
self.roi_pct = 0.0
def calculate_position_size(self, confidence: float) -> float:
"""
Kelly Criterion-inspired position sizing based on confidence.
Higher confidence = larger bet size.
"""
if confidence < 0.5:
return 0.0
# Simple scaling: 50% conf = 0% size, 100% conf = 10% of capital
# This is a conservative simulation
max_position_pct = 0.10
scale_factor = (confidence - 0.5) * 2 # 0.0 to 1.0
return self.current_capital * max_position_pct * scale_factor
def record_prediction(self, prediction_id: str, vertical: str, target: str,
predicted_value: any, confidence: float,
expected_timeline_days: int):
"""
Log a new prediction and "open" a hypothetical position.
"""
position_size = self.calculate_position_size(confidence)
record = {
'id': prediction_id,
'date': datetime.now().isoformat(),
'vertical': vertical,
'target': target,
'prediction': predicted_value,
'confidence': confidence,
'position_size': position_size,
'status': 'OPEN',
'expected_close_date': (datetime.now() + timedelta(days=expected_timeline_days)).isoformat()
}
self.predictions.append(record)
if position_size > 0:
self.positions.append(record)
return position_size
def resolve_prediction(self, prediction_id: str, actual_value: any, success: bool, pnl_pct: float):
"""
Close a position based on real-world outcome.
pnl_pct: The simulated return on the position (e.g., 0.20 for 20% gain)
"""
# Find the position
position = next((p for p in self.positions if p['id'] == prediction_id), None)
if not position:
return
# Calculate P&L
invested_amount = position['position_size']
pnl_amount = invested_amount * pnl_pct
self.current_capital += pnl_amount
self.total_pnl += pnl_amount
self.roi_pct = (self.current_capital - self.initial_capital) / self.initial_capital * 100
if success:
self.win_count += 1
else:
self.loss_count += 1
# Move to closed
position['status'] = 'CLOSED'
position['actual_value'] = actual_value
position['pnl_amount'] = pnl_amount
position['pnl_pct'] = pnl_pct
position['close_date'] = datetime.now().isoformat()
self.closed_trades.append(position)
self.positions.remove(position)
def get_performance_metrics(self) -> Dict:
"""
Return comprehensive performance stats for the dashboard.
"""
total_trades = self.win_count + self.loss_count
win_rate = (self.win_count / total_trades * 100) if total_trades > 0 else 0.0
return {
'current_capital': self.current_capital,
'total_pnl': self.total_pnl,
'roi_pct': round(self.roi_pct, 2),
'win_rate': round(win_rate, 1),
'total_trades': total_trades,
'active_positions': len(self.positions),
'avg_win_pct': self._calculate_avg_pnl(wins_only=True),
'avg_loss_pct': self._calculate_avg_pnl(losses_only=True)
}
def _calculate_avg_pnl(self, wins_only=False, losses_only=False) -> float:
trades = self.closed_trades
if wins_only:
trades = [t for t in trades if t['pnl_amount'] > 0]
if losses_only:
trades = [t for t in trades if t['pnl_amount'] <= 0]
if not trades:
return 0.0
avg = sum(t['pnl_pct'] for t in trades) / len(trades) * 100
return round(avg, 1)