use anyhow::{anyhow, Result};
use super::{ArgCount, FunctionCategory, FunctionSignature, SqlFunction};
use crate::data::datatable::DataValue;
pub struct ReturnsFunction;
impl SqlFunction for ReturnsFunction {
fn signature(&self) -> FunctionSignature {
FunctionSignature {
name: "RETURNS",
category: FunctionCategory::Mathematical,
arg_count: ArgCount::Fixed(2),
description: "Calculate returns from current and previous price",
returns: "FLOAT",
examples: vec![
"SELECT RETURNS(close, LAG(close) OVER (ORDER BY date)) FROM stocks",
"SELECT RETURNS(100, 95)", "SELECT RETURNS(95, 100)", ],
}
}
fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
if args.len() != 2 {
return Err(anyhow!(
"RETURNS expects exactly 2 arguments: current_price, previous_price"
));
}
let current = match &args[0] {
DataValue::Integer(i) => *i as f64,
DataValue::Float(f) => *f,
DataValue::Null => return Ok(DataValue::Null),
_ => return Err(anyhow!("RETURNS expects numeric values")),
};
let previous = match &args[1] {
DataValue::Integer(i) => *i as f64,
DataValue::Float(f) => *f,
DataValue::Null => return Ok(DataValue::Null),
_ => return Err(anyhow!("RETURNS expects numeric values")),
};
if previous == 0.0 {
return Err(anyhow!("Cannot calculate returns with previous price of 0"));
}
let returns = (current - previous) / previous;
Ok(DataValue::Float(returns))
}
}
pub struct LogReturnsFunction;
impl SqlFunction for LogReturnsFunction {
fn signature(&self) -> FunctionSignature {
FunctionSignature {
name: "LOG_RETURNS",
category: FunctionCategory::Mathematical,
arg_count: ArgCount::Fixed(2),
description: "Calculate logarithmic returns from current and previous price",
returns: "FLOAT",
examples: vec![
"SELECT LOG_RETURNS(close, LAG(close) OVER (ORDER BY date)) FROM stocks",
"SELECT LOG_RETURNS(100, 95)", ],
}
}
fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
if args.len() != 2 {
return Err(anyhow!(
"LOG_RETURNS expects exactly 2 arguments: current_price, previous_price"
));
}
let current = match &args[0] {
DataValue::Integer(i) => *i as f64,
DataValue::Float(f) => *f,
DataValue::Null => return Ok(DataValue::Null),
_ => return Err(anyhow!("LOG_RETURNS expects numeric values")),
};
let previous = match &args[1] {
DataValue::Integer(i) => *i as f64,
DataValue::Float(f) => *f,
DataValue::Null => return Ok(DataValue::Null),
_ => return Err(anyhow!("LOG_RETURNS expects numeric values")),
};
if previous <= 0.0 || current <= 0.0 {
return Err(anyhow!(
"Cannot calculate log returns with non-positive prices"
));
}
let log_returns = (current / previous).ln();
Ok(DataValue::Float(log_returns))
}
}
pub struct VolatilityFunction;
impl SqlFunction for VolatilityFunction {
fn signature(&self) -> FunctionSignature {
FunctionSignature {
name: "VOLATILITY",
category: FunctionCategory::Mathematical,
arg_count: ArgCount::Variadic,
description: "Calculate volatility (standard deviation) of returns",
returns: "FLOAT",
examples: vec![
"SELECT VOLATILITY(0.01, -0.02, 0.015, -0.005, 0.008)",
"WITH returns AS (SELECT RETURNS(close, LAG(close) OVER (ORDER BY date)) as r FROM stocks) SELECT VOLATILITY(r) FROM returns",
],
}
}
fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
if args.is_empty() {
return Err(anyhow!("VOLATILITY requires at least one value"));
}
let mut values = Vec::new();
for arg in args {
match arg {
DataValue::Integer(i) => values.push(*i as f64),
DataValue::Float(f) => values.push(*f),
DataValue::Null => continue, _ => return Err(anyhow!("VOLATILITY expects numeric values")),
}
}
if values.is_empty() {
return Ok(DataValue::Null);
}
if values.len() == 1 {
return Ok(DataValue::Float(0.0)); }
let mean = values.iter().sum::<f64>() / values.len() as f64;
let variance =
values.iter().map(|x| (x - mean).powi(2)).sum::<f64>() / (values.len() - 1) as f64;
let std_dev = variance.sqrt();
Ok(DataValue::Float(std_dev))
}
}
pub struct SharpeRatioFunction;
impl SqlFunction for SharpeRatioFunction {
fn signature(&self) -> FunctionSignature {
FunctionSignature {
name: "SHARPE_RATIO",
category: FunctionCategory::Mathematical,
arg_count: ArgCount::Fixed(3),
description: "Calculate Sharpe ratio: (mean_return - risk_free_rate) / volatility",
returns: "FLOAT",
examples: vec![
"SELECT SHARPE_RATIO(0.08, 0.02, 0.15)", "SELECT SHARPE_RATIO(mean_return, 0.02, volatility) FROM portfolio_stats",
],
}
}
fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
if args.len() != 3 {
return Err(anyhow!(
"SHARPE_RATIO expects 3 arguments: mean_return, risk_free_rate, volatility"
));
}
let mean_return = match &args[0] {
DataValue::Integer(i) => *i as f64,
DataValue::Float(f) => *f,
DataValue::Null => return Ok(DataValue::Null),
_ => return Err(anyhow!("SHARPE_RATIO expects numeric values")),
};
let risk_free_rate = match &args[1] {
DataValue::Integer(i) => *i as f64,
DataValue::Float(f) => *f,
DataValue::Null => 0.0, _ => return Err(anyhow!("SHARPE_RATIO expects numeric values")),
};
let volatility = match &args[2] {
DataValue::Integer(i) => *i as f64,
DataValue::Float(f) => *f,
DataValue::Null => return Ok(DataValue::Null),
_ => return Err(anyhow!("SHARPE_RATIO expects numeric values")),
};
if volatility == 0.0 {
return Err(anyhow!(
"Cannot calculate Sharpe ratio with zero volatility"
));
}
let sharpe = (mean_return - risk_free_rate) / volatility;
Ok(DataValue::Float(sharpe))
}
}
pub struct StdDevFunction;
impl SqlFunction for StdDevFunction {
fn signature(&self) -> FunctionSignature {
FunctionSignature {
name: "STDDEV",
category: FunctionCategory::Mathematical,
arg_count: ArgCount::Variadic,
description: "Calculate sample standard deviation",
returns: "FLOAT",
examples: vec![
"SELECT STDDEV(1, 2, 3, 4, 5)", "SELECT STDDEV(returns) OVER (ORDER BY date ROWS 19 PRECEDING) FROM stocks",
],
}
}
fn evaluate(&self, args: &[DataValue]) -> Result<DataValue> {
VolatilityFunction.evaluate(args)
}
}
pub fn register_financial_functions(registry: &mut super::FunctionRegistry) {
registry.register(Box::new(ReturnsFunction));
registry.register(Box::new(LogReturnsFunction));
registry.register(Box::new(VolatilityFunction));
registry.register(Box::new(StdDevFunction));
registry.register(Box::new(SharpeRatioFunction));
}