Files
foxhunt/crates/market-data/src/lib.rs
jgrusewski 9c3d741a08 refactor: restructure repo — crates/, bin/, testing/ layout
Move 17 library crates into crates/, CLI binary into bin/fxt,
consolidate 10 test crates into testing/, split config crate
from deployment config files.

Root directory reduced from 38+ to ~17 directories.
All Cargo.toml paths and build.rs proto refs updated.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-25 11:56:00 +01:00

263 lines
8.4 KiB
Rust

//! # Market Data Repository
//!
//! This crate provides production-ready market data repository implementations
//! for the Foxhunt HFT Trading System. It offers clean abstractions for storing
//! and retrieving price data, order book data, and technical indicators.
#![deny(clippy::unwrap_used, clippy::expect_used)]
pub mod error;
pub mod indicators;
pub mod models;
pub mod orderbook;
pub mod prices;
#[cfg(test)]
#[allow(clippy::unwrap_used, clippy::expect_used)]
mod compile_test;
/// Database schema creation helper functions
///
/// Provides utilities for creating and managing database schema
/// for market data storage including tables, indexes, and types.
pub mod schema {
use sqlx::{PgPool, Result};
/// Create all market data tables
///
/// Creates all required tables for market data storage including:
/// - prices: Real-time price data
///
/// - candles: OHLCV candle data
/// - order_book_levels: Order book depth data
///
/// - technical_indicators: Computed technical indicators
///
/// # Arguments
///
/// * `pool` - PostgreSQL connection pool
///
/// # Returns
///
/// `Ok(())` if all tables are created successfully
///
/// # Errors
///
/// Returns `sqlx::Error` if table creation fails
pub async fn create_tables(pool: &PgPool) -> Result<()> {
create_prices_table(pool).await?;
create_candles_table(pool).await?;
create_order_book_tables(pool).await?;
create_indicators_table(pool).await?;
create_indexes(pool).await?;
Ok(())
}
/// Create prices table for real-time price data
///
/// Creates a table to store tick-by-tick price data including
/// bid/ask spreads, last traded price, and basic OHLC data.
///
/// # Arguments
///
/// * `pool` - PostgreSQL connection pool
///
/// # Returns
///
/// `Ok(())` if table creation succeeds
async fn create_prices_table(pool: &PgPool) -> Result<()> {
sqlx::query(
r#"
CREATE TABLE IF NOT EXISTS prices (
id UUID PRIMARY KEY,
symbol VARCHAR(20) NOT NULL,
timestamp TIMESTAMPTZ NOT NULL,
bid DECIMAL(20,8),
ask DECIMAL(20,8),
last DECIMAL(20,8),
volume DECIMAL(20,8),
open DECIMAL(20,8),
high DECIMAL(20,8),
low DECIMAL(20,8),
close DECIMAL(20,8),
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(symbol, timestamp)
);
"#,
)
.execute(pool)
.await?;
Ok(())
}
/// Create candles table for OHLCV data
///
/// Creates a table to store aggregated candle data for different
/// time periods (1m, 5m, 1h, etc.) with OHLCV information.
///
/// # Arguments
///
/// * `pool` - PostgreSQL connection pool
///
/// # Returns
///
/// `Ok(())` if table creation succeeds
async fn create_candles_table(pool: &PgPool) -> Result<()> {
sqlx::query(
r#"
CREATE TABLE IF NOT EXISTS candles (
id UUID PRIMARY KEY,
symbol VARCHAR(20) NOT NULL,
period VARCHAR(20) NOT NULL,
timestamp TIMESTAMPTZ NOT NULL,
open DECIMAL(20,8) NOT NULL,
high DECIMAL(20,8) NOT NULL,
low DECIMAL(20,8) NOT NULL,
close DECIMAL(20,8) NOT NULL,
volume DECIMAL(20,8) NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(symbol, period, timestamp)
);
"#,
)
.execute(pool)
.await?;
Ok(())
}
/// Create order book related tables and types
///
/// Creates tables and custom types for storing order book depth data
/// including bid/ask levels with price and quantity information.
///
/// # Arguments
///
/// * `pool` - PostgreSQL connection pool
///
/// # Returns
///
/// `Ok(())` if all order book tables and types are created successfully
async fn create_order_book_tables(pool: &PgPool) -> Result<()> {
// Create order side enum
sqlx::query(
r#"
DO $$ BEGIN
CREATE TYPE order_side AS ENUM ('bid', 'ask');
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
"#,
)
.execute(pool)
.await?;
// Create order book levels table
sqlx::query(
r#"
CREATE TABLE IF NOT EXISTS order_book_levels (
id UUID PRIMARY KEY,
symbol VARCHAR(20) NOT NULL,
timestamp TIMESTAMPTZ NOT NULL,
side order_side NOT NULL,
price DECIMAL(20,8) NOT NULL,
quantity DECIMAL(20,8) NOT NULL,
level INTEGER NOT NULL,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(symbol, timestamp, side, level)
);
"#,
)
.execute(pool)
.await?;
Ok(())
}
/// Create technical indicators table and types
///
/// Creates tables and custom types for storing computed technical
/// indicators including SMA, EMA, RSI, MACD, Bollinger Bands, etc.
///
/// # Arguments
///
/// * `pool` - PostgreSQL connection pool
///
/// # Returns
///
/// `Ok(())` if indicator tables and types are created successfully
async fn create_indicators_table(pool: &PgPool) -> Result<()> {
// Create indicator type enum
sqlx::query(
r#"
DO $$ BEGIN
CREATE TYPE indicator_type AS ENUM (
'sma', 'ema', 'rsi', 'macd', 'bollinger_bands',
'stochastic', 'atr', 'volume_weighted_average_price'
);
EXCEPTION
WHEN duplicate_object THEN null;
END $$;
"#,
)
.execute(pool)
.await?;
// Create technical indicators table
sqlx::query(
r#"
CREATE TABLE IF NOT EXISTS technical_indicators (
id UUID PRIMARY KEY,
symbol VARCHAR(20) NOT NULL,
indicator_type indicator_type NOT NULL,
timestamp TIMESTAMPTZ NOT NULL,
value DECIMAL(20,8) NOT NULL,
parameters JSONB NOT NULL DEFAULT '{}',
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
UNIQUE(symbol, indicator_type, timestamp)
);
"#,
)
.execute(pool)
.await?;
Ok(())
}
/// Create performance indexes for market data tables
///
/// Creates database indexes optimized for time-series queries
/// including symbol-timestamp combinations and timestamp ordering.
///
/// # Arguments
///
/// * `pool` - PostgreSQL connection pool
///
/// # Returns
///
/// `Ok(())` if all indexes are created successfully
async fn create_indexes(pool: &PgPool) -> Result<()> {
// Price indexes
sqlx::query("CREATE INDEX IF NOT EXISTS idx_prices_symbol_timestamp ON prices(symbol, timestamp DESC);")
.execute(pool).await?;
sqlx::query("CREATE INDEX IF NOT EXISTS idx_prices_timestamp ON prices(timestamp DESC);")
.execute(pool)
.await?;
// Candle indexes
sqlx::query("CREATE INDEX IF NOT EXISTS idx_candles_symbol_period_timestamp ON candles(symbol, period, timestamp DESC);")
.execute(pool).await?;
// Order book indexes
sqlx::query("CREATE INDEX IF NOT EXISTS idx_order_book_symbol_timestamp ON order_book_levels(symbol, timestamp DESC);")
.execute(pool).await?;
sqlx::query("CREATE INDEX IF NOT EXISTS idx_order_book_symbol_side_timestamp ON order_book_levels(symbol, side, timestamp DESC);")
.execute(pool).await?;
// Indicator indexes
sqlx::query("CREATE INDEX IF NOT EXISTS idx_indicators_symbol_type_timestamp ON technical_indicators(symbol, indicator_type, timestamp DESC);")
.execute(pool).await?;
sqlx::query("CREATE INDEX IF NOT EXISTS idx_indicators_symbol_timestamp ON technical_indicators(symbol, timestamp DESC);")
.execute(pool).await?;
Ok(())
}
}