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>
164 lines
5.0 KiB
Rust
164 lines
5.0 KiB
Rust
//! Risk Data Repository
|
|
//!
|
|
//! Production-ready risk management data layer for high-frequency trading systems.
|
|
//! Provides repositories for `VaR` calculations, compliance logging, position limits,
|
|
//! and risk data models with `PostgreSQL` and Redis integration.
|
|
|
|
#![allow(missing_docs)] // Internal implementation details
|
|
// Allow pedantic lints for repository implementation
|
|
#![allow(clippy::missing_docs_in_private_items)]
|
|
#![allow(clippy::clone_on_ref_ptr)]
|
|
#![allow(clippy::str_to_string)]
|
|
#![deny(clippy::unwrap_used, clippy::expect_used)]
|
|
|
|
use std::sync::Arc;
|
|
|
|
pub mod compliance;
|
|
pub mod limits;
|
|
pub mod models;
|
|
pub mod var;
|
|
|
|
// Import the repository traits and implementations
|
|
use crate::compliance::{ComplianceRepository, ComplianceRepositoryImpl};
|
|
use crate::limits::{LimitsRepository, LimitsRepositoryImpl};
|
|
use crate::var::{VarRepository, VarRepositoryImpl};
|
|
|
|
/// Configuration for the risk data repository
|
|
#[derive(Debug, Clone)]
|
|
pub struct RiskDataConfig {
|
|
/// `PostgreSQL` database URL
|
|
pub database_url: String,
|
|
/// Redis connection URL
|
|
pub redis_url: String,
|
|
/// Maximum database connections
|
|
pub max_connections: u32,
|
|
/// Connection timeout in seconds
|
|
pub connection_timeout_secs: u64,
|
|
}
|
|
|
|
impl Default for RiskDataConfig {
|
|
fn default() -> Self {
|
|
Self {
|
|
database_url: "postgresql://localhost/foxhunt".to_owned(),
|
|
redis_url: "redis://localhost:6379".to_owned(),
|
|
max_connections: 10,
|
|
connection_timeout_secs: 30,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// Main risk data repository facade providing access to all risk data operations
|
|
#[derive(Debug, Clone)]
|
|
pub struct RiskDataRepository {
|
|
var_repo: Arc<dyn VarRepository>,
|
|
compliance_repo: Arc<dyn ComplianceRepository>,
|
|
limits_repo: Arc<dyn LimitsRepository>,
|
|
}
|
|
|
|
impl RiskDataRepository {
|
|
/// Create a new risk data repository with the given configuration
|
|
///
|
|
/// # Errors
|
|
/// Returns error if the operation fails
|
|
pub async fn new(config: RiskDataConfig) -> Result<Self, RiskDataError> {
|
|
let pool = sqlx::postgres::PgPoolOptions::new()
|
|
.max_connections(config.max_connections)
|
|
.acquire_timeout(std::time::Duration::from_secs(
|
|
config.connection_timeout_secs,
|
|
))
|
|
.connect(&config.database_url)
|
|
.await?;
|
|
|
|
let redis_client = redis::Client::open(config.redis_url)?;
|
|
let redis_conn = redis::aio::ConnectionManager::new(redis_client).await?;
|
|
|
|
let var_repo = Arc::new(VarRepositoryImpl::new(pool.clone(), redis_conn.clone()));
|
|
let compliance_repo = Arc::new(ComplianceRepositoryImpl::new(
|
|
pool.clone(),
|
|
redis_conn.clone(),
|
|
));
|
|
let limits_repo = Arc::new(LimitsRepositoryImpl::new(pool.clone(), redis_conn));
|
|
|
|
Ok(Self {
|
|
var_repo,
|
|
compliance_repo,
|
|
limits_repo,
|
|
})
|
|
}
|
|
|
|
/// Get `VaR` repository
|
|
pub fn var(&self) -> Arc<dyn VarRepository> {
|
|
Arc::clone(&self.var_repo)
|
|
}
|
|
|
|
/// Get compliance repository
|
|
pub fn compliance(&self) -> Arc<dyn ComplianceRepository> {
|
|
Arc::clone(&self.compliance_repo)
|
|
}
|
|
|
|
/// Get limits repository
|
|
pub fn limits(&self) -> Arc<dyn LimitsRepository> {
|
|
Arc::clone(&self.limits_repo)
|
|
}
|
|
}
|
|
|
|
/// Common error types for risk data operations
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum RiskDataError {
|
|
/// Database connection or query error
|
|
#[error("Database error: {0}")]
|
|
Database(#[from] sqlx::Error),
|
|
|
|
/// Redis cache operation error
|
|
#[error("Redis error: {0}")]
|
|
Redis(#[from] redis::RedisError),
|
|
|
|
/// JSON serialization/deserialization error
|
|
#[error("Serialization error: {0}")]
|
|
Serialization(#[from] serde_json::Error),
|
|
|
|
/// `VaR` calculation or modeling error
|
|
#[error("VaR calculation error: {0}")]
|
|
VarCalculation(String),
|
|
|
|
/// Compliance rule validation error
|
|
#[error("Compliance validation error: {0}")]
|
|
ComplianceValidation(String),
|
|
|
|
/// Position limits validation error
|
|
#[error("Position limits validation error: {0}")]
|
|
LimitsValidation(String),
|
|
|
|
/// Configuration setup or validation error
|
|
#[error("Configuration error: {0}")]
|
|
Configuration(String),
|
|
|
|
/// Invalid input data error
|
|
#[error("Invalid data: {0}")]
|
|
InvalidData(String),
|
|
}
|
|
|
|
/// Result type for risk data operations
|
|
pub type RiskDataResult<T> = Result<T, RiskDataError>;
|
|
|
|
#[cfg(test)]
|
|
#[allow(clippy::unwrap_used, clippy::expect_used)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn test_risk_data_config_default() {
|
|
let config = RiskDataConfig::default();
|
|
assert_eq!(config.database_url, "postgresql://localhost/foxhunt");
|
|
assert_eq!(config.redis_url, "redis://localhost:6379");
|
|
assert_eq!(config.max_connections, 10);
|
|
assert_eq!(config.connection_timeout_secs, 30);
|
|
}
|
|
|
|
#[test]
|
|
fn test_risk_data_error_display() {
|
|
let error = RiskDataError::VarCalculation("Test error".to_string());
|
|
assert_eq!(error.to_string(), "VaR calculation error: Test error");
|
|
}
|
|
}
|