orderflow-metrics 0.24.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 (134) hide show
  1. package/CHANGELOG.md +229 -0
  2. package/LICENSE +21 -0
  3. package/README.md +556 -0
  4. package/dist/bars.d.ts +67 -0
  5. package/dist/bars.d.ts.map +1 -0
  6. package/dist/bars.js +92 -0
  7. package/dist/bars.js.map +1 -0
  8. package/dist/classify.d.ts +25 -0
  9. package/dist/classify.d.ts.map +1 -0
  10. package/dist/classify.js +57 -0
  11. package/dist/classify.js.map +1 -0
  12. package/dist/covariance.d.ts +37 -0
  13. package/dist/covariance.d.ts.map +1 -0
  14. package/dist/covariance.js +72 -0
  15. package/dist/covariance.js.map +1 -0
  16. package/dist/efficiency.d.ts +24 -0
  17. package/dist/efficiency.d.ts.map +1 -0
  18. package/dist/efficiency.js +65 -0
  19. package/dist/efficiency.js.map +1 -0
  20. package/dist/entropy.d.ts +40 -0
  21. package/dist/entropy.d.ts.map +1 -0
  22. package/dist/entropy.js +73 -0
  23. package/dist/entropy.js.map +1 -0
  24. package/dist/execution.d.ts +40 -0
  25. package/dist/execution.d.ts.map +1 -0
  26. package/dist/execution.js +64 -0
  27. package/dist/execution.js.map +1 -0
  28. package/dist/fairvalue.d.ts +19 -0
  29. package/dist/fairvalue.d.ts.map +1 -0
  30. package/dist/fairvalue.js +27 -0
  31. package/dist/fairvalue.js.map +1 -0
  32. package/dist/hurst.d.ts +23 -0
  33. package/dist/hurst.d.ts.map +1 -0
  34. package/dist/hurst.js +71 -0
  35. package/dist/hurst.js.map +1 -0
  36. package/dist/imbalance.d.ts +12 -0
  37. package/dist/imbalance.d.ts.map +1 -0
  38. package/dist/imbalance.js +19 -0
  39. package/dist/imbalance.js.map +1 -0
  40. package/dist/impact.d.ts +59 -0
  41. package/dist/impact.d.ts.map +1 -0
  42. package/dist/impact.js +63 -0
  43. package/dist/impact.js.map +1 -0
  44. package/dist/index.d.ts +39 -0
  45. package/dist/index.d.ts.map +1 -0
  46. package/dist/index.js +25 -0
  47. package/dist/index.js.map +1 -0
  48. package/dist/jumps.d.ts +33 -0
  49. package/dist/jumps.d.ts.map +1 -0
  50. package/dist/jumps.js +63 -0
  51. package/dist/jumps.js.map +1 -0
  52. package/dist/liquidity.d.ts +18 -0
  53. package/dist/liquidity.d.ts.map +1 -0
  54. package/dist/liquidity.js +22 -0
  55. package/dist/liquidity.js.map +1 -0
  56. package/dist/moments.d.ts +20 -0
  57. package/dist/moments.d.ts.map +1 -0
  58. package/dist/moments.js +48 -0
  59. package/dist/moments.js.map +1 -0
  60. package/dist/ofi.d.ts +22 -0
  61. package/dist/ofi.d.ts.map +1 -0
  62. package/dist/ofi.js +25 -0
  63. package/dist/ofi.js.map +1 -0
  64. package/dist/online.d.ts +101 -0
  65. package/dist/online.d.ts.map +1 -0
  66. package/dist/online.js +205 -0
  67. package/dist/online.js.map +1 -0
  68. package/dist/orderbook.d.ts +30 -0
  69. package/dist/orderbook.d.ts.map +1 -0
  70. package/dist/orderbook.js +66 -0
  71. package/dist/orderbook.js.map +1 -0
  72. package/dist/rangevol.d.ts +40 -0
  73. package/dist/rangevol.d.ts.map +1 -0
  74. package/dist/rangevol.js +101 -0
  75. package/dist/rangevol.js.map +1 -0
  76. package/dist/scheduling.d.ts +19 -0
  77. package/dist/scheduling.d.ts.map +1 -0
  78. package/dist/scheduling.js +45 -0
  79. package/dist/scheduling.js.map +1 -0
  80. package/dist/semivar.d.ts +49 -0
  81. package/dist/semivar.d.ts.map +1 -0
  82. package/dist/semivar.js +59 -0
  83. package/dist/semivar.js.map +1 -0
  84. package/dist/shortfall.d.ts +42 -0
  85. package/dist/shortfall.d.ts.map +1 -0
  86. package/dist/shortfall.js +32 -0
  87. package/dist/shortfall.js.map +1 -0
  88. package/dist/simulate.d.ts +29 -0
  89. package/dist/simulate.d.ts.map +1 -0
  90. package/dist/simulate.js +36 -0
  91. package/dist/simulate.js.map +1 -0
  92. package/dist/spreadest.d.ts +38 -0
  93. package/dist/spreadest.d.ts.map +1 -0
  94. package/dist/spreadest.js +68 -0
  95. package/dist/spreadest.js.map +1 -0
  96. package/dist/types.d.ts +24 -0
  97. package/dist/types.d.ts.map +1 -0
  98. package/dist/types.js +9 -0
  99. package/dist/types.js.map +1 -0
  100. package/dist/volatility.d.ts +21 -0
  101. package/dist/volatility.d.ts.map +1 -0
  102. package/dist/volatility.js +33 -0
  103. package/dist/volatility.js.map +1 -0
  104. package/dist/vpin.d.ts +38 -0
  105. package/dist/vpin.d.ts.map +1 -0
  106. package/dist/vpin.js +81 -0
  107. package/dist/vpin.js.map +1 -0
  108. package/package.json +70 -0
  109. package/src/bars.ts +141 -0
  110. package/src/classify.ts +59 -0
  111. package/src/covariance.ts +72 -0
  112. package/src/efficiency.ts +64 -0
  113. package/src/entropy.ts +67 -0
  114. package/src/execution.ts +100 -0
  115. package/src/fairvalue.ts +33 -0
  116. package/src/hurst.ts +77 -0
  117. package/src/imbalance.ts +25 -0
  118. package/src/impact.ts +99 -0
  119. package/src/index.ts +87 -0
  120. package/src/jumps.ts +61 -0
  121. package/src/liquidity.ts +29 -0
  122. package/src/moments.ts +45 -0
  123. package/src/ofi.ts +46 -0
  124. package/src/online.ts +228 -0
  125. package/src/orderbook.ts +76 -0
  126. package/src/rangevol.ts +108 -0
  127. package/src/scheduling.ts +50 -0
  128. package/src/semivar.ts +67 -0
  129. package/src/shortfall.ts +69 -0
  130. package/src/simulate.ts +68 -0
  131. package/src/spreadest.ts +75 -0
  132. package/src/types.ts +26 -0
  133. package/src/volatility.ts +36 -0
  134. package/src/vpin.ts +98 -0
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Execution-cost and price-impact metrics.
3
+ *
4
+ * Standard transaction-cost analysis (TCA) building blocks:
5
+ * - effective spread — realized cost against the quote midpoint
6
+ * - realized spread — liquidity-provider revenue (post-trade reversion)
7
+ * - price impact — permanent component (effective − realized)
8
+ * - Kyle's lambda — price impact per unit of signed order flow
9
+ * - Roll's estimator — effective spread implied by price-change autocovariance
10
+ *
11
+ * Sign convention: buys are +1, sells are −1. All spread measures are in the
12
+ * same price units as the inputs.
13
+ */
14
+ import type { Side } from "./types.ts";
15
+
16
+ function dir(side: Side): 1 | -1 {
17
+ return side === "buy" ? 1 : -1;
18
+ }
19
+
20
+ /** Effective half-spread: d·(price − mid). */
21
+ export function effectiveHalfSpread(
22
+ price: number,
23
+ mid: number,
24
+ side: Side,
25
+ ): number {
26
+ return dir(side) * (price - mid);
27
+ }
28
+
29
+ /** Effective (full) spread: 2·d·(price − mid). */
30
+ export function effectiveSpread(price: number, mid: number, side: Side): number {
31
+ return 2 * effectiveHalfSpread(price, mid, side);
32
+ }
33
+
34
+ /** Realized (full) spread using the mid observed Δ later: 2·d·(price − midAfter). */
35
+ export function realizedSpread(
36
+ price: number,
37
+ midAfter: number,
38
+ side: Side,
39
+ ): number {
40
+ return 2 * dir(side) * (price - midAfter);
41
+ }
42
+
43
+ /** Permanent price impact: 2·d·(midAfter − mid) = effectiveSpread − realizedSpread. */
44
+ export function priceImpact(mid: number, midAfter: number, side: Side): number {
45
+ return 2 * dir(side) * (midAfter - mid);
46
+ }
47
+
48
+ export interface FlowObservation {
49
+ /** mid-price change over the interval */
50
+ priceChange: number;
51
+ /** signed traded volume (positive = net buy) */
52
+ signedVolume: number;
53
+ }
54
+
55
+ /**
56
+ * Kyle's lambda — OLS slope of price change on signed order flow
57
+ * (ΔP = λ · signedVolume + ε). Returns 0 for degenerate input.
58
+ */
59
+ export function kyleLambda(obs: FlowObservation[]): number {
60
+ const n = obs.length;
61
+ if (n < 2) return 0;
62
+
63
+ let mx = 0;
64
+ let my = 0;
65
+ for (const o of obs) {
66
+ mx += o.signedVolume;
67
+ my += o.priceChange;
68
+ }
69
+ mx /= n;
70
+ my /= n;
71
+
72
+ let cov = 0;
73
+ let varx = 0;
74
+ for (const o of obs) {
75
+ const dx = o.signedVolume - mx;
76
+ cov += dx * (o.priceChange - my);
77
+ varx += dx * dx;
78
+ }
79
+ return varx === 0 ? 0 : cov / varx;
80
+ }
81
+
82
+ /**
83
+ * Roll's (1984) implied effective spread from a series of trade prices:
84
+ * 2·√(−cov(ΔP_t, ΔP_{t-1})). Returns 0 when the autocovariance is
85
+ * non-negative (the estimator is undefined there).
86
+ */
87
+ export function rollSpread(prices: number[]): number {
88
+ if (prices.length < 3) return 0;
89
+
90
+ const dp: number[] = [];
91
+ for (let i = 1; i < prices.length; i++) dp.push(prices[i] - prices[i - 1]);
92
+
93
+ const n = dp.length;
94
+ const mean = dp.reduce((a, b) => a + b, 0) / n;
95
+ let cov = 0;
96
+ for (let i = 1; i < n; i++) cov += (dp[i] - mean) * (dp[i - 1] - mean);
97
+ cov /= n - 1;
98
+
99
+ return cov < 0 ? 2 * Math.sqrt(-cov) : 0;
100
+ }
@@ -0,0 +1,33 @@
1
+ /**
2
+ * Fair-value and quoting helpers derived from the top of book.
3
+ */
4
+ import type { L1Quote } from "./types.ts";
5
+
6
+ /**
7
+ * Imbalance-weighted mid price (a simple micro-price).
8
+ *
9
+ * weightedMid = bid · askSize/(bidSize+askSize) + ask · bidSize/(bidSize+askSize)
10
+ *
11
+ * Each side is weighted by the *opposite* size, so heavy resting bid size
12
+ * pulls the estimate toward the ask (upward pressure), and vice versa. Falls
13
+ * back to the arithmetic mid when both sizes are zero.
14
+ */
15
+ export function weightedMid(q: L1Quote): number {
16
+ const total = q.bidSize + q.askSize;
17
+ if (total === 0) return (q.bidPrice + q.askPrice) / 2;
18
+ return (
19
+ q.bidPrice * (q.askSize / total) + q.askPrice * (q.bidSize / total)
20
+ );
21
+ }
22
+
23
+ /** Arithmetic mid price: (bid + ask) / 2. */
24
+ export function mid(q: L1Quote): number {
25
+ return (q.bidPrice + q.askPrice) / 2;
26
+ }
27
+
28
+ /** Quoted spread in basis points: (ask − bid) / mid · 10_000. */
29
+ export function relativeSpreadBps(q: L1Quote): number {
30
+ const m = mid(q);
31
+ if (m === 0) return 0;
32
+ return ((q.askPrice - q.bidPrice) / m) * 10_000;
33
+ }
package/src/hurst.ts ADDED
@@ -0,0 +1,77 @@
1
+ /**
2
+ * Hurst exponent via rescaled-range (R/S) analysis.
3
+ *
4
+ * The Hurst exponent H characterises the long-memory of a series:
5
+ * - H ≈ 0.5 — no memory (white noise; the increments of a random walk)
6
+ * - H > 0.5 — persistent / trending (moves tend to continue)
7
+ * - H < 0.5 — anti-persistent / mean-reverting (moves tend to reverse)
8
+ *
9
+ * It is estimated from the classic rescaled range: over windows of growing
10
+ * size n, the average R/S statistic scales like n^H, so H is the slope of
11
+ * log(R/S) against log(n). A companion to the market-efficiency metrics
12
+ * (autocorrelation, variance ratio).
13
+ *
14
+ * Feed it a roughly stationary series — typically returns, not raw prices.
15
+ * Returns NaN when the series is too short to form at least two window scales
16
+ * (with the default minimum window, that means fewer than ~32 points).
17
+ */
18
+
19
+ export interface HurstOptions {
20
+ /** Smallest window size for the R/S regression (default 8). */
21
+ minWindow?: number;
22
+ }
23
+
24
+ export function hurstExponent(
25
+ series: readonly number[],
26
+ opts: HurstOptions = {},
27
+ ): number {
28
+ const minWindow = opts.minWindow ?? 8;
29
+ const N = series.length;
30
+ const scales: number[] = [];
31
+ const rsMeans: number[] = [];
32
+
33
+ for (let n = minWindow; n <= Math.floor(N / 2); n *= 2) {
34
+ const k = Math.floor(N / n);
35
+ const rsVals: number[] = [];
36
+ for (let j = 0; j < k; j++) {
37
+ const start = j * n;
38
+ let mean = 0;
39
+ for (let i = 0; i < n; i++) mean += series[start + i];
40
+ mean /= n;
41
+ let cum = 0;
42
+ let min = Infinity;
43
+ let max = -Infinity;
44
+ let sumSq = 0;
45
+ for (let i = 0; i < n; i++) {
46
+ const d = series[start + i] - mean;
47
+ cum += d;
48
+ if (cum < min) min = cum;
49
+ if (cum > max) max = cum;
50
+ sumSq += d * d;
51
+ }
52
+ const range = max - min;
53
+ const stdev = Math.sqrt(sumSq / n);
54
+ if (stdev > 0) rsVals.push(range / stdev);
55
+ }
56
+ if (rsVals.length > 0) {
57
+ scales.push(n);
58
+ rsMeans.push(rsVals.reduce((a, b) => a + b, 0) / rsVals.length);
59
+ }
60
+ }
61
+
62
+ if (scales.length < 2) return NaN;
63
+
64
+ // Hurst exponent = slope of log(R/S) vs log(n).
65
+ const xs = scales.map((s) => Math.log(s));
66
+ const ys = rsMeans.map((r) => Math.log(r));
67
+ const m = xs.length;
68
+ const mx = xs.reduce((a, b) => a + b, 0) / m;
69
+ const my = ys.reduce((a, b) => a + b, 0) / m;
70
+ let num = 0;
71
+ let den = 0;
72
+ for (let i = 0; i < m; i++) {
73
+ num += (xs[i] - mx) * (ys[i] - my);
74
+ den += (xs[i] - mx) ** 2;
75
+ }
76
+ return num / den;
77
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Book and trade imbalance metrics.
3
+ *
4
+ * Both return a value in [-1, 1]: positive = buy-side heavy, negative =
5
+ * sell-side heavy, 0 = balanced (or empty input).
6
+ */
7
+ import type { L1Quote, Trade } from "./types.ts";
8
+
9
+ /** Top-of-book depth imbalance: (bidSize − askSize) / (bidSize + askSize). */
10
+ export function depthImbalance(q: L1Quote): number {
11
+ const denom = q.bidSize + q.askSize;
12
+ return denom === 0 ? 0 : (q.bidSize - q.askSize) / denom;
13
+ }
14
+
15
+ /** Trade imbalance: (buyVolume − sellVolume) / (buyVolume + sellVolume). */
16
+ export function tradeImbalance(trades: Trade[]): number {
17
+ let buy = 0;
18
+ let sell = 0;
19
+ for (const t of trades) {
20
+ if (t.side === "buy") buy += t.size;
21
+ else sell += t.size;
22
+ }
23
+ const denom = buy + sell;
24
+ return denom === 0 ? 0 : (buy - sell) / denom;
25
+ }
package/src/impact.ts ADDED
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Market-impact models and trade markouts.
3
+ *
4
+ * Two pre-trade cost models — the empirical square-root law and the linear
5
+ * Almgren-Chriss temporary/permanent decomposition — plus post-trade markouts,
6
+ * the realized adverse-selection drift after a fill. Together they cover the
7
+ * "what will it cost / what did it cost" pair around execution.
8
+ *
9
+ * Sign convention for markouts: buys are +1, sells are -1.
10
+ */
11
+ import type { Side } from "./types.ts";
12
+
13
+ function dir(side: Side): 1 | -1 {
14
+ return side === "buy" ? 1 : -1;
15
+ }
16
+
17
+ /**
18
+ * Square-root law of market impact: `coefficient * sigma * sqrt(Q / V)`.
19
+ *
20
+ * The widely-observed empirical scaling of impact with participation: cost
21
+ * grows with volatility `sigma` and the square root of the order size `Q`
22
+ * relative to market volume `V`. `coefficient` (Y, typically ~0.5-1) absorbs
23
+ * the asset/venue-specific constant. Returns a dimensionless cost fraction, or
24
+ * 0 for non-positive volume or size.
25
+ */
26
+ export function squareRootImpact(
27
+ sigma: number,
28
+ orderSize: number,
29
+ marketVolume: number,
30
+ coefficient = 1,
31
+ ): number {
32
+ if (!(marketVolume > 0) || orderSize <= 0) return 0;
33
+ return coefficient * sigma * Math.sqrt(orderSize / marketVolume);
34
+ }
35
+
36
+ /** Linear permanent impact: the lasting price shift `gamma * quantity`. */
37
+ export function linearPermanentImpact(gamma: number, quantity: number): number {
38
+ return gamma * quantity;
39
+ }
40
+
41
+ /** Linear temporary impact at trading rate `rate`: `eta * rate`. */
42
+ export function linearTemporaryImpact(eta: number, rate: number): number {
43
+ return eta * rate;
44
+ }
45
+
46
+ /** Expected impact cost of a schedule, split into its two components. */
47
+ export interface ImpactCost {
48
+ permanent: number;
49
+ temporary: number;
50
+ total: number;
51
+ }
52
+
53
+ /**
54
+ * Almgren-Chriss expected impact cost of a uniform (TWAP) liquidation of
55
+ * `quantity` over `duration`, under linear temporary (`eta`) and permanent
56
+ * (`gamma`) impact:
57
+ *
58
+ * permanent = 0.5 * gamma * Q^2 (paid on average over the trade)
59
+ * temporary = eta * Q^2 / T (uniform rate Q/T over T)
60
+ *
61
+ * `duration` must be positive.
62
+ */
63
+ export function almgrenChrissCost(
64
+ quantity: number,
65
+ duration: number,
66
+ eta: number,
67
+ gamma: number,
68
+ ): ImpactCost {
69
+ if (!(duration > 0)) throw new Error("duration must be positive");
70
+ const permanent = 0.5 * gamma * quantity * quantity;
71
+ const temporary = (eta * quantity * quantity) / duration;
72
+ return { permanent, temporary, total: permanent + temporary };
73
+ }
74
+
75
+ /**
76
+ * Signed post-trade markout: `d * (midAfter - midAtTrade)`.
77
+ *
78
+ * How far the mid moved in the aggressor's favour over some horizon after the
79
+ * fill. Positive = the trade "looked informed" (price kept going its way);
80
+ * negative = it faded. This is the liquidity taker's realized edge and the
81
+ * provider's adverse selection, with the sign flipped.
82
+ */
83
+ export function markout(side: Side, midAtTrade: number, midAfter: number): number {
84
+ return dir(side) * (midAfter - midAtTrade);
85
+ }
86
+
87
+ export interface MarkoutObservation {
88
+ side: Side;
89
+ midAtTrade: number;
90
+ midAfter: number;
91
+ }
92
+
93
+ /** Average markout across observations (0 for an empty set). */
94
+ export function averageMarkout(obs: readonly MarkoutObservation[]): number {
95
+ if (obs.length === 0) return 0;
96
+ let s = 0;
97
+ for (const o of obs) s += markout(o.side, o.midAtTrade, o.midAfter);
98
+ return s / obs.length;
99
+ }
package/src/index.ts ADDED
@@ -0,0 +1,87 @@
1
+ export type { L1Quote, Trade, Side } from "./types.ts";
2
+ export { ofi, ofiSeries, ofiContribution } from "./ofi.ts";
3
+ export { depthImbalance, tradeImbalance } from "./imbalance.ts";
4
+ export type { VolumeBucket } from "./vpin.ts";
5
+ export {
6
+ vpin,
7
+ bucketByVolume,
8
+ bvcBuyFraction,
9
+ standardNormalCdf,
10
+ } from "./vpin.ts";
11
+ export type { FlowObservation } from "./execution.ts";
12
+ export {
13
+ effectiveSpread,
14
+ effectiveHalfSpread,
15
+ realizedSpread,
16
+ priceImpact,
17
+ kyleLambda,
18
+ rollSpread,
19
+ } from "./execution.ts";
20
+ export { mid, weightedMid, relativeSpreadBps } from "./fairvalue.ts";
21
+ export type { Sign, PriceVsMid } from "./classify.ts";
22
+ export { tickRule, leeReady } from "./classify.ts";
23
+ export type { ReturnVolume } from "./liquidity.ts";
24
+ export { amihudIlliquidity } from "./liquidity.ts";
25
+ export {
26
+ realizedVariance,
27
+ realizedVolatility,
28
+ annualizedVolatility,
29
+ } from "./volatility.ts";
30
+ export { autocorrelation, varianceRatio } from "./efficiency.ts";
31
+ export type { BookSide, Level } from "./orderbook.ts";
32
+ export { OrderBook } from "./orderbook.ts";
33
+ export type { Fill, MarketOrderResult } from "./simulate.ts";
34
+ export { simulateMarketOrder } from "./simulate.ts";
35
+ export { twap, pov } from "./scheduling.ts";
36
+ export type { Bar } from "./bars.ts";
37
+ export { tickBars, volumeBars, dollarBars } from "./bars.ts";
38
+ export type { ImpactCost, MarkoutObservation } from "./impact.ts";
39
+ export {
40
+ squareRootImpact,
41
+ linearPermanentImpact,
42
+ linearTemporaryImpact,
43
+ almgrenChrissCost,
44
+ markout,
45
+ averageMarkout,
46
+ } from "./impact.ts";
47
+ export type { ShortfallResult } from "./shortfall.ts";
48
+ export { implementationShortfall, arrivalSlippageBps } from "./shortfall.ts";
49
+ export type { Ohlc } from "./spreadest.ts";
50
+ export { corwinSchultz, abdiRanaldo } from "./spreadest.ts";
51
+ export type { Candle } from "./rangevol.ts";
52
+ export {
53
+ parkinsonVolatility,
54
+ garmanKlassVolatility,
55
+ rogersSatchellVolatility,
56
+ yangZhangVolatility,
57
+ } from "./rangevol.ts";
58
+ export type { HurstOptions } from "./hurst.ts";
59
+ export { hurstExponent } from "./hurst.ts";
60
+ export { realizedSkewness, realizedKurtosis } from "./moments.ts";
61
+ export {
62
+ bipowerVariation,
63
+ jumpVariation,
64
+ relativeJumpVariation,
65
+ } from "./jumps.ts";
66
+ export type { Semivariance } from "./semivar.ts";
67
+ export {
68
+ realizedSemivariance,
69
+ downsideVarianceRatio,
70
+ signedJumpVariation,
71
+ } from "./semivar.ts";
72
+ export {
73
+ shannonEntropy,
74
+ normalizedEntropy,
75
+ signEntropy,
76
+ } from "./entropy.ts";
77
+ export {
78
+ Welford,
79
+ Ewma,
80
+ EwmaVariance,
81
+ RollingWindow,
82
+ } from "./online.ts";
83
+ export {
84
+ realizedCovariance,
85
+ realizedCorrelation,
86
+ realizedBeta,
87
+ } from "./covariance.ts";
package/src/jumps.ts ADDED
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Jump detection via bipower variation (Barndorff-Nielsen & Shephard, 2004/2006).
3
+ *
4
+ * Realized variance (RV = Σ rᵢ²) mixes two very different kinds of risk: the
5
+ * continuous diffusion of price, and discrete jumps. Bipower variation (BV)
6
+ * estimates only the *continuous* part — multiplying adjacent absolute returns
7
+ * is robust to a lone large jump (a jump inflates one return but is paired with
8
+ * a small neighbour). The difference RV − BV isolates the jump contribution.
9
+ *
10
+ * BV = (π/2) · Σᵢ |rᵢ₋₁| · |rᵢ|
11
+ * jump variation = max(RV − BV, 0)
12
+ *
13
+ * Each function takes a return series and returns a non-negative number; the
14
+ * relative jump is the share of realized variance attributable to jumps, in
15
+ * [0, 1].
16
+ */
17
+
18
+ // 1 / μ₁² where μ₁ = E[|Z|] = √(2/π) for Z ~ N(0,1); 1/μ₁² = π/2.
19
+ const MU1_INV_SQ = Math.PI / 2;
20
+
21
+ /** Realized variance Σ rᵢ² (local helper). */
22
+ function realizedVar(returns: readonly number[]): number {
23
+ let s = 0;
24
+ for (const r of returns) s += r * r;
25
+ return s;
26
+ }
27
+
28
+ /**
29
+ * Bipower variation: (π/2) · Σ |rᵢ₋₁||rᵢ|. A jump-robust estimate of the
30
+ * continuous (diffusive) part of realized variance. Returns 0 for fewer than
31
+ * two returns.
32
+ */
33
+ export function bipowerVariation(returns: readonly number[]): number {
34
+ const n = returns.length;
35
+ if (n < 2) return 0;
36
+ let s = 0;
37
+ for (let i = 1; i < n; i++) s += Math.abs(returns[i - 1]) * Math.abs(returns[i]);
38
+ return MU1_INV_SQ * s;
39
+ }
40
+
41
+ /**
42
+ * Jump variation: max(RV − BV, 0), the part of realized variance attributable
43
+ * to discrete jumps. Returns 0 for fewer than two returns.
44
+ */
45
+ export function jumpVariation(returns: readonly number[]): number {
46
+ if (returns.length < 2) return 0;
47
+ const j = realizedVar(returns) - bipowerVariation(returns);
48
+ return j > 0 ? j : 0;
49
+ }
50
+
51
+ /**
52
+ * Relative jump: jump variation as a share of realized variance, in [0, 1].
53
+ * Returns 0 when realized variance is 0 or there are fewer than two returns.
54
+ */
55
+ export function relativeJumpVariation(returns: readonly number[]): number {
56
+ if (returns.length < 2) return 0;
57
+ const total = realizedVar(returns);
58
+ if (total === 0) return 0;
59
+ const j = total - bipowerVariation(returns);
60
+ return j > 0 ? j / total : 0;
61
+ }
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Liquidity measures.
3
+ */
4
+
5
+ export interface ReturnVolume {
6
+ /** period return (e.g. close-to-close), as a fraction */
7
+ ret: number;
8
+ /** traded volume (or dollar volume) over the same period */
9
+ volume: number;
10
+ }
11
+
12
+ /**
13
+ * Amihud (2002) illiquidity: the average of |return| / volume across periods.
14
+ *
15
+ * It captures how much price moves per unit of volume — a high value means
16
+ * even small trades push the price a lot (thin, illiquid). Periods with zero
17
+ * volume are skipped. Returns 0 when there is no usable data.
18
+ */
19
+ export function amihudIlliquidity(obs: readonly ReturnVolume[]): number {
20
+ let sum = 0;
21
+ let n = 0;
22
+ for (const o of obs) {
23
+ if (o.volume > 0) {
24
+ sum += Math.abs(o.ret) / o.volume;
25
+ n += 1;
26
+ }
27
+ }
28
+ return n === 0 ? 0 : sum / n;
29
+ }
package/src/moments.ts ADDED
@@ -0,0 +1,45 @@
1
+ /**
2
+ * Realized higher moments from an intraday return series.
3
+ *
4
+ * Following Amaya, Christoffersen, Jacobs & Vasquez (2015), the realized
5
+ * skewness and kurtosis of a set of high-frequency returns summarise the shape
6
+ * of the intraday return distribution — asymmetry and tail heaviness — beyond
7
+ * what realized variance (see `volatility`) captures. Realized skewness in
8
+ * particular has been shown to predict the cross-section of subsequent returns.
9
+ *
10
+ * With N returns r and realized variance RV = Σ r²:
11
+ * realized skewness = √N · Σ r³ / RV^(3/2)
12
+ * realized kurtosis = N · Σ r⁴ / RV²
13
+ * The √N and N scalings make the estimates comparable across sampling
14
+ * frequencies. Both return 0 for an empty series or one with no variation.
15
+ */
16
+
17
+ /** Realized skewness: √N · Σ r³ / (Σ r²)^(3/2). */
18
+ export function realizedSkewness(returns: readonly number[]): number {
19
+ const n = returns.length;
20
+ if (n === 0) return 0;
21
+ let s2 = 0;
22
+ let s3 = 0;
23
+ for (const r of returns) {
24
+ const r2 = r * r;
25
+ s2 += r2;
26
+ s3 += r2 * r;
27
+ }
28
+ if (s2 === 0) return 0;
29
+ return (Math.sqrt(n) * s3) / Math.pow(s2, 1.5);
30
+ }
31
+
32
+ /** Realized kurtosis: N · Σ r⁴ / (Σ r²)². */
33
+ export function realizedKurtosis(returns: readonly number[]): number {
34
+ const n = returns.length;
35
+ if (n === 0) return 0;
36
+ let s2 = 0;
37
+ let s4 = 0;
38
+ for (const r of returns) {
39
+ const r2 = r * r;
40
+ s2 += r2;
41
+ s4 += r2 * r2;
42
+ }
43
+ if (s2 === 0) return 0;
44
+ return (n * s4) / (s2 * s2);
45
+ }
package/src/ofi.ts ADDED
@@ -0,0 +1,46 @@
1
+ /**
2
+ * Order Flow Imbalance (OFI).
3
+ *
4
+ * Implements the level-1 OFI of Cont, Kukanov & Stoikov (2014), "The price
5
+ * impact of order book events". For consecutive best-quote observations
6
+ * (n-1, n) the event contribution is:
7
+ *
8
+ * e_n = q^b_n · 1{P^b_n ≥ P^b_{n-1}} − q^b_{n-1} · 1{P^b_n ≤ P^b_{n-1}}
9
+ * − q^a_n · 1{P^a_n ≤ P^a_{n-1}} + q^a_{n-1} · 1{P^a_n ≥ P^a_{n-1}}
10
+ *
11
+ * OFI over a window is the sum of e_n. Positive OFI means net buy-side
12
+ * pressure at the top of book; it is a strong linear predictor of short-term
13
+ * price moves.
14
+ */
15
+ import type { L1Quote } from "./types.ts";
16
+
17
+ /** OFI contribution of a single best-quote transition (prev → curr). */
18
+ export function ofiContribution(prev: L1Quote, curr: L1Quote): number {
19
+ const bidTerm =
20
+ (curr.bidPrice >= prev.bidPrice ? curr.bidSize : 0) -
21
+ (curr.bidPrice <= prev.bidPrice ? prev.bidSize : 0);
22
+
23
+ const askTerm =
24
+ (curr.askPrice >= prev.askPrice ? prev.askSize : 0) -
25
+ (curr.askPrice <= prev.askPrice ? curr.askSize : 0);
26
+
27
+ return bidTerm + askTerm;
28
+ }
29
+
30
+ /** Per-step OFI contributions for a sequence of quotes (length n-1). */
31
+ export function ofiSeries(quotes: L1Quote[]): number[] {
32
+ const out: number[] = [];
33
+ for (let i = 1; i < quotes.length; i++) {
34
+ out.push(ofiContribution(quotes[i - 1], quotes[i]));
35
+ }
36
+ return out;
37
+ }
38
+
39
+ /** Cumulative OFI over the whole sequence of quotes. */
40
+ export function ofi(quotes: L1Quote[]): number {
41
+ let total = 0;
42
+ for (let i = 1; i < quotes.length; i++) {
43
+ total += ofiContribution(quotes[i - 1], quotes[i]);
44
+ }
45
+ return total;
46
+ }