use super::storage::StorageBackend;
use super::types::{TokenPnl, RealizedPnlResult, TradeRow, PositionSummary};
fn round(value: f64, decimals: u32) -> f64 {
let factor = 10f64.powi(decimals as i32);
(value * factor).round() / factor
}
pub fn compute_realized_pnl(backend: &dyn StorageBackend, wallet: &str) -> RealizedPnlResult {
let w = wallet.to_lowercase();
let token_ids = backend.wallet_token_ids(&w);
let positions = backend.wallet_positions(&w).unwrap_or_default();
let backfill_complete = backend.wallet_backfill_complete(&w);
let pos_map: std::collections::HashMap<&str, &PositionSummary> =
positions.iter().map(|p| (p.token_id.as_str(), p)).collect();
let mut tokens: Vec<TokenPnl> = Vec::new();
let mut total_trades: usize = 0;
let mut processed_tokens = std::collections::HashSet::new();
for token_id in &token_ids {
let trades = backend.wallet_token_trades(&w, token_id);
if trades.is_empty() {
continue;
}
processed_tokens.insert(token_id.clone());
let mut cost_basis: f64 = 0.0;
let mut position_size: f64 = 0.0;
let mut realized: f64 = 0.0;
let mut buys: usize = 0;
let mut sells: usize = 0;
let condition_id = trades[0].condition_id.clone();
let market_title = trades[0].market_title.clone();
let outcome = trades[0].outcome.clone();
for trade in &trades {
let effective_side = effective_side_for_wallet(&w, trade);
let fee = trade.fee.unwrap_or(0.0);
match effective_side.as_str() {
"BUY" => {
cost_basis += trade.price * trade.size + fee;
position_size += trade.size;
buys += 1;
}
"SELL" => {
let sell_size = trade.size.min(position_size);
if sell_size > 0.0 && position_size > 0.0 {
let avg_cost = cost_basis / position_size;
realized += (trade.price * sell_size - fee) - (avg_cost * sell_size);
cost_basis -= avg_cost * sell_size;
position_size -= sell_size;
}
sells += 1;
}
_ => {}
}
}
let pos = pos_map.get(token_id.as_str());
if let Some(p) = pos {
if let Some(rpnl) = p.realized_pnl {
if rpnl != 0.0 {
realized = rpnl;
}
}
}
if position_size < 1e-9 {
position_size = 0.0;
cost_basis = 0.0;
}
let avg_cost = if position_size > 0.0 { cost_basis / position_size } else { 0.0 };
let cur_price = pos.and_then(|p| p.cur_price);
let still_held = pos.map_or(false, |p| p.size > 0.0 && !p.redeemable);
let unrealized = if still_held {
cur_price.map_or(0.0, |cp| (cp - avg_cost) * position_size)
} else {
0.0
};
let trade_count = trades.len();
total_trades += trade_count;
tokens.push(TokenPnl {
token_id: token_id.clone(),
condition_id,
market_title,
outcome,
realized_pnl: round(realized, 6),
unrealized_pnl: round(unrealized, 6),
remaining_size: round(position_size, 6),
avg_cost: round(avg_cost, 6),
cur_price,
trades_analyzed: trade_count,
buys,
sells,
});
}
for pos in &positions {
if processed_tokens.contains(&pos.token_id) { continue; }
let rpnl = pos.realized_pnl.unwrap_or(0.0);
if rpnl == 0.0 { continue; }
tokens.push(TokenPnl {
token_id: pos.token_id.clone(),
condition_id: pos.condition_id.clone(),
market_title: pos.market_title.clone(),
outcome: pos.outcome.clone(),
realized_pnl: rpnl,
unrealized_pnl: 0.0,
remaining_size: pos.size,
avg_cost: pos.avg_price,
cur_price: pos.cur_price,
trades_analyzed: 0,
buys: 0,
sells: 0,
});
}
let total_realized: f64 = tokens.iter().map(|t| t.realized_pnl).sum();
let total_unrealized: f64 = tokens.iter().map(|t| t.unrealized_pnl).sum();
let has_onchain = positions.iter().any(|p| p.realized_pnl.unwrap_or(0.0) != 0.0 && p.total_bought.is_some());
let confidence = if has_onchain || backfill_complete { "full" } else { "partial" };
RealizedPnlResult {
wallet: w,
total_realized_pnl: round(total_realized, 2),
total_unrealized_pnl: round(total_unrealized, 2),
total_pnl: round(total_realized + total_unrealized, 2),
tokens,
trades_analyzed: total_trades,
confidence: confidence.into(),
}
}
fn effective_side_for_wallet(wallet: &str, trade: &TradeRow) -> String {
let taker = trade.taker.to_lowercase();
let maker = trade.maker.to_lowercase();
if taker == wallet {
trade.side.to_uppercase()
} else if maker == wallet {
match trade.side.to_uppercase().as_str() {
"BUY" => "SELL".into(),
"SELL" => "BUY".into(),
other => other.into(),
}
} else {
trade.side.to_uppercase()
}
}