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,75 @@
1
+ /**
2
+ * Bid-ask spread estimators from daily OHLC data.
3
+ *
4
+ * When all you have is low-frequency bars — daily high, low, and close — you can
5
+ * still back out an estimate of the effective bid-ask spread. Two well-known,
6
+ * dependency-free estimators:
7
+ *
8
+ * - Corwin & Schultz (2012): the high-low range over two consecutive periods
9
+ * reflects both fundamental volatility and the bid-ask bounce. The bounce can
10
+ * be isolated because volatility scales with the time interval while the
11
+ * spread does not.
12
+ * - Abdi & Ranaldo (2017): compares each close to the mid-point of the current
13
+ * and the next period's high-low range; their covariance recovers the spread.
14
+ *
15
+ * Both return a *proportional* spread (a fraction of price, e.g. 0.01 = 100 bps).
16
+ * Negative estimates — noise when the true spread is close to zero — are floored
17
+ * at 0. Prices must be strictly positive.
18
+ */
19
+
20
+ export interface Ohlc {
21
+ high: number;
22
+ low: number;
23
+ close: number;
24
+ }
25
+
26
+ /** 3 - 2√2 ≈ 0.1716, the Corwin-Schultz normalizing constant. */
27
+ const K = 3 - 2 * Math.SQRT2;
28
+
29
+ /**
30
+ * Corwin-Schultz (2012) high-low proportional spread estimate, averaged over all
31
+ * consecutive bar pairs. Per-pair negative estimates are set to 0 before
32
+ * averaging (as recommended in the paper). Returns 0 for fewer than two bars.
33
+ */
34
+ export function corwinSchultz(bars: Ohlc[]): number {
35
+ if (bars.length < 2) return 0;
36
+ let sum = 0;
37
+ let n = 0;
38
+ for (let i = 0; i + 1 < bars.length; i++) {
39
+ const a = bars[i];
40
+ const b = bars[i + 1];
41
+ const hlA = Math.log(a.high / a.low);
42
+ const hlB = Math.log(b.high / b.low);
43
+ const beta = hlA * hlA + hlB * hlB;
44
+ const g = Math.log(Math.max(a.high, b.high) / Math.min(a.low, b.low));
45
+ const gamma = g * g;
46
+ const alpha =
47
+ (Math.sqrt(2 * beta) - Math.sqrt(beta)) / K - Math.sqrt(gamma / K);
48
+ const s = (2 * (Math.exp(alpha) - 1)) / (1 + Math.exp(alpha));
49
+ sum += s > 0 ? s : 0;
50
+ n++;
51
+ }
52
+ return n === 0 ? 0 : sum / n;
53
+ }
54
+
55
+ /**
56
+ * Abdi-Ranaldo (2017) proportional spread estimate from close, high, and low.
57
+ * Uses the covariance of each log-close with the mid-range of the current and
58
+ * next bar: S = sqrt(max(4 · E[(c_t − η_t)(c_t − η_{t+1})], 0)), where
59
+ * η = (log high + log low) / 2. Returns 0 for fewer than two bars or when the
60
+ * estimate is negative.
61
+ */
62
+ export function abdiRanaldo(bars: Ohlc[]): number {
63
+ if (bars.length < 2) return 0;
64
+ let sum = 0;
65
+ let n = 0;
66
+ for (let t = 0; t + 1 < bars.length; t++) {
67
+ const c = Math.log(bars[t].close);
68
+ const etaT = (Math.log(bars[t].high) + Math.log(bars[t].low)) / 2;
69
+ const etaN = (Math.log(bars[t + 1].high) + Math.log(bars[t + 1].low)) / 2;
70
+ sum += (c - etaT) * (c - etaN);
71
+ n++;
72
+ }
73
+ const s2 = 4 * (n === 0 ? 0 : sum / n);
74
+ return s2 > 0 ? Math.sqrt(s2) : 0;
75
+ }
package/src/types.ts ADDED
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Core input types for order-flow metrics.
3
+ *
4
+ * Prices and sizes are plain numbers; timestamps (if provided) are opaque to
5
+ * this library — pass whatever epoch unit your feed uses, we never compare
6
+ * across records.
7
+ */
8
+
9
+ export type Side = "buy" | "sell";
10
+
11
+ /** Top-of-book (level-1) snapshot. */
12
+ export interface L1Quote {
13
+ ts?: number;
14
+ bidPrice: number;
15
+ bidSize: number;
16
+ askPrice: number;
17
+ askSize: number;
18
+ }
19
+
20
+ /** A single executed trade. */
21
+ export interface Trade {
22
+ ts?: number;
23
+ price: number;
24
+ size: number;
25
+ side: Side;
26
+ }
@@ -0,0 +1,36 @@
1
+ /**
2
+ * Realized volatility from a return series.
3
+ *
4
+ * Returns are period returns (e.g. log returns between consecutive prints or
5
+ * bars). Realized variance is the sum of squared returns; realized volatility
6
+ * is its square root. Annualized volatility scales the per-period variance by
7
+ * the number of periods in a year (zero-mean convention, standard for
8
+ * high-frequency returns).
9
+ */
10
+
11
+ /** Realized variance: Σ rᵢ². */
12
+ export function realizedVariance(returns: readonly number[]): number {
13
+ let sum = 0;
14
+ for (const r of returns) sum += r * r;
15
+ return sum;
16
+ }
17
+
18
+ /** Realized volatility over the sample: √(Σ rᵢ²). */
19
+ export function realizedVolatility(returns: readonly number[]): number {
20
+ return Math.sqrt(realizedVariance(returns));
21
+ }
22
+
23
+ /**
24
+ * Annualized volatility: √( mean(rᵢ²) · periodsPerYear ).
25
+ *
26
+ * ``periodsPerYear`` is how many return periods make up a year (e.g. 252 for
27
+ * daily, 252·390 for 1-minute equity bars). Returns 0 for an empty series.
28
+ */
29
+ export function annualizedVolatility(
30
+ returns: readonly number[],
31
+ periodsPerYear: number,
32
+ ): number {
33
+ const n = returns.length;
34
+ if (n === 0) return 0;
35
+ return Math.sqrt((realizedVariance(returns) / n) * periodsPerYear);
36
+ }
package/src/vpin.ts ADDED
@@ -0,0 +1,98 @@
1
+ /**
2
+ * VPIN — Volume-Synchronized Probability of Informed Trading.
3
+ *
4
+ * Easley, López de Prado & O'Hara (2012), "Flow Toxicity and Liquidity in a
5
+ * High-Frequency World". Trades are grouped into equal-volume buckets; each
6
+ * bucket is split into buy/sell volume by Bulk Volume Classification (BVC)
7
+ * using the standardized close-to-close price change; VPIN is the average
8
+ * absolute order imbalance across a rolling window of buckets.
9
+ *
10
+ * V_buy = V · Φ(ΔP / σ)
11
+ * V_sell = V · (1 − Φ(ΔP / σ))
12
+ * VPIN = Σ |V_buy − V_sell| / Σ V over the window
13
+ */
14
+
15
+ export interface VolumeBucket {
16
+ /** close-to-close price change over the bucket */
17
+ priceChange: number;
18
+ /** total traded volume in the bucket */
19
+ volume: number;
20
+ }
21
+
22
+ // Abramowitz & Stegun 7.1.26 — max error ≈ 1.5e-7.
23
+ function erf(x: number): number {
24
+ const t = 1 / (1 + 0.3275911 * Math.abs(x));
25
+ const y =
26
+ 1 -
27
+ ((((1.061405429 * t - 1.453152027) * t + 1.421413741) * t - 0.284496736) *
28
+ t +
29
+ 0.254829592) *
30
+ t *
31
+ Math.exp(-x * x);
32
+ return x >= 0 ? y : -y;
33
+ }
34
+
35
+ /** Standard normal CDF Φ(x). */
36
+ export function standardNormalCdf(x: number): number {
37
+ return 0.5 * (1 + erf(x / Math.SQRT2));
38
+ }
39
+
40
+ /** BVC buy-volume fraction for a bucket, given its price change and σ. */
41
+ export function bvcBuyFraction(priceChange: number, sigma: number): number {
42
+ if (!(sigma > 0)) return 0.5;
43
+ return standardNormalCdf(priceChange / sigma);
44
+ }
45
+
46
+ /** Aggregate a trade stream into equal-volume buckets (splitting trades). */
47
+ export function bucketByVolume(
48
+ trades: readonly { price: number; size: number }[],
49
+ bucketSize: number,
50
+ ): VolumeBucket[] {
51
+ if (bucketSize <= 0) throw new Error("bucketSize must be positive");
52
+ const buckets: VolumeBucket[] = [];
53
+ let filled = 0;
54
+ let prevClose = trades.length ? trades[0].price : 0;
55
+
56
+ for (const t of trades) {
57
+ let remaining = t.size;
58
+ while (filled + remaining >= bucketSize) {
59
+ remaining -= bucketSize - filled;
60
+ buckets.push({ priceChange: t.price - prevClose, volume: bucketSize });
61
+ prevClose = t.price;
62
+ filled = 0;
63
+ }
64
+ filled += remaining;
65
+ }
66
+ return buckets;
67
+ }
68
+
69
+ function populationStd(xs: number[]): number {
70
+ if (xs.length === 0) return 0;
71
+ const mean = xs.reduce((a, b) => a + b, 0) / xs.length;
72
+ const v = xs.reduce((a, b) => a + (b - mean) ** 2, 0) / xs.length;
73
+ return Math.sqrt(v);
74
+ }
75
+
76
+ /**
77
+ * VPIN over the last `window` buckets (default: all of them).
78
+ * σ for BVC defaults to the std dev of the window's price changes.
79
+ * Returns a value in [0, 1].
80
+ */
81
+ export function vpin(
82
+ buckets: VolumeBucket[],
83
+ opts: { window?: number; sigma?: number } = {},
84
+ ): number {
85
+ if (buckets.length === 0) return 0;
86
+ const window = opts.window ?? buckets.length;
87
+ const slice = buckets.slice(-window);
88
+ const sigma = opts.sigma ?? populationStd(slice.map((b) => b.priceChange));
89
+
90
+ let imbalance = 0;
91
+ let volume = 0;
92
+ for (const b of slice) {
93
+ const buyFrac = bvcBuyFraction(b.priceChange, sigma);
94
+ imbalance += b.volume * Math.abs(2 * buyFrac - 1);
95
+ volume += b.volume;
96
+ }
97
+ return volume === 0 ? 0 : imbalance / volume;
98
+ }