use serde::{Deserialize, Serialize};
use std::fmt;
use std::path::PathBuf;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum EntityType {
Wallet,
Market,
Token,
}
impl fmt::Display for EntityType {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Wallet => write!(f, "wallet"),
Self::Market => write!(f, "market"),
Self::Token => write!(f, "token"),
}
}
}
impl EntityType {
pub fn from_str(s: &str) -> Option<Self> {
match s {
"wallet" => Some(Self::Wallet),
"market" => Some(Self::Market),
"token" => Some(Self::Token),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum BackfillStatus {
Pending,
InProgress,
Complete,
Failed,
}
impl fmt::Display for BackfillStatus {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Pending => write!(f, "pending"),
Self::InProgress => write!(f, "in_progress"),
Self::Complete => write!(f, "complete"),
Self::Failed => write!(f, "failed"),
}
}
}
impl BackfillStatus {
pub fn from_str(s: &str) -> Option<Self> {
match s {
"pending" => Some(Self::Pending),
"in_progress" => Some(Self::InProgress),
"complete" => Some(Self::Complete),
"failed" => Some(Self::Failed),
_ => None,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum TradeSource {
Settlement,
TradeEvent,
Backfill,
}
impl fmt::Display for TradeSource {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Settlement => write!(f, "settlement"),
Self::TradeEvent => write!(f, "trade_event"),
Self::Backfill => write!(f, "backfill"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TradeRow {
pub tx_hash: String,
pub log_index: i64,
pub block_number: Option<i64>,
pub timestamp: f64,
pub maker: String,
pub taker: String,
pub token_id: String,
pub condition_id: String,
pub market_title: String,
pub market_slug: String,
pub outcome: String,
pub side: String,
pub price: f64,
pub size: f64,
pub maker_amount: String,
pub taker_amount: String,
pub fee: Option<f64>,
pub source: String,
pub raw_json: Option<String>,
pub cached_at: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct SettlementRow {
pub tx_hash: String,
pub status: String,
pub detected_at: f64,
pub block_number: Option<i64>,
pub taker_wallet: String,
pub taker_token: String,
pub taker_side: String,
pub taker_price: f64,
pub taker_size: f64,
pub condition_id: String,
pub market_title: String,
pub market_slug: String,
pub outcome: String,
pub trade_count: i64,
pub raw_json: String,
pub cached_at: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BackfillStateRow {
pub entity_type: String,
pub entity_id: String,
pub label: String,
pub status: String,
pub last_offset: i64,
pub fetched: i64,
pub last_error: Option<String>,
pub started_at: f64,
pub updated_at: f64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WatchlistSnapshotRow {
pub entity_type: String,
pub entity_id: String,
pub label: String,
pub backfill: bool,
pub added_at: f64,
}
#[derive(Clone)]
pub struct CacheConfig {
pub db_path: PathBuf,
pub watchlist_path: PathBuf,
pub ttl_seconds: u64,
pub backfill_rate_per_second: f64,
pub backfill_pages: u32,
pub backfill_page_size: u32,
pub purge_on_remove: bool,
pub on_backfill_progress: Option<std::sync::Arc<dyn Fn(BackfillProgress) + Send + Sync>>,
}
impl Default for CacheConfig {
fn default() -> Self {
Self {
db_path: PathBuf::from("./polynode-cache.db"),
watchlist_path: PathBuf::from("./polynode.watch.json"),
ttl_seconds: 30 * 86400,
backfill_rate_per_second: 1.0,
backfill_pages: 1,
backfill_page_size: 500,
purge_on_remove: false,
on_backfill_progress: None,
}
}
}
#[derive(Debug, Clone, Default)]
pub struct QueryOptions {
pub limit: Option<u32>,
pub offset: Option<u32>,
pub since: Option<f64>,
pub until: Option<f64>,
pub side: Option<String>,
pub order_by: Option<OrderBy>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum OrderBy {
TimestampAsc,
TimestampDesc,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PositionSummary {
pub wallet: String,
pub token_id: String,
pub condition_id: String,
pub market_title: String,
pub market_slug: String,
pub outcome: String,
pub size: f64,
pub avg_price: f64,
pub cur_price: Option<f64>,
pub current_value: Option<f64>,
pub initial_value: Option<f64>,
pub cash_pnl: Option<f64>,
pub percent_pnl: Option<f64>,
pub realized_pnl: Option<f64>,
pub total_bought: Option<f64>,
pub redeemable: bool,
pub end_date: Option<String>,
pub trade_count: Option<i64>,
pub first_trade_at: Option<f64>,
pub last_trade_at: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct TokenPnl {
pub token_id: String,
pub condition_id: String,
pub market_title: String,
pub outcome: String,
pub realized_pnl: f64,
pub unrealized_pnl: f64,
pub remaining_size: f64,
pub avg_cost: f64,
pub cur_price: Option<f64>,
pub trades_analyzed: usize,
pub buys: usize,
pub sells: usize,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct RealizedPnlResult {
pub wallet: String,
pub total_realized_pnl: f64,
pub total_unrealized_pnl: f64,
pub total_pnl: f64,
pub tokens: Vec<TokenPnl>,
pub trades_analyzed: usize,
pub confidence: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct BackfillProgress {
pub entity_type: String,
pub entity_id: String,
pub label: String,
pub status: String,
pub fetched: i64,
pub offset: i64,
pub message: Option<String>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct CacheStats {
pub trade_count: i64,
pub settlement_count: i64,
pub db_size_bytes: u64,
pub oldest_trade_at: Option<f64>,
pub newest_trade_at: Option<f64>,
pub backfill_entities: i64,
pub backfill_complete: i64,
pub backfill_pending: i64,
pub backfill_in_progress: i64,
pub backfill_failed: i64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WatchlistFile {
pub version: u32,
#[serde(default)]
pub wallets: Vec<WatchlistWallet>,
#[serde(default)]
pub markets: Vec<WatchlistMarket>,
#[serde(default)]
pub tokens: Vec<WatchlistToken>,
#[serde(default)]
pub settings: Option<WatchlistSettings>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WatchlistWallet {
pub address: String,
#[serde(default)]
pub label: String,
#[serde(default = "default_true")]
pub backfill: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WatchlistMarket {
pub condition_id: String,
#[serde(default)]
pub label: String,
#[serde(default = "default_true")]
pub backfill: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WatchlistToken {
pub token_id: String,
#[serde(default)]
pub label: String,
#[serde(default = "default_true")]
pub backfill: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WatchlistSettings {
pub ttl_days: Option<u32>,
pub backfill_rate: Option<f64>,
pub purge_on_remove: Option<bool>,
}
fn default_true() -> bool {
true
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct StorageEstimate {
pub estimated_trades: u64,
pub estimated_size_mb: u64,
pub ttl_days: u32,
pub wallet_count: usize,
pub market_count: usize,
pub token_count: usize,
}
pub fn normalize_timestamp(value: &serde_json::Value) -> f64 {
match value {
serde_json::Value::Number(n) => {
let v = n.as_f64().unwrap_or(0.0);
if v > 1e12 { v / 1000.0 } else { v }
}
serde_json::Value::String(s) => {
if let Ok(n) = s.parse::<f64>() {
if n > 1e12 { n / 1000.0 } else { n }
} else {
now_secs()
}
}
_ => now_secs(),
}
}
pub fn normalize_f64(v: f64) -> f64 {
if v > 1e12 { v / 1000.0 } else { v }
}
pub fn now_secs() -> f64 {
std::time::SystemTime::now()
.duration_since(std::time::UNIX_EPOCH)
.unwrap_or_default()
.as_secs_f64()
}