# Obol Architecture
A comprehensive Rust portfolio tracker with real-time price monitoring, historical data collection, and interactive visualization.
## System Overview
```
┌─────────────────────────────────────────────────────────────┐
│ Obol Portfolio Tracker │
├─────────────────────────────────────────────────────────────┤
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ TUI Module │ │ Background │ │ CLI Commands │
│ (Interactive) │ │ Daemon │ │ (One-shot) │
└───────────────┘ └───────────────┘ └───────────────┘
│ │ │
└────────────────────┼────────────────────┘
│
▼
┌─────────────────┐
│ Portfolio Core │
│ (lib.rs) │
└─────────────────┘
│
┌────────────────────┼────────────────────┐
│ │ │
▼ ▼ ▼
┌───────────────┐ ┌───────────────┐ ┌───────────────┐
│ Configuration │ │ Price Engine │ │ Database │
│ & Parsing │ │ (Multi-API) │ │ (Time Series) │
└───────────────┘ └───────────────┘ └───────────────┘
```
## Core Architecture Components
### 1. Entry Points (`main.rs`)
- **CLI Router**: Dispatches commands (`nw`, `edit`, `start`, `stop`)
- **TUI Launcher**: Default action launches interactive interface
- **Configuration Management**: Auto-creates `~/.obol/portfolio.toml`
### 2. Library Core (`lib.rs`)
Central re-export hub exposing:
```rust
pub use config::*; // TOML configuration & parsing
pub use portfolio::*; // Portfolio builder & data structures
pub use price::*; // Multi-provider price engine
pub use database::*; // SQLite time series storage
pub use error::*; // Unified error handling
pub use validation::*; // Multi-tier validation system
```
### 3. Configuration System (`config.rs`)
```
Portfolio Config
├── currencies: HashMap<String, Decimal> // "USD" -> 1000.00
├── stocks: HashMap<String, i32> // "AAPL" -> 10
└── crypto: HashMap<String, Decimal> // "bitcoin" -> 0.5
// "0x.../ethereum" -> 100.0
```
**Key Features:**
- **TOML Format**: Human-readable configuration
- **Auto-validation**: Two-tier validation (basic + comprehensive)
- **Contract Support**: Ethereum addresses with chain specification
- **Backwards Compatibility**: Graceful upgrades
**Validation Tiers:**
```rust
validate_basic() // Fast, synchronous format checks
validate() // Full async validation with external APIs
```
### 4. Portfolio Engine (`portfolio.rs`)
**Builder Pattern Architecture:**
```rust
PortfolioBuilder::new(config)
.base_currency("USD")
.with_price_provider(provider)
.cache_first(true) // Skip API calls
.build() // Result<Portfolio, Error>
.build_with_fallback() // (Portfolio, Vec<String>)
```
**Asset Holdings:**
```rust
AssetHolding {
quantity: Decimal, // User's amount
price: Decimal, // Current market price
value: Decimal, // quantity * price in base currency
}
```
### 5. Price Engine (`price/`)
**Multi-Provider System:**
```
CombinedPriceProvider
├── CurrencyProvider (FawazCurrencyAPI) → 300 req/min
├── StockProvider (Yahoo Finance) → 100 req/min
└── CryptoProvider (CoinGecko) → 10 req/min
```
**Rate Limiting Architecture:**
```rust
RateLimiter::with_retry("CoinGecko", || async {
// API call with automatic exponential backoff
}).await
```
**Provider Configurations:**
- **CoinGecko**: 10 req/min, 6s delays, conservative backoff
- **Yahoo Finance**: 100 req/min, 600ms delays
- **FawazCurrency**: 300 req/min, 200ms delays
### 6. Database Layer (`database/`)
**SQLite Schema:**
```sql
assets price_data
├── id (INTEGER PK) ├── id (INTEGER PK)
├── symbol (TEXT) ├── asset_id (INTEGER FK)
├── asset_type (TEXT) ├── price (REAL)
├── contract_address ├── timestamp (INTEGER)
└── chain (TEXT) └── source (TEXT)
portfolio_snapshots
├── id (INTEGER PK)
├── timestamp (INTEGER)
├── total_value (REAL)
└── breakdown (JSON)
```
**Time Series Features:**
- **WAL Mode**: Concurrent daemon + TUI access
- **Asset Resolution**: Symbol → ID mapping with contract support
- **Historical Queries**: Price trends, portfolio snapshots
- **Automatic Schema**: Self-initializing on first run
### 7. Background Daemon (`daemon/`)
**Service Architecture:**
```
DaemonService
├── PriceScheduler → Fetches prices every 5 minutes
├── RateLimiter → Prevents API overload
└── Database → Stores historical data
```
**Process Management:**
- **PID Tracking**: `/tmp/obol-daemon.pid`
- **Signal Handling**: Graceful shutdown on SIGTERM
- **Error Recovery**: Continues on individual API failures
### 8. TUI Interface (`tui/`)
**Real-time Components:**
```
App State
├── Portfolio Data → Live calculations
├── Chart History → Database queries
├── Refresh Timer → Auto-updates
└── Error Display → User feedback
```
**Chart System:**
- **Ratatui Framework**: Terminal-based UI
- **Real-time Updates**: Automatic refresh with rate limiting
- **Historical Charts**: Price trends over time
- **Interactive Navigation**: Keyboard controls
### 9. Validation System (`validation.rs`)
**Multi-Validator Architecture:**
```rust
ValidationPipeline
├── CurrencyValidator → ISO codes, exchange rates
├── StockValidator → Symbol format, market existence
└── CryptoValidator → Ticker symbols, contract addresses
```
**Validation Strategy:**
- **Format Validation**: Immediate syntax checking
- **Market Validation**: External API verification
- **Contract Validation**: Ethereum address format + chain
## Trait System & Dependencies
### Core Traits
**Price Provider Trait:**
```rust
#[async_trait]
trait PriceProvider {
async fn get_currency_rate(&self, from: &str, to: &str) -> Result<Decimal>;
async fn get_stock_price(&self, symbol: &str) -> Result<Decimal>;
async fn get_crypto_price(&self, asset_key: &str) -> Result<Decimal>;
}
```
**Database Trait:**
```rust
trait TimeSeriesDB {
fn store_price_data(&self, data: &[PriceDataPoint]) -> Result<()>;
fn get_price_history(&self, symbol: &str, days: i32) -> Result<Vec<PricePoint>>;
fn get_latest_prices(&self) -> Result<HashMap<String, Decimal>>;
}
```
### Error Handling Strategy
**Unified Error Types:**
```rust
#[derive(thiserror::Error)]
enum Error {
#[error("Configuration error: {message}")]
Config { message: String },
#[error("Price fetch failed: {provider} - {message}")]
PriceFetch { provider: String, message: String },
#[error("Database error: {0}")]
Database(#[from] rusqlite::Error),
#[error("Validation failed: {field} - {message}")]
Validation { field: String, message: String },
}
```
**Context Chaining:**
```rust
.with_context(|| format!("Failed to fetch {} price", symbol))
.map_err(|e| Error::price_fetch("CoinGecko", e.to_string()))
```
### Key Dependencies
**Core Runtime:**
- `tokio` - Async runtime for concurrent price fetching
- `serde` + `toml` - Configuration serialization
- `thiserror` + `anyhow` - Structured error handling
**Data & Calculation:**
- `rust_decimal` - Precise financial calculations
- `rusqlite` - SQLite database interface
- `time` - Timestamp handling and formatting
**Networking & APIs:**
- `reqwest` - HTTP client for API calls
- `serde_json` - API response parsing
**User Interface:**
- `ratatui` - Terminal UI framework
- `crossterm` - Cross-platform terminal control
**Utilities:**
- `dirs` - Cross-platform directory discovery
- `tempfile` - Test file management
- `tracing` - Structured logging
## Data Flow Architecture
### Price Collection Flow
```
1. Daemon Scheduler (every 5 min)
↓
2. Rate Limiter → Acquire permits
↓
3. Price Providers → Fetch from APIs
↓
4. Database Storage → Store time series
↓
5. TUI Auto-refresh → Display updates
```
### Portfolio Building Flow
```
1. Config Loading → Parse TOML file
↓
2. Asset Parsing → currencies/stocks/crypto
↓
3. Price Fetching → Concurrent API calls
↓
4. Portfolio Assembly → Calculate holdings
↓
5. Display/Storage → TUI or database
```
### Validation Flow
```
1. Basic Validation → Format checks (sync)
↓
2. Full Validation → Market verification (async)
↓
3. Error Collection → User-friendly messages
↓
4. Graceful Degradation → Partial success handling
```
## Performance & Scalability
### Concurrent Design
- **Async-first**: All I/O operations use `tokio`
- **Parallel Price Fetching**: Multiple APIs simultaneously
- **Non-blocking UI**: TUI remains responsive during data loading
### Caching Strategy
- **Database Cache**: Historical prices stored locally
- **Cache-first Mode**: Skip API calls entirely when possible
- **Fallback Mechanism**: Use cache when APIs fail
### Resource Management
- **Connection Pooling**: Reuse HTTP connections
- **Memory Efficiency**: Stream large datasets
- **Rate Limit Compliance**: Prevent API bans
### Error Resilience
- **Graceful Degradation**: Partial data better than complete failure
- **Retry Logic**: Exponential backoff for transient failures
- **Timeout Protection**: All network operations have limits
## Security & Reliability
### Data Protection
- **No API Keys Required**: Uses public endpoints only
- **Local Storage**: All data remains on user's machine
- **Read-only APIs**: No sensitive operations
### Reliability Features
- **Two-tier Validation**: Catch errors early and comprehensively
- **Timeout Protection**: Prevent hanging operations
- **Graceful Error Handling**: Clear user feedback
- **Atomic Operations**: Database consistency
This architecture provides a robust, scalable foundation for portfolio tracking with clean separation of concerns, comprehensive error handling, and excellent user experience.