@ebowwa/quant-rust 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +161 -0
- package/bun-ffi.d.ts +54 -0
- package/dist/index.js +576 -0
- package/dist/src/index.d.ts +324 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/types/index.d.ts +403 -0
- package/dist/types/index.d.ts.map +1 -0
- package/native/README.md +62 -0
- package/native/darwin-arm64/libquant_rust.dylib +0 -0
- package/package.json +70 -0
- package/scripts/postinstall.cjs +85 -0
- package/src/ffi.rs +496 -0
- package/src/index.ts +1073 -0
- package/src/indicators/ma.rs +222 -0
- package/src/indicators/mod.rs +18 -0
- package/src/indicators/momentum.rs +353 -0
- package/src/indicators/sr.rs +195 -0
- package/src/indicators/trend.rs +351 -0
- package/src/indicators/volatility.rs +270 -0
- package/src/indicators/volume.rs +213 -0
- package/src/lib.rs +130 -0
- package/src/patterns/breakout.rs +431 -0
- package/src/patterns/chart.rs +772 -0
- package/src/patterns/mod.rs +394 -0
- package/src/patterns/sr.rs +423 -0
- package/src/prediction/amm.rs +338 -0
- package/src/prediction/arbitrage.rs +230 -0
- package/src/prediction/calibration.rs +317 -0
- package/src/prediction/kelly.rs +232 -0
- package/src/prediction/lmsr.rs +194 -0
- package/src/prediction/mod.rs +59 -0
- package/src/prediction/odds.rs +229 -0
- package/src/prediction/pnl.rs +254 -0
- package/src/prediction/risk.rs +228 -0
- package/src/risk/beta.rs +257 -0
- package/src/risk/drawdown.rs +256 -0
- package/src/risk/leverage.rs +201 -0
- package/src/risk/mod.rs +388 -0
- package/src/risk/portfolio.rs +287 -0
- package/src/risk/ratios.rs +290 -0
- package/src/risk/sizing.rs +194 -0
- package/src/risk/var.rs +222 -0
- package/src/stats/cdf.rs +257 -0
- package/src/stats/correlation.rs +225 -0
- package/src/stats/distribution.rs +194 -0
- package/src/stats/hypothesis.rs +177 -0
- package/src/stats/matrix.rs +346 -0
- package/src/stats/mod.rs +257 -0
- package/src/stats/regression.rs +239 -0
- package/src/stats/rolling.rs +193 -0
- package/src/stats/timeseries.rs +263 -0
- package/src/types.rs +224 -0
- package/src/utils/mod.rs +215 -0
- package/src/utils/normalize.rs +192 -0
- package/src/utils/price.rs +167 -0
- package/src/utils/quantiles.rs +177 -0
- package/src/utils/returns.rs +158 -0
- package/src/utils/rolling.rs +97 -0
- package/src/utils/stats.rs +154 -0
- package/types/index.ts +513 -0
package/src/ffi.rs
ADDED
|
@@ -0,0 +1,496 @@
|
|
|
1
|
+
//! FFI (Foreign Function Interface) exports for Bun
|
|
2
|
+
//!
|
|
3
|
+
//! This module provides C ABI compatible functions for calling from TypeScript/Bun.
|
|
4
|
+
|
|
5
|
+
use std::ffi::CString;
|
|
6
|
+
use std::os::raw::c_char;
|
|
7
|
+
use std::ptr;
|
|
8
|
+
use std::sync::Mutex;
|
|
9
|
+
|
|
10
|
+
use crate::types::*;
|
|
11
|
+
|
|
12
|
+
/// Thread-safe error storage
|
|
13
|
+
static LAST_ERROR: Mutex<Option<CString>> = Mutex::new(None);
|
|
14
|
+
|
|
15
|
+
/// Set the last error message
|
|
16
|
+
fn set_last_error(msg: &str) {
|
|
17
|
+
if let Ok(mut guard) = LAST_ERROR.lock() {
|
|
18
|
+
*guard = Some(CString::new(msg).unwrap_or_else(|_| CString::new("Invalid error message").unwrap()));
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/// Get the last error message (returns null if no error)
|
|
23
|
+
#[no_mangle]
|
|
24
|
+
pub extern "C" fn quant_last_error() -> *const c_char {
|
|
25
|
+
if let Ok(guard) = LAST_ERROR.lock() {
|
|
26
|
+
if let Some(ref s) = *guard {
|
|
27
|
+
return s.as_ptr();
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
ptr::null()
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/// Clear the last error
|
|
34
|
+
#[no_mangle]
|
|
35
|
+
pub extern "C" fn quant_clear_error() {
|
|
36
|
+
if let Ok(mut guard) = LAST_ERROR.lock() {
|
|
37
|
+
*guard = None;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/// Helper to return a JSON string via FFI
|
|
42
|
+
fn return_json<T: serde::Serialize>(result: Result<T, String>) -> *mut c_char {
|
|
43
|
+
match result {
|
|
44
|
+
Ok(value) => {
|
|
45
|
+
match serde_json::to_string(&value) {
|
|
46
|
+
Ok(json) => CString::new(json).unwrap().into_raw(),
|
|
47
|
+
Err(e) => {
|
|
48
|
+
set_last_error(&format!("JSON serialization error: {}", e));
|
|
49
|
+
ptr::null_mut()
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
Err(e) => {
|
|
54
|
+
set_last_error(&e);
|
|
55
|
+
ptr::null_mut()
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/// Helper to return a JSON value directly
|
|
61
|
+
fn return_json_value(value: serde_json::Value) -> *mut c_char {
|
|
62
|
+
match serde_json::to_string(&value) {
|
|
63
|
+
Ok(json) => CString::new(json).unwrap().into_raw(),
|
|
64
|
+
Err(e) => {
|
|
65
|
+
set_last_error(&format!("JSON serialization error: {}", e));
|
|
66
|
+
ptr::null_mut()
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// ============================================================================
|
|
72
|
+
// OHLCV Functions
|
|
73
|
+
// ============================================================================
|
|
74
|
+
|
|
75
|
+
/// Create an OHLCV struct and return as JSON
|
|
76
|
+
#[no_mangle]
|
|
77
|
+
pub extern "C" fn quant_ohlcv_new(
|
|
78
|
+
timestamp: i64,
|
|
79
|
+
open: f64,
|
|
80
|
+
high: f64,
|
|
81
|
+
low: f64,
|
|
82
|
+
close: f64,
|
|
83
|
+
volume: f64,
|
|
84
|
+
) -> *mut c_char {
|
|
85
|
+
let ohlcv = OHLCV::new(timestamp, open, high, low, close, volume);
|
|
86
|
+
return_json(Ok(ohlcv))
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/// Calculate typical price for OHLCV data array
|
|
90
|
+
/// Input: pointer to f64 array of [open, high, low, close, volume, open2, high2, ...]
|
|
91
|
+
/// count: number of OHLCV candles
|
|
92
|
+
/// Output: pointer to f64 array of typical prices (caller must free with quant_free_f64_array)
|
|
93
|
+
#[no_mangle]
|
|
94
|
+
pub extern "C" fn quant_ohlcv_typical_prices(
|
|
95
|
+
data: *const f64,
|
|
96
|
+
count: usize,
|
|
97
|
+
out_len: *mut usize,
|
|
98
|
+
) -> *mut f64 {
|
|
99
|
+
if data.is_null() || out_len.is_null() {
|
|
100
|
+
set_last_error("Null pointer provided");
|
|
101
|
+
return ptr::null_mut();
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
let data_slice = unsafe { std::slice::from_raw_parts(data, count * 6) };
|
|
105
|
+
let mut results = Vec::with_capacity(count);
|
|
106
|
+
|
|
107
|
+
for i in 0..count {
|
|
108
|
+
let base = i * 6;
|
|
109
|
+
let high = data_slice[base + 1];
|
|
110
|
+
let low = data_slice[base + 2];
|
|
111
|
+
let close = data_slice[base + 3];
|
|
112
|
+
results.push((high + low + close) / 3.0);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
unsafe { *out_len = results.len(); }
|
|
116
|
+
results.leak().as_mut_ptr()
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// ============================================================================
|
|
120
|
+
// AMM Functions
|
|
121
|
+
// ============================================================================
|
|
122
|
+
|
|
123
|
+
/// Create an AMM state and return as JSON
|
|
124
|
+
#[no_mangle]
|
|
125
|
+
pub extern "C" fn quant_amm_new(pool_yes: f64, pool_no: f64, fee: f64) -> *mut c_char {
|
|
126
|
+
let amm = AMMState::new(pool_yes, pool_no, fee);
|
|
127
|
+
return_json(Ok(amm))
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/// Calculate cost to buy shares from AMM
|
|
131
|
+
#[no_mangle]
|
|
132
|
+
pub extern "C" fn quant_amm_calculate_cost(
|
|
133
|
+
pool_yes: f64,
|
|
134
|
+
pool_no: f64,
|
|
135
|
+
buy_yes: bool,
|
|
136
|
+
shares: f64,
|
|
137
|
+
) -> *mut c_char {
|
|
138
|
+
let result = if shares <= 0.0 {
|
|
139
|
+
Err("Shares must be positive".to_string())
|
|
140
|
+
} else if pool_yes <= 0.0 || pool_no <= 0.0 {
|
|
141
|
+
Err("Pool reserves must be positive".to_string())
|
|
142
|
+
} else {
|
|
143
|
+
let k = pool_yes * pool_no;
|
|
144
|
+
let cost = if buy_yes {
|
|
145
|
+
// Cost to buy YES shares: new_pool_no - pool_no where new_pool_no = k / (pool_yes + shares)
|
|
146
|
+
let new_pool_yes = pool_yes + shares;
|
|
147
|
+
let new_pool_no = k / new_pool_yes;
|
|
148
|
+
new_pool_no - pool_no
|
|
149
|
+
} else {
|
|
150
|
+
// Cost to buy NO shares
|
|
151
|
+
let new_pool_no = pool_no + shares;
|
|
152
|
+
let new_pool_yes = k / new_pool_no;
|
|
153
|
+
new_pool_yes - pool_yes
|
|
154
|
+
};
|
|
155
|
+
Ok(serde_json::json!({"cost": cost, "avg_price": cost / shares}))
|
|
156
|
+
};
|
|
157
|
+
match result {
|
|
158
|
+
Ok(value) => return_json_value(value),
|
|
159
|
+
Err(e) => return_json(Err::<serde_json::Value, String>(e))
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/// Calculate price impact for a trade
|
|
164
|
+
#[no_mangle]
|
|
165
|
+
pub extern "C" fn quant_amm_price_impact(
|
|
166
|
+
pool_yes: f64,
|
|
167
|
+
pool_no: f64,
|
|
168
|
+
buy_yes: bool,
|
|
169
|
+
shares: f64,
|
|
170
|
+
) -> *mut c_char {
|
|
171
|
+
let result = if shares <= 0.0 {
|
|
172
|
+
Err("Shares must be positive".to_string())
|
|
173
|
+
} else if pool_yes <= 0.0 || pool_no <= 0.0 {
|
|
174
|
+
Err("Pool reserves must be positive".to_string())
|
|
175
|
+
} else {
|
|
176
|
+
let initial_price = pool_no / (pool_yes + pool_no);
|
|
177
|
+
let k = pool_yes * pool_no;
|
|
178
|
+
|
|
179
|
+
let (new_pool_yes, new_pool_no) = if buy_yes {
|
|
180
|
+
let new_yes = pool_yes + shares;
|
|
181
|
+
(new_yes, k / new_yes)
|
|
182
|
+
} else {
|
|
183
|
+
let new_no = pool_no + shares;
|
|
184
|
+
(k / new_no, new_no)
|
|
185
|
+
};
|
|
186
|
+
|
|
187
|
+
let final_price = new_pool_no / (new_pool_yes + new_pool_no);
|
|
188
|
+
let impact = (final_price - initial_price).abs() / initial_price;
|
|
189
|
+
|
|
190
|
+
Ok(serde_json::json!({
|
|
191
|
+
"initial_price": initial_price,
|
|
192
|
+
"final_price": final_price,
|
|
193
|
+
"price_impact": impact,
|
|
194
|
+
"impact_bps": impact * 10000.0
|
|
195
|
+
}))
|
|
196
|
+
};
|
|
197
|
+
match result {
|
|
198
|
+
Ok(value) => return_json_value(value),
|
|
199
|
+
Err(e) => return_json(Err::<serde_json::Value, String>(e))
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// ============================================================================
|
|
204
|
+
// LMSR Functions
|
|
205
|
+
// ============================================================================
|
|
206
|
+
|
|
207
|
+
/// Calculate LMSR price
|
|
208
|
+
#[no_mangle]
|
|
209
|
+
pub extern "C" fn quant_lmsr_price(yes_shares: f64, no_shares: f64, b: f64) -> *mut c_char {
|
|
210
|
+
let result = if b <= 0.0 {
|
|
211
|
+
Err("Liquidity parameter b must be positive".to_string())
|
|
212
|
+
} else {
|
|
213
|
+
let state = LMSRState::new(yes_shares, no_shares, b);
|
|
214
|
+
Ok(serde_json::json!({
|
|
215
|
+
"yes_price": state.yes_price(),
|
|
216
|
+
"no_price": state.no_price()
|
|
217
|
+
}))
|
|
218
|
+
};
|
|
219
|
+
match result {
|
|
220
|
+
Ok(value) => return_json_value(value),
|
|
221
|
+
Err(e) => return_json(Err::<serde_json::Value, String>(e))
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/// Calculate LMSR cost to buy shares
|
|
226
|
+
#[no_mangle]
|
|
227
|
+
pub extern "C" fn quant_lmsr_cost(
|
|
228
|
+
yes_shares: f64,
|
|
229
|
+
no_shares: f64,
|
|
230
|
+
b: f64,
|
|
231
|
+
buy_yes: bool,
|
|
232
|
+
shares: f64,
|
|
233
|
+
) -> *mut c_char {
|
|
234
|
+
let result = if b <= 0.0 {
|
|
235
|
+
Err("Liquidity parameter b must be positive".to_string())
|
|
236
|
+
} else if shares <= 0.0 {
|
|
237
|
+
Err("Shares must be positive".to_string())
|
|
238
|
+
} else {
|
|
239
|
+
// LMSR cost formula: C(q') - C(q) = b * ln(sum(exp(q'_i/b))) - b * ln(sum(exp(q_i/b)))
|
|
240
|
+
let cost = if buy_yes {
|
|
241
|
+
let before = b * ((yes_shares / b).exp() + (no_shares / b).exp()).ln();
|
|
242
|
+
let after = b * (((yes_shares + shares) / b).exp() + (no_shares / b).exp()).ln();
|
|
243
|
+
after - before
|
|
244
|
+
} else {
|
|
245
|
+
let before = b * ((yes_shares / b).exp() + (no_shares / b).exp()).ln();
|
|
246
|
+
let after = b * ((yes_shares / b).exp() + ((no_shares + shares) / b).exp()).ln();
|
|
247
|
+
after - before
|
|
248
|
+
};
|
|
249
|
+
Ok(serde_json::json!({"cost": cost, "avg_price": cost / shares}))
|
|
250
|
+
};
|
|
251
|
+
match result {
|
|
252
|
+
Ok(value) => return_json_value(value),
|
|
253
|
+
Err(e) => return_json(Err::<serde_json::Value, String>(e))
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// ============================================================================
|
|
258
|
+
// Kelly Criterion Functions
|
|
259
|
+
// ============================================================================
|
|
260
|
+
|
|
261
|
+
/// Calculate Kelly criterion for prediction market
|
|
262
|
+
#[no_mangle]
|
|
263
|
+
pub extern "C" fn quant_kelly_criterion(
|
|
264
|
+
your_probability: f64,
|
|
265
|
+
market_price: f64,
|
|
266
|
+
bankroll: f64,
|
|
267
|
+
) -> *mut c_char {
|
|
268
|
+
let result = if your_probability <= 0.0 || your_probability >= 1.0 {
|
|
269
|
+
Err("Probability must be between 0 and 1".to_string())
|
|
270
|
+
} else if market_price <= 0.0 || market_price >= 1.0 {
|
|
271
|
+
Err("Market price must be between 0 and 1".to_string())
|
|
272
|
+
} else if bankroll <= 0.0 {
|
|
273
|
+
Err("Bankroll must be positive".to_string())
|
|
274
|
+
} else {
|
|
275
|
+
let edge = your_probability - market_price;
|
|
276
|
+
let odds = (1.0 - market_price) / market_price;
|
|
277
|
+
|
|
278
|
+
// Kelly fraction: (bp - q) / b where b = odds, p = your_prob, q = 1 - your_prob
|
|
279
|
+
let kelly = (odds * your_probability - (1.0 - your_probability)) / odds;
|
|
280
|
+
let kelly = kelly.max(0.0); // Never bet negative
|
|
281
|
+
|
|
282
|
+
let half_kelly = kelly / 2.0;
|
|
283
|
+
let quarter_kelly = kelly / 4.0;
|
|
284
|
+
|
|
285
|
+
let result = KellyResult {
|
|
286
|
+
kelly_fraction: kelly,
|
|
287
|
+
half_kelly,
|
|
288
|
+
quarter_kelly,
|
|
289
|
+
full_bet_size: kelly * bankroll,
|
|
290
|
+
half_bet_size: half_kelly * bankroll,
|
|
291
|
+
quarter_bet_size: quarter_kelly * bankroll,
|
|
292
|
+
edge,
|
|
293
|
+
odds,
|
|
294
|
+
};
|
|
295
|
+
Ok(result)
|
|
296
|
+
};
|
|
297
|
+
return_json(result)
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// ============================================================================
|
|
301
|
+
// Arbitrage Detection
|
|
302
|
+
// ============================================================================
|
|
303
|
+
|
|
304
|
+
/// Detect arbitrage opportunity (if yes_price + no_price < 1)
|
|
305
|
+
#[no_mangle]
|
|
306
|
+
pub extern "C" fn quant_detect_arbitrage(yes_price: f64, no_price: f64) -> *mut c_char {
|
|
307
|
+
let result = if yes_price < 0.0 || yes_price > 1.0 || no_price < 0.0 || no_price > 1.0 {
|
|
308
|
+
Err("Prices must be between 0 and 1".to_string())
|
|
309
|
+
} else {
|
|
310
|
+
let total = yes_price + no_price;
|
|
311
|
+
let has_arb = total < 1.0;
|
|
312
|
+
let profit_per_share = if has_arb { 1.0 - total } else { 0.0 };
|
|
313
|
+
|
|
314
|
+
Ok(serde_json::json!({
|
|
315
|
+
"has_arbitrage": has_arb,
|
|
316
|
+
"yes_price": yes_price,
|
|
317
|
+
"no_price": no_price,
|
|
318
|
+
"total_price": total,
|
|
319
|
+
"profit_per_share": profit_per_share,
|
|
320
|
+
"profit_bps": profit_per_share * 10000.0
|
|
321
|
+
}))
|
|
322
|
+
};
|
|
323
|
+
match result {
|
|
324
|
+
Ok(value) => return_json_value(value),
|
|
325
|
+
Err(e) => return_json(Err::<serde_json::Value, String>(e))
|
|
326
|
+
}
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
// ============================================================================
|
|
330
|
+
// Odds Conversion
|
|
331
|
+
// ============================================================================
|
|
332
|
+
|
|
333
|
+
/// Convert between probability, decimal odds, and American odds
|
|
334
|
+
#[no_mangle]
|
|
335
|
+
pub extern "C" fn quant_convert_odds(value: f64, from_type: i32) -> *mut c_char {
|
|
336
|
+
// from_type: 0 = probability, 1 = decimal odds, 2 = american odds
|
|
337
|
+
let result: Result<serde_json::Value, String> = match from_type {
|
|
338
|
+
0 => {
|
|
339
|
+
// Input is probability
|
|
340
|
+
if value <= 0.0 || value >= 1.0 {
|
|
341
|
+
Err("Probability must be between 0 and 1".to_string())
|
|
342
|
+
} else {
|
|
343
|
+
let decimal = 1.0 / value;
|
|
344
|
+
let american = if value >= 0.5 {
|
|
345
|
+
-((value / (1.0 - value)) * 100.0).round()
|
|
346
|
+
} else {
|
|
347
|
+
((1.0 - value) / value * 100.0).round()
|
|
348
|
+
};
|
|
349
|
+
Ok(serde_json::json!({
|
|
350
|
+
"probability": value,
|
|
351
|
+
"decimal_odds": decimal,
|
|
352
|
+
"american_odds": american
|
|
353
|
+
}))
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
1 => {
|
|
357
|
+
// Input is decimal odds
|
|
358
|
+
if value < 1.0 {
|
|
359
|
+
Err("Decimal odds must be >= 1".to_string())
|
|
360
|
+
} else {
|
|
361
|
+
let probability = 1.0 / value;
|
|
362
|
+
let american = if probability >= 0.5 {
|
|
363
|
+
-((probability / (1.0 - probability)) * 100.0).round()
|
|
364
|
+
} else {
|
|
365
|
+
((1.0 - probability) / probability * 100.0).round()
|
|
366
|
+
};
|
|
367
|
+
Ok(serde_json::json!({
|
|
368
|
+
"probability": probability,
|
|
369
|
+
"decimal_odds": value,
|
|
370
|
+
"american_odds": american
|
|
371
|
+
}))
|
|
372
|
+
}
|
|
373
|
+
}
|
|
374
|
+
2 => {
|
|
375
|
+
// Input is American odds
|
|
376
|
+
if value == 0.0 {
|
|
377
|
+
Err("American odds cannot be 0".to_string())
|
|
378
|
+
} else {
|
|
379
|
+
let probability = if value > 0.0 {
|
|
380
|
+
100.0 / (value + 100.0)
|
|
381
|
+
} else {
|
|
382
|
+
(-value) / ((-value) + 100.0)
|
|
383
|
+
};
|
|
384
|
+
let decimal = 1.0 / probability;
|
|
385
|
+
Ok(serde_json::json!({
|
|
386
|
+
"probability": probability,
|
|
387
|
+
"decimal_odds": decimal,
|
|
388
|
+
"american_odds": value
|
|
389
|
+
}))
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
_ => Err("Invalid from_type: must be 0 (probability), 1 (decimal), or 2 (american)".to_string()),
|
|
393
|
+
};
|
|
394
|
+
match result {
|
|
395
|
+
Ok(value) => return_json_value(value),
|
|
396
|
+
Err(e) => return_json(Err::<serde_json::Value, String>(e))
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// ============================================================================
|
|
401
|
+
// Memory Management
|
|
402
|
+
// ============================================================================
|
|
403
|
+
|
|
404
|
+
/// Free a string returned from FFI
|
|
405
|
+
#[no_mangle]
|
|
406
|
+
pub extern "C" fn quant_free_string(s: *mut c_char) {
|
|
407
|
+
if !s.is_null() {
|
|
408
|
+
unsafe { drop(CString::from_raw(s)); }
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
/// Free a f64 array returned from FFI
|
|
413
|
+
#[no_mangle]
|
|
414
|
+
pub extern "C" fn quant_free_f64_array(ptr: *mut f64, len: usize) {
|
|
415
|
+
if !ptr.is_null() {
|
|
416
|
+
unsafe { drop(Vec::from_raw_parts(ptr, len, len)); }
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
/// Get library version
|
|
421
|
+
#[no_mangle]
|
|
422
|
+
pub extern "C" fn quant_version() -> *mut c_char {
|
|
423
|
+
CString::new(env!("CARGO_PKG_VERSION")).unwrap().into_raw()
|
|
424
|
+
}
|
|
425
|
+
|
|
426
|
+
// ============================================================================
|
|
427
|
+
// Simple Statistical Functions
|
|
428
|
+
// ============================================================================
|
|
429
|
+
|
|
430
|
+
/// Calculate mean of f64 array
|
|
431
|
+
#[no_mangle]
|
|
432
|
+
pub extern "C" fn quant_mean(data: *const f64, len: usize) -> f64 {
|
|
433
|
+
if data.is_null() || len == 0 {
|
|
434
|
+
return f64::NAN;
|
|
435
|
+
}
|
|
436
|
+
let slice = unsafe { std::slice::from_raw_parts(data, len) };
|
|
437
|
+
slice.iter().sum::<f64>() / len as f64
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
/// Calculate standard deviation of f64 array
|
|
441
|
+
#[no_mangle]
|
|
442
|
+
pub extern "C" fn quant_std_dev(data: *const f64, len: usize) -> f64 {
|
|
443
|
+
if data.is_null() || len == 0 {
|
|
444
|
+
return f64::NAN;
|
|
445
|
+
}
|
|
446
|
+
let slice = unsafe { std::slice::from_raw_parts(data, len) };
|
|
447
|
+
let mean = slice.iter().sum::<f64>() / len as f64;
|
|
448
|
+
let variance = slice.iter()
|
|
449
|
+
.map(|x| (x - mean).powi(2))
|
|
450
|
+
.sum::<f64>() / len as f64;
|
|
451
|
+
variance.sqrt()
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
/// Calculate variance of f64 array
|
|
455
|
+
#[no_mangle]
|
|
456
|
+
pub extern "C" fn quant_variance(data: *const f64, len: usize) -> f64 {
|
|
457
|
+
if data.is_null() || len == 0 {
|
|
458
|
+
return f64::NAN;
|
|
459
|
+
}
|
|
460
|
+
let slice = unsafe { std::slice::from_raw_parts(data, len) };
|
|
461
|
+
let mean = slice.iter().sum::<f64>() / len as f64;
|
|
462
|
+
slice.iter()
|
|
463
|
+
.map(|x| (x - mean).powi(2))
|
|
464
|
+
.sum::<f64>() / len as f64
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
/// Calculate correlation between two f64 arrays
|
|
468
|
+
#[no_mangle]
|
|
469
|
+
pub extern "C" fn quant_correlation(x: *const f64, y: *const f64, len: usize) -> f64 {
|
|
470
|
+
if x.is_null() || y.is_null() || len == 0 {
|
|
471
|
+
return f64::NAN;
|
|
472
|
+
}
|
|
473
|
+
let x_slice = unsafe { std::slice::from_raw_parts(x, len) };
|
|
474
|
+
let y_slice = unsafe { std::slice::from_raw_parts(y, len) };
|
|
475
|
+
|
|
476
|
+
let mean_x = x_slice.iter().sum::<f64>() / len as f64;
|
|
477
|
+
let mean_y = y_slice.iter().sum::<f64>() / len as f64;
|
|
478
|
+
|
|
479
|
+
let mut cov = 0.0;
|
|
480
|
+
let mut var_x = 0.0;
|
|
481
|
+
let mut var_y = 0.0;
|
|
482
|
+
|
|
483
|
+
for i in 0..len {
|
|
484
|
+
let dx = x_slice[i] - mean_x;
|
|
485
|
+
let dy = y_slice[i] - mean_y;
|
|
486
|
+
cov += dx * dy;
|
|
487
|
+
var_x += dx * dx;
|
|
488
|
+
var_y += dy * dy;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
if var_x == 0.0 || var_y == 0.0 {
|
|
492
|
+
return f64::NAN;
|
|
493
|
+
}
|
|
494
|
+
|
|
495
|
+
cov / (var_x.sqrt() * var_y.sqrt())
|
|
496
|
+
}
|