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 @@
1
+ {"version":3,"file":"volatility.d.ts","sourceRoot":"","sources":["../src/volatility.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,gCAAgC;AAChC,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAInE;AAED,qDAAqD;AACrD,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,SAAS,MAAM,EAAE,GAAG,MAAM,CAErE;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAClC,OAAO,EAAE,SAAS,MAAM,EAAE,EAC1B,cAAc,EAAE,MAAM,GACrB,MAAM,CAIR"}
@@ -0,0 +1,33 @@
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
+ /** Realized variance: Σ rᵢ². */
11
+ export function realizedVariance(returns) {
12
+ let sum = 0;
13
+ for (const r of returns)
14
+ sum += r * r;
15
+ return sum;
16
+ }
17
+ /** Realized volatility over the sample: √(Σ rᵢ²). */
18
+ export function realizedVolatility(returns) {
19
+ return Math.sqrt(realizedVariance(returns));
20
+ }
21
+ /**
22
+ * Annualized volatility: √( mean(rᵢ²) · periodsPerYear ).
23
+ *
24
+ * ``periodsPerYear`` is how many return periods make up a year (e.g. 252 for
25
+ * daily, 252·390 for 1-minute equity bars). Returns 0 for an empty series.
26
+ */
27
+ export function annualizedVolatility(returns, periodsPerYear) {
28
+ const n = returns.length;
29
+ if (n === 0)
30
+ return 0;
31
+ return Math.sqrt((realizedVariance(returns) / n) * periodsPerYear);
32
+ }
33
+ //# sourceMappingURL=volatility.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"volatility.js","sourceRoot":"","sources":["../src/volatility.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,gCAAgC;AAChC,MAAM,UAAU,gBAAgB,CAAC,OAA0B;IACzD,IAAI,GAAG,GAAG,CAAC,CAAC;IACZ,KAAK,MAAM,CAAC,IAAI,OAAO;QAAE,GAAG,IAAI,CAAC,GAAG,CAAC,CAAC;IACtC,OAAO,GAAG,CAAC;AACb,CAAC;AAED,qDAAqD;AACrD,MAAM,UAAU,kBAAkB,CAAC,OAA0B;IAC3D,OAAO,IAAI,CAAC,IAAI,CAAC,gBAAgB,CAAC,OAAO,CAAC,CAAC,CAAC;AAC9C,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,oBAAoB,CAClC,OAA0B,EAC1B,cAAsB;IAEtB,MAAM,CAAC,GAAG,OAAO,CAAC,MAAM,CAAC;IACzB,IAAI,CAAC,KAAK,CAAC;QAAE,OAAO,CAAC,CAAC;IACtB,OAAO,IAAI,CAAC,IAAI,CAAC,CAAC,gBAAgB,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,GAAG,cAAc,CAAC,CAAC;AACrE,CAAC"}
package/dist/vpin.d.ts ADDED
@@ -0,0 +1,38 @@
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
+ export interface VolumeBucket {
15
+ /** close-to-close price change over the bucket */
16
+ priceChange: number;
17
+ /** total traded volume in the bucket */
18
+ volume: number;
19
+ }
20
+ /** Standard normal CDF Φ(x). */
21
+ export declare function standardNormalCdf(x: number): number;
22
+ /** BVC buy-volume fraction for a bucket, given its price change and σ. */
23
+ export declare function bvcBuyFraction(priceChange: number, sigma: number): number;
24
+ /** Aggregate a trade stream into equal-volume buckets (splitting trades). */
25
+ export declare function bucketByVolume(trades: readonly {
26
+ price: number;
27
+ size: number;
28
+ }[], bucketSize: number): VolumeBucket[];
29
+ /**
30
+ * VPIN over the last `window` buckets (default: all of them).
31
+ * σ for BVC defaults to the std dev of the window's price changes.
32
+ * Returns a value in [0, 1].
33
+ */
34
+ export declare function vpin(buckets: VolumeBucket[], opts?: {
35
+ window?: number;
36
+ sigma?: number;
37
+ }): number;
38
+ //# sourceMappingURL=vpin.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vpin.d.ts","sourceRoot":"","sources":["../src/vpin.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,MAAM,WAAW,YAAY;IAC3B,kDAAkD;IAClD,WAAW,EAAE,MAAM,CAAC;IACpB,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;CAChB;AAeD,gCAAgC;AAChC,wBAAgB,iBAAiB,CAAC,CAAC,EAAE,MAAM,GAAG,MAAM,CAEnD;AAED,0EAA0E;AAC1E,wBAAgB,cAAc,CAAC,WAAW,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAGzE;AAED,6EAA6E;AAC7E,wBAAgB,cAAc,CAC5B,MAAM,EAAE,SAAS;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAA;CAAE,EAAE,EAClD,UAAU,EAAE,MAAM,GACjB,YAAY,EAAE,CAiBhB;AASD;;;;GAIG;AACH,wBAAgB,IAAI,CAClB,OAAO,EAAE,YAAY,EAAE,EACvB,IAAI,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAA;CAAO,GAC7C,MAAM,CAcR"}
package/dist/vpin.js ADDED
@@ -0,0 +1,81 @@
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
+ // Abramowitz & Stegun 7.1.26 — max error ≈ 1.5e-7.
15
+ function erf(x) {
16
+ const t = 1 / (1 + 0.3275911 * Math.abs(x));
17
+ const y = 1 -
18
+ ((((1.061405429 * t - 1.453152027) * t + 1.421413741) * t - 0.284496736) *
19
+ t +
20
+ 0.254829592) *
21
+ t *
22
+ Math.exp(-x * x);
23
+ return x >= 0 ? y : -y;
24
+ }
25
+ /** Standard normal CDF Φ(x). */
26
+ export function standardNormalCdf(x) {
27
+ return 0.5 * (1 + erf(x / Math.SQRT2));
28
+ }
29
+ /** BVC buy-volume fraction for a bucket, given its price change and σ. */
30
+ export function bvcBuyFraction(priceChange, sigma) {
31
+ if (!(sigma > 0))
32
+ return 0.5;
33
+ return standardNormalCdf(priceChange / sigma);
34
+ }
35
+ /** Aggregate a trade stream into equal-volume buckets (splitting trades). */
36
+ export function bucketByVolume(trades, bucketSize) {
37
+ if (bucketSize <= 0)
38
+ throw new Error("bucketSize must be positive");
39
+ const buckets = [];
40
+ let filled = 0;
41
+ let prevClose = trades.length ? trades[0].price : 0;
42
+ for (const t of trades) {
43
+ let remaining = t.size;
44
+ while (filled + remaining >= bucketSize) {
45
+ remaining -= bucketSize - filled;
46
+ buckets.push({ priceChange: t.price - prevClose, volume: bucketSize });
47
+ prevClose = t.price;
48
+ filled = 0;
49
+ }
50
+ filled += remaining;
51
+ }
52
+ return buckets;
53
+ }
54
+ function populationStd(xs) {
55
+ if (xs.length === 0)
56
+ return 0;
57
+ const mean = xs.reduce((a, b) => a + b, 0) / xs.length;
58
+ const v = xs.reduce((a, b) => a + (b - mean) ** 2, 0) / xs.length;
59
+ return Math.sqrt(v);
60
+ }
61
+ /**
62
+ * VPIN over the last `window` buckets (default: all of them).
63
+ * σ for BVC defaults to the std dev of the window's price changes.
64
+ * Returns a value in [0, 1].
65
+ */
66
+ export function vpin(buckets, opts = {}) {
67
+ if (buckets.length === 0)
68
+ return 0;
69
+ const window = opts.window ?? buckets.length;
70
+ const slice = buckets.slice(-window);
71
+ const sigma = opts.sigma ?? populationStd(slice.map((b) => b.priceChange));
72
+ let imbalance = 0;
73
+ let volume = 0;
74
+ for (const b of slice) {
75
+ const buyFrac = bvcBuyFraction(b.priceChange, sigma);
76
+ imbalance += b.volume * Math.abs(2 * buyFrac - 1);
77
+ volume += b.volume;
78
+ }
79
+ return volume === 0 ? 0 : imbalance / volume;
80
+ }
81
+ //# sourceMappingURL=vpin.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"vpin.js","sourceRoot":"","sources":["../src/vpin.ts"],"names":[],"mappings":"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"}
package/package.json ADDED
@@ -0,0 +1,70 @@
1
+ {
2
+ "name": "orderflow-metrics",
3
+ "version": "0.24.0",
4
+ "description": "Microstructure metrics in dependency-free TypeScript — OFI, VPIN, information-driven bars, market impact (square-root & Almgren-Chriss), markouts, implementation shortfall, high-low spread estimators (Corwin-Schultz, Abdi-Ranaldo), range-based volatility (Parkinson, Garman-Klass, Rogers-Satchell, Yang-Zhang), Hurst exponent, realized skewness & kurtosis, bipower variation & jump detection, realized semivariance & signed jump variation, order-flow entropy, online/streaming estimators (Welford, EWMA, rolling window), realized covariance/correlation/beta, Kyle's lambda, trade-sign classification, Amihud illiquidity.",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "module": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ },
14
+ "./package.json": "./package.json"
15
+ },
16
+ "files": [
17
+ "dist",
18
+ "src",
19
+ "README.md",
20
+ "CHANGELOG.md",
21
+ "LICENSE"
22
+ ],
23
+ "sideEffects": false,
24
+ "engines": {
25
+ "node": ">=18"
26
+ },
27
+ "scripts": {
28
+ "build": "tsc -p tsconfig.build.json",
29
+ "test": "node --experimental-strip-types --test",
30
+ "typecheck": "tsc --noEmit",
31
+ "prepublishOnly": "npm run build"
32
+ },
33
+ "keywords": [
34
+ "market-microstructure",
35
+ "order-flow-imbalance",
36
+ "ofi",
37
+ "vpin",
38
+ "order-book",
39
+ "information-driven-bars",
40
+ "dollar-bars",
41
+ "realized-volatility",
42
+ "realized-covariance",
43
+ "market-impact",
44
+ "kyle-lambda",
45
+ "market-data",
46
+ "hft",
47
+ "algorithmic-trading",
48
+ "trading",
49
+ "quantitative-finance",
50
+ "typescript"
51
+ ],
52
+ "author": "TwoWayMind (RATE LTD)",
53
+ "license": "MIT",
54
+ "repository": {
55
+ "type": "git",
56
+ "url": "git+https://github.com/twowaymind/orderflow-metrics.git"
57
+ },
58
+ "bugs": {
59
+ "url": "https://github.com/twowaymind/orderflow-metrics/issues"
60
+ },
61
+ "homepage": "https://twowaymind.com",
62
+ "publishConfig": {
63
+ "access": "public",
64
+ "provenance": true
65
+ },
66
+ "devDependencies": {
67
+ "@types/node": "^22.5.0",
68
+ "typescript": "^5.7.0"
69
+ }
70
+ }
package/src/bars.ts ADDED
@@ -0,0 +1,141 @@
1
+ /**
2
+ * Information-driven bars.
3
+ *
4
+ * Sampling a raw trade stream on a fixed *time* grid (1m, 5m, …) oversamples
5
+ * quiet periods and undersamples busy ones, and produces returns that are far
6
+ * from IID. Sampling on *activity* instead — a bar every N ticks, every N
7
+ * units of volume, or every N units of traded value — yields bars with much
8
+ * better statistical properties (López de Prado, *Advances in Financial
9
+ * Machine Learning*, ch. 2). These bars are the natural upstream sampling
10
+ * layer for the rest of this library: build them first, then compute OFI,
11
+ * imbalance, volatility, VPIN, … on the resulting series.
12
+ *
13
+ * Trades are never split across bars: the trade that crosses the threshold is
14
+ * included whole and closes the bar, so `volume`/`dollar` may slightly exceed
15
+ * the threshold. A trailing partial bar (below threshold at end of stream) is
16
+ * dropped, matching `bucketByVolume` in the VPIN module.
17
+ */
18
+ import type { Trade } from "./types.ts";
19
+
20
+ /** One OHLCV bar aggregated from a slice of trades. */
21
+ export interface Bar {
22
+ /** Price of the first trade in the bar. */
23
+ open: number;
24
+ /** Highest trade price in the bar. */
25
+ high: number;
26
+ /** Lowest trade price in the bar. */
27
+ low: number;
28
+ /** Price of the last trade in the bar. */
29
+ close: number;
30
+ /** Total traded size. */
31
+ volume: number;
32
+ /** Total traded value, Σ price·size. */
33
+ dollar: number;
34
+ /** Volume-weighted average price, `dollar / volume`. */
35
+ vwap: number;
36
+ /** Number of trades aggregated into the bar. */
37
+ ticks: number;
38
+ /** Size of buyer-initiated trades. */
39
+ buyVolume: number;
40
+ /** Size of seller-initiated trades. */
41
+ sellVolume: number;
42
+ /** Timestamp of the first trade, if the feed provided one. */
43
+ start?: number;
44
+ /** Timestamp of the last trade, if the feed provided one. */
45
+ end?: number;
46
+ }
47
+
48
+ /** Aggregate a non-empty slice of trades into a single bar. */
49
+ function buildBar(trades: readonly Trade[]): Bar {
50
+ const first = trades[0];
51
+ const last = trades[trades.length - 1];
52
+
53
+ let high = first.price;
54
+ let low = first.price;
55
+ let volume = 0;
56
+ let dollar = 0;
57
+ let buyVolume = 0;
58
+ let sellVolume = 0;
59
+
60
+ for (const t of trades) {
61
+ if (t.price > high) high = t.price;
62
+ if (t.price < low) low = t.price;
63
+ volume += t.size;
64
+ dollar += t.price * t.size;
65
+ if (t.side === "buy") buyVolume += t.size;
66
+ else sellVolume += t.size;
67
+ }
68
+
69
+ const bar: Bar = {
70
+ open: first.price,
71
+ high,
72
+ low,
73
+ close: last.price,
74
+ volume,
75
+ dollar,
76
+ vwap: volume > 0 ? dollar / volume : first.price,
77
+ ticks: trades.length,
78
+ buyVolume,
79
+ sellVolume,
80
+ };
81
+ if (first.ts !== undefined) bar.start = first.ts;
82
+ if (last.ts !== undefined) bar.end = last.ts;
83
+ return bar;
84
+ }
85
+
86
+ /**
87
+ * Emit a bar every `threshold` trades (tick bars).
88
+ *
89
+ * @param threshold number of trades per bar (integer ≥ 1)
90
+ */
91
+ export function tickBars(trades: readonly Trade[], threshold: number): Bar[] {
92
+ const step = Math.floor(threshold);
93
+ if (step < 1) return [];
94
+ const bars: Bar[] = [];
95
+ for (let i = 0; i + step <= trades.length; i += step) {
96
+ bars.push(buildBar(trades.slice(i, i + step)));
97
+ }
98
+ return bars;
99
+ }
100
+
101
+ /**
102
+ * Emit a bar each time cumulative size reaches `threshold` (volume bars).
103
+ * The trade that crosses the threshold closes the bar and is included whole.
104
+ *
105
+ * @param threshold volume per bar (> 0)
106
+ */
107
+ export function volumeBars(trades: readonly Trade[], threshold: number): Bar[] {
108
+ return accumulate(trades, threshold, (t) => t.size);
109
+ }
110
+
111
+ /**
112
+ * Emit a bar each time cumulative traded value (Σ price·size) reaches
113
+ * `threshold` (dollar bars). Dollar bars are the most robust of the three to
114
+ * changes in price level and are usually preferred (López de Prado, 2018).
115
+ *
116
+ * @param threshold traded value per bar (> 0)
117
+ */
118
+ export function dollarBars(trades: readonly Trade[], threshold: number): Bar[] {
119
+ return accumulate(trades, threshold, (t) => t.price * t.size);
120
+ }
121
+
122
+ /** Shared accumulator for volume / dollar bars. */
123
+ function accumulate(
124
+ trades: readonly Trade[],
125
+ threshold: number,
126
+ weight: (t: Trade) => number,
127
+ ): Bar[] {
128
+ if (!(threshold > 0)) return [];
129
+ const bars: Bar[] = [];
130
+ let start = 0;
131
+ let acc = 0;
132
+ for (let i = 0; i < trades.length; i++) {
133
+ acc += weight(trades[i]);
134
+ if (acc >= threshold) {
135
+ bars.push(buildBar(trades.slice(start, i + 1)));
136
+ start = i + 1;
137
+ acc = 0;
138
+ }
139
+ }
140
+ return bars;
141
+ }
@@ -0,0 +1,59 @@
1
+ /**
2
+ * Trade-sign classification.
3
+ *
4
+ * Public trade prints usually don't tell you which side was the aggressor.
5
+ * These rules infer it, so downstream OFI / imbalance / VPIN inputs can be
6
+ * signed. Output is +1 (buyer-initiated), −1 (seller-initiated), 0 (unknown).
7
+ */
8
+
9
+ export type Sign = 1 | -1 | 0;
10
+
11
+ /**
12
+ * Tick rule: classify by the change vs the previous trade price.
13
+ * An unchanged ("zero tick") price carries the last non-zero sign; the first
14
+ * trade is unclassifiable (0).
15
+ */
16
+ export function tickRule(prices: readonly number[]): Sign[] {
17
+ const out: Sign[] = [];
18
+ let last: Sign = 0;
19
+ for (let i = 0; i < prices.length; i++) {
20
+ if (i > 0) {
21
+ const d = prices[i] - prices[i - 1];
22
+ if (d > 0) last = 1;
23
+ else if (d < 0) last = -1;
24
+ // d === 0 -> keep last (zero-tick)
25
+ }
26
+ out.push(last);
27
+ }
28
+ return out;
29
+ }
30
+
31
+ export interface PriceVsMid {
32
+ price: number;
33
+ mid: number;
34
+ }
35
+
36
+ /**
37
+ * Lee-Ready (1991): quote rule first — a print above the prevailing mid is
38
+ * buyer-initiated, below is seller-initiated — with the tick rule breaking
39
+ * at-the-mid ties.
40
+ */
41
+ export function leeReady(obs: readonly PriceVsMid[]): Sign[] {
42
+ const out: Sign[] = [];
43
+ let last: Sign = 0;
44
+ for (let i = 0; i < obs.length; i++) {
45
+ const { price, mid } = obs[i];
46
+ let sign: Sign;
47
+ if (price > mid) sign = 1;
48
+ else if (price < mid) sign = -1;
49
+ else if (i > 0) {
50
+ const d = price - obs[i - 1].price;
51
+ sign = d > 0 ? 1 : d < 0 ? -1 : last;
52
+ } else {
53
+ sign = 0;
54
+ }
55
+ if (sign !== 0) last = sign;
56
+ out.push(sign);
57
+ }
58
+ return out;
59
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Realized covariance, correlation, and beta between two return series.
3
+ *
4
+ * Single-asset volatility says how much one instrument moved; trading and risk
5
+ * live in how instruments move *together*. Summing the products of contemporaneous
6
+ * returns gives the realized (co)variance — the model-free, high-frequency analogue
7
+ * of covariance:
8
+ *
9
+ * realized covariance = Σ xᵢ · yᵢ
10
+ * realized correlation = Σ xᵢyᵢ / ( √Σxᵢ² · √Σyᵢ² ) (in [−1, 1])
11
+ * realized beta = Σ aᵢ·mᵢ / Σ mᵢ² (asset a vs market m)
12
+ *
13
+ * The two series are paired element-wise over their common length, so they must
14
+ * be aligned to the same sampling grid (equal length, matching timestamps).
15
+ * Correlation is scale-free; beta is the covariance of an asset with a market,
16
+ * normalized by the market's variance — the sensitivity of the asset to it.
17
+ */
18
+
19
+ /** Σ xᵢyᵢ over the two series' common length. Both empty → 0. */
20
+ function sumProduct(x: readonly number[], y: readonly number[]): number {
21
+ const n = Math.min(x.length, y.length);
22
+ let s = 0;
23
+ for (let i = 0; i < n; i++) s += x[i] * y[i];
24
+ return s;
25
+ }
26
+
27
+ /**
28
+ * Realized covariance: Σ xᵢyᵢ over contemporaneous returns. Symmetric in its
29
+ * arguments; returns 0 for empty input. Series are paired over their common
30
+ * length, so align them to the same sampling grid first.
31
+ */
32
+ export function realizedCovariance(x: readonly number[], y: readonly number[]): number {
33
+ return sumProduct(x, y);
34
+ }
35
+
36
+ /**
37
+ * Realized correlation: Σ xᵢyᵢ / (√Σxᵢ² · √Σyᵢ²), in [−1, 1]. Scale-free measure
38
+ * of co-movement. Returns 0 when either series has zero realized variance (or is
39
+ * empty).
40
+ */
41
+ export function realizedCorrelation(x: readonly number[], y: readonly number[]): number {
42
+ const n = Math.min(x.length, y.length);
43
+ if (n === 0) return 0;
44
+ let sxy = 0;
45
+ let sxx = 0;
46
+ let syy = 0;
47
+ for (let i = 0; i < n; i++) {
48
+ sxy += x[i] * y[i];
49
+ sxx += x[i] * x[i];
50
+ syy += y[i] * y[i];
51
+ }
52
+ const denom = Math.sqrt(sxx) * Math.sqrt(syy);
53
+ return denom > 0 ? sxy / denom : 0;
54
+ }
55
+
56
+ /**
57
+ * Realized beta of an asset against a market/benchmark: Σ aᵢmᵢ / Σ mᵢ², i.e. the
58
+ * realized covariance of the two divided by the market's realized variance — the
59
+ * asset's sensitivity to the market. Returns 0 when the market has zero realized
60
+ * variance (or the input is empty).
61
+ */
62
+ export function realizedBeta(asset: readonly number[], market: readonly number[]): number {
63
+ const n = Math.min(asset.length, market.length);
64
+ if (n === 0) return 0;
65
+ let cov = 0;
66
+ let varM = 0;
67
+ for (let i = 0; i < n; i++) {
68
+ cov += asset[i] * market[i];
69
+ varM += market[i] * market[i];
70
+ }
71
+ return varM > 0 ? cov / varM : 0;
72
+ }
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Market-efficiency diagnostics on a return series.
3
+ *
4
+ * Autocorrelation and the Lo-MacKinlay variance ratio tell you whether a
5
+ * series behaves like a random walk, mean-reverts, or trends — the kind of
6
+ * structure execution and market-making models care about.
7
+ */
8
+
9
+ /**
10
+ * Lag-`lag` autocorrelation of a return series (biased estimator, denominator
11
+ * is the full-sample variance). Range roughly [-1, 1]. 0 for degenerate input.
12
+ */
13
+ export function autocorrelation(returns: readonly number[], lag: number): number {
14
+ const n = returns.length;
15
+ if (lag < 1 || n <= lag) return 0;
16
+
17
+ const mean = returns.reduce((a, b) => a + b, 0) / n;
18
+ let den = 0;
19
+ for (const r of returns) den += (r - mean) * (r - mean);
20
+ if (den === 0) return 0;
21
+
22
+ let num = 0;
23
+ for (let t = lag; t < n; t++) {
24
+ num += (returns[t] - mean) * (returns[t - lag] - mean);
25
+ }
26
+ return num / den;
27
+ }
28
+
29
+ /**
30
+ * Variance ratio VR(q) = Var(q-period return) / (q · Var(1-period return))
31
+ * over overlapping q-period returns (Lo & MacKinlay, 1988).
32
+ *
33
+ * VR ≈ 1 random walk
34
+ * VR < 1 mean-reverting
35
+ * VR > 1 trending / positively autocorrelated
36
+ *
37
+ * Returns 1 for degenerate input (q ≥ length, or zero one-period variance).
38
+ */
39
+ export function varianceRatio(returns: readonly number[], q: number): number {
40
+ const n = returns.length;
41
+ // Need at least two overlapping q-period returns for a meaningful variance,
42
+ // i.e. n > q. At n <= q the estimate is degenerate — return 1 (random walk).
43
+ if (q < 1 || n <= q) return 1;
44
+
45
+ const mean = returns.reduce((a, b) => a + b, 0) / n;
46
+ let var1 = 0;
47
+ for (const r of returns) var1 += (r - mean) * (r - mean);
48
+ var1 /= n;
49
+ if (var1 === 0) return 1;
50
+
51
+ const qSums: number[] = [];
52
+ for (let j = 0; j + q <= n; j++) {
53
+ let s = 0;
54
+ for (let i = j; i < j + q; i++) s += returns[i];
55
+ qSums.push(s);
56
+ }
57
+
58
+ const meanQ = qSums.reduce((a, b) => a + b, 0) / qSums.length;
59
+ let varQ = 0;
60
+ for (const s of qSums) varQ += (s - meanQ) * (s - meanQ);
61
+ varQ /= qSums.length;
62
+
63
+ return varQ / (q * var1);
64
+ }
package/src/entropy.ts ADDED
@@ -0,0 +1,67 @@
1
+ /**
2
+ * Order-flow entropy: how predictable is a stream of trades or returns?
3
+ *
4
+ * Shannon entropy measures the average surprise in a discrete distribution.
5
+ * Applied to microstructure, it quantifies the *balance* and *predictability*
6
+ * of order flow: a stream that is almost all buys (or a return series that only
7
+ * ticks one way) carries little surprise — low entropy — and is easier to
8
+ * anticipate, while a perfectly balanced, unpredictable stream is at maximum
9
+ * entropy. Persistently low flow entropy is a hallmark of directional,
10
+ * potentially informed activity.
11
+ *
12
+ * H = − Σᵢ pᵢ · log₂ pᵢ (in bits; pᵢ = countᵢ / Σ counts)
13
+ *
14
+ * Entropy is reported in bits (base-2), so two equally likely outcomes give
15
+ * exactly 1 bit and k equally likely outcomes give log₂ k bits.
16
+ */
17
+
18
+ /**
19
+ * Shannon entropy (in bits) of a discrete distribution given category counts
20
+ * (or probabilities — any non-negative weights). Zero and negative entries are
21
+ * ignored; the remaining weights are normalized to sum to 1. Returns 0 when
22
+ * fewer than two categories carry positive weight.
23
+ */
24
+ export function shannonEntropy(counts: readonly number[]): number {
25
+ let total = 0;
26
+ for (const c of counts) if (c > 0) total += c;
27
+ if (total <= 0) return 0;
28
+ let h = 0;
29
+ for (const c of counts) {
30
+ if (c > 0) {
31
+ const p = c / total;
32
+ h -= p * Math.log2(p);
33
+ }
34
+ }
35
+ return h;
36
+ }
37
+
38
+ /**
39
+ * Normalized entropy: Shannon entropy divided by log₂(k), where k is the number
40
+ * of categories carrying positive weight. Maps entropy onto [0, 1] — 0 is fully
41
+ * concentrated (one-sided), 1 is perfectly uniform — so distributions with
42
+ * different numbers of categories are comparable. Returns 0 when fewer than two
43
+ * categories carry positive weight.
44
+ */
45
+ export function normalizedEntropy(counts: readonly number[]): number {
46
+ let k = 0;
47
+ for (const c of counts) if (c > 0) k++;
48
+ if (k < 2) return 0;
49
+ return shannonEntropy(counts) / Math.log2(k);
50
+ }
51
+
52
+ /**
53
+ * Sign entropy: the Shannon entropy (in bits, in [0, 1]) of the up/down split
54
+ * of a return or signed-flow series. Zero entries are ignored. 1 bit means
55
+ * perfectly balanced two-sided flow; values near 0 mean the flow is heavily
56
+ * one-sided (and thus more predictable). Returns 0 for an empty or single-sided
57
+ * series.
58
+ */
59
+ export function signEntropy(values: readonly number[]): number {
60
+ let up = 0;
61
+ let down = 0;
62
+ for (const v of values) {
63
+ if (v > 0) up++;
64
+ else if (v < 0) down++;
65
+ }
66
+ return shannonEntropy([up, down]);
67
+ }