@ebowwa/quant-rust 0.1.0 → 0.2.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/dist/index.js +176 -21
- package/dist/src/index.d.ts +121 -6
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/ts-fns.d.ts +115 -0
- package/dist/src/ts-fns.d.ts.map +1 -0
- package/package.json +1 -1
- package/src/ffi.rs +151 -0
- package/src/index.ts +203 -10
- package/src/ts-fns.ts +275 -0
- package/native/README.md +0 -62
- package/native/darwin-arm64/libquant_rust.dylib +0 -0
package/src/ts-fns.ts
ADDED
|
@@ -0,0 +1,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pure TypeScript implementations for scalar operations
|
|
3
|
+
*
|
|
4
|
+
* WHY USE THESE INSTEAD OF RUST FFI?
|
|
5
|
+
*
|
|
6
|
+
* For simple scalar operations (Kelly, Arbitrage, Odds conversion), the FFI
|
|
7
|
+
* crossing overhead dominates the computation time:
|
|
8
|
+
*
|
|
9
|
+
* ┌─────────────────────────────────────────────────────────────┐
|
|
10
|
+
* │ FFI Call Breakdown (per call) │
|
|
11
|
+
* ├─────────────────────────────────────────────────────────────┤
|
|
12
|
+
* │ JS → C type conversion: ~500ns │
|
|
13
|
+
* │ Cross FFI boundary: ~500ns │
|
|
14
|
+
* │ Rust computation: ~5ns ← Rust IS fast! │
|
|
15
|
+
* │ C → JS type conversion: ~500ns │
|
|
16
|
+
* │ JSON parse (result): ~500ns │
|
|
17
|
+
* │ Cross FFI boundary back: ~500ns │
|
|
18
|
+
* │ ───────────────────────────────────── │
|
|
19
|
+
* │ Total FFI overhead: ~2500ns │
|
|
20
|
+
* │ Actual computation: ~5ns │
|
|
21
|
+
* │ Overhead/computation ratio: 500:1 │
|
|
22
|
+
* └─────────────────────────────────────────────────────────────┘
|
|
23
|
+
*
|
|
24
|
+
* For array operations (SMA, Drawdown, etc.), the O(n) computation
|
|
25
|
+
* amortizes the FFI cost, making Rust 10-20x faster.
|
|
26
|
+
*
|
|
27
|
+
* For scalar operations, TypeScript is 20-40x faster because there's
|
|
28
|
+
* no FFI overhead - just pure arithmetic in V8.
|
|
29
|
+
*
|
|
30
|
+
* @module ts-fns
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import type { KellyResult, ArbitrageResult, OddsConversion } from "../types/index.js";
|
|
34
|
+
|
|
35
|
+
// ============================================================================
|
|
36
|
+
// KELLY CRITERION - TypeScript Implementation (20-40x faster than FFI)
|
|
37
|
+
// ============================================================================
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* Calculate optimal bet size using Kelly criterion
|
|
41
|
+
*
|
|
42
|
+
* Kelly formula for prediction markets:
|
|
43
|
+
* b = (1 - marketPrice) / marketPrice (odds received)
|
|
44
|
+
* kelly = (b * yourProb - (1 - yourProb)) / b
|
|
45
|
+
*
|
|
46
|
+
* @param yourProbability - Your estimated probability of winning (0-1)
|
|
47
|
+
* @param marketPrice - Current market price to buy shares (0-1)
|
|
48
|
+
* @param bankroll - Your total bankroll in currency units
|
|
49
|
+
* @returns Kelly calculation results
|
|
50
|
+
*/
|
|
51
|
+
export function kellyCriterion(
|
|
52
|
+
yourProbability: number,
|
|
53
|
+
marketPrice: number,
|
|
54
|
+
bankroll: number
|
|
55
|
+
): KellyResult {
|
|
56
|
+
// Validate inputs
|
|
57
|
+
if (yourProbability <= 0 || yourProbability >= 1) {
|
|
58
|
+
throw new Error("Probability must be between 0 and 1 (exclusive)");
|
|
59
|
+
}
|
|
60
|
+
if (marketPrice <= 0 || marketPrice >= 1) {
|
|
61
|
+
throw new Error("Market price must be between 0 and 1 (exclusive)");
|
|
62
|
+
}
|
|
63
|
+
if (bankroll <= 0) {
|
|
64
|
+
throw new Error("Bankroll must be positive");
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const edge = yourProbability - marketPrice;
|
|
68
|
+
const odds = (1 - marketPrice) / marketPrice;
|
|
69
|
+
|
|
70
|
+
// Kelly fraction: (bp - q) / b where b = odds, p = yourProb, q = 1 - yourProb
|
|
71
|
+
let kellyFraction = (odds * yourProbability - (1 - yourProbability)) / odds;
|
|
72
|
+
kellyFraction = Math.max(0, kellyFraction); // Never bet negative
|
|
73
|
+
|
|
74
|
+
const halfKelly = kellyFraction / 2;
|
|
75
|
+
const quarterKelly = kellyFraction / 4;
|
|
76
|
+
|
|
77
|
+
return {
|
|
78
|
+
kelly_fraction: kellyFraction,
|
|
79
|
+
half_kelly: halfKelly,
|
|
80
|
+
quarter_kelly: quarterKelly,
|
|
81
|
+
full_bet_size: kellyFraction * bankroll,
|
|
82
|
+
half_bet_size: halfKelly * bankroll,
|
|
83
|
+
quarter_bet_size: quarterKelly * bankroll,
|
|
84
|
+
edge,
|
|
85
|
+
odds,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Calculate fractional Kelly (for risk management)
|
|
91
|
+
*/
|
|
92
|
+
export function fractionalKelly(
|
|
93
|
+
yourProbability: number,
|
|
94
|
+
marketPrice: number,
|
|
95
|
+
bankroll: number,
|
|
96
|
+
fraction: number = 0.5
|
|
97
|
+
): number {
|
|
98
|
+
const kelly = kellyCriterion(yourProbability, marketPrice, bankroll);
|
|
99
|
+
return kelly.full_bet_size * fraction;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// ============================================================================
|
|
103
|
+
// ARBITRAGE DETECTION - TypeScript Implementation (15-40x faster than FFI)
|
|
104
|
+
// ============================================================================
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Detect arbitrage opportunity in prediction market prices
|
|
108
|
+
*
|
|
109
|
+
* Arbitrage exists when: yesPrice + noPrice < 1
|
|
110
|
+
* Profit = 1 - (yesPrice + noPrice)
|
|
111
|
+
*
|
|
112
|
+
* @param yesPrice - Current YES share price (0-1)
|
|
113
|
+
* @param noPrice - Current NO share price (0-1)
|
|
114
|
+
* @returns Arbitrage analysis with profit calculation
|
|
115
|
+
*/
|
|
116
|
+
export function detectArbitrage(
|
|
117
|
+
yesPrice: number,
|
|
118
|
+
noPrice: number
|
|
119
|
+
): ArbitrageResult & { profit: number } {
|
|
120
|
+
const totalPrice = yesPrice + noPrice;
|
|
121
|
+
const hasArbitrage = totalPrice < 1;
|
|
122
|
+
const profitPerShare = hasArbitrage ? 1 - totalPrice : 0;
|
|
123
|
+
|
|
124
|
+
return {
|
|
125
|
+
has_arbitrage: hasArbitrage,
|
|
126
|
+
yes_price: yesPrice,
|
|
127
|
+
no_price: noPrice,
|
|
128
|
+
total_price: totalPrice,
|
|
129
|
+
profit_per_share: profitPerShare,
|
|
130
|
+
profit_bps: profitPerShare * 10000,
|
|
131
|
+
profit: profitPerShare, // Alias for convenience
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Find arbitrage opportunities across multiple markets
|
|
137
|
+
*/
|
|
138
|
+
export function findCrossMarketArbitrage(
|
|
139
|
+
markets: Array<{ yesPrice: number; noPrice: number; name?: string }>
|
|
140
|
+
): Array<ArbitrageResult & { profit: number; name?: string }> {
|
|
141
|
+
return markets
|
|
142
|
+
.map((m) => ({
|
|
143
|
+
...detectArbitrage(m.yesPrice, m.noPrice),
|
|
144
|
+
name: m.name,
|
|
145
|
+
}))
|
|
146
|
+
.filter((r) => r.has_arbitrage)
|
|
147
|
+
.sort((a, b) => b.profit - a.profit);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// ============================================================================
|
|
151
|
+
// ODDS CONVERSION - TypeScript Implementation (20-40x faster than FFI)
|
|
152
|
+
// ============================================================================
|
|
153
|
+
|
|
154
|
+
export type OddsType = "probability" | "decimal" | "american";
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Convert between probability, decimal odds, and American odds
|
|
158
|
+
*
|
|
159
|
+
* @param value - The value to convert
|
|
160
|
+
* @param fromType - The type of the input value
|
|
161
|
+
* @returns All three formats
|
|
162
|
+
*/
|
|
163
|
+
export function convertOdds(
|
|
164
|
+
value: number,
|
|
165
|
+
fromType: OddsType = "probability"
|
|
166
|
+
): OddsConversion {
|
|
167
|
+
let probability: number;
|
|
168
|
+
|
|
169
|
+
switch (fromType) {
|
|
170
|
+
case "probability":
|
|
171
|
+
probability = value;
|
|
172
|
+
break;
|
|
173
|
+
case "decimal":
|
|
174
|
+
probability = 1 / value;
|
|
175
|
+
break;
|
|
176
|
+
case "american":
|
|
177
|
+
probability = value > 0 ? 100 / (value + 100) : -value / (-value + 100);
|
|
178
|
+
break;
|
|
179
|
+
default:
|
|
180
|
+
throw new Error(`Unknown odds type: ${fromType}`);
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
// Calculate decimal odds
|
|
184
|
+
const decimalOdds = 1 / probability;
|
|
185
|
+
|
|
186
|
+
// Calculate American odds
|
|
187
|
+
let americanOdds: number;
|
|
188
|
+
if (probability >= 0.5) {
|
|
189
|
+
americanOdds = -Math.round((probability / (1 - probability)) * 100);
|
|
190
|
+
} else {
|
|
191
|
+
americanOdds = Math.round(((1 - probability) / probability) * 100);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
return {
|
|
195
|
+
probability,
|
|
196
|
+
decimal_odds: decimalOdds,
|
|
197
|
+
american_odds: americanOdds,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* Convert probability to decimal odds
|
|
203
|
+
*/
|
|
204
|
+
export function probToDecimalOdds(prob: number): number {
|
|
205
|
+
return 1 / prob;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Convert probability to American odds
|
|
210
|
+
*/
|
|
211
|
+
export function probToAmericanOdds(prob: number): number {
|
|
212
|
+
if (prob >= 0.5) {
|
|
213
|
+
return -Math.round((prob / (1 - prob)) * 100);
|
|
214
|
+
}
|
|
215
|
+
return Math.round(((1 - prob) / prob) * 100);
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Convert decimal odds to probability
|
|
220
|
+
*/
|
|
221
|
+
export function decimalOddsToProb(odds: number): number {
|
|
222
|
+
return 1 / odds;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Convert American odds to probability
|
|
227
|
+
*/
|
|
228
|
+
export function americanOddsToProb(odds: number): number {
|
|
229
|
+
if (odds > 0) {
|
|
230
|
+
return 100 / (odds + 100);
|
|
231
|
+
}
|
|
232
|
+
return -odds / (-odds + 100);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// ============================================================================
|
|
236
|
+
// EDGE CALCULATIONS - TypeScript (no FFI overhead)
|
|
237
|
+
// ============================================================================
|
|
238
|
+
|
|
239
|
+
/**
|
|
240
|
+
* Calculate your edge in a bet
|
|
241
|
+
*/
|
|
242
|
+
export function calculateEdge(
|
|
243
|
+
yourProbability: number,
|
|
244
|
+
marketPrice: number
|
|
245
|
+
): number {
|
|
246
|
+
return yourProbability - marketPrice;
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
/**
|
|
250
|
+
* Calculate expected value of a bet
|
|
251
|
+
*/
|
|
252
|
+
export function expectedValue(
|
|
253
|
+
yourProbability: number,
|
|
254
|
+
winAmount: number,
|
|
255
|
+
loseAmount: number
|
|
256
|
+
): number {
|
|
257
|
+
return yourProbability * winAmount - (1 - yourProbability) * loseAmount;
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* Check if a bet has positive expected value
|
|
262
|
+
*/
|
|
263
|
+
export function hasPositiveEV(
|
|
264
|
+
yourProbability: number,
|
|
265
|
+
marketPrice: number
|
|
266
|
+
): boolean {
|
|
267
|
+
return calculateEdge(yourProbability, marketPrice) > 0;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
/**
|
|
271
|
+
* Calculate break-even probability for a given price
|
|
272
|
+
*/
|
|
273
|
+
export function breakEvenProbability(marketPrice: number): number {
|
|
274
|
+
return marketPrice;
|
|
275
|
+
}
|
package/native/README.md
DELETED
|
@@ -1,62 +0,0 @@
|
|
|
1
|
-
# Native Binaries
|
|
2
|
-
|
|
3
|
-
This directory contains prebuilt native binaries for the `@ebowwa/quant-rust` package.
|
|
4
|
-
|
|
5
|
-
## Directory Structure
|
|
6
|
-
|
|
7
|
-
```
|
|
8
|
-
native/
|
|
9
|
-
├── darwin-x64/ # macOS (Intel)
|
|
10
|
-
│ └── libquant_rust.dylib
|
|
11
|
-
├── darwin-arm64/ # macOS (Apple Silicon)
|
|
12
|
-
│ └── libquant_rust.dylib
|
|
13
|
-
├── linux-x64/ # Linux (x86_64)
|
|
14
|
-
│ └── libquant_rust.so
|
|
15
|
-
└── win32-x64/ # Windows (x86_64)
|
|
16
|
-
└── quant_rust.dll
|
|
17
|
-
```
|
|
18
|
-
|
|
19
|
-
## Building from Source
|
|
20
|
-
|
|
21
|
-
If a prebuilt binary is not available for your platform, you can build from source:
|
|
22
|
-
|
|
23
|
-
```bash
|
|
24
|
-
# Navigate to the package directory
|
|
25
|
-
cd node_modules/@ebowwa/quant-rust
|
|
26
|
-
|
|
27
|
-
# Build with Cargo
|
|
28
|
-
cargo build --release
|
|
29
|
-
```
|
|
30
|
-
|
|
31
|
-
The compiled library will be placed in `target/release/`.
|
|
32
|
-
|
|
33
|
-
## Supported Platforms
|
|
34
|
-
|
|
35
|
-
| Platform | Architecture | Binary Name |
|
|
36
|
-
|----------|--------------|-------------|
|
|
37
|
-
| macOS | x64 (Intel) | libquant_rust.dylib |
|
|
38
|
-
| macOS | arm64 (Apple Silicon) | libquant_rust.dylib |
|
|
39
|
-
| Linux | x64 | libquant_rust.so |
|
|
40
|
-
| Windows | x64 | quant_rust.dll |
|
|
41
|
-
|
|
42
|
-
## Adding Prebuilt Binaries
|
|
43
|
-
|
|
44
|
-
To add prebuilt binaries for distribution:
|
|
45
|
-
|
|
46
|
-
1. Build the library on the target platform:
|
|
47
|
-
```bash
|
|
48
|
-
cargo build --release
|
|
49
|
-
```
|
|
50
|
-
|
|
51
|
-
2. Copy the binary to the appropriate directory:
|
|
52
|
-
```bash
|
|
53
|
-
cp target/release/libquant_rust.dylib native/darwin-arm64/
|
|
54
|
-
```
|
|
55
|
-
|
|
56
|
-
3. The postinstall script will automatically detect and use the correct binary.
|
|
57
|
-
|
|
58
|
-
## Binary Requirements
|
|
59
|
-
|
|
60
|
-
- Rust 1.70 or later
|
|
61
|
-
- Target platform toolchain
|
|
62
|
-
- For cross-compilation, see the Rust cross-compilation documentation
|
|
Binary file
|