diff --git a/crates/pyopenquant/src/bars.rs b/crates/pyopenquant/src/bars.rs index e4de166..cdd8e07 100644 --- a/crates/pyopenquant/src/bars.rs +++ b/crates/pyopenquant/src/bars.rs @@ -1,4 +1,6 @@ -use openquant::data_structures::{imbalance_bars, run_bars, standard_bars, time_bars, ImbalanceBarType, StandardBarType}; +use openquant::data_structures::{ + imbalance_bars, run_bars, standard_bars, time_bars, ImbalanceBarType, StandardBarType, +}; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; diff --git a/crates/pyopenquant/src/bet_sizing.rs b/crates/pyopenquant/src/bet_sizing.rs index b3aa69c..ecc5d20 100644 --- a/crates/pyopenquant/src/bet_sizing.rs +++ b/crates/pyopenquant/src/bet_sizing.rs @@ -29,8 +29,14 @@ fn bet_sizing_bet_size_power(w_param: f64, price_div: f64) -> PyResult { } #[pyfunction(name = "inv_price")] -fn bet_sizing_inv_price(forecast_price: f64, w_param: f64, m_bet_size: f64, func: String) -> PyResult { - openquant::bet_sizing::inv_price_checked(forecast_price, w_param, m_bet_size, &func).map_err(to_py_err) +fn bet_sizing_inv_price( + forecast_price: f64, + w_param: f64, + m_bet_size: f64, + func: String, +) -> PyResult { + openquant::bet_sizing::inv_price_checked(forecast_price, w_param, m_bet_size, &func) + .map_err(to_py_err) } #[pyfunction(name = "inv_price_sigmoid")] @@ -59,22 +65,45 @@ fn bet_sizing_get_w_power(price_div: f64, m_bet_size: f64) -> PyResult { } #[pyfunction(name = "get_target_pos")] -fn bet_sizing_get_target_pos(w: f64, f: f64, m_p: f64, max_pos: f64, func: String) -> PyResult { +fn bet_sizing_get_target_pos( + w: f64, + f: f64, + m_p: f64, + max_pos: f64, + func: String, +) -> PyResult { openquant::bet_sizing::get_target_pos_checked(w, f, m_p, max_pos, &func).map_err(to_py_err) } #[pyfunction(name = "get_target_pos_sigmoid")] -fn bet_sizing_get_target_pos_sigmoid(w_param: f64, forecast_price: f64, market_price: f64, max_pos: f64) -> f64 { +fn bet_sizing_get_target_pos_sigmoid( + w_param: f64, + forecast_price: f64, + market_price: f64, + max_pos: f64, +) -> f64 { openquant::bet_sizing::get_target_pos_sigmoid(w_param, forecast_price, market_price, max_pos) } #[pyfunction(name = "get_target_pos_power")] -fn bet_sizing_get_target_pos_power(w_param: f64, forecast_price: f64, market_price: f64, max_pos: f64) -> f64 { +fn bet_sizing_get_target_pos_power( + w_param: f64, + forecast_price: f64, + market_price: f64, + max_pos: f64, +) -> f64 { openquant::bet_sizing::get_target_pos_power(w_param, forecast_price, market_price, max_pos) } #[pyfunction(name = "limit_price")] -fn bet_sizing_limit_price(t_pos: f64, pos: f64, f: f64, w: f64, max_pos: f64, func: String) -> PyResult { +fn bet_sizing_limit_price( + t_pos: f64, + pos: f64, + f: f64, + w: f64, + max_pos: f64, + func: String, +) -> PyResult { openquant::bet_sizing::limit_price_checked(t_pos, pos, f, w, max_pos, &func).map_err(to_py_err) } @@ -94,13 +123,15 @@ fn bet_sizing_avg_active_signals( signal_values: Vec, t1_timestamps: Vec, ) -> PyResult> { - let signal = pair_timestamps_values(signal_timestamps, signal_values, "signal_timestamps", "signal_values")?; + let signal = pair_timestamps_values( + signal_timestamps, + signal_values, + "signal_timestamps", + "signal_values", + )?; let t1 = parse_naive_datetimes(t1_timestamps)?; let result = openquant::bet_sizing::avg_active_signals(&signal, &t1); - Ok(result - .into_iter() - .map(|(ts, v)| (ts.format("%Y-%m-%d %H:%M:%S").to_string(), v)) - .collect()) + Ok(result.into_iter().map(|(ts, v)| (ts.format("%Y-%m-%d %H:%M:%S").to_string(), v)).collect()) } #[pyfunction(name = "bet_size_dynamic")] @@ -132,7 +163,9 @@ fn bet_sizing_get_concurrent_sides( let starts = parse_naive_datetimes(t1_starts)?; let ends = parse_naive_datetimes(t1_ends)?; if starts.len() != ends.len() || starts.len() != side.len() { - return Err(pyo3::exceptions::PyValueError::new_err("t1_starts/t1_ends/side length mismatch")); + return Err(pyo3::exceptions::PyValueError::new_err( + "t1_starts/t1_ends/side length mismatch", + )); } let t1: Vec<(chrono::NaiveDateTime, chrono::NaiveDateTime)> = starts.into_iter().zip(ends).collect(); @@ -152,15 +185,14 @@ fn bet_sizing_bet_size_budget( let starts = parse_naive_datetimes(t1_starts)?; let ends = parse_naive_datetimes(t1_ends)?; if starts.len() != ends.len() || starts.len() != side.len() { - return Err(pyo3::exceptions::PyValueError::new_err("t1_starts/t1_ends/side length mismatch")); + return Err(pyo3::exceptions::PyValueError::new_err( + "t1_starts/t1_ends/side length mismatch", + )); } let t1: Vec<(chrono::NaiveDateTime, chrono::NaiveDateTime)> = starts.into_iter().zip(ends).collect(); let result = openquant::bet_sizing::bet_size_budget(&t1, &side); - Ok(result - .into_iter() - .map(|(ts, v)| (ts.format("%Y-%m-%d %H:%M:%S").to_string(), v)) - .collect()) + Ok(result.into_iter().map(|(ts, v)| (ts.format("%Y-%m-%d %H:%M:%S").to_string(), v)).collect()) } #[pyfunction(name = "bet_size_probability")] @@ -187,11 +219,13 @@ fn bet_sizing_bet_size_probability( .zip(sides) .map(|(((s, e), p), sd)| (s, e, p, sd)) .collect(); - let result = openquant::bet_sizing::bet_size_probability(&events, num_classes, step_size, average_active); - Ok(result - .into_iter() - .map(|(ts, v)| (ts.format("%Y-%m-%d %H:%M:%S").to_string(), v)) - .collect()) + let result = openquant::bet_sizing::bet_size_probability( + &events, + num_classes, + step_size, + average_active, + ); + Ok(result.into_iter().map(|(ts, v)| (ts.format("%Y-%m-%d %H:%M:%S").to_string(), v)).collect()) } #[pyfunction(name = "mp_avg_active_signals")] @@ -201,14 +235,16 @@ fn bet_sizing_mp_avg_active_signals( t1_timestamps: Vec, molecule_timestamps: Vec, ) -> PyResult> { - let signal = pair_timestamps_values(signal_timestamps, signal_values, "signal_timestamps", "signal_values")?; + let signal = pair_timestamps_values( + signal_timestamps, + signal_values, + "signal_timestamps", + "signal_values", + )?; let t1 = parse_naive_datetimes(t1_timestamps)?; let molecule = parse_naive_datetimes(molecule_timestamps)?; let result = openquant::bet_sizing::mp_avg_active_signals(&signal, &t1, &molecule); - Ok(result - .into_iter() - .map(|(ts, v)| (ts.format("%Y-%m-%d %H:%M:%S").to_string(), v)) - .collect()) + Ok(result.into_iter().map(|(ts, v)| (ts.format("%Y-%m-%d %H:%M:%S").to_string(), v)).collect()) } #[pyfunction(name = "bet_size_reserve")] @@ -221,7 +257,9 @@ fn bet_sizing_bet_size_reserve( let starts = parse_naive_datetimes(t1_starts)?; let ends = parse_naive_datetimes(t1_ends)?; if starts.len() != ends.len() || starts.len() != side.len() { - return Err(pyo3::exceptions::PyValueError::new_err("t1_starts/t1_ends/side length mismatch")); + return Err(pyo3::exceptions::PyValueError::new_err( + "t1_starts/t1_ends/side length mismatch", + )); } let t1: Vec<(chrono::NaiveDateTime, chrono::NaiveDateTime)> = starts.into_iter().zip(ends).collect(); @@ -242,7 +280,9 @@ fn bet_sizing_bet_size_reserve_with_fit( let starts = parse_naive_datetimes(t1_starts)?; let ends = parse_naive_datetimes(t1_ends)?; if starts.len() != ends.len() || starts.len() != side.len() { - return Err(pyo3::exceptions::PyValueError::new_err("t1_starts/t1_ends/side length mismatch")); + return Err(pyo3::exceptions::PyValueError::new_err( + "t1_starts/t1_ends/side length mismatch", + )); } let t1: Vec<(chrono::NaiveDateTime, chrono::NaiveDateTime)> = starts.into_iter().zip(ends).collect(); @@ -266,11 +306,20 @@ fn bet_sizing_bet_size_reserve_full( let starts = parse_naive_datetimes(t1_starts)?; let ends = parse_naive_datetimes(t1_ends)?; if starts.len() != ends.len() || starts.len() != side.len() { - return Err(pyo3::exceptions::PyValueError::new_err("t1_starts/t1_ends/side length mismatch")); + return Err(pyo3::exceptions::PyValueError::new_err( + "t1_starts/t1_ends/side length mismatch", + )); } let t1: Vec<(chrono::NaiveDateTime, chrono::NaiveDateTime)> = starts.into_iter().zip(ends).collect(); - let (events, params) = openquant::bet_sizing::bet_size_reserve_full(&t1, &side, fit_runs, epsilon, max_iter, return_parameters); + let (events, params) = openquant::bet_sizing::bet_size_reserve_full( + &t1, + &side, + fit_runs, + epsilon, + max_iter, + return_parameters, + ); let out_events = events .into_iter() .map(|(ts, l, s, c, b)| (ts.format("%Y-%m-%d %H:%M:%S").to_string(), l, s, c, b)) diff --git a/crates/pyopenquant/src/cla.rs b/crates/pyopenquant/src/cla.rs index 89b608c..93e638c 100644 --- a/crates/pyopenquant/src/cla.rs +++ b/crates/pyopenquant/src/cla.rs @@ -33,9 +33,7 @@ fn cla_allocate( let prices_m = asset_prices.map(matrix_from_rows).transpose()?; let cov_m = covariance_matrix.map(matrix_from_rows).transpose()?; - let expected_ret_m = expected_returns.map(|v| { - nalgebra::DMatrix::from_vec(v.len(), 1, v) - }); + let expected_ret_m = expected_returns.map(|v| nalgebra::DMatrix::from_vec(v.len(), 1, v)); cla.allocate( prices_m.as_ref().map(|m| openquant::cla::AssetPricesInput::RawMatrix(m)), diff --git a/crates/pyopenquant/src/data.rs b/crates/pyopenquant/src/data.rs index 40e4ec9..81a8b5a 100644 --- a/crates/pyopenquant/src/data.rs +++ b/crates/pyopenquant/src/data.rs @@ -1,4 +1,6 @@ -use openquant::data_processing::{align_calendar_columns, clean_ohlcv_columns, quality_report_columns}; +use openquant::data_processing::{ + align_calendar_columns, clean_ohlcv_columns, quality_report_columns, +}; use polars::prelude::DataFrame; use pyo3::prelude::*; use pyo3::types::PyDict; diff --git a/crates/pyopenquant/src/ef3m.rs b/crates/pyopenquant/src/ef3m.rs index aba6abe..faeb65f 100644 --- a/crates/pyopenquant/src/ef3m.rs +++ b/crates/pyopenquant/src/ef3m.rs @@ -1,7 +1,6 @@ use pyo3::prelude::*; use pyo3::types::PyDict; - #[pyfunction(name = "centered_moment")] fn ef3m_centered_moment(moments: Vec, order: usize) -> f64 { openquant::ef3m::centered_moment(&moments, order) @@ -21,7 +20,12 @@ fn ef3m_most_likely_parameters( let rows: Vec = data .into_iter() .map(|(mu_1, mu_2, sigma_1, sigma_2, p_1, error)| openquant::ef3m::FitResultRow { - mu_1, mu_2, sigma_1, sigma_2, p_1, error, + mu_1, + mu_2, + sigma_1, + sigma_2, + p_1, + error, }) .collect(); let result = openquant::ef3m::most_likely_parameters(&rows, None, res); diff --git a/crates/pyopenquant/src/hcaa.rs b/crates/pyopenquant/src/hcaa.rs index 9c32af2..dd3996d 100644 --- a/crates/pyopenquant/src/hcaa.rs +++ b/crates/pyopenquant/src/hcaa.rs @@ -31,7 +31,8 @@ fn hcaa_allocate( let returns_m = asset_returns.map(matrix_from_rows).transpose()?; let cov_m = covariance_matrix.map(matrix_from_rows).transpose()?; - let mut hcaa = openquant::hcaa::HierarchicalClusteringAssetAllocation::new(calculate_expected_returns); + let mut hcaa = + openquant::hcaa::HierarchicalClusteringAssetAllocation::new(calculate_expected_returns); hcaa.allocate( &asset_names, prices_m.as_ref(), diff --git a/crates/pyopenquant/src/helpers.rs b/crates/pyopenquant/src/helpers.rs index 5b08451..50924c8 100644 --- a/crates/pyopenquant/src/helpers.rs +++ b/crates/pyopenquant/src/helpers.rs @@ -168,7 +168,16 @@ pub fn build_ohlcv_columns( adj_close.len(), ))); } - Ok(openquant::data_processing::OhlcvColumns { timestamps_us, symbols, open, high, low, close, volume, adj_close }) + Ok(openquant::data_processing::OhlcvColumns { + timestamps_us, + symbols, + open, + high, + low, + close, + volume, + adj_close, + }) } pub fn report_to_pydict( @@ -226,7 +235,12 @@ pub fn build_labeling_events( &close, &t_events, &target, - openquant::labeling::TripleBarrierConfig { pt, sl, min_ret, vertical_barrier_times: vbars.as_deref() }, + openquant::labeling::TripleBarrierConfig { + pt, + sl, + min_ret, + vertical_barrier_times: vbars.as_deref(), + }, side_storage.as_deref(), ); Ok((close, events)) diff --git a/crates/pyopenquant/src/labeling.rs b/crates/pyopenquant/src/labeling.rs index 01c6117..6bdc4ae 100644 --- a/crates/pyopenquant/src/labeling.rs +++ b/crates/pyopenquant/src/labeling.rs @@ -1,8 +1,7 @@ use pyo3::prelude::*; use crate::helpers::{ - build_labeling_events, pair_timestamps_values, - parse_naive_datetimes, parse_vertical_barriers, + build_labeling_events, pair_timestamps_values, parse_naive_datetimes, parse_vertical_barriers, }; #[pyfunction(name = "add_vertical_barrier")] @@ -16,21 +15,20 @@ fn labeling_add_vertical_barrier( num_seconds: i64, ) -> PyResult> { let t_events = parse_naive_datetimes(t_events)?; - let close = pair_timestamps_values( - close_timestamps, - close_prices, - "close_timestamps", - "close_prices", - )?; - let barriers = - openquant::labeling::add_vertical_barrier(&t_events, &close, num_days, num_hours, num_minutes, num_seconds); + let close = + pair_timestamps_values(close_timestamps, close_prices, "close_timestamps", "close_prices")?; + let barriers = openquant::labeling::add_vertical_barrier( + &t_events, + &close, + num_days, + num_hours, + num_minutes, + num_seconds, + ); Ok(barriers .into_iter() .map(|(a, b)| { - ( - a.format("%Y-%m-%d %H:%M:%S").to_string(), - b.format("%Y-%m-%d %H:%M:%S").to_string(), - ) + (a.format("%Y-%m-%d %H:%M:%S").to_string(), b.format("%Y-%m-%d %H:%M:%S").to_string()) }) .collect()) } @@ -212,12 +210,8 @@ fn labeling_get_events( vertical_barrier_times: Option>, side_prediction: Option>, ) -> PyResult, f64, Option, f64, f64)>> { - let close = pair_timestamps_values( - close_timestamps, - close_prices, - "close_timestamps", - "close_prices", - )?; + let close = + pair_timestamps_values(close_timestamps, close_prices, "close_timestamps", "close_prices")?; let t_ev = parse_naive_datetimes(t_events)?; let target = pair_timestamps_values( target_timestamps, @@ -266,21 +260,21 @@ fn labeling_get_bins( close_timestamps: Vec, close_prices: Vec, ) -> PyResult)>> { - let close = pair_timestamps_values( - close_timestamps, - close_prices, - "close_timestamps", - "close_prices", - )?; + let close = + pair_timestamps_values(close_timestamps, close_prices, "close_timestamps", "close_prices")?; let parsed_events: Vec<(chrono::NaiveDateTime, openquant::labeling::Event)> = events .into_iter() .map(|(ts_str, t1_str, trgt, side, pt, sl)| { - let ts = chrono::NaiveDateTime::parse_from_str(&ts_str, "%Y-%m-%d %H:%M:%S") - .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("invalid datetime: {e}")))?; + let ts = chrono::NaiveDateTime::parse_from_str(&ts_str, "%Y-%m-%d %H:%M:%S").map_err( + |e| pyo3::exceptions::PyValueError::new_err(format!("invalid datetime: {e}")), + )?; let t1 = t1_str - .map(|s| chrono::NaiveDateTime::parse_from_str(&s, "%Y-%m-%d %H:%M:%S") - .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("invalid datetime: {e}")))) + .map(|s| { + chrono::NaiveDateTime::parse_from_str(&s, "%Y-%m-%d %H:%M:%S").map_err(|e| { + pyo3::exceptions::PyValueError::new_err(format!("invalid datetime: {e}")) + }) + }) .transpose()?; Ok((ts, openquant::labeling::Event { t1, trgt, side, pt, sl })) }) diff --git a/crates/pyopenquant/src/microstructural.rs b/crates/pyopenquant/src/microstructural.rs index 2a56099..825da20 100644 --- a/crates/pyopenquant/src/microstructural.rs +++ b/crates/pyopenquant/src/microstructural.rs @@ -30,20 +30,40 @@ fn ms_get_bar_based_kyle_lambda(close: Vec, volume: Vec, window: usize } #[pyfunction(name = "get_bar_based_amihud_lambda")] -fn ms_get_bar_based_amihud_lambda(close: Vec, dollar_volume: Vec, window: usize) -> Vec { +fn ms_get_bar_based_amihud_lambda( + close: Vec, + dollar_volume: Vec, + window: usize, +) -> Vec { openquant::microstructural_features::get_bar_based_amihud_lambda(&close, &dollar_volume, window) } #[pyfunction(name = "get_bar_based_hasbrouck_lambda")] -fn ms_get_bar_based_hasbrouck_lambda(close: Vec, dollar_volume: Vec, window: usize) -> Vec { - openquant::microstructural_features::get_bar_based_hasbrouck_lambda(&close, &dollar_volume, window) +fn ms_get_bar_based_hasbrouck_lambda( + close: Vec, + dollar_volume: Vec, + window: usize, +) -> Vec { + openquant::microstructural_features::get_bar_based_hasbrouck_lambda( + &close, + &dollar_volume, + window, + ) } // --- Trade-based features --- #[pyfunction(name = "get_trades_based_kyle_lambda")] -fn ms_get_trades_based_kyle_lambda(price_diff: Vec, volume: Vec, aggressor_flags: Vec) -> f64 { - openquant::microstructural_features::get_trades_based_kyle_lambda(&price_diff, &volume, &aggressor_flags) +fn ms_get_trades_based_kyle_lambda( + price_diff: Vec, + volume: Vec, + aggressor_flags: Vec, +) -> f64 { + openquant::microstructural_features::get_trades_based_kyle_lambda( + &price_diff, + &volume, + &aggressor_flags, + ) } #[pyfunction(name = "get_trades_based_amihud_lambda")] @@ -52,8 +72,16 @@ fn ms_get_trades_based_amihud_lambda(log_ret: Vec, dollar_volume: Vec) } #[pyfunction(name = "get_trades_based_hasbrouck_lambda")] -fn ms_get_trades_based_hasbrouck_lambda(log_ret: Vec, dollar_volume: Vec, aggressor_flags: Vec) -> f64 { - openquant::microstructural_features::get_trades_based_hasbrouck_lambda(&log_ret, &dollar_volume, &aggressor_flags) +fn ms_get_trades_based_hasbrouck_lambda( + log_ret: Vec, + dollar_volume: Vec, + aggressor_flags: Vec, +) -> f64 { + openquant::microstructural_features::get_trades_based_hasbrouck_lambda( + &log_ret, + &dollar_volume, + &aggressor_flags, + ) } // --- VPIN --- diff --git a/crates/pyopenquant/src/pipeline.rs b/crates/pyopenquant/src/pipeline.rs index 9d88013..8e626d9 100644 --- a/crates/pyopenquant/src/pipeline.rs +++ b/crates/pyopenquant/src/pipeline.rs @@ -1,4 +1,6 @@ -use openquant::pipeline::{run_mid_frequency_pipeline, ResearchPipelineConfig, ResearchPipelineInput}; +use openquant::pipeline::{ + run_mid_frequency_pipeline, ResearchPipelineConfig, ResearchPipelineInput, +}; use pyo3::prelude::*; use pyo3::types::PyDict; diff --git a/crates/pyopenquant/src/portfolio.rs b/crates/pyopenquant/src/portfolio.rs index 68dedd6..0242e69 100644 --- a/crates/pyopenquant/src/portfolio.rs +++ b/crates/pyopenquant/src/portfolio.rs @@ -12,7 +12,8 @@ fn portfolio_allocate_inverse_variance( prices: Vec>, ) -> PyResult<(Vec, f64, f64, f64)> { let m = matrix_from_rows(prices)?; - let out = openquant::portfolio_optimization::allocate_inverse_variance(&m).map_err(to_py_err)?; + let out = + openquant::portfolio_optimization::allocate_inverse_variance(&m).map_err(to_py_err)?; Ok((out.weights, out.portfolio_risk, out.portfolio_return, out.portfolio_sharpe)) } @@ -24,7 +25,9 @@ fn portfolio_allocate_min_vol( tuple_bounds: Option<(f64, f64)>, ) -> PyResult<(Vec, f64, f64, f64)> { let m = matrix_from_rows(prices)?; - let out = openquant::portfolio_optimization::allocate_min_vol(&m, parse_bounds(bounds), tuple_bounds).map_err(to_py_err)?; + let out = + openquant::portfolio_optimization::allocate_min_vol(&m, parse_bounds(bounds), tuple_bounds) + .map_err(to_py_err)?; Ok((out.weights, out.portfolio_risk, out.portfolio_return, out.portfolio_sharpe)) } @@ -89,7 +92,9 @@ fn portfolio_allocate_with_solution( ) -> PyResult<(Vec, f64, f64, f64)> { let m = matrix_from_rows(prices)?; let rm = match returns_method { - Some(name) => openquant::portfolio_optimization::returns_method_from_str(&name).map_err(to_py_err)?, + Some(name) => { + openquant::portfolio_optimization::returns_method_from_str(&name).map_err(to_py_err)? + } None => openquant::portfolio_optimization::ReturnsMethod::Mean, }; let opts = openquant::portfolio_optimization::AllocationOptions { @@ -100,7 +105,8 @@ fn portfolio_allocate_with_solution( resample_by: resample_by.as_deref(), returns_method: rm, }; - let out = openquant::portfolio_optimization::allocate_with_solution(&m, &solution, &opts).map_err(to_py_err)?; + let out = openquant::portfolio_optimization::allocate_with_solution(&m, &solution, &opts) + .map_err(to_py_err)?; Ok((out.weights, out.portfolio_risk, out.portfolio_return, out.portfolio_sharpe)) } diff --git a/crates/pyopenquant/src/risk.rs b/crates/pyopenquant/src/risk.rs index e04d456..1114f54 100644 --- a/crates/pyopenquant/src/risk.rs +++ b/crates/pyopenquant/src/risk.rs @@ -18,16 +18,11 @@ fn risk_calculate_conditional_drawdown_risk( returns: Vec, confidence_level: f64, ) -> PyResult { - RiskMetrics - .calculate_conditional_drawdown_risk(&returns, confidence_level) - .map_err(to_py_err) + RiskMetrics.calculate_conditional_drawdown_risk(&returns, confidence_level).map_err(to_py_err) } #[pyfunction(name = "calculate_variance")] -fn risk_calculate_variance( - covariance: Vec>, - weights: Vec, -) -> PyResult { +fn risk_calculate_variance(covariance: Vec>, weights: Vec) -> PyResult { let cov = matrix_from_rows(covariance)?; RiskMetrics.calculate_variance(&cov, &weights).map_err(to_py_err) } diff --git a/crates/pyopenquant/src/sample_weights.rs b/crates/pyopenquant/src/sample_weights.rs index 09fd526..e6c5f77 100644 --- a/crates/pyopenquant/src/sample_weights.rs +++ b/crates/pyopenquant/src/sample_weights.rs @@ -12,25 +12,32 @@ fn sw_get_weights_by_return( .into_iter() .map(|(t_in, t_out, label)| { let t_in_dt = chrono::NaiveDateTime::parse_from_str(&t_in, "%Y-%m-%d %H:%M:%S") - .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("invalid datetime '{t_in}': {e}")))?; + .map_err(|e| { + pyo3::exceptions::PyValueError::new_err(format!( + "invalid datetime '{t_in}': {e}" + )) + })?; let t_out_dt = chrono::NaiveDateTime::parse_from_str(&t_out, "%Y-%m-%d %H:%M:%S") - .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("invalid datetime '{t_out}': {e}")))?; + .map_err(|e| { + pyo3::exceptions::PyValueError::new_err(format!( + "invalid datetime '{t_out}': {e}" + )) + })?; Ok((t_in_dt, t_out_dt, label)) }) .collect::>>()?; let close_ts = parse_naive_datetimes(close_timestamps)?; if close_ts.len() != close_prices.len() { - return Err(pyo3::exceptions::PyValueError::new_err("close timestamps/prices length mismatch")); + return Err(pyo3::exceptions::PyValueError::new_err( + "close timestamps/prices length mismatch", + )); } let close: Vec<(chrono::NaiveDateTime, f64)> = close_ts.into_iter().zip(close_prices).collect(); let result = openquant::sample_weights::get_weights_by_return(&parsed_events, &close) .map_err(to_py_err)?; - Ok(result - .into_iter() - .map(|(ts, v)| (ts.format("%Y-%m-%d %H:%M:%S").to_string(), v)) - .collect()) + Ok(result.into_iter().map(|(ts, v)| (ts.format("%Y-%m-%d %H:%M:%S").to_string(), v)).collect()) } #[pyfunction(name = "get_weights_by_time_decay")] @@ -44,25 +51,33 @@ fn sw_get_weights_by_time_decay( .into_iter() .map(|(t_in, t_out, label)| { let t_in_dt = chrono::NaiveDateTime::parse_from_str(&t_in, "%Y-%m-%d %H:%M:%S") - .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("invalid datetime '{t_in}': {e}")))?; + .map_err(|e| { + pyo3::exceptions::PyValueError::new_err(format!( + "invalid datetime '{t_in}': {e}" + )) + })?; let t_out_dt = chrono::NaiveDateTime::parse_from_str(&t_out, "%Y-%m-%d %H:%M:%S") - .map_err(|e| pyo3::exceptions::PyValueError::new_err(format!("invalid datetime '{t_out}': {e}")))?; + .map_err(|e| { + pyo3::exceptions::PyValueError::new_err(format!( + "invalid datetime '{t_out}': {e}" + )) + })?; Ok((t_in_dt, t_out_dt, label)) }) .collect::>>()?; let close_ts = parse_naive_datetimes(close_timestamps)?; if close_ts.len() != close_prices.len() { - return Err(pyo3::exceptions::PyValueError::new_err("close timestamps/prices length mismatch")); + return Err(pyo3::exceptions::PyValueError::new_err( + "close timestamps/prices length mismatch", + )); } let close: Vec<(chrono::NaiveDateTime, f64)> = close_ts.into_iter().zip(close_prices).collect(); - let result = openquant::sample_weights::get_weights_by_time_decay(&parsed_events, &close, decay) - .map_err(to_py_err)?; - Ok(result - .into_iter() - .map(|(ts, v)| (ts.format("%Y-%m-%d %H:%M:%S").to_string(), v)) - .collect()) + let result = + openquant::sample_weights::get_weights_by_time_decay(&parsed_events, &close, decay) + .map_err(to_py_err)?; + Ok(result.into_iter().map(|(ts, v)| (ts.format("%Y-%m-%d %H:%M:%S").to_string(), v)).collect()) } pub fn register(py: Python<'_>, parent: &Bound<'_, PyModule>) -> PyResult<()> { diff --git a/crates/pyopenquant/src/sampling.rs b/crates/pyopenquant/src/sampling.rs index 287d8f6..cf0b9be 100644 --- a/crates/pyopenquant/src/sampling.rs +++ b/crates/pyopenquant/src/sampling.rs @@ -19,10 +19,7 @@ fn sampling_get_ind_mat_label_uniqueness(ind_mat: Vec>) -> Vec> } #[pyfunction(name = "bootstrap_loop_run")] -fn sampling_bootstrap_loop_run( - ind_mat: Vec>, - prev_concurrency: Vec, -) -> Vec { +fn sampling_bootstrap_loop_run(ind_mat: Vec>, prev_concurrency: Vec) -> Vec { openquant::sampling::bootstrap_loop_run(&ind_mat, &prev_concurrency) } diff --git a/crates/pyopenquant/src/sb_bagging.rs b/crates/pyopenquant/src/sb_bagging.rs index a225f90..1461942 100644 --- a/crates/pyopenquant/src/sb_bagging.rs +++ b/crates/pyopenquant/src/sb_bagging.rs @@ -27,7 +27,8 @@ fn sb_fit_predict_classifier( ) -> PyResult { let x_mat = matrix_from_rows(x)?; - let mut clf = openquant::sb_bagging::SequentiallyBootstrappedBaggingClassifier::new(random_state); + let mut clf = + openquant::sb_bagging::SequentiallyBootstrappedBaggingClassifier::new(random_state); clf.n_estimators = n_estimators; clf.max_samples = openquant::sb_bagging::MaxSamples::Float(max_samples); clf.max_features = openquant::sb_bagging::MaxFeatures::Float(max_features); @@ -66,7 +67,8 @@ fn sb_fit_predict_regressor( ) -> PyResult { let x_mat = matrix_from_rows(x)?; - let mut reg = openquant::sb_bagging::SequentiallyBootstrappedBaggingRegressor::new(random_state); + let mut reg = + openquant::sb_bagging::SequentiallyBootstrappedBaggingRegressor::new(random_state); reg.n_estimators = n_estimators; reg.max_samples = openquant::sb_bagging::MaxSamples::Float(max_samples); reg.max_features = openquant::sb_bagging::MaxFeatures::Float(max_features); diff --git a/crates/pyopenquant/src/strategy_risk.rs b/crates/pyopenquant/src/strategy_risk.rs index 53dd223..de9d8c0 100644 --- a/crates/pyopenquant/src/strategy_risk.rs +++ b/crates/pyopenquant/src/strategy_risk.rs @@ -40,8 +40,12 @@ fn sr_implied_precision_asymmetric( pi_minus: f64, ) -> PyResult { let payout = openquant::strategy_risk::AsymmetricPayout { pi_plus, pi_minus }; - openquant::strategy_risk::implied_precision_asymmetric(target_sharpe, annual_bet_frequency, payout) - .map_err(to_py_err) + openquant::strategy_risk::implied_precision_asymmetric( + target_sharpe, + annual_bet_frequency, + payout, + ) + .map_err(to_py_err) } #[pyfunction(name = "implied_frequency_asymmetric")] @@ -84,8 +88,9 @@ fn sr_estimate_strategy_failure_probability( seed, kde_bandwidth, }; - let report = openquant::strategy_risk::estimate_strategy_failure_probability(&bet_outcomes, cfg) - .map_err(to_py_err)?; + let report = + openquant::strategy_risk::estimate_strategy_failure_probability(&bet_outcomes, cfg) + .map_err(to_py_err)?; let d = PyDict::new(py); d.set_item("pi_plus", report.payout.pi_plus)?; diff --git a/crates/pyopenquant/src/streaming_hpc.rs b/crates/pyopenquant/src/streaming_hpc.rs index 2e7ef0d..557dbad 100644 --- a/crates/pyopenquant/src/streaming_hpc.rs +++ b/crates/pyopenquant/src/streaming_hpc.rs @@ -33,8 +33,8 @@ fn shpc_run_streaming_pipeline( }, }; - let report = openquant::streaming_hpc::run_streaming_pipeline(&stream_events, cfg) - .map_err(to_py_err)?; + let report = + openquant::streaming_hpc::run_streaming_pipeline(&stream_events, cfg).map_err(to_py_err)?; let d = PyDict::new(py); @@ -71,8 +71,8 @@ fn shpc_generate_synthetic_flash_crash_stream( calm_venues, shock_venue, }; - let stream = openquant::streaming_hpc::generate_synthetic_flash_crash_stream(cfg) - .map_err(to_py_err)?; + let stream = + openquant::streaming_hpc::generate_synthetic_flash_crash_stream(cfg).map_err(to_py_err)?; Ok(stream .into_iter() .map(|e| (e.timestamp_ns, e.price, e.buy_volume, e.sell_volume, e.venue_id)) diff --git a/crates/pyopenquant/src/synthetic_bt.rs b/crates/pyopenquant/src/synthetic_bt.rs index b9d6fd0..3654608 100644 --- a/crates/pyopenquant/src/synthetic_bt.rs +++ b/crates/pyopenquant/src/synthetic_bt.rs @@ -3,7 +3,10 @@ use pyo3::types::PyDict; use crate::helpers::to_py_err; -fn ou_params_to_dict(py: Python<'_>, p: &openquant::synthetic_backtesting::OuProcessParams) -> PyResult { +fn ou_params_to_dict( + py: Python<'_>, + p: &openquant::synthetic_backtesting::OuProcessParams, +) -> PyResult { let d = PyDict::new(py); d.set_item("phi", p.phi)?; d.set_item("intercept", p.intercept)?; @@ -14,7 +17,10 @@ fn ou_params_to_dict(py: Python<'_>, p: &openquant::synthetic_backtesting::OuPro Ok(d.into_pyobject(py).unwrap().into_any().unbind()) } -fn surface_point_to_dict(py: Python<'_>, p: &openquant::synthetic_backtesting::RuleSurfacePoint) -> PyResult { +fn surface_point_to_dict( + py: Python<'_>, + p: &openquant::synthetic_backtesting::RuleSurfacePoint, +) -> PyResult { let d = PyDict::new(py); d.set_item("profit_taking", p.rule.profit_taking)?; d.set_item("stop_loss", p.rule.stop_loss)?; @@ -26,7 +32,10 @@ fn surface_point_to_dict(py: Python<'_>, p: &openquant::synthetic_backtesting::R Ok(d.into_pyobject(py).unwrap().into_any().unbind()) } -fn diagnostics_to_dict(py: Python<'_>, d_in: &openquant::synthetic_backtesting::StabilityDiagnostics) -> PyResult { +fn diagnostics_to_dict( + py: Python<'_>, + d_in: &openquant::synthetic_backtesting::StabilityDiagnostics, +) -> PyResult { let d = PyDict::new(py); d.set_item("no_stable_optimum", d_in.no_stable_optimum)?; d.set_item("reason", &d_in.reason)?; @@ -38,7 +47,10 @@ fn diagnostics_to_dict(py: Python<'_>, d_in: &openquant::synthetic_backtesting:: Ok(d.into_pyobject(py).unwrap().into_any().unbind()) } -fn otr_result_to_dict(py: Python<'_>, r: openquant::synthetic_backtesting::OtrSearchResult) -> PyResult { +fn otr_result_to_dict( + py: Python<'_>, + r: openquant::synthetic_backtesting::OtrSearchResult, +) -> PyResult { let d = PyDict::new(py); d.set_item("params", ou_params_to_dict(py, &r.params)?)?; let rule = PyDict::new(py); @@ -46,7 +58,8 @@ fn otr_result_to_dict(py: Python<'_>, r: openquant::synthetic_backtesting::OtrSe rule.set_item("stop_loss", r.best_rule.stop_loss)?; d.set_item("best_rule", rule)?; d.set_item("best_point", surface_point_to_dict(py, &r.best_point)?)?; - let surface: Vec = r.response_surface.iter().map(|p| surface_point_to_dict(py, p)).collect::>()?; + let surface: Vec = + r.response_surface.iter().map(|p| surface_point_to_dict(py, p)).collect::>()?; d.set_item("response_surface", surface)?; d.set_item("diagnostics", diagnostics_to_dict(py, &r.diagnostics)?)?; Ok(d.into_pyobject(py).unwrap().into_any().unbind()) @@ -54,7 +67,8 @@ fn otr_result_to_dict(py: Python<'_>, r: openquant::synthetic_backtesting::OtrSe #[pyfunction(name = "calibrate_ou_params")] fn sbt_calibrate_ou_params(py: Python<'_>, prices: Vec) -> PyResult { - let params = openquant::synthetic_backtesting::calibrate_ou_params(&prices).map_err(to_py_err)?; + let params = + openquant::synthetic_backtesting::calibrate_ou_params(&prices).map_err(to_py_err)?; ou_params_to_dict(py, ¶ms) } @@ -72,10 +86,21 @@ fn sbt_generate_ou_paths( seed: u64, ) -> PyResult>> { let params = openquant::synthetic_backtesting::OuProcessParams { - phi, intercept, equilibrium, sigma, r_squared, stationary, + phi, + intercept, + equilibrium, + sigma, + r_squared, + stationary, }; - openquant::synthetic_backtesting::generate_ou_paths(params, initial_price, n_paths, horizon, seed) - .map_err(to_py_err) + openquant::synthetic_backtesting::generate_ou_paths( + params, + initial_price, + n_paths, + horizon, + seed, + ) + .map_err(to_py_err) } #[pyfunction(name = "evaluate_rule_on_paths")] @@ -89,7 +114,10 @@ fn sbt_evaluate_rule_on_paths( ) -> PyResult { let rule = openquant::synthetic_backtesting::TradingRule { profit_taking, stop_loss }; let result = openquant::synthetic_backtesting::evaluate_rule_on_paths( - &paths, rule, max_holding_steps, annualization_factor, + &paths, + rule, + max_holding_steps, + annualization_factor, ) .map_err(to_py_err)?; surface_point_to_dict(py, &result) @@ -109,7 +137,10 @@ fn sbt_detect_no_stable_optimum( .into_iter() .map(|(pt, sl, sharpe, mean_ret, std_ret, win_rate, avg_hold)| { openquant::synthetic_backtesting::RuleSurfacePoint { - rule: openquant::synthetic_backtesting::TradingRule { profit_taking: pt, stop_loss: sl }, + rule: openquant::synthetic_backtesting::TradingRule { + profit_taking: pt, + stop_loss: sl, + }, sharpe, mean_return: mean_ret, std_return: std_ret, @@ -124,8 +155,12 @@ fn sbt_detect_no_stable_optimum( min_surface_std, min_best_sharpe, }; - let result = openquant::synthetic_backtesting::detect_no_stable_optimum(&surface, estimated_phi, criteria) - .map_err(to_py_err)?; + let result = openquant::synthetic_backtesting::detect_no_stable_optimum( + &surface, + estimated_phi, + criteria, + ) + .map_err(to_py_err)?; diagnostics_to_dict(py, &result) } @@ -166,12 +201,10 @@ fn sbt_run_synthetic_otr_workflow( n_paths, horizon, seed, - profit_taking_grid: profit_taking_grid.unwrap_or_else(|| { - (1..=20).map(|i| i as f64 * 0.25).collect() - }), - stop_loss_grid: stop_loss_grid.unwrap_or_else(|| { - (1..=20).map(|i| i as f64 * -0.25).collect() - }), + profit_taking_grid: profit_taking_grid + .unwrap_or_else(|| (1..=20).map(|i| i as f64 * 0.25).collect()), + stop_loss_grid: stop_loss_grid + .unwrap_or_else(|| (1..=20).map(|i| i as f64 * -0.25).collect()), max_holding_steps, annualization_factor, stability_criteria: openquant::synthetic_backtesting::StabilityCriteria { @@ -181,8 +214,9 @@ fn sbt_run_synthetic_otr_workflow( min_best_sharpe, }, }; - let result = openquant::synthetic_backtesting::run_synthetic_otr_workflow(&historical_prices, &config) - .map_err(to_py_err)?; + let result = + openquant::synthetic_backtesting::run_synthetic_otr_workflow(&historical_prices, &config) + .map_err(to_py_err)?; otr_result_to_dict(py, result) } @@ -206,7 +240,12 @@ fn sbt_search_optimal_trading_rule( min_best_sharpe: f64, ) -> PyResult { let params = openquant::synthetic_backtesting::OuProcessParams { - phi, intercept, equilibrium, sigma, r_squared, stationary, + phi, + intercept, + equilibrium, + sigma, + r_squared, + stationary, }; let criteria = openquant::synthetic_backtesting::StabilityCriteria { random_walk_phi_threshold, @@ -215,8 +254,13 @@ fn sbt_search_optimal_trading_rule( min_best_sharpe, }; let result = openquant::synthetic_backtesting::search_optimal_trading_rule( - params, &paths, &profit_taking_grid, &stop_loss_grid, - max_holding_steps, annualization_factor, criteria, + params, + &paths, + &profit_taking_grid, + &stop_loss_grid, + max_holding_steps, + annualization_factor, + criteria, ) .map_err(to_py_err)?; otr_result_to_dict(py, result) diff --git a/crates/pyopenquant/src/volatility.rs b/crates/pyopenquant/src/volatility.rs index b31a2b1..09c8231 100644 --- a/crates/pyopenquant/src/volatility.rs +++ b/crates/pyopenquant/src/volatility.rs @@ -8,12 +8,10 @@ fn volatility_get_daily_vol( close_prices: Vec, lookback: usize, ) -> PyResult> { - let close = pair_timestamps_values(close_timestamps, close_prices, "close_timestamps", "close_prices")?; + let close = + pair_timestamps_values(close_timestamps, close_prices, "close_timestamps", "close_prices")?; let result = openquant::util::volatility::get_daily_vol(&close, lookback); - Ok(result - .into_iter() - .map(|(ts, v)| (ts.format("%Y-%m-%d %H:%M:%S").to_string(), v)) - .collect()) + Ok(result.into_iter().map(|(ts, v)| (ts.format("%Y-%m-%d %H:%M:%S").to_string(), v)).collect()) } #[pyfunction(name = "get_parksinson_vol")]