@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.
Files changed (60) hide show
  1. package/README.md +161 -0
  2. package/bun-ffi.d.ts +54 -0
  3. package/dist/index.js +576 -0
  4. package/dist/src/index.d.ts +324 -0
  5. package/dist/src/index.d.ts.map +1 -0
  6. package/dist/types/index.d.ts +403 -0
  7. package/dist/types/index.d.ts.map +1 -0
  8. package/native/README.md +62 -0
  9. package/native/darwin-arm64/libquant_rust.dylib +0 -0
  10. package/package.json +70 -0
  11. package/scripts/postinstall.cjs +85 -0
  12. package/src/ffi.rs +496 -0
  13. package/src/index.ts +1073 -0
  14. package/src/indicators/ma.rs +222 -0
  15. package/src/indicators/mod.rs +18 -0
  16. package/src/indicators/momentum.rs +353 -0
  17. package/src/indicators/sr.rs +195 -0
  18. package/src/indicators/trend.rs +351 -0
  19. package/src/indicators/volatility.rs +270 -0
  20. package/src/indicators/volume.rs +213 -0
  21. package/src/lib.rs +130 -0
  22. package/src/patterns/breakout.rs +431 -0
  23. package/src/patterns/chart.rs +772 -0
  24. package/src/patterns/mod.rs +394 -0
  25. package/src/patterns/sr.rs +423 -0
  26. package/src/prediction/amm.rs +338 -0
  27. package/src/prediction/arbitrage.rs +230 -0
  28. package/src/prediction/calibration.rs +317 -0
  29. package/src/prediction/kelly.rs +232 -0
  30. package/src/prediction/lmsr.rs +194 -0
  31. package/src/prediction/mod.rs +59 -0
  32. package/src/prediction/odds.rs +229 -0
  33. package/src/prediction/pnl.rs +254 -0
  34. package/src/prediction/risk.rs +228 -0
  35. package/src/risk/beta.rs +257 -0
  36. package/src/risk/drawdown.rs +256 -0
  37. package/src/risk/leverage.rs +201 -0
  38. package/src/risk/mod.rs +388 -0
  39. package/src/risk/portfolio.rs +287 -0
  40. package/src/risk/ratios.rs +290 -0
  41. package/src/risk/sizing.rs +194 -0
  42. package/src/risk/var.rs +222 -0
  43. package/src/stats/cdf.rs +257 -0
  44. package/src/stats/correlation.rs +225 -0
  45. package/src/stats/distribution.rs +194 -0
  46. package/src/stats/hypothesis.rs +177 -0
  47. package/src/stats/matrix.rs +346 -0
  48. package/src/stats/mod.rs +257 -0
  49. package/src/stats/regression.rs +239 -0
  50. package/src/stats/rolling.rs +193 -0
  51. package/src/stats/timeseries.rs +263 -0
  52. package/src/types.rs +224 -0
  53. package/src/utils/mod.rs +215 -0
  54. package/src/utils/normalize.rs +192 -0
  55. package/src/utils/price.rs +167 -0
  56. package/src/utils/quantiles.rs +177 -0
  57. package/src/utils/returns.rs +158 -0
  58. package/src/utils/rolling.rs +97 -0
  59. package/src/utils/stats.rs +154 -0
  60. package/types/index.ts +513 -0
