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
package/README.md ADDED
@@ -0,0 +1,556 @@
1
+ # orderflow-metrics
2
+
3
+ [![CI](https://github.com/twowaymind/orderflow-metrics/actions/workflows/ci.yml/badge.svg)](https://github.com/twowaymind/orderflow-metrics/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/orderflow-metrics.svg?logo=npm)](https://www.npmjs.com/package/orderflow-metrics)
5
+ [![PyPI](https://img.shields.io/pypi/v/orderflow-metrics.svg?logo=pypi&logoColor=white)](https://pypi.org/project/orderflow-metrics/)
6
+ [![Python](https://img.shields.io/pypi/pyversions/orderflow-metrics.svg?logo=python&logoColor=white)](https://pypi.org/project/orderflow-metrics/)
7
+ [![License: MIT](https://img.shields.io/badge/license-MIT-green.svg)](LICENSE)
8
+
9
+ Microstructure **order-flow metrics** in dependency-free TypeScript and Python:
10
+ Order Flow Imbalance (OFI), VPIN, information-driven bars, transaction-cost /
11
+ price-impact metrics, trade-sign classification, limit-order-book reconstruction
12
+ and execution scheduling. The npm package ships as compiled ESM with type
13
+ declarations (Node ≥ 18) and has zero runtime dependencies; the TypeScript
14
+ source is also vendorable directly (Node 22+ type-stripping, no build step).
15
+
16
+ ## Metrics
17
+
18
+ - **Order flow & imbalance** — [Order Flow Imbalance](#order-flow-imbalance) · [Imbalance](#imbalance) · [VPIN](#vpin) · [Trade-sign classification](#trade-sign-classification) · [Order-flow entropy](#order-flow-entropy)
19
+ - **Bars & sampling** — [Information-driven bars](#information-driven-bars)
20
+ - **Fair value & spreads** — [Fair value](#fair-value) · [Spread estimators (OHLC)](#spread-estimators-from-ohlc)
21
+ - **Execution & impact** — [Execution cost & price impact](#execution-cost--price-impact) · [Market impact](#market-impact) · [Implementation shortfall](#implementation-shortfall) · [Execution scheduling](#execution-scheduling)
22
+ - **Order book** — [Order book](#order-book)
23
+ - **Volatility & risk** — [Volatility](#volatility) · [Range-based volatility (OHLC)](#range-based-volatility-from-ohlc) · [Realized moments](#realized-moments) · [Jumps & bipower variation](#jumps--bipower-variation) · [Realized semivariance](#realized-semivariance)
24
+ - **Market efficiency** — [Market efficiency](#market-efficiency) · [Hurst exponent](#hurst-exponent)
25
+ - **Liquidity** — [Liquidity](#liquidity)
26
+ - **Streaming** — [Online / streaming estimators](#online--streaming-estimators)
27
+ - **Cross-asset** — [Realized covariance, correlation & beta](#realized-covariance-correlation--beta)
28
+
29
+ Runnable quickstarts live in [`examples/`](examples/).
30
+
31
+ ## Install
32
+
33
+ ```bash
34
+ npm install orderflow-metrics
35
+ # or vendor the src/ directory directly — it's tiny and dependency-free
36
+ ```
37
+
38
+ ## Usage
39
+
40
+ ```ts
41
+ import { ofi, depthImbalance, tradeImbalance } from "orderflow-metrics";
42
+
43
+ // Order Flow Imbalance across a stream of best-quote updates
44
+ const quotes = [
45
+ { bidPrice: 100, bidSize: 5, askPrice: 101, askSize: 4 },
46
+ { bidPrice: 100, bidSize: 8, askPrice: 101, askSize: 1 },
47
+ { bidPrice: 100.5, bidSize: 2, askPrice: 101, askSize: 1 },
48
+ ];
49
+ ofi(quotes); // 8 (net buy-side pressure)
50
+
51
+ depthImbalance(quotes[0]); // (5 - 4) / (5 + 4) ≈ 0.111
52
+
53
+ tradeImbalance([
54
+ { price: 100, size: 2, side: "buy" },
55
+ { price: 100, size: 1, side: "sell" },
56
+ ]); // 0.333
57
+ ```
58
+
59
+ ## Examples
60
+
61
+ Runnable, dependency-free quickstarts that tour the library end-to-end on
62
+ deterministic synthetic data live in [`examples/`](examples/):
63
+
64
+ ```bash
65
+ node --experimental-strip-types examples/quickstart.ts # TypeScript
66
+ python examples/quickstart.py # Python
67
+ ```
68
+
69
+ Both use the same seeded data and print the same numbers — a quick check that
70
+ the two ports agree.
71
+
72
+ ## Order Flow Imbalance
73
+
74
+ `ofi` implements the level-1 OFI of Cont, Kukanov & Stoikov (2014). For two
75
+ consecutive best-quote observations the event contribution is:
76
+
77
+ ```
78
+ e_n = q_bid_n · 1{P_bid_n ≥ P_bid_{n-1}} − q_bid_{n-1} · 1{P_bid_n ≤ P_bid_{n-1}}
79
+ − q_ask_n · 1{P_ask_n ≤ P_ask_{n-1}} + q_ask_{n-1} · 1{P_ask_n ≥ P_ask_{n-1}}
80
+ ```
81
+
82
+ OFI over a window is the sum of `e_n`. Intuitively it counts size added to the
83
+ bid and removed from the ask (buy pressure) against the reverse. Empirically it
84
+ is a strong linear predictor of short-horizon price changes.
85
+
86
+ - `ofiContribution(prev, curr)` — one transition
87
+ - `ofiSeries(quotes)` — per-step contributions (for bucketing / regression)
88
+ - `ofi(quotes)` — cumulative
89
+
90
+ ## Imbalance
91
+
92
+ - `depthImbalance(quote)` — `(bidSize − askSize) / (bidSize + askSize)`, in `[-1, 1]`
93
+ - `tradeImbalance(trades)` — `(buyVol − sellVol) / (buyVol + sellVol)`, in `[-1, 1]`
94
+
95
+ ## VPIN
96
+
97
+ `vpin` implements Volume-Synchronized Probability of Informed Trading (Easley,
98
+ López de Prado & O'Hara, 2012). Trades are grouped into equal-volume buckets;
99
+ each bucket is split into buy/sell volume by Bulk Volume Classification (BVC)
100
+ from the standardized price change, and VPIN is the average absolute imbalance
101
+ across a rolling window.
102
+
103
+ ```ts
104
+ import { bucketByVolume, vpin } from "orderflow-metrics";
105
+
106
+ const buckets = bucketByVolume(trades, 1_000); // equal-volume buckets
107
+ vpin(buckets, { window: 50 }); // flow toxicity in [0, 1]
108
+ ```
109
+
110
+ - `bucketByVolume(trades, bucketSize)` — split a trade stream into equal-volume buckets
111
+ - `bvcBuyFraction(priceChange, sigma)` — BVC buy fraction Φ(ΔP/σ)
112
+ - `vpin(buckets, { window, sigma })` — VPIN over the last `window` buckets
113
+ - `standardNormalCdf(z)` — Φ, the standard normal CDF (Abramowitz & Stegun 7.1.26)
114
+
115
+ ## Execution cost & price impact
116
+
117
+ Transaction-cost analysis (TCA) building blocks (buys `+1`, sells `−1`):
118
+
119
+ ```ts
120
+ import { effectiveSpread, realizedSpread, priceImpact, kyleLambda } from "orderflow-metrics";
121
+
122
+ effectiveSpread(101, 100, "buy"); // 2 — cost vs the midpoint
123
+ realizedSpread(101, 100.5, "buy"); // 1 — LP revenue after reversion
124
+ priceImpact(100, 100.5, "buy"); // 1 — permanent impact (effective − realized)
125
+
126
+ kyleLambda([ // price impact per unit signed flow
127
+ { signedVolume: 2, priceChange: 1 },
128
+ { signedVolume: -2, priceChange: -1 },
129
+ ]); // 0.5
130
+ ```
131
+
132
+ - `effectiveSpread` / `effectiveHalfSpread` — realized cost vs the quote mid
133
+ - `realizedSpread` — post-trade reversion component
134
+ - `priceImpact` — permanent impact
135
+ - `kyleLambda` — OLS impact slope of ΔP on signed volume
136
+ - `rollSpread` — Roll's (1984) spread from price-change autocovariance
137
+
138
+ ## Fair value
139
+
140
+ ```ts
141
+ import { weightedMid, relativeSpreadBps } from "orderflow-metrics";
142
+
143
+ weightedMid({ bidPrice: 100, bidSize: 9, askPrice: 101, askSize: 1 }); // ~100.9 — heavy bid pulls toward ask
144
+ relativeSpreadBps({ bidPrice: 99.99, bidSize: 1, askPrice: 100.01, askSize: 1 }); // 2 (bps)
145
+ ```
146
+
147
+ - `weightedMid` — imbalance-weighted mid (a simple micro-price)
148
+ - `mid` — arithmetic mid
149
+ - `relativeSpreadBps` — quoted spread in basis points
150
+
151
+ ## Trade-sign classification
152
+
153
+ Public prints rarely say who was the aggressor. Infer it so OFI / imbalance /
154
+ VPIN inputs can be signed (+1 buyer-initiated, −1 seller-initiated, 0 unknown):
155
+
156
+ ```ts
157
+ import { tickRule, leeReady } from "orderflow-metrics";
158
+
159
+ tickRule([100, 101, 101, 100]); // [0, 1, 1, -1]
160
+ leeReady([{ price: 101, mid: 100 }, { price: 99, mid: 100 }]); // [1, -1]
161
+ ```
162
+
163
+ - `tickRule` — sign from the change vs the previous price (zero ticks carry)
164
+ - `leeReady` — Lee-Ready (1991): quote rule, with the tick rule breaking ties
165
+
166
+ ## Liquidity
167
+
168
+ ```ts
169
+ import { amihudIlliquidity } from "orderflow-metrics";
170
+
171
+ amihudIlliquidity([
172
+ { ret: 0.02, volume: 100 },
173
+ { ret: -0.01, volume: 50 },
174
+ ]); // 0.0002 — price move per unit of volume; higher = thinner
175
+ ```
176
+
177
+ - `amihudIlliquidity` — Amihud (2002): average |return| / volume across periods
178
+
179
+ ## Volatility
180
+
181
+ ```ts
182
+ import { realizedVolatility, annualizedVolatility } from "orderflow-metrics";
183
+
184
+ realizedVolatility([0.03, 0.04]); // 0.05 — √(Σ rᵢ²)
185
+ annualizedVolatility(minuteReturns, 252 * 390); // scaled to a year
186
+ ```
187
+
188
+ - `realizedVariance` — Σ rᵢ²
189
+ - `realizedVolatility` — √ of the realized variance
190
+ - `annualizedVolatility` — √( mean(rᵢ²) · periodsPerYear )
191
+
192
+ ## Market efficiency
193
+
194
+ ```ts
195
+ import { varianceRatio, autocorrelation } from "orderflow-metrics";
196
+
197
+ varianceRatio(returns, 2); // <1 mean-reverting · ~1 random walk · >1 trending
198
+ autocorrelation(returns, 1); // lag-1 return autocorrelation
199
+ ```
200
+
201
+ - `varianceRatio` — Lo-MacKinlay variance ratio over overlapping q-period returns
202
+ - `autocorrelation` — lag-k autocorrelation of a return series
203
+
204
+ ## Order book
205
+
206
+ Reconstruct a limit order book from incremental level updates and read the
207
+ usual top-of-book / depth signals:
208
+
209
+ ```ts
210
+ import { OrderBook } from "orderflow-metrics";
211
+
212
+ const ob = new OrderBook();
213
+ ob.update("bid", 100, 5);
214
+ ob.update("ask", 101, 3);
215
+
216
+ ob.bestBid(); // { price: 100, size: 5 }
217
+ ob.mid(); // 100.5
218
+ ob.spread(); // 1
219
+ ob.imbalance(1); // 0.25 — top-of-book bid/ask size imbalance
220
+ ob.update("bid", 100, 0); // size 0 removes the level
221
+ ```
222
+
223
+ - `update(side, price, size)` · `bestBid` / `bestAsk` · `mid` · `spread`
224
+ - `depth(side, n)` — top n levels · `imbalance(n)` — depth imbalance in `[-1, 1]`
225
+
226
+ ### Market-order simulation
227
+
228
+ Sweep the book with a market order and see the real fill — VWAP price, slippage
229
+ and any unfilled size (read-only, the book isn't touched):
230
+
231
+ ```ts
232
+ import { simulateMarketOrder } from "orderflow-metrics";
233
+
234
+ const r = simulateMarketOrder(ob, "buy", 4);
235
+ r.avgPrice; // volume-weighted fill price
236
+ r.slippageBps; // cost vs mid, in basis points
237
+ r.remainingSize; // > 0 if the book was too thin
238
+ ```
239
+
240
+ ## Execution scheduling
241
+
242
+ Split a parent order into child slices:
243
+
244
+ ```ts
245
+ import { twap, pov } from "orderflow-metrics";
246
+
247
+ twap(100, 4); // [25, 25, 25, 25] — even time slices
248
+ pov(30, [100, 100, 100], 0.1); // [10, 10, 10] — 10% of each interval's volume
249
+ ```
250
+
251
+ - `twap` — time-weighted: even slices that sum exactly to the parent size
252
+ - `pov` — percentage-of-volume: participate at a fixed fraction of each interval
253
+
254
+ ## Information-driven bars
255
+
256
+ Sampling trades on a fixed time grid oversamples quiet periods and produces
257
+ non-IID returns. Sampling on **activity** instead — a bar every N ticks, N
258
+ units of volume, or N units of traded value — gives bars with much better
259
+ statistical properties (López de Prado, *Advances in Financial ML*, ch. 2).
260
+ Build them first, then run the other metrics on the resulting series.
261
+
262
+ ```ts
263
+ import { tickBars, volumeBars, dollarBars } from "orderflow-metrics";
264
+
265
+ const trades = [
266
+ { price: 100, size: 3, side: "buy" },
267
+ { price: 101, size: 4, side: "buy" },
268
+ { price: 100, size: 2, side: "sell" },
269
+ { price: 102, size: 5, side: "sell" },
270
+ ];
271
+
272
+ tickBars(trades, 2); // one bar per 2 trades
273
+ volumeBars(trades, 5); // new bar each time cumulative size ≥ 5
274
+ dollarBars(trades, 500); // new bar each time cumulative price·size ≥ 500
275
+ ```
276
+
277
+ Each `Bar` carries `open`/`high`/`low`/`close`, `volume`, `dollar` (traded
278
+ value), `vwap`, `ticks`, and signed `buyVolume` / `sellVolume` (plus `start` /
279
+ `end` timestamps when the feed provides them). The trade that crosses the
280
+ threshold is included whole (never split), and a trailing partial bar is
281
+ dropped. Dollar bars are usually preferred — they are the most robust of the
282
+ three to changes in price level.
283
+
284
+ - `tickBars(trades, threshold)` — a bar every `threshold` trades
285
+ - `volumeBars(trades, threshold)` — a bar every `threshold` units of volume
286
+ - `dollarBars(trades, threshold)` — a bar every `threshold` units of traded value
287
+
288
+ ## Market impact
289
+
290
+ Pre-trade cost models and post-trade markouts:
291
+
292
+ ```ts
293
+ import { squareRootImpact, almgrenChrissCost, markout } from "orderflow-metrics";
294
+
295
+ squareRootImpact(0.02, 1_000, 1_000_000); // Y·σ·√(Q/V) — empirical impact
296
+ almgrenChrissCost(10_000, 30, 1e-6, 2e-7); // { permanent, temporary, total }
297
+ markout("buy", 100, 100.5); // +0.5 — price moved with the trade
298
+ ```
299
+
300
+ - `squareRootImpact` — the empirical square-root law of impact
301
+ - `linearPermanentImpact` / `linearTemporaryImpact` — Almgren-Chriss impact terms
302
+ - `almgrenChrissCost` — expected TWAP cost, split into permanent vs temporary
303
+ - `markout` / `averageMarkout` — realized post-trade adverse-selection drift
304
+
305
+ ## Implementation shortfall
306
+
307
+ Execution-quality analytics against a decision / arrival benchmark:
308
+
309
+ ```ts
310
+ import { implementationShortfall, arrivalSlippageBps } from "orderflow-metrics";
311
+
312
+ implementationShortfall("buy", 100, 100.5, 800, 1000, 101, 5);
313
+ // { execution: 400, opportunity: 200, fees: 5, total: 605 }
314
+
315
+ arrivalSlippageBps("buy", 100, 100.5); // 50 bps paid up vs arrival
316
+ ```
317
+
318
+ - `implementationShortfall` — Perold's execution + opportunity + fees decomposition
319
+ - `arrivalSlippageBps` — signed slippage of the fill vs the arrival price
320
+
321
+ ## Spread estimators (from OHLC)
322
+
323
+ Recover the effective bid-ask spread when all you have is daily high, low, and
324
+ close — no tick data required:
325
+
326
+ ```ts
327
+ import { corwinSchultz, abdiRanaldo } from "orderflow-metrics";
328
+
329
+ const bars = [
330
+ { high: 10.2, low: 9.8, close: 10.18 },
331
+ { high: 10.25, low: 9.85, close: 9.88 },
332
+ { high: 10.3, low: 9.9, close: 10.27 },
333
+ ];
334
+
335
+ corwinSchultz(bars); // proportional spread from the two-day high-low range
336
+ abdiRanaldo(bars); // proportional spread from close vs high-low mid-range
337
+ ```
338
+
339
+ - `corwinSchultz` — Corwin & Schultz (2012) high-low estimator
340
+ - `abdiRanaldo` — Abdi & Ranaldo (2017) close/high/low estimator
341
+
342
+ Both return a proportional spread (a fraction of price); negative estimates are
343
+ floored at 0.
344
+
345
+ ## Range-based volatility (from OHLC)
346
+
347
+ Estimate volatility from the open, high, low, and close — far more efficient than
348
+ close-to-close when you have candles:
349
+
350
+ ```ts
351
+ import {
352
+ parkinsonVolatility,
353
+ garmanKlassVolatility,
354
+ rogersSatchellVolatility,
355
+ yangZhangVolatility,
356
+ } from "orderflow-metrics";
357
+
358
+ const candles = [
359
+ { open: 100, high: 105, low: 99, close: 102 },
360
+ { open: 102, high: 106, low: 101, close: 104 },
361
+ { open: 104, high: 104, low: 98, close: 99 },
362
+ ];
363
+
364
+ parkinsonVolatility(candles); // high-low range
365
+ garmanKlassVolatility(candles); // adds open & close
366
+ rogersSatchellVolatility(candles); // drift-independent
367
+ yangZhangVolatility(candles); // + overnight jumps (needs >= 3 bars)
368
+ ```
369
+
370
+ - `parkinsonVolatility` — Parkinson (1980), high-low range
371
+ - `garmanKlassVolatility` — Garman & Klass (1980), OHLC
372
+ - `rogersSatchellVolatility` — Rogers & Satchell (1991), drift-independent
373
+ - `yangZhangVolatility` — Yang & Zhang (2000), drift- and jump-robust
374
+
375
+ Each returns the volatility (standard deviation) per bar; multiply the variance
376
+ by bars-per-year to annualize.
377
+
378
+ ## Hurst exponent
379
+
380
+ Detect long-memory — trending vs mean-reverting — from a return series via
381
+ rescaled-range (R/S) analysis:
382
+
383
+ ```ts
384
+ import { hurstExponent } from "orderflow-metrics";
385
+
386
+ hurstExponent(returns);
387
+ // ~0.5 random walk · >0.5 persistent/trending · <0.5 mean-reverting
388
+ // NaN if the series is too short (needs ~32+ points)
389
+ ```
390
+
391
+ - `hurstExponent` — R/S Hurst estimate; a companion to `varianceRatio` and
392
+ `autocorrelation` for gauging market efficiency
393
+
394
+ ## Realized moments
395
+
396
+ Higher moments of the intraday return distribution (Amaya et al., 2015):
397
+
398
+ ```ts
399
+ import { realizedSkewness, realizedKurtosis } from "orderflow-metrics";
400
+
401
+ realizedSkewness(returns); // √N · Σr³ / RV^1.5 — intraday asymmetry
402
+ realizedKurtosis(returns); // N · Σr⁴ / RV² — intraday tail heaviness
403
+ ```
404
+
405
+ - `realizedSkewness` — asymmetry of the intraday return distribution
406
+ - `realizedKurtosis` — tail heaviness of the intraday return distribution
407
+
408
+ Both return 0 for an empty or zero-variance series.
409
+
410
+ ## Jumps & bipower variation
411
+
412
+ Split realized variance into its continuous (diffusive) part and its jump part
413
+ (Barndorff-Nielsen & Shephard, 2004). Bipower variation is jump-robust because
414
+ multiplying adjacent absolute returns damps a lone spike:
415
+
416
+ ```ts
417
+ import { bipowerVariation, jumpVariation, relativeJumpVariation } from "orderflow-metrics";
418
+
419
+ bipowerVariation(returns); // (π/2)·Σ|rᵢ₋₁||rᵢ| — continuous variance
420
+ jumpVariation(returns); // max(RV − BV, 0) — variance from jumps
421
+ relativeJumpVariation(returns); // jump share of RV, in [0, 1]
422
+ ```
423
+
424
+ - `bipowerVariation` — jump-robust estimate of continuous variance
425
+ - `jumpVariation` — the realized-variance contribution of discrete jumps
426
+ - `relativeJumpVariation` — that jump contribution as a fraction of RV
427
+
428
+ All three return 0 for fewer than two returns (and a jumpless series gives a jump
429
+ variation of 0).
430
+
431
+ ## Realized semivariance
432
+
433
+ Realized variance treats an up-move and a down-move of equal size as identical
434
+ risk. Realized semivariance splits it by the *sign* of each return, isolating
435
+ downside ("bad") from upside ("good") volatility — Barndorff-Nielsen,
436
+ Kinnebrock & Shephard (2010) and Patton & Shephard (2015):
437
+
438
+ ```ts
439
+ import {
440
+ realizedSemivariance,
441
+ downsideVarianceRatio,
442
+ signedJumpVariation,
443
+ } from "orderflow-metrics";
444
+
445
+ realizedSemivariance(returns); // { upside: Σr²·1{r>0}, downside: Σr²·1{r<0} }
446
+ downsideVarianceRatio(returns); // RS⁻ / (RS⁺ + RS⁻), in [0, 1]
447
+ signedJumpVariation(returns); // RS⁺ − RS⁻ — keeps the direction of jump risk
448
+ ```
449
+
450
+ - `realizedSemivariance` — upside/downside split (their sum is realized variance)
451
+ - `downsideVarianceRatio` — the negative-return share of RV; > 0.5 is downside-heavy
452
+ - `signedJumpVariation` — RS⁺ − RS⁻; positive when upside dominates, negative when downside does
453
+
454
+ Zero returns contribute to neither half, and an empty series returns zeros.
455
+
456
+ ## Order-flow entropy
457
+
458
+ Shannon entropy of order flow measures how *predictable* a stream of trades or
459
+ returns is. One-sided flow (nearly all buys, or a series that only ticks up)
460
+ carries little surprise — low entropy — and is easier to anticipate; balanced,
461
+ unpredictable flow sits at maximum entropy. Persistently low flow entropy is a
462
+ signature of directional, potentially informed activity. Reported in bits:
463
+
464
+ ```ts
465
+ import { shannonEntropy, normalizedEntropy, signEntropy } from "orderflow-metrics";
466
+
467
+ shannonEntropy([3, 1]); // 0.811… bits — H of a count/probability vector
468
+ normalizedEntropy([2, 1, 1]); // 0.946 — H / log₂(k), in [0, 1]
469
+ signEntropy(returns); // up/down balance of a series, in [0, 1] bits
470
+ ```
471
+
472
+ - `shannonEntropy` — H = −Σ pᵢ log₂ pᵢ over positive weights; two equal outcomes = 1 bit
473
+ - `normalizedEntropy` — that entropy scaled by log₂(k) so distributions of different sizes compare
474
+ - `signEntropy` — 1 bit is perfectly balanced two-sided flow, near 0 is one-sided and predictable
475
+
476
+ Zero and negative weights are ignored, and fewer than two live categories returns 0.
477
+
478
+ ## Online / streaming estimators
479
+
480
+ Batch metrics rescan the whole history on every tick. In a live pipeline you
481
+ want estimators that update in **O(1) time and memory** as each observation
482
+ streams in. These are stateful classes — `push` one value at a time and read the
483
+ current estimate — and they are numerically stable (Welford / West, not the
484
+ naive Σx² − (Σx)²/n form that loses precision when the mean dwarfs the variance):
485
+
486
+ ```ts
487
+ import { Welford, Ewma, EwmaVariance, RollingWindow } from "orderflow-metrics";
488
+
489
+ const w = new Welford();
490
+ for (const r of returns) w.push(r);
491
+ w.mean; w.variance; w.std; // running, exact, updated in O(1)
492
+
493
+ const vol = new EwmaVariance(0.94); // RiskMetrics daily λ
494
+ for (const r of returns) vol.push(r);
495
+ vol.std; // current EWMA volatility
496
+
497
+ const win = new RollingWindow(20); // trailing 20-observation window
498
+ for (const r of returns) win.push(r);
499
+ win.mean; win.variance; // O(1) add + evict (West 1979)
500
+ ```
501
+
502
+ - `Welford` — running mean & variance over all data (`variance` sample, `populationVariance`, `std`, `count`)
503
+ - `Ewma` — exponentially weighted moving average of a level; `lambda` in (0, 1) is the decay
504
+ - `EwmaVariance` — RiskMetrics-style EWMA variance/volatility; assumes ~zero-mean returns
505
+ - `RollingWindow` — mean & variance over the last `size` values, with O(1) add/remove
506
+
507
+ `Welford` and `RollingWindow` are verified in the test suite to equal a batch
508
+ recomputation at every step; the EWMA classes seed on their first value.
509
+
510
+ ## Realized covariance, correlation & beta
511
+
512
+ Single-asset volatility says how much one instrument moved; risk lives in how
513
+ instruments move *together*. Summing products of contemporaneous returns gives
514
+ the model-free, high-frequency analogue of covariance, correlation, and beta:
515
+
516
+ ```ts
517
+ import { realizedCovariance, realizedCorrelation, realizedBeta } from "orderflow-metrics";
518
+
519
+ realizedCovariance(x, y); // Σ xᵢyᵢ
520
+ realizedCorrelation(x, y); // Σxy / (√Σx²·√Σy²) — in [−1, 1]
521
+ realizedBeta(asset, market); // Σa·m / Σm² — sensitivity of asset to market
522
+ ```
523
+
524
+ - `realizedCovariance` — Σ xᵢyᵢ (symmetric)
525
+ - `realizedCorrelation` — scale-free co-movement in [−1, 1]
526
+ - `realizedBeta` — an asset's realized covariance with a market over the market's realized variance
527
+
528
+ The two series are paired element-wise over their common length, so align them to
529
+ the same sampling grid first; empty or zero-variance inputs return 0.
530
+
531
+ ## Python
532
+
533
+ A dependency-free Python port lives in [`python/`](python/) and ships the same
534
+ metrics (OFI, VPIN, information-driven bars, spreads, price impact, order-book
535
+ reconstruction). Install from PyPI:
536
+
537
+ ```bash
538
+ pip install orderflow-metrics
539
+ ```
540
+
541
+ See [python/README.md](python/README.md) for the Python API.
542
+
543
+ ## Tests
544
+
545
+ ```bash
546
+ node --experimental-strip-types --test
547
+ ```
548
+
549
+ ## License
550
+
551
+ MIT © RATE LTD (TwoWayMind). See [LICENSE](LICENSE).
552
+
553
+ ---
554
+
555
+ Part of [TwoWayMind](https://twowaymind.com)'s open microstructure tooling.
556
+ Educational and technical material only — not investment advice.
package/dist/bars.d.ts ADDED
@@ -0,0 +1,67 @@
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
+ /** One OHLCV bar aggregated from a slice of trades. */
20
+ export interface Bar {
21
+ /** Price of the first trade in the bar. */
22
+ open: number;
23
+ /** Highest trade price in the bar. */
24
+ high: number;
25
+ /** Lowest trade price in the bar. */
26
+ low: number;
27
+ /** Price of the last trade in the bar. */
28
+ close: number;
29
+ /** Total traded size. */
30
+ volume: number;
31
+ /** Total traded value, Σ price·size. */
32
+ dollar: number;
33
+ /** Volume-weighted average price, `dollar / volume`. */
34
+ vwap: number;
35
+ /** Number of trades aggregated into the bar. */
36
+ ticks: number;
37
+ /** Size of buyer-initiated trades. */
38
+ buyVolume: number;
39
+ /** Size of seller-initiated trades. */
40
+ sellVolume: number;
41
+ /** Timestamp of the first trade, if the feed provided one. */
42
+ start?: number;
43
+ /** Timestamp of the last trade, if the feed provided one. */
44
+ end?: number;
45
+ }
46
+ /**
47
+ * Emit a bar every `threshold` trades (tick bars).
48
+ *
49
+ * @param threshold number of trades per bar (integer ≥ 1)
50
+ */
51
+ export declare function tickBars(trades: readonly Trade[], threshold: number): Bar[];
52
+ /**
53
+ * Emit a bar each time cumulative size reaches `threshold` (volume bars).
54
+ * The trade that crosses the threshold closes the bar and is included whole.
55
+ *
56
+ * @param threshold volume per bar (> 0)
57
+ */
58
+ export declare function volumeBars(trades: readonly Trade[], threshold: number): Bar[];
59
+ /**
60
+ * Emit a bar each time cumulative traded value (Σ price·size) reaches
61
+ * `threshold` (dollar bars). Dollar bars are the most robust of the three to
62
+ * changes in price level and are usually preferred (López de Prado, 2018).
63
+ *
64
+ * @param threshold traded value per bar (> 0)
65
+ */
66
+ export declare function dollarBars(trades: readonly Trade[], threshold: number): Bar[];
67
+ //# sourceMappingURL=bars.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"bars.d.ts","sourceRoot":"","sources":["../src/bars.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AACH,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,YAAY,CAAC;AAExC,uDAAuD;AACvD,MAAM,WAAW,GAAG;IAClB,2CAA2C;IAC3C,IAAI,EAAE,MAAM,CAAC;IACb,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,qCAAqC;IACrC,GAAG,EAAE,MAAM,CAAC;IACZ,0CAA0C;IAC1C,KAAK,EAAE,MAAM,CAAC;IACd,yBAAyB;IACzB,MAAM,EAAE,MAAM,CAAC;IACf,wCAAwC;IACxC,MAAM,EAAE,MAAM,CAAC;IACf,wDAAwD;IACxD,IAAI,EAAE,MAAM,CAAC;IACb,gDAAgD;IAChD,KAAK,EAAE,MAAM,CAAC;IACd,sCAAsC;IACtC,SAAS,EAAE,MAAM,CAAC;IAClB,uCAAuC;IACvC,UAAU,EAAE,MAAM,CAAC;IACnB,8DAA8D;IAC9D,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,6DAA6D;IAC7D,GAAG,CAAC,EAAE,MAAM,CAAC;CACd;AAwCD;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,GAAG,EAAE,CAQ3E;AAED;;;;;GAKG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,GAAG,EAAE,CAE7E;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,SAAS,KAAK,EAAE,EAAE,SAAS,EAAE,MAAM,GAAG,GAAG,EAAE,CAE7E"}