- Fixed systematic array indexing corruption: [0_i32] → [0] - Fixed numeric literal suffixes across 835 files - Fixed iterator patterns on RwLockReadGuard (.iter() required) - Fixed float type annotations (365.25_f64 for sqrt) - Fixed missing semicolons in position manager - Fixed reference dereferencing in data loader Root cause: Mass refactoring incorrectly added _i32 suffixes to array indices Impact: Complete compilation failure (463 errors) Resolution: Automated regex + targeted fixes Result: 100% compilation success (0 errors) Validated: cargo check --workspace passes Ready for: Production deployment
495 lines
14 KiB
Rust
495 lines
14 KiB
Rust
use chrono::{DateTime, Utc};
|
|
use rust_decimal::Decimal;
|
|
use serde::{Deserialize, Serialize};
|
|
use sqlx::FromRow;
|
|
use uuid::Uuid;
|
|
|
|
/// Price data for a financial instrument
|
|
///
|
|
/// Represents tick-level price data including bid/ask spreads,
|
|
/// last traded price, and basic OHLCV information.
|
|
#[derive(Debug, Clone, Serialize, Deserialize, FromRow, PartialEq)]
|
|
pub struct PriceRecord {
|
|
/// Unique identifier for this price record
|
|
pub id: Uuid,
|
|
/// Trading symbol (e.g., "AAPL", "BTC/USD")
|
|
pub symbol: String,
|
|
/// Timestamp when this price was recorded
|
|
pub timestamp: DateTime<Utc>,
|
|
/// Best bid price
|
|
pub bid: Option<Decimal>,
|
|
/// Best ask price
|
|
pub ask: Option<Decimal>,
|
|
/// Last traded price
|
|
pub last: Option<Decimal>,
|
|
/// Trading volume
|
|
pub volume: Option<Decimal>,
|
|
/// Opening price for the period
|
|
pub open: Option<Decimal>,
|
|
/// Highest price for the period
|
|
pub high: Option<Decimal>,
|
|
/// Lowest price for the period
|
|
pub low: Option<Decimal>,
|
|
/// Closing price for the period
|
|
pub close: Option<Decimal>,
|
|
/// Timestamp when this record was created in the database
|
|
pub created_at: DateTime<Utc>,
|
|
}
|
|
|
|
impl PriceRecord {
|
|
/// Create a new price record with the given symbol and timestamp
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `symbol` - Trading symbol for the instrument
|
|
/// * `timestamp` - When this price was recorded
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A new `PriceRecord` with all price fields set to `None`
|
|
pub fn new(symbol: String, timestamp: DateTime<Utc>) -> Self {
|
|
Self {
|
|
id: Uuid::new_v4(),
|
|
symbol,
|
|
timestamp,
|
|
bid: None,
|
|
ask: None,
|
|
last: None,
|
|
volume: None,
|
|
open: None,
|
|
high: None,
|
|
low: None,
|
|
close: None,
|
|
created_at: Utc::now(),
|
|
}
|
|
}
|
|
|
|
/// Calculate mid price from bid and ask
|
|
///
|
|
/// Returns the average of bid and ask prices if both are available.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// `Some(mid_price)` if both bid and ask are available, `None` otherwise
|
|
pub fn mid_price(&self) -> Option<Decimal> {
|
|
match (self.bid, self.ask) {
|
|
(Some(bid), Some(ask)) => Some((bid + ask) / Decimal::from(2)),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Calculate spread from bid and ask
|
|
///
|
|
/// Returns the difference between ask and bid prices.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// `Some(spread)` if both bid and ask are available, `None` otherwise
|
|
pub fn spread(&self) -> Option<Decimal> {
|
|
match (self.bid, self.ask) {
|
|
(Some(bid), Some(ask)) => Some(ask - bid),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Order book side enumeration
|
|
///
|
|
/// Represents which side of the order book a level belongs to.
|
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, sqlx::Type, PartialEq, Eq, Hash)]
|
|
#[sqlx(type_name = "order_side", rename_all = "lowercase")]
|
|
pub enum BookSide {
|
|
/// Bid side (buy orders)
|
|
#[sqlx(rename = "bid")]
|
|
Bid,
|
|
/// Ask side (sell orders)
|
|
#[sqlx(rename = "ask")]
|
|
Ask,
|
|
}
|
|
|
|
/// Order book level data for database persistence
|
|
///
|
|
/// Represents a single level in the order book depth at a specific price point.
|
|
#[derive(Debug, Clone, Serialize, Deserialize, FromRow, PartialEq)]
|
|
pub struct OrderBookLevelDb {
|
|
/// Unique identifier for this order book level
|
|
pub id: Uuid,
|
|
/// Trading symbol
|
|
pub symbol: String,
|
|
/// Timestamp when this level was recorded
|
|
pub timestamp: DateTime<Utc>,
|
|
/// Which side of the book (bid or ask)
|
|
pub side: BookSide,
|
|
/// Price level
|
|
pub price: Decimal,
|
|
/// Total quantity available at this price level
|
|
pub quantity: Decimal,
|
|
/// Level depth (0 = best, 1 = second best, etc.)
|
|
pub level: i32,
|
|
/// Timestamp when this record was created in the database
|
|
pub created_at: DateTime<Utc>,
|
|
}
|
|
|
|
impl OrderBookLevelDb {
|
|
/// Create a new order book level
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `symbol` - Trading symbol
|
|
/// * `timestamp` - When this level was recorded
|
|
///
|
|
/// * `side` - Which side of the book (bid or ask)
|
|
/// * `price` - Price level
|
|
///
|
|
/// * `quantity` - Quantity available at this price
|
|
/// * `level` - Depth level (0 = best)
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A new `OrderBookLevelDb` instance
|
|
pub fn new(
|
|
symbol: String,
|
|
timestamp: DateTime<Utc>,
|
|
side: BookSide,
|
|
price: Decimal,
|
|
quantity: Decimal,
|
|
level: i32,
|
|
) -> Self {
|
|
Self {
|
|
id: Uuid::new_v4(),
|
|
symbol,
|
|
timestamp,
|
|
side,
|
|
price,
|
|
quantity,
|
|
level,
|
|
created_at: Utc::now(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Complete order book snapshot
|
|
///
|
|
/// Represents a full order book with all bid and ask levels at a specific point in time.
|
|
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
|
|
pub struct OrderBook {
|
|
/// Trading symbol
|
|
pub symbol: String,
|
|
/// Timestamp of this order book snapshot
|
|
pub timestamp: DateTime<Utc>,
|
|
/// All bid levels (buy orders) sorted by price descending
|
|
pub bids: Vec<OrderBookLevelDb>,
|
|
/// All ask levels (sell orders) sorted by price ascending
|
|
pub asks: Vec<OrderBookLevelDb>,
|
|
}
|
|
|
|
impl OrderBook {
|
|
pub fn new(symbol: String, timestamp: DateTime<Utc>) -> Self {
|
|
Self {
|
|
symbol,
|
|
timestamp,
|
|
bids: Vec::new(),
|
|
asks: Vec::new(),
|
|
}
|
|
}
|
|
|
|
/// Get the best bid price
|
|
///
|
|
/// Returns the highest bid price (best buy price) from the order book.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// `Some(price)` if there are any bids, `None` if the bid side is empty
|
|
pub fn best_bid(&self) -> Option<Decimal> {
|
|
self.bids.first().map(|level| level.price)
|
|
}
|
|
|
|
/// Get the best ask price
|
|
///
|
|
/// Returns the lowest ask price (best sell price) from the order book.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// `Some(price)` if there are any asks, `None` if the ask side is empty
|
|
pub fn best_ask(&self) -> Option<Decimal> {
|
|
self.asks.first().map(|level| level.price)
|
|
}
|
|
|
|
/// Calculate mid price from best bid and ask
|
|
///
|
|
/// Returns the average of the best bid and best ask prices.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// `Some(mid_price)` if both best bid and ask are available, `None` otherwise
|
|
pub fn mid_price(&self) -> Option<Decimal> {
|
|
match (self.best_bid(), self.best_ask()) {
|
|
(Some(bid), Some(ask)) => Some((bid + ask) / Decimal::from(2)),
|
|
_ => None,
|
|
}
|
|
}
|
|
|
|
/// Calculate spread between best bid and ask
|
|
///
|
|
/// Returns the difference between the best ask and best bid prices.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// `Some(spread)` if both best bid and ask are available, `None` otherwise
|
|
pub fn spread(&self) -> Option<Decimal> {
|
|
match (self.best_bid(), self.best_ask()) {
|
|
(Some(bid), Some(ask)) => Some(ask - bid),
|
|
_ => None,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Technical indicator types
|
|
///
|
|
/// Enumeration of supported technical analysis indicators.
|
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, sqlx::Type, PartialEq, Eq, Hash)]
|
|
#[sqlx(type_name = "indicator_type", rename_all = "lowercase")]
|
|
pub enum IndicatorType {
|
|
/// Simple Moving Average
|
|
#[sqlx(rename = "sma")]
|
|
Sma,
|
|
/// Exponential Moving Average
|
|
#[sqlx(rename = "ema")]
|
|
Ema,
|
|
/// Relative Strength Index
|
|
#[sqlx(rename = "rsi")]
|
|
Rsi,
|
|
/// Moving Average Convergence Divergence
|
|
#[sqlx(rename = "macd")]
|
|
Macd,
|
|
/// Bollinger Bands
|
|
#[sqlx(rename = "bollinger_bands")]
|
|
BollingerBands,
|
|
/// Stochastic Oscillator
|
|
#[sqlx(rename = "stochastic")]
|
|
Stochastic,
|
|
/// Average True Range
|
|
#[sqlx(rename = "atr")]
|
|
Atr,
|
|
/// Volume Weighted Average Price
|
|
#[sqlx(rename = "volume_weighted_average_price")]
|
|
VolumeWeightedAveragePrice,
|
|
}
|
|
|
|
/// Technical indicator data
|
|
///
|
|
/// Represents a computed technical indicator value at a specific point in time.
|
|
#[derive(Debug, Clone, Serialize, Deserialize, FromRow, PartialEq)]
|
|
pub struct TechnicalIndicator {
|
|
/// Unique identifier for this indicator record
|
|
pub id: Uuid,
|
|
/// Trading symbol this indicator applies to
|
|
pub symbol: String,
|
|
/// Type of technical indicator
|
|
pub indicator_type: IndicatorType,
|
|
/// Timestamp when this indicator value was computed
|
|
pub timestamp: DateTime<Utc>,
|
|
/// The computed indicator value
|
|
pub value: Decimal,
|
|
/// Parameters used for calculation (e.g., period, smoothing factor)
|
|
pub parameters: serde_json::Value,
|
|
/// Timestamp when this record was created in the database
|
|
pub created_at: DateTime<Utc>,
|
|
}
|
|
|
|
impl TechnicalIndicator {
|
|
/// Create a new technical indicator record
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `symbol` - Trading symbol
|
|
/// * `indicator_type` - Type of indicator
|
|
///
|
|
/// * `timestamp` - When this indicator was computed
|
|
/// * `value` - The computed indicator value
|
|
///
|
|
/// * `parameters` - Parameters used for calculation
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A new `TechnicalIndicator` instance
|
|
pub fn new(
|
|
symbol: String,
|
|
indicator_type: IndicatorType,
|
|
timestamp: DateTime<Utc>,
|
|
value: Decimal,
|
|
parameters: serde_json::Value,
|
|
) -> Self {
|
|
Self {
|
|
id: Uuid::new_v4(),
|
|
symbol,
|
|
indicator_type,
|
|
timestamp,
|
|
value,
|
|
parameters,
|
|
created_at: Utc::now(),
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Time series aggregation periods
|
|
///
|
|
/// Standard time periods used for aggregating market data into candles.
|
|
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
|
|
pub enum TimePeriod {
|
|
/// 1 second interval
|
|
Second,
|
|
/// 1 minute interval
|
|
Minute,
|
|
/// 5 minute interval
|
|
FiveMinutes,
|
|
/// 15 minute interval
|
|
FifteenMinutes,
|
|
/// 30 minute interval
|
|
ThirtyMinutes,
|
|
/// 1 hour interval
|
|
Hour,
|
|
/// 4 hour interval
|
|
FourHours,
|
|
/// 1 day interval
|
|
Daily,
|
|
/// 1 week interval
|
|
Weekly,
|
|
/// 1 month interval
|
|
Monthly,
|
|
}
|
|
|
|
impl TimePeriod {
|
|
/// Get the duration in seconds for this time period
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// The number of seconds in this time period
|
|
pub fn duration_seconds(&self) -> i64 {
|
|
match self {
|
|
TimePeriod::Second => 1_i64,
|
|
TimePeriod::Minute => 60_i64,
|
|
TimePeriod::FiveMinutes => 300_i64,
|
|
TimePeriod::FifteenMinutes => 900_i64,
|
|
TimePeriod::ThirtyMinutes => 1800_i64,
|
|
TimePeriod::Hour => 3600_i64,
|
|
TimePeriod::FourHours => 14400_i64,
|
|
TimePeriod::Daily => 86400_i64,
|
|
TimePeriod::Weekly => 604800_i64,
|
|
TimePeriod::Monthly => 2592000_i64, // 30 days
|
|
}
|
|
}
|
|
}
|
|
|
|
/// OHLCV (Open, High, Low, Close, Volume) candle data
|
|
///
|
|
/// Represents aggregated price data for a specific time period.
|
|
#[derive(Debug, Clone, Serialize, Deserialize, FromRow, PartialEq)]
|
|
pub struct Candle {
|
|
/// Unique identifier for this candle
|
|
pub id: Uuid,
|
|
/// Trading symbol
|
|
pub symbol: String,
|
|
/// Time period as string (for database compatibility)
|
|
pub period: String,
|
|
/// Timestamp for the start of this candle period
|
|
pub timestamp: DateTime<Utc>,
|
|
/// Opening price for the period
|
|
pub open: Decimal,
|
|
/// Highest price during the period
|
|
pub high: Decimal,
|
|
/// Lowest price during the period
|
|
pub low: Decimal,
|
|
/// Closing price for the period
|
|
pub close: Decimal,
|
|
/// Total volume traded during the period
|
|
pub volume: Decimal,
|
|
/// Timestamp when this record was created in the database
|
|
pub created_at: DateTime<Utc>,
|
|
}
|
|
|
|
impl Candle {
|
|
/// Create a new candle
|
|
///
|
|
/// # Arguments
|
|
///
|
|
/// * `symbol` - Trading symbol
|
|
/// * `period` - Time period for this candle
|
|
///
|
|
/// * `timestamp` - Start time for this candle period
|
|
/// * `open` - Opening price
|
|
///
|
|
/// * `high` - Highest price
|
|
/// * `low` - Lowest price
|
|
///
|
|
/// * `close` - Closing price
|
|
/// * `volume` - Total volume
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// A new `Candle` instance
|
|
pub fn new(
|
|
symbol: String,
|
|
period: TimePeriod,
|
|
timestamp: DateTime<Utc>,
|
|
open: Decimal,
|
|
high: Decimal,
|
|
low: Decimal,
|
|
close: Decimal,
|
|
volume: Decimal,
|
|
) -> Self {
|
|
Self {
|
|
id: Uuid::new_v4(),
|
|
symbol,
|
|
period: format!("{:?}", period),
|
|
timestamp,
|
|
open,
|
|
high,
|
|
low,
|
|
close,
|
|
volume,
|
|
created_at: Utc::now(),
|
|
}
|
|
}
|
|
|
|
/// Calculate the range (high - low)
|
|
///
|
|
/// Returns the difference between the highest and lowest prices.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// The price range for this candle
|
|
pub fn range(&self) -> Decimal {
|
|
self.high - self.low
|
|
}
|
|
|
|
/// Calculate the body (|close - open|)
|
|
///
|
|
/// Returns the absolute difference between closing and opening prices.
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// The body size of this candle
|
|
pub fn body(&self) -> Decimal {
|
|
(self.close - self.open).abs()
|
|
}
|
|
|
|
/// Check if candle is bullish (close > open)
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// `true` if closing price is higher than opening price
|
|
pub fn is_bullish(&self) -> bool {
|
|
self.close > self.open
|
|
}
|
|
|
|
/// Check if candle is bearish (close < open)
|
|
///
|
|
/// # Returns
|
|
///
|
|
/// `true` if closing price is lower than opening price
|
|
pub fn is_bearish(&self) -> bool {
|
|
self.close < self.open
|
|
}
|
|
}
|