9.2 KiB

Module: Metrics Calculation System

Purpose

The metrics calculation system provides high-performance computation of Order Book Imbalance (OBI) and Cumulative Volume Delta (CVD) indicators for cryptocurrency trading analysis. It processes orderbook snapshots and trade data to generate financial metrics with per-snapshot granularity.

Public Interface

Classes

Metric (dataclass)

Represents calculated metrics for a single orderbook snapshot.

@dataclass(slots=True)
class Metric:
    snapshot_id: int        # Reference to source snapshot
    timestamp: int          # Unix timestamp
    obi: float             # Order Book Imbalance [-1, 1]
    cvd: float             # Cumulative Volume Delta
    best_bid: float | None # Best bid price
    best_ask: float | None # Best ask price

MetricCalculator (static class)

Provides calculation methods for financial metrics.

class MetricCalculator:
    @staticmethod
    def calculate_obi(snapshot: BookSnapshot) -> float
    
    @staticmethod
    def calculate_volume_delta(trades: List[Trade]) -> float
    
    @staticmethod  
    def calculate_cvd(previous_cvd: float, volume_delta: float) -> float
    
    @staticmethod
    def get_best_bid_ask(snapshot: BookSnapshot) -> tuple[float | None, float | None]

Functions

Order Book Imbalance (OBI) Calculation

def calculate_obi(snapshot: BookSnapshot) -> float:
    """
    Calculate Order Book Imbalance using the standard formula.
    
    Formula: OBI = (Vb - Va) / (Vb + Va)
    Where:
        Vb = Total volume on bid side
        Va = Total volume on ask side
    
    Args:
        snapshot: BookSnapshot containing bids and asks data
        
    Returns:
        float: OBI value between -1 and 1, or 0.0 if no volume
        
    Example:
        >>> snapshot = BookSnapshot(bids={50000.0: OrderbookLevel(...)}, ...)
        >>> obi = MetricCalculator.calculate_obi(snapshot)
        >>> print(f"OBI: {obi:.3f}")
        OBI: 0.333
    """

Volume Delta Calculation

def calculate_volume_delta(trades: List[Trade]) -> float:
    """
    Calculate Volume Delta for a list of trades.
    
    Volume Delta = Buy Volume - Sell Volume
    - Buy trades (side = "buy"): positive contribution
    - Sell trades (side = "sell"): negative contribution
    
    Args:
        trades: List of Trade objects for specific timestamp
        
    Returns:
        float: Net volume delta (positive = buy pressure, negative = sell pressure)
        
    Example:
        >>> trades = [
        ...     Trade(side="buy", size=10.0, ...),
        ...     Trade(side="sell", size=3.0, ...)
        ... ]
        >>> vd = MetricCalculator.calculate_volume_delta(trades)
        >>> print(f"Volume Delta: {vd}")
        Volume Delta: 7.0
    """

Cumulative Volume Delta (CVD) Calculation

def calculate_cvd(previous_cvd: float, volume_delta: float) -> float:
    """
    Calculate Cumulative Volume Delta with incremental support.
    
    Formula: CVD_t = CVD_{t-1} + Volume_Delta_t
    
    Args:
        previous_cvd: Previous CVD value (use 0.0 for reset)
        volume_delta: Current volume delta to add
        
    Returns:
        float: New cumulative volume delta value
        
    Example:
        >>> cvd = 0.0  # Starting value
        >>> cvd = MetricCalculator.calculate_cvd(cvd, 10.0)  # First trade
        >>> cvd = MetricCalculator.calculate_cvd(cvd, -5.0)  # Second trade
        >>> print(f"CVD: {cvd}")
        CVD: 5.0
    """

Usage Examples

Basic OBI Calculation

from models import MetricCalculator, BookSnapshot, OrderbookLevel

# Create sample orderbook snapshot
snapshot = BookSnapshot(
    id=1,
    timestamp=1640995200,
    bids={
        50000.0: OrderbookLevel(price=50000.0, size=10.0, liquidation_count=0, order_count=1),
        49999.0: OrderbookLevel(price=49999.0, size=5.0, liquidation_count=0, order_count=1),
    },
    asks={
        50001.0: OrderbookLevel(price=50001.0, size=3.0, liquidation_count=0, order_count=1),
        50002.0: OrderbookLevel(price=50002.0, size=2.0, liquidation_count=0, order_count=1),
    }
)

# Calculate OBI
obi = MetricCalculator.calculate_obi(snapshot)
print(f"OBI: {obi:.3f}")  # Output: OBI: 0.500
# Explanation: (15 - 5) / (15 + 5) = 10/20 = 0.5

CVD Calculation with Reset

from models import MetricCalculator, Trade

# Simulate trading session
cvd = 0.0  # Reset CVD at session start

# Process trades for first timestamp
trades_t1 = [
    Trade(id=1, trade_id=1.0, price=50000.0, size=8.0, side="buy", timestamp=1000),
    Trade(id=2, trade_id=2.0, price=50001.0, size=3.0, side="sell", timestamp=1000),
]

