Processing Financial Data in Rust
Financial data processing is where Rust truly shines. Reading millions of rows, computing indicators, and crunching numbers โ all with speed that makes Python look pedestrian. This guide shows you how.
Reading CSV Files
Most financial data comes in CSV format. Rust's csv crate combined with serde makes this painless.
// Cargo.toml dependencies: // csv = "1.3" // serde = {{ version = "1.0", features = ["derive"] }} use csv::ReaderBuilder; use serde::Deserialize; use std::error::Error; // Define the structure of our CSV data #[derive(Debug, Deserialize)] struct OhlcvBar {{ #[serde(rename = "Date")] date: String, #[serde(rename = "Open")] open: f64, #[serde(rename = "High")] high: f64, #[serde(rename = "Low")] low: f64, #[serde(rename = "Close")] close: f64, #[serde(rename = "Volume")] volume: u64, }} fn load_csv(path: &str) -> Result<Vec<OhlcvBar>, Box<dyn Error>> {{ let mut reader = ReaderBuilder::new() .has_headers(true) .from_path(path)?; let mut bars: Vec<OhlcvBar> = Vec::new(); for result in reader.deserialize() {{ let bar: OhlcvBar = result?; bars.push(bar); }} Ok(bars) }} fn main() -> Result<(), Box<dyn Error>> {{ let bars = load_csv("aapl_daily.csv")?; println!("Loaded {{}} bars", bars.len()); println!("First bar: {{:?}}", bars[0]); println!("Last bar: {{:?}}", bars[bars.len() - 1]); Ok(()) }}Calculating Returns
fn calculate_returns(bars: &[OhlcvBar]) -> Vec<f64> {{ // Calculate daily percentage returns from close prices bars.windows(2) .map(|pair| (pair[1].close - pair[0].close) / pair[0].close) .collect() }} fn mean(values: &[f64]) -> f64 {{ values.iter().sum::<f64>() / values.len() as f64 }} fn std_dev(values: &[f64]) -> f64 {{ let avg = mean(values); let variance = values.iter() .map(|x| (x - avg).powi(2)) .sum::<f64>() / values.len() as f64; variance.sqrt() }} // Usage in main: // let returns = calculate_returns(&bars); // let avg = mean(&returns); // let vol = std_dev(&returns); // println!("Mean daily return: {{:.4}}%", avg * 100.0); // println!("Daily volatility: {{:.4}}%", vol * 100.0); // println!("Annual volatility: {{:.2}}%", vol * (252.0_f64).sqrt() * 100.0);Moving Averages with Iterators
fn simple_moving_average(prices: &[f64], window: usize) -> Vec<f64> {{ // Calculate SMA using a sliding window prices .windows(window) .map(|w| w.iter().sum::<f64>() / window as f64) .collect() }} fn exponential_moving_average(prices: &[f64], window: usize) -> Vec<f64> {{ let multiplier = 2.0 / (window as f64 + 1.0); let mut ema = Vec::with_capacity(prices.len()); // First EMA value is the SMA of the first 'window' prices let first_sma: f64 = prices[..window].iter().sum::<f64>() / window as f64; ema.push(first_sma); // Subsequent values use the EMA formula for i in window..prices.len() {{ let prev = *ema.last().unwrap(); let new_ema = (prices[i] - prev) * multiplier + prev; ema.push(new_ema); }} ema }} // Usage: // let closes: Vec<f64> = bars.iter().map(|b| b.close).collect(); // let sma_50 = simple_moving_average(&closes, 50); // let ema_20 = exponential_moving_average(&closes, 20);Error Handling: The Result Type
Rust Forces You to Handle Errors
In Python, errors crash your program unless you catch them. In Rust, functions that can fail return a Result type. The compiler will not let you ignore errors. This means your financial system will not crash at 3am because of an unhandled exception.
use std::fs; use std::io; // This function can fail โ it returns Result fn read_price_file(path: &str) -> Result<Vec<f64>, io::Error> {{ let content = fs::read_to_string(path)?; // ? propagates errors up let prices: Vec<f64> = content .lines() .filter_map(|line| line.trim().parse::<f64>().ok()) .collect(); Ok(prices) }} fn main() {{ // You MUST handle the Result โ the compiler enforces this match read_price_file("prices.txt") {{ Ok(prices) => println!("Loaded {{}} prices", prices.len()), Err(e) => eprintln!("Error loading prices: {{}}", e), }} }}Benchmark: Rust vs Python
Here is the same operation โ calculating a 50-day SMA over 1 million data points โ in both languages. The speed difference is dramatic.
Benchmark: 50-day SMA on 1,000,000 data points
Rust is approximately 20x faster than numpy and 400x faster than a pure Python loop. For processing millions of bars across thousands of instruments, this matters.