package/src/index.ts ADDED
@@ -0,0 +1,1073 @@
1
+ /**
2
+ * Bun FFI wrapper for quant-rust
3
+ *
4
+ * This module provides TypeScript bindings for the Rust quant library
5
+ * using Bun's FFI capabilities.
6
+ *
7
+ * @packageDocumentation
8
+ */
9
+
10
+ import { dlopen, FFIType, ptr } from "bun:ffi";
11
+ import { join, dirname } from "path";
12
+ import { fileURLToPath } from "url";
13
+ import { existsSync } from "fs";
14
+
15
+ // Import types - re-exported at the end
16
+ import type {
17
+ OHLCV,
18
+ AMMState,
19
+ AMMCostResult,
20
+ AMMPriceImpactResult,
21
+ LMSRState,
22
+ LMSRPriceResult,
23
+ KellyResult,
24
+ ArbitrageResult,
25
+ OddsConversion,
26
+ VaRResult,
27
+ DrawdownResult,
28
+ SharpeResult,
29
+ } from "../types/index.js";
30
+
31
+ // Get the path to the compiled library
32
+ const __dirname = dirname(fileURLToPath(import.meta.url));
33
+
34
+ /**
35
+ * Get the path to the native library based on platform
36
+ */
37
+ function getNativeLibPath(): string {
38
+ const platform = process.platform;
39
+ const arch = process.arch;
40
+
41
+ // Map platform/arch to library name
42
+ const libName = (() => {
43
+ switch (platform) {
44
+ case "darwin":
45
+ return "libquant_rust.dylib";
46
+ case "linux":
47
+ return "libquant_rust.so";
48
+ case "win32":
49
+ return "quant_rust.dll";
50
+ default:
51
+ throw new Error(`Unsupported platform: ${platform}`);
52
+ }
53
+ })();
54
+
55
+ // Try multiple paths in order
56
+ const possiblePaths = [
57
+ // Prebuilt binaries in native/ directory
58
+ join(__dirname, "..", "native", `${platform}-${arch}`, libName),
59
+ // Development build
60
+ join(__dirname, "..", "target", "release", libName),
61
+ // Relative from dist
62
+ join(__dirname, "..", "..", "target", "release", libName),
63
+ // Check multiple levels up
64
+ join(__dirname, "..", "..", "..", "target", "release", libName),
65
+ ];
66
+
67
+ for (const p of possiblePaths) {
68
+ if (existsSync(p)) {
69
+ return p;
70
+ }
71
+ }
72
+
73
+ throw new Error(
74
+ `Could not find quant_rust library for ${platform}-${arch}. ` +
75
+ `Please run 'cargo build --release' or ensure prebuilt binaries are in native/${platform}-${arch}/`
76
+ );
77
+ }
78
+
79
+ // Load the native library
80
+ const libPath = getNativeLibPath();
81
+
82
+ // Define FFI symbols for the native library
83
+ const symbols = {
84
+ // Version
85
+ quant_version: {
86
+ returns: FFIType.cstring,
87
+ args: [],
88
+ },
89
+
90
+ // Error handling
91
+ quant_last_error: {
92
+ returns: FFIType.cstring,
93
+ args: [],
94
+ },
95
+ quant_clear_error: {
96
+ returns: FFIType.void,
97
+ args: [],
98
+ },
99
+
100
+ // OHLCV
101
+ quant_ohlcv_new: {
102
+ returns: FFIType.cstring,
103
+ args: [FFIType.i64, FFIType.f64, FFIType.f64, FFIType.f64, FFIType.f64, FFIType.f64],
104
+ },
105
+
106
+ // AMM
107
+ quant_amm_new: {
108
+ returns: FFIType.cstring,
109
+ args: [FFIType.f64, FFIType.f64, FFIType.f64],
110
+ },
111
+ quant_amm_calculate_cost: {
112
+ returns: FFIType.cstring,
113
+ args: [FFIType.f64, FFIType.f64, FFIType.bool, FFIType.f64],
114
+ },
115
+ quant_amm_price_impact: {
116
+ returns: FFIType.cstring,
117
+ args: [FFIType.f64, FFIType.f64, FFIType.bool, FFIType.f64],
118
+ },
119
+
120
+ // LMSR
121
+ quant_lmsr_price: {
122
+ returns: FFIType.cstring,
123
+ args: [FFIType.f64, FFIType.f64, FFIType.f64],
124
+ },
125
+ quant_lmsr_cost: {
126
+ returns: FFIType.cstring,
127
+ args: [FFIType.f64, FFIType.f64, FFIType.f64, FFIType.bool, FFIType.f64],
128
+ },
129
+
130
+ // Kelly Criterion
131
+ quant_kelly_criterion: {
132
+ returns: FFIType.cstring,
133
+ args: [FFIType.f64, FFIType.f64, FFIType.f64],
134
+ },
135
+
136
+ // Arbitrage
137
+ quant_detect_arbitrage: {
138
+ returns: FFIType.cstring,
139
+ args: [FFIType.f64, FFIType.f64],
140
+ },
141
+
142
+ // Odds Conversion
143
+ quant_convert_odds: {
144
+ returns: FFIType.cstring,
145
+ args: [FFIType.f64, FFIType.i32],
146
+ },
147
+
148
+ // Statistics
149
+ quant_mean: {
150
+ returns: FFIType.f64,
151
+ args: [FFIType.ptr, FFIType.usize],
152
+ },
153
+ quant_std_dev: {
154
+ returns: FFIType.f64,
155
+ args: [FFIType.ptr, FFIType.usize],
156
+ },
157
+ quant_variance: {
158
+ returns: FFIType.f64,
159
+ args: [FFIType.ptr, FFIType.usize],
160
+ },
161
+ quant_correlation: {
162
+ returns: FFIType.f64,
163
+ args: [FFIType.ptr, FFIType.ptr, FFIType.usize],
164
+ },
165
+
166
+ // Memory management
167
+ quant_free_string: {
168
+ returns: FFIType.void,
169
+ args: [FFIType.ptr],
170
+ },
171
+ };
172
+
173
+ // Load the library
174
+ const lib = dlopen(libPath, symbols);
175
+
176
+ // ============================================================================
177
+ // Helper Functions
178
+ // ============================================================================
179
+
180
+ /**
181
+ * Parse a JSON string response from the native library
182
+ * @internal
183
+ */
184
+ function parseJsonResponse<T>(response: string | null): T {
185
+ if (!response) {
186
+ const error = lib.symbols.quant_last_error() as string;
187
+ throw new Error(error || "Unknown error from quant-rust");
188
+ }
189
+ try {
190
+ return JSON.parse(response) as T;
191
+ } catch (e) {
192
+ throw new Error(`Failed to parse JSON response: ${response}`);
193
+ }
194
+ }
195
+
196
+ /**
197
+ * Create a Float64Array pointer for FFI calls
198
+ * @internal
199
+ */
200
+ function createFloat64Ptr(data: number[]): { buffer: Float64Array; ptr: number } {
201
+ const buffer = new Float64Array(data);
202
+ return { buffer, ptr: ptr(buffer) };
203
+ }
204
+
205
+ // ============================================================================
206
+ // Library Info
207
+ // ============================================================================
208
+
209
+ /**
210
+ * Get the library version
211
+ */
212
+ export function getVersion(): string {
213
+ return lib.symbols.quant_version() as string;
214
+ }
215
+
216
+ /**
217
+ * Clear the last error
218
+ */
219
+ export function clearError(): void {
220
+ lib.symbols.quant_clear_error();
221
+ }
222
+
223
+ /**
224
+ * Get the path to the loaded native library
225
+ */
226
+ export function getLibraryPath(): string {
227
+ return libPath;
228
+ }
229
+
230
+ // ============================================================================
231
+ // OHLCV Functions
232
+ // ============================================================================
233
+
234
+ /**
235
+ * Create an OHLCV candlestick data structure
236
+ *
237
+ * @param timestamp - Unix timestamp in milliseconds
238
+ * @param open - Opening price
239
+ * @param high - High price
240
+ * @param low - Low price
241
+ * @param close - Closing price
242
+ * @param volume - Volume
243
+ * @returns OHLCV object
244
+ */
245
+ export function createOHLCV(
246
+ timestamp: number,
247
+ open: number,
248
+ high: number,
249
+ low: number,
250
+ close: number,
251
+ volume: number
252
+ ): OHLCV {
253
+ const response = lib.symbols.quant_ohlcv_new(
254
+ BigInt(timestamp),
255
+ open,
256
+ high,
257
+ low,
258
+ close,
259
+ volume
260
+ ) as string;
261
+ return parseJsonResponse(response);
262
+ }
263
+
264
+ // ============================================================================
265
+ // AMM Functions (Automated Market Maker)
266
+ // ============================================================================
267
+
268
+ /**
269
+ * Create an AMM state for constant-product AMMs
270
+ *
271
+ * @param poolYes - YES pool reserves
272
+ * @param poolNo - NO pool reserves
273
+ * @param fee - Fee as decimal (e.g., 0.003 for 0.3%)
274
+ * @returns AMMState object
275
+ */
276
+ export function createAMM(poolYes: number, poolNo: number, fee: number): AMMState {
277
+ const response = lib.symbols.quant_amm_new(poolYes, poolNo, fee) as string;
278
+ return parseJsonResponse(response);
279
+ }
280
+
281
+ /**
282
+ * Calculate the cost to buy shares from a constant-product AMM
283
+ *
284
+ * Uses the formula: cost = new_pool_no - pool_no where new_pool_no = k / (pool_yes + shares)
285
+ *
286
+ * @param poolYes - Current YES pool reserves
287
+ * @param poolNo - Current NO pool reserves
288
+ * @param outcome - Whether to buy 'yes' or 'no' shares
289
+ * @param shares - Number of shares to buy
290
+ * @returns Cost in currency units (always positive, number for API compatibility)
291
+ */
292
+ export function ammCalculateCost(
293
+ poolYes: number,
294
+ poolNo: number,
295
+ outcome: 'yes' | 'no' | boolean,
296
+ shares: number
297
+ ): number {
298
+ // Convert string outcome to boolean for FFI
299
+ const buyYes = typeof outcome === 'string' ? outcome === 'yes' : outcome;
300
+ const response = lib.symbols.quant_amm_calculate_cost(
301
+ poolYes,
302
+ poolNo,
303
+ buyYes,
304
+ shares
305
+ ) as string;
306
+ const result = parseJsonResponse<AMMCostResult>(response);
307
+ // Return absolute cost value for API compatibility with @ebowwa/quant
308
+ // (Rust returns negative values for the pool delta, we want positive cost)
309
+ return Math.abs(result.cost);
310
+ }
311
+
312
+ /**
313
+ * Calculate the cost to buy shares from a constant-product AMM (full result)
314
+ *
315
+ * @param poolYes - Current YES pool reserves
316
+ * @param poolNo - Current NO pool reserves
317
+ * @param outcome - Whether to buy 'yes' or 'no' shares
318
+ * @param shares - Number of shares to buy
319
+ * @returns Full cost calculation result with cost and avg_price (always positive)
320
+ */
321
+ export function ammCalculateCostFull(
322
+ poolYes: number,
323
+ poolNo: number,
324
+ outcome: 'yes' | 'no' | boolean,
325
+ shares: number
326
+ ): AMMCostResult {
327
+ const buyYes = typeof outcome === 'string' ? outcome === 'yes' : outcome;
328
+ const response = lib.symbols.quant_amm_calculate_cost(
329
+ poolYes,
330
+ poolNo,
331
+ buyYes,
332
+ shares
333
+ ) as string;
334
+ const result = parseJsonResponse<AMMCostResult>(response);
335
+ // Return absolute values for cost and avg_price
336
+ return {
337
+ cost: Math.abs(result.cost),
338
+ avg_price: Math.abs(result.avg_price)
339
+ };
340
+ }
341
+
342
+ /**
343
+ * Calculate the price impact for a trade on a constant-product AMM
344
+ *
345
+ * @param poolYes - Current YES pool reserves
346
+ * @param poolNo - Current NO pool reserves
347
+ * @param outcome - Whether to buy 'yes' or 'no' shares
348
+ * @param shares - Number of shares to buy
349
+ * @returns Price impact analysis
350
+ */
351
+ export function ammPriceImpact(
352
+ poolYes: number,
353
+ poolNo: number,
354
+ outcome: 'yes' | 'no' | boolean,
355
+ shares: number
356
+ ): AMMPriceImpactResult {
357
+ const buyYes = typeof outcome === 'string' ? outcome === 'yes' : outcome;
358
+ const response = lib.symbols.quant_amm_price_impact(
359
+ poolYes,
360
+ poolNo,
361
+ buyYes,
362
+ shares
363
+ ) as string;
364
+ return parseJsonResponse(response);
365
+ }
366
+
367
+ // Convenience aliases matching @ebowwa/quant API
368
+ export const amm_buy_cost = ammCalculateCost;
369
+ export const amm_calculate_cost = ammCalculateCost;
370
+ export const amm_price_impact = ammPriceImpact;
371
+
372
+ // ============================================================================
373
+ // LMSR Functions (Logarithmic Market Scoring Rule)
374
+ // ============================================================================
375
+
376
+ /**
377
+ * Calculate LMSR prices for a prediction market
378
+ *
379
+ * Uses the LMSR formula: price_yes = exp(q_yes/b) / (exp(q_yes/b) + exp(q_no/b))
380
+ *
381
+ * @param yesShares - YES shares outstanding
382
+ * @param noShares - NO shares outstanding
383
+ * @param b - Liquidity parameter (controls price sensitivity)
384
+ * @returns LMSR price result with yes_price and no_price
385
+ */
386
+ export function lmsrPrice(
387
+ yesShares: number,
388
+ noShares: number,
389
+ b: number
390
+ ): LMSRPriceResult {
391
+ const response = lib.symbols.quant_lmsr_price(yesShares, noShares, b) as string;
392
+ return parseJsonResponse(response);
393
+ }
394
+
395
+ /**
396
+ * Calculate the cost to buy shares using LMSR
397
+ *
398
+ * Uses the LMSR cost formula: C(q') - C(q) = b * ln(sum(exp(q'_i/b))) - b * ln(sum(exp(q_i/b)))
399
+ *
400
+ * @param yesShares - Current YES shares outstanding
401
+ * @param noShares - Current NO shares outstanding
402
+ * @param b - Liquidity parameter
403
+ * @param outcome - Whether to buy 'yes' or 'no' shares (or boolean)
404
+ * @param shares - Number of shares to buy
405
+ * @returns Cost calculation result
406
+ */
407
+ export function lmsrCost(
408
+ yesShares: number,
409
+ noShares: number,
410
+ b: number,
411
+ outcome: 'yes' | 'no' | boolean,
412
+ shares: number
413
+ ): AMMCostResult {
414
+ const buyYes = typeof outcome === 'string' ? outcome === 'yes' : outcome;
415
+ const response = lib.symbols.quant_lmsr_cost(
416
+ yesShares,
417
+ noShares,
418
+ b,
419
+ buyYes,
420
+ shares
421
+ ) as string;
422
+ return parseJsonResponse(response);
423
+ }
424
+
425
+ /**
426
+ * Calculate LMSR price and cost (unified function)
427
+ *
428
+ * @param yesShares - Current YES shares outstanding
429
+ * @param noShares - Current NO shares outstanding
430
+ * @param liquidityParam - Liquidity parameter (b)
431
+ * @param operation - "price" or "cost"
432
+ * @param outcome - "yes" or "no"
433
+ * @param sharesToBuy - Number of shares (only for cost operation)
434
+ * @returns Price or cost result
435
+ */
436
+ export function lmsrCalculate(
437
+ yesShares: number,
438
+ noShares: number,
439
+ liquidityParam: number,
440
+ operation: "price" | "cost",
441
+ outcome: "yes" | "no",
442
+ sharesToBuy?: number
443
+ ): LMSRPriceResult | AMMCostResult {
444
+ if (operation === "price") {
445
+ return lmsrPrice(yesShares, noShares, liquidityParam);
446
+ } else {
447
+ if (sharesToBuy === undefined) {
448
+ throw new Error("sharesToBuy is required for cost operation");
449
+ }
450
+ return lmsrCost(yesShares, noShares, liquidityParam, outcome === "yes", sharesToBuy);
451
+ }
452
+ }
453
+
454
+ // ============================================================================
455
+ // Kelly Criterion Functions
456
+ // ============================================================================
457
+
458
+ /**
459
+ * Calculate the Kelly criterion for optimal position sizing
460
+ *
461
+ * The Kelly criterion determines the optimal fraction of bankroll to bet
462
+ * based on your estimated probability vs the market price.
463
+ *
464
+ * @param yourProbability - Your estimated probability of winning (0-1)
465
+ * @param marketPrice - Current market price to buy shares (0-1)
466
+ * @param bankroll - Your total bankroll in currency units
467
+ * @returns Kelly criterion result with various bet sizes
468
+ */
469
+ export function kellyCriterion(
470
+ yourProbability: number,
471
+ marketPrice: number,
472
+ bankroll: number
473
+ ): KellyResult {
474
+ const response = lib.symbols.quant_kelly_criterion(
475
+ yourProbability,
476
+ marketPrice,
477
+ bankroll
478
+ ) as string;
479
+ return parseJsonResponse(response);
480
+ }
481
+
482
+ // Alias matching @ebowwa/quant API
483
+ export const kelly_criterion = kellyCriterion;
484
+
485
+ // ============================================================================
486
+ // Arbitrage Detection Functions
487
+ // ============================================================================
488
+
489
+ /**
490
+ * Detect arbitrage opportunities in prediction markets
491
+ *
492
+ * If yes_price + no_price < 1, there's an arbitrage opportunity.
493
+ *
494
+ * @param yesPrice - Current YES share price (0-1)
495
+ * @param noPrice - Current NO share price (0-1)
496
+ * @returns Arbitrage analysis with profit field for API compatibility
497
+ */
498
+ export function detectArbitrage(yesPrice: number, noPrice: number): ArbitrageResult & { profit: number } {
499
+ const response = lib.symbols.quant_detect_arbitrage(yesPrice, noPrice) as string;
500
+ const result = parseJsonResponse<ArbitrageResult>(response);
501
+ // Add profit alias for API compatibility (same as profit_per_share)
502
+ return { ...result, profit: result.profit_per_share };
503
+ }
504
+
505
+ // Alias matching @ebowwa/quant API
506
+ export const detect_arbitrage = detectArbitrage;
507
+
508
+ // ============================================================================
509
+ // Odds Conversion Functions
510
+ // ============================================================================
511
+
512
+ /** Odds input type */
513
+ export type OddsType = "probability" | "decimal" | "american";
514
+
515
+ /**
516
+ * Convert between probability, decimal odds, and American odds
517
+ *
518
+ * @param value - The value to convert
519
+ * @param fromType - The type of the input value
520
+ * @param toType - Optional target type (if specified, returns just that value)
521
+ * @returns Converted odds in all formats, or single value if toType specified
522
+ */
523
+ export function convertOdds(value: number, fromType: OddsType, toType?: OddsType): OddsConversion | number {
524
+ const typeMap: Record<OddsType, number> = {
525
+ probability: 0,
526
+ decimal: 1,
527
+ american: 2,
528
+ };
529
+ const response = lib.symbols.quant_convert_odds(value, typeMap[fromType]) as string;
530
+ const result = parseJsonResponse<OddsConversion>(response);
531
+
532
+ // If toType is specified, return just that value for API compatibility
533
+ if (toType) {
534
+ switch (toType) {
535
+ case 'probability':
536
+ return result.probability;
537
+ case 'decimal':
538
+ return result.decimal_odds;
539
+ case 'american':
540
+ return result.american_odds;
541
+ default:
542
+ return result;
543
+ }
544
+ }
545
+
546
+ return result;
547
+ }
548
+
549
+ // Alias matching @ebowwa/quant API
550
+ export const convert_odds = convertOdds;
551
+
552
+ // ============================================================================
553
+ // Statistical Functions
554
+ // ============================================================================
555
+
556
+ /**
557
+ * Calculate the mean (average) of an array of numbers
558
+ *
559
+ * @param data - Array of numbers
560
+ * @returns Mean value, or NaN if array is empty
561
+ */
562
+ export function mean(data: number[]): number {
563
+ if (data.length === 0) return NaN;
564
+ const { buffer, ptr: dataPtr } = createFloat64Ptr(data);
565
+ return lib.symbols.quant_mean(dataPtr, data.length) as number;
566
+ }
567
+
568
+ /**
569
+ * Calculate the standard deviation of an array of numbers
570
+ *
571
+ * @param data - Array of numbers
572
+ * @returns Standard deviation, or NaN if array is empty
573
+ */
574
+ export function stdDev(data: number[]): number {
575
+ if (data.length === 0) return NaN;
576
+ const { buffer, ptr: dataPtr } = createFloat64Ptr(data);
577
+ return lib.symbols.quant_std_dev(dataPtr, data.length) as number;
578
+ }
579
+
580
+ /**
581
+ * Calculate the variance of an array of numbers
582
+ *
583
+ * @param data - Array of numbers
584
+ * @returns Variance, or NaN if array is empty
585
+ */
586
+ export function variance(data: number[]): number {
587
+ if (data.length === 0) return NaN;
588
+ const { buffer, ptr: dataPtr } = createFloat64Ptr(data);
589
+ return lib.symbols.quant_variance(dataPtr, data.length) as number;
590
+ }
591
+
592
+ /**
593
+ * Calculate the Pearson correlation coefficient between two arrays
594
+ *
595
+ * @param x - First array of numbers
596
+ * @param y - Second array of numbers
597
+ * @returns Correlation coefficient (-1 to 1), or NaN if arrays are invalid
598
+ */
599
+ export function correlation(x: number[], y: number[]): number {
600
+ if (x.length === 0 || y.length === 0 || x.length !== y.length) return NaN;
601
+ const { buffer: bufferX, ptr: ptrX } = createFloat64Ptr(x);
602
+ const { buffer: bufferY, ptr: ptrY } = createFloat64Ptr(y);
603
+ return lib.symbols.quant_correlation(ptrX, ptrY, x.length) as number;
604
+ }
605
+
606
+ // Aliases matching @ebowwa/quant API
607
+ export const std_dev = stdDev;
608
+
609
+ // ============================================================================
610
+ // Technical Indicators (TypeScript implementations for array-based operations)
611
+ // ============================================================================
612
+
613
+ /**
614
+ * Simple Moving Average (SMA)
615
+ *
616
+ * @param prices - Array of prices
617
+ * @param period - Number of periods to average
618
+ * @returns Array of SMA values
619
+ */
620
+ export function sma(prices: number[], period: number): number[] {
621
+ if (prices.length < period || period <= 0) return [];
622
+
623
+ const result: number[] = [];
624
+ for (let i = period - 1; i < prices.length; i++) {
625
+ let sum = 0;
626
+ for (let j = i - period + 1; j <= i; j++) {
627
+ sum += prices[j];
628
+ }
629
+ result.push(sum / period);
630
+ }
631
+ return result;
632
+ }
633
+
634
+ /**
635
+ * Exponential Moving Average (EMA)
636
+ *
637
+ * @param prices - Array of prices
638
+ * @param period - EMA period
639
+ * @returns Array of EMA values
640
+ */
641
+ export function ema(prices: number[], period: number): number[] {
642
+ if (prices.length < period || period <= 0) return [];
643
+
644
+ const multiplier = 2 / (period + 1);
645
+ const result: number[] = [];
646
+
647
+ // First EMA value is SMA
648
+ let prevEma = prices.slice(0, period).reduce((a, b) => a + b, 0) / period;
649
+ result.push(prevEma);
650
+
651
+ // Calculate EMA for remaining values
652
+ for (let i = period; i < prices.length; i++) {
653
+ const currentEma = (prices[i] - prevEma) * multiplier + prevEma;
654
+ result.push(currentEma);
655
+ prevEma = currentEma;
656
+ }
657
+
658
+ return result;
659
+ }
660
+
661
+ /**
662
+ * Relative Strength Index (RSI)
663
+ *
664
+ * @param prices - Array of prices
665
+ * @param period - RSI period (default: 14)
666
+ * @returns Array of RSI values (0-100)
667
+ */
668
+ export function rsi(prices: number[], period: number = 14): number[] {
669
+ if (prices.length < period + 1 || period <= 0) return [];
670
+
671
+ const gains: number[] = [];
672
+ const losses: number[] = [];
673
+
674
+ // Calculate price changes
675
+ for (let i = 1; i < prices.length; i++) {
676
+ const change = prices[i] - prices[i - 1];
677
+ gains.push(change > 0 ? change : 0);
678
+ losses.push(change < 0 ? Math.abs(change) : 0);
679
+ }
680
+
681
+ // First average
682
+ let avgGain = gains.slice(0, period).reduce((a, b) => a + b, 0) / period;
683
+ let avgLoss = losses.slice(0, period).reduce((a, b) => a + b, 0) / period;
684
+
685
+ const result: number[] = [];
686
+
687
+ // First RSI
688
+ if (avgLoss === 0) {
689
+ result.push(100);
690
+ } else {
691
+ const rs = avgGain / avgLoss;
692
+ result.push(100 - 100 / (1 + rs));
693
+ }
694
+
695
+ // Subsequent values using smoothed average
696
+ for (let i = period; i < gains.length; i++) {
697
+ avgGain = (avgGain * (period - 1) + gains[i]) / period;
698
+ avgLoss = (avgLoss * (period - 1) + losses[i]) / period;
699
+
700
+ if (avgLoss === 0) {
701
+ result.push(100);
702
+ } else {
703
+ const rs = avgGain / avgLoss;
704
+ result.push(100 - 100 / (1 + rs));
705
+ }
706
+ }
707
+
708
+ return result;
709
+ }
710
+
711
+ /**
712
+ * MACD (Moving Average Convergence Divergence)
713
+ *
714
+ * @param prices - Array of prices
715
+ * @param fastPeriod - Fast EMA period (default: 12)
716
+ * @param slowPeriod - Slow EMA period (default: 26)
717
+ * @param signalPeriod - Signal line period (default: 9)
718
+ * @returns Object with macd, signal, and histogram arrays
719
+ */
720
+ export function macd(
721
+ prices: number[],
722
+ fastPeriod: number = 12,
723
+ slowPeriod: number = 26,
724
+ signalPeriod: number = 9
725
+ ): { macd: number[]; signal: number[]; histogram: number[] } {
726
+ if (prices.length < slowPeriod) {
727
+ return { macd: [], signal: [], histogram: [] };
728
+ }
729
+
730
+ const fastEma = ema(prices, fastPeriod);
731
+ const slowEma = ema(prices, slowPeriod);
732
+
733
+ // Align arrays
734
+ const offset = slowPeriod - fastPeriod;
735
+ const macdLine: number[] = [];
736
+
737
+ for (let i = 0; i < slowEma.length; i++) {
738
+ if (i + offset < fastEma.length) {
739
+ macdLine.push(fastEma[i + offset] - slowEma[i]);
740
+ }
741
+ }
742
+
743
+ const signalLine = ema(macdLine, signalPeriod);
744
+
745
+ // Align histogram with signal
746
+ const signalOffset = signalPeriod - 1;
747
+ const histogram: number[] = [];
748
+
749
+ for (let i = 0; i < signalLine.length; i++) {
750
+ if (i + signalOffset < macdLine.length) {
751
+ histogram.push(macdLine[i + signalOffset] - signalLine[i]);
752
+ }
753
+ }
754
+
755
+ return {
756
+ macd: macdLine.slice(signalOffset, signalOffset + histogram.length),
757
+ signal: signalLine,
758
+ histogram,
759
+ };
760
+ }
761
+
762
+ // ============================================================================
763
+ // Risk Management Functions (TypeScript implementations)
764
+ // ============================================================================
765
+
766
+ /**
767
+ * Calculate Value at Risk (VaR) and Expected Shortfall (CVaR)
768
+ *
769
+ * @param returns - Array of returns
770
+ * @param confidenceLevel - Confidence level (default: 0.95)
771
+ * @returns VaR result
772
+ */
773
+ export function calculateVar(
774
+ returns: number[],
775
+ confidenceLevel: number = 0.95
776
+ ): VaRResult {
777
+ if (returns.length === 0) {
778
+ return { var: 0, cvar: 0, confidence_level: confidenceLevel };
779
+ }
780
+
781
+ const sorted = [...returns].sort((a, b) => a - b);
782
+ const index = Math.floor((1 - confidenceLevel) * returns.length);
783
+ const clampedIndex = Math.min(index, sorted.length - 1);
784
+
785
+ const varValue = -sorted[clampedIndex];
786
+
787
+ // CVaR is the average of returns below VaR
788
+ const tailReturns = sorted.slice(0, clampedIndex + 1);
789
+ const cvar = tailReturns.length > 0
790
+ ? -tailReturns.reduce((a, b) => a + b, 0) / tailReturns.length
791
+ : varValue;
792
+
793
+ return { var: varValue, cvar, confidence_level: confidenceLevel };
794
+ }
795
+
796
+ /**
797
+ * Calculate drawdown analysis from equity curve
798
+ *
799
+ * @param equityCurve - Array of equity values over time
800
+ * @returns Drawdown analysis result
801
+ */
802
+ export function calculateDrawdown(equityCurve: number[]): DrawdownResult {
803
+ if (equityCurve.length < 2) {
804
+ return { max_drawdown: 0, max_duration: 0, current_drawdown: 0, recovery_factor: 0 };
805
+ }
806
+
807
+ let maxDrawdown = 0;
808
+ let maxDuration = 0;
809
+ let peak = equityCurve[0];
810
+ let lastPeakIdx = 0;
811
+
812
+ for (let i = 0; i < equityCurve.length; i++) {
813
+ const equity = equityCurve[i];
814
+ if (equity > peak) {
815
+ peak = equity;
816
+ lastPeakIdx = i;
817
+ } else {
818
+ const duration = i - lastPeakIdx;
819
+ const drawdown = (peak - equity) / peak;
820
+ if (drawdown > maxDrawdown) {
821
+ maxDrawdown = drawdown;
822
+ maxDuration = duration;
823
+ }
824
+ }
825
+ }
826
+
827
+ const currentDrawdown = peak > 0
828
+ ? (peak - equityCurve[equityCurve.length - 1]) / peak
829
+ : 0;
830
+
831
+ const totalReturn = equityCurve[0] > 0
832
+ ? (equityCurve[equityCurve.length - 1] - equityCurve[0]) / equityCurve[0]
833
+ : 0;
834
+
835
+ const recoveryFactor = maxDrawdown > 0 ? totalReturn / maxDrawdown : Infinity;
836
+
837
+ return {
838
+ max_drawdown: maxDrawdown,
839
+ max_duration: maxDuration,
840
+ current_drawdown: currentDrawdown,
841
+ recovery_factor: recoveryFactor,
842
+ };
843
+ }
844
+
845
+ /**
846
+ * Calculate Sharpe ratio
847
+ *
848
+ * @param returns - Array of returns
849
+ * @param riskFreeRate - Annual risk-free rate (default: 0.04)
850
+ * @param periodsPerYear - Trading periods per year (default: 252)
851
+ * @returns Annualized Sharpe ratio (number for API compatibility)
852
+ */
853
+ export function calculateSharpeRatio(
854
+ returns: number[],
855
+ riskFreeRate: number = 0.04,
856
+ periodsPerYear: number = 252
857
+ ): number {
858
+ if (returns.length === 0) {
859
+ return 0;
860
+ }
861
+
862
+ const avgReturn = returns.reduce((a, b) => a + b, 0) / returns.length;
863
+ const stdDev = Math.sqrt(
864
+ returns.reduce((sum, r) => sum + Math.pow(r - avgReturn, 2), 0) / returns.length
865
+ );
866
+
867
+ if (stdDev === 0) {
868
+ return 0;
869
+ }
870
+
871
+ const excessReturn = avgReturn - riskFreeRate / periodsPerYear;
872
+ const sharpeRatio = excessReturn / stdDev;
873
+ const annualizedSharpe = sharpeRatio * Math.sqrt(periodsPerYear);
874
+
875
+ return annualizedSharpe;
876
+ }
877
+
878
+ /**
879
+ * Calculate Sharpe ratio with full result
880
+ *
881
+ * @param returns - Array of returns
882
+ * @param riskFreeRate - Annual risk-free rate (default: 0.04)
883
+ * @param periodsPerYear - Trading periods per year (default: 252)
884
+ * @returns Full Sharpe ratio result object
885
+ */
886
+ export function calculateSharpeRatioFull(
887
+ returns: number[],
888
+ riskFreeRate: number = 0.04,
889
+ periodsPerYear: number = 252
890
+ ): SharpeResult {
891
+ if (returns.length === 0) {
892
+ return { sharpe_ratio: 0, annualized_sharpe: 0, risk_free_rate: riskFreeRate, avg_return: 0, std_dev: 0 };
893
+ }
894
+
895
+ const avgReturn = returns.reduce((a, b) => a + b, 0) / returns.length;
896
+ const stdDev = Math.sqrt(
897
+ returns.reduce((sum, r) => sum + Math.pow(r - avgReturn, 2), 0) / returns.length
898
+ );
899
+
900
+ if (stdDev === 0) {
901
+ return { sharpe_ratio: 0, annualized_sharpe: 0, risk_free_rate: riskFreeRate, avg_return: avgReturn, std_dev: 0 };
902
+ }
903
+
904
+ const excessReturn = avgReturn - riskFreeRate / periodsPerYear;
905
+ const sharpeRatio = excessReturn / stdDev;
906
+ const annualizedSharpe = sharpeRatio * Math.sqrt(periodsPerYear);
907
+
908
+ return {
909
+ sharpe_ratio: sharpeRatio,
910
+ annualized_sharpe: annualizedSharpe,
911
+ risk_free_rate: riskFreeRate,
912
+ avg_return: avgReturn,
913
+ std_dev: stdDev,
914
+ };
915
+ }
916
+
917
+ // Aliases matching @ebowwa/quant API
918
+ export const calculate_var = calculateVar;
919
+ export const calculate_drawdown = calculateDrawdown;
920
+ export const calculate_sharpe_ratio = calculateSharpeRatio;
921
+ export const sharpeRatio = calculateSharpeRatio;
922
+
923
+ /**
924
+ * Calculate Sortino ratio (only penalizes downside volatility)
925
+ *
926
+ * @param returns - Array of returns
927
+ * @param riskFreeRate - Annual risk-free rate (default: 0.04)
928
+ * @param periodsPerYear - Trading periods per year (default: 252)
929
+ * @returns Sortino ratio
930
+ */
931
+ export function calculateSortinoRatio(
932
+ returns: number[],
933
+ riskFreeRate: number = 0.04,
934
+ periodsPerYear: number = 252
935
+ ): number {
936
+ if (returns.length === 0) return 0;
937
+
938
+ const avgReturn = returns.reduce((a, b) => a + b, 0) / returns.length;
939
+ const target = riskFreeRate / periodsPerYear;
940
+
941
+ // Downside deviation
942
+ const downsideReturns = returns.filter(r => r < target);
943
+ const downsideVariance = downsideReturns.length > 0
944
+ ? downsideReturns.reduce((sum, r) => sum + Math.pow(r - target, 2), 0) / returns.length
945
+ : 0;
946
+
947
+ const downsideDev = Math.sqrt(downsideVariance);
948
+
949
+ if (downsideDev === 0) return Infinity;
950
+
951
+ const excessReturn = avgReturn - target;
952
+ return (excessReturn / downsideDev) * Math.sqrt(periodsPerYear);
953
+ }
954
+
955
+ /**
956
+ * Calculate beta and alpha against a benchmark
957
+ *
958
+ * @param assetReturns - Array of asset returns
959
+ * @param benchmarkReturns - Array of benchmark returns
960
+ * @returns Object with beta and alpha values
961
+ */
962
+ export function calculateBetaAlpha(
963
+ assetReturns: number[],
964
+ benchmarkReturns: number[]
965
+ ): { beta: number; alpha: number } {
966
+ if (
967
+ assetReturns.length === 0 ||
968
+ benchmarkReturns.length === 0 ||
969
+ assetReturns.length !== benchmarkReturns.length
970
+ ) {
971
+ return { beta: 0, alpha: 0 };
972
+ }
973
+
974
+ const n = assetReturns.length;
975
+ const avgAsset = assetReturns.reduce((a, b) => a + b, 0) / n;
976
+ const avgBench = benchmarkReturns.reduce((a, b) => a + b, 0) / n;
977
+
978
+ // Covariance
979
+ const cov = assetReturns.reduce(
980
+ (sum, a, i) => sum + (a - avgAsset) * (benchmarkReturns[i] - avgBench),
981
+ 0
982
+ ) / n;
983
+
984
+ // Benchmark variance
985
+ const varBench = benchmarkReturns.reduce(
986
+ (sum, r) => sum + Math.pow(r - avgBench, 2),
987
+ 0
988
+ ) / n;
989
+
990
+ if (varBench === 0) return { beta: 0, alpha: 0 };
991
+
992
+ const beta = cov / varBench;
993
+ const alpha = avgAsset - beta * avgBench;
994
+
995
+ return { beta, alpha };
996
+ }
997
+
998
+ // Aliases matching @ebowwa/quant API
999
+ export const calculate_sortino_ratio = calculateSortinoRatio;
1000
+ export const calculate_beta_alpha = calculateBetaAlpha;
1001
+
1002
+ // ============================================================================
1003
+ // Re-export Types
1004
+ // ============================================================================
1005
+
1006
+ export * from "../types/index.js";
1007
+
1008
+ // ============================================================================
1009
+ // Default Export
1010
+ // ============================================================================
1011
+
1012
+ export default {
1013
+ // Library info
1014
+ getVersion,
1015
+ clearError,
1016
+ getLibraryPath,
1017
+
1018
+ // OHLCV
1019
+ createOHLCV,
1020
+
1021
+ // AMM
1022
+ createAMM,
1023
+ ammCalculateCost,
1024
+ ammCalculateCostFull,
1025
+ ammPriceImpact,
1026
+ amm_buy_cost,
1027
+ amm_calculate_cost,
1028
+ amm_price_impact,
1029
+
1030
+ // LMSR
1031
+ lmsrPrice,
1032
+ lmsrCost,
1033
+ lmsrCalculate,
1034
+
1035
+ // Kelly
1036
+ kellyCriterion,
1037
+ kelly_criterion,
1038
+
1039
+ // Arbitrage
1040
+ detectArbitrage,
1041
+ detect_arbitrage,
1042
+
1043
+ // Odds
1044
+ convertOdds,
1045
+ convert_odds,
1046
+
1047
+ // Statistics
1048
+ mean,
1049
+ stdDev,
1050
+ std_dev,
1051
+ variance,
1052
+ correlation,
1053
+
1054
+ // Indicators
1055
+ sma,
1056
+ ema,
1057
+ rsi,
1058
+ macd,
1059
+
1060
+ // Risk
1061
+ calculateVar,
1062
+ calculate_var,
1063
+ calculateDrawdown,
1064
+ calculate_drawdown,
1065
+ calculateSharpeRatio,
1066
+ calculateSharpeRatioFull,
1067
+ calculate_sharpe_ratio,
1068
+ sharpeRatio,
1069
+ calculateSortinoRatio,
1070
+ calculate_sortino_ratio,
1071
+ calculateBetaAlpha,
1072
+ calculate_beta_alpha,
1073
+ };