vd_t1 = MetricCalculator.calculate_volume_delta(trades_t1)  # 8.0 - 3.0 = 5.0
cvd = MetricCalculator.calculate_cvd(cvd, vd_t1)           # 0.0 + 5.0 = 5.0

# Process trades for second timestamp
trades_t2 = [
    Trade(id=3, trade_id=3.0, price=49999.0, size=2.0, side="buy", timestamp=1001),
    Trade(id=4, trade_id=4.0, price=50000.0, size=7.0, side="sell", timestamp=1001),
]

vd_t2 = MetricCalculator.calculate_volume_delta(trades_t2)  # 2.0 - 7.0 = -5.0
cvd = MetricCalculator.calculate_cvd(cvd, vd_t2)           # 5.0 + (-5.0) = 0.0

print(f"Final CVD: {cvd}")  # Output: Final CVD: 0.0

Complete Metrics Processing

from models import MetricCalculator, Metric

def process_snapshot_metrics(snapshot, trades, previous_cvd=0.0):
    """Process complete metrics for a single snapshot."""
    
    # Calculate OBI
    obi = MetricCalculator.calculate_obi(snapshot)
    
    # Calculate volume delta and CVD
    volume_delta = MetricCalculator.calculate_volume_delta(trades)
    cvd = MetricCalculator.calculate_cvd(previous_cvd, volume_delta)
    
    # Extract best bid/ask
    best_bid, best_ask = MetricCalculator.get_best_bid_ask(snapshot)
    
    # Create metric record
    metric = Metric(
        snapshot_id=snapshot.id,
        timestamp=snapshot.timestamp,
        obi=obi,
        cvd=cvd,
        best_bid=best_bid,
        best_ask=best_ask
    )
    
    return metric, cvd

# Usage in processing loop
current_cvd = 0.0
for snapshot, trades in snapshot_trade_pairs:
    metric, current_cvd = process_snapshot_metrics(snapshot, trades, current_cvd)
    # Store metric to database...

Dependencies

Internal

  • models.BookSnapshot: Orderbook state data
  • models.Trade: Individual trade execution data
  • models.OrderbookLevel: Price level information

External

  • Python Standard Library: typing for type hints
  • No external packages required

Performance Characteristics

Computational Complexity

  • OBI Calculation: O(n) where n = number of price levels
  • Volume Delta: O(m) where m = number of trades
  • CVD Calculation: O(1) - simple addition
  • Best Bid/Ask: O(n) for min/max operations

Memory Usage

  • Static Methods: No instance state, minimal memory overhead
  • Calculations: Process data in-place without copying
  • Results: Lightweight Metric objects with slots optimization

Typical Performance

# Benchmark results (approximate)
Snapshot with 50 price levels:     ~0.1ms per OBI calculation
Timestamp with 20 trades:          ~0.05ms per volume delta
CVD update:                        ~0.001ms per calculation
Complete metric processing:        ~0.2ms per snapshot

Error Handling

Edge Cases Handled

# Empty orderbook
empty_snapshot = BookSnapshot(bids={}, asks={})
obi = MetricCalculator.calculate_obi(empty_snapshot)  # Returns 0.0

# No trades
empty_trades = []
vd = MetricCalculator.calculate_volume_delta(empty_trades)  # Returns 0.0

# Zero volume scenario
zero_vol_snapshot = BookSnapshot(
    bids={50000.0: OrderbookLevel(price=50000.0, size=0.0, ...)},
    asks={50001.0: OrderbookLevel(price=50001.0, size=0.0, ...)}
)
obi = MetricCalculator.calculate_obi(zero_vol_snapshot)  # Returns 0.0

Validation

  • OBI Range: Results automatically bounded to [-1, 1]
  • Division by Zero: Handled gracefully with 0.0 return
  • Invalid Data: Empty collections handled without errors

Testing

Test Coverage

  • Unit Tests: tests/test_metric_calculator.py
  • Integration Tests: Included in storage and strategy tests
  • Edge Cases: Empty data, zero volume, boundary conditions

Running Tests

# Run metric calculator tests specifically
uv run pytest tests/test_metric_calculator.py -v

# Run all tests with metrics
uv run pytest -k "metric" -v

# Performance tests
uv run pytest tests/test_metric_calculator.py::test_calculate_obi_performance

Known Issues

Current Limitations

  • Precision: Floating-point arithmetic limitations for very small numbers
  • Scale: No optimization for extremely large orderbooks (>10k levels)
  • Currency: No multi-currency support (assumes single denomination)

Planned Enhancements

  • Decimal Precision: Consider decimal.Decimal for high-precision calculations
  • Vectorization: NumPy integration for batch calculations
  • Additional Metrics: Volume Profile, Liquidity metrics, Delta Flow

The metrics calculation system provides a robust foundation for financial analysis with clean interfaces, comprehensive error handling, and optimal performance for high-frequency trading data.