@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/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
|
+
};
|