use thiserror::Error;
pub mod forecasting;
pub mod moving_averages;
pub mod oscillators;
pub mod volatility;
pub mod volume;
pub mod simple_api {
use super::{
moving_averages::{ExponentialMovingAverage, SimpleMovingAverage},
oscillators::RelativeStrengthIndex,
volatility::BollingerBands,
};
use crate::simple_types::{NyxsOwlError, Price, Result};
pub fn sma(prices: &[Price], period: usize) -> Result<Vec<Price>> {
if prices.len() < period {
return Err(NyxsOwlError::InvalidParameter(format!(
"Need at least {} prices for period {}",
period, period
)));
}
let mut indicator = SimpleMovingAverage::new(period)
.map_err(|e| NyxsOwlError::InvalidParameter(e.to_string()))?;
let mut results = Vec::new();
for &price in prices {
indicator
.update(price)
.map_err(|e| NyxsOwlError::DataError(e.to_string()))?;
if let Ok(value) = indicator.value() {
results.push(value);
}
}
Ok(results)
}
pub fn ema(prices: &[Price], period: usize) -> Result<Vec<Price>> {
if prices.len() < period {
return Err(NyxsOwlError::InvalidParameter(format!(
"Need at least {} prices for period {}",
period, period
)));
}
let mut indicator = ExponentialMovingAverage::new(period)
.map_err(|e| NyxsOwlError::InvalidParameter(e.to_string()))?;
let mut results = Vec::new();
for &price in prices {
indicator
.update(price)
.map_err(|e| NyxsOwlError::DataError(e.to_string()))?;
if let Ok(value) = indicator.value() {
results.push(value);
}
}
Ok(results)
}
pub fn rsi(prices: &[Price], period: usize) -> Result<Vec<Price>> {
if prices.len() < period + 1 {
return Err(NyxsOwlError::InvalidParameter(format!(
"Need at least {} prices for RSI period {}",
period + 1,
period
)));
}
let mut indicator = RelativeStrengthIndex::new(period)
.map_err(|e| NyxsOwlError::InvalidParameter(e.to_string()))?;
let mut results = Vec::new();
for &price in prices {
indicator
.update(price)
.map_err(|e| NyxsOwlError::DataError(e.to_string()))?;
if let Ok(value) = indicator.value() {
results.push(value);
}
}
Ok(results)
}
#[derive(Debug, Clone)]
pub struct BollingerBandsResult {
pub upper: Vec<Price>,
pub middle: Vec<Price>,
pub lower: Vec<Price>,
}
pub fn bollinger_bands(
prices: &[Price],
period: usize,
std_dev: Price,
) -> Result<BollingerBandsResult> {
if prices.len() < period {
return Err(NyxsOwlError::InvalidParameter(format!(
"Need at least {} prices for period {}",
period, period
)));
}
let mut indicator = BollingerBands::new(period, std_dev)
.map_err(|e| NyxsOwlError::InvalidParameter(e.to_string()))?;
let mut upper = Vec::new();
let mut middle = Vec::new();
let mut lower = Vec::new();
for &price in prices {
indicator
.update(price)
.map_err(|e| NyxsOwlError::DataError(e.to_string()))?;
if let (Ok(u), Ok(m), Ok(l)) = (
indicator.upper_band(),
indicator.middle_band(),
indicator.lower_band(),
) {
upper.push(u);
middle.push(m);
lower.push(l);
}
}
Ok(BollingerBandsResult {
upper,
middle,
lower,
})
}
}
#[derive(Error, Debug)]
pub enum MathError {
#[error("Insufficient data for calculation: {0}")]
InsufficientData(String),
#[error("Invalid input: {0}")]
InvalidInput(String),
#[error("Calculation error: {0}")]
CalculationError(String),
}
pub type Result<T> = std::result::Result<T, MathError>;
#[cfg(test)]
mod tests {
#[test]
fn it_works() {
assert_eq!(2 + 2, 4);
}
}