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.
- package/CHANGELOG.md +229 -0
- package/LICENSE +21 -0
- package/README.md +556 -0
- package/dist/bars.d.ts +67 -0
- package/dist/bars.d.ts.map +1 -0
- package/dist/bars.js +92 -0
- package/dist/bars.js.map +1 -0
- package/dist/classify.d.ts +25 -0
- package/dist/classify.d.ts.map +1 -0
- package/dist/classify.js +57 -0
- package/dist/classify.js.map +1 -0
- package/dist/covariance.d.ts +37 -0
- package/dist/covariance.d.ts.map +1 -0
- package/dist/covariance.js +72 -0
- package/dist/covariance.js.map +1 -0
- package/dist/efficiency.d.ts +24 -0
- package/dist/efficiency.d.ts.map +1 -0
- package/dist/efficiency.js +65 -0
- package/dist/efficiency.js.map +1 -0
- package/dist/entropy.d.ts +40 -0
- package/dist/entropy.d.ts.map +1 -0
- package/dist/entropy.js +73 -0
- package/dist/entropy.js.map +1 -0
- package/dist/execution.d.ts +40 -0
- package/dist/execution.d.ts.map +1 -0
- package/dist/execution.js +64 -0
- package/dist/execution.js.map +1 -0
- package/dist/fairvalue.d.ts +19 -0
- package/dist/fairvalue.d.ts.map +1 -0
- package/dist/fairvalue.js +27 -0
- package/dist/fairvalue.js.map +1 -0
- package/dist/hurst.d.ts +23 -0
- package/dist/hurst.d.ts.map +1 -0
- package/dist/hurst.js +71 -0
- package/dist/hurst.js.map +1 -0
- package/dist/imbalance.d.ts +12 -0
- package/dist/imbalance.d.ts.map +1 -0
- package/dist/imbalance.js +19 -0
- package/dist/imbalance.js.map +1 -0
- package/dist/impact.d.ts +59 -0
- package/dist/impact.d.ts.map +1 -0
- package/dist/impact.js +63 -0
- package/dist/impact.js.map +1 -0
- package/dist/index.d.ts +39 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +25 -0
- package/dist/index.js.map +1 -0
- package/dist/jumps.d.ts +33 -0
- package/dist/jumps.d.ts.map +1 -0
- package/dist/jumps.js +63 -0
- package/dist/jumps.js.map +1 -0
- package/dist/liquidity.d.ts +18 -0
- package/dist/liquidity.d.ts.map +1 -0
- package/dist/liquidity.js +22 -0
- package/dist/liquidity.js.map +1 -0
- package/dist/moments.d.ts +20 -0
- package/dist/moments.d.ts.map +1 -0
- package/dist/moments.js +48 -0
- package/dist/moments.js.map +1 -0
- package/dist/ofi.d.ts +22 -0
- package/dist/ofi.d.ts.map +1 -0
- package/dist/ofi.js +25 -0
- package/dist/ofi.js.map +1 -0
- package/dist/online.d.ts +101 -0
- package/dist/online.d.ts.map +1 -0
- package/dist/online.js +205 -0
- package/dist/online.js.map +1 -0
- package/dist/orderbook.d.ts +30 -0
- package/dist/orderbook.d.ts.map +1 -0
- package/dist/orderbook.js +66 -0
- package/dist/orderbook.js.map +1 -0
- package/dist/rangevol.d.ts +40 -0
- package/dist/rangevol.d.ts.map +1 -0
- package/dist/rangevol.js +101 -0
- package/dist/rangevol.js.map +1 -0
- package/dist/scheduling.d.ts +19 -0
- package/dist/scheduling.d.ts.map +1 -0
- package/dist/scheduling.js +45 -0
- package/dist/scheduling.js.map +1 -0
- package/dist/semivar.d.ts +49 -0
- package/dist/semivar.d.ts.map +1 -0
- package/dist/semivar.js +59 -0
- package/dist/semivar.js.map +1 -0
- package/dist/shortfall.d.ts +42 -0
- package/dist/shortfall.d.ts.map +1 -0
- package/dist/shortfall.js +32 -0
- package/dist/shortfall.js.map +1 -0
- package/dist/simulate.d.ts +29 -0
- package/dist/simulate.d.ts.map +1 -0
- package/dist/simulate.js +36 -0
- package/dist/simulate.js.map +1 -0
- package/dist/spreadest.d.ts +38 -0
- package/dist/spreadest.d.ts.map +1 -0
- package/dist/spreadest.js +68 -0
- package/dist/spreadest.js.map +1 -0
- package/dist/types.d.ts +24 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +9 -0
- package/dist/types.js.map +1 -0
- package/dist/volatility.d.ts +21 -0
- package/dist/volatility.d.ts.map +1 -0
- package/dist/volatility.js +33 -0
- package/dist/volatility.js.map +1 -0
- package/dist/vpin.d.ts +38 -0
- package/dist/vpin.d.ts.map +1 -0
- package/dist/vpin.js +81 -0
- package/dist/vpin.js.map +1 -0
- package/package.json +70 -0
- package/src/bars.ts +141 -0
- package/src/classify.ts +59 -0
- package/src/covariance.ts +72 -0
- package/src/efficiency.ts +64 -0
- package/src/entropy.ts +67 -0
- package/src/execution.ts +100 -0
- package/src/fairvalue.ts +33 -0
- package/src/hurst.ts +77 -0
- package/src/imbalance.ts +25 -0
- package/src/impact.ts +99 -0
- package/src/index.ts +87 -0
- package/src/jumps.ts +61 -0
- package/src/liquidity.ts +29 -0
- package/src/moments.ts +45 -0
- package/src/ofi.ts +46 -0
- package/src/online.ts +228 -0
- package/src/orderbook.ts +76 -0
- package/src/rangevol.ts +108 -0
- package/src/scheduling.ts +50 -0
- package/src/semivar.ts +67 -0
- package/src/shortfall.ts +69 -0
- package/src/simulate.ts +68 -0
- package/src/spreadest.ts +75 -0
- package/src/types.ts +26 -0
- package/src/volatility.ts +36 -0
- package/src/vpin.ts +98 -0
package/README.md
ADDED
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
# orderflow-metrics
|
|
2
|
+
|
|
3
|
+
[](https://github.com/twowaymind/orderflow-metrics/actions/workflows/ci.yml)
|
|
4
|
+
[](https://www.npmjs.com/package/orderflow-metrics)
|
|
5
|
+
[](https://pypi.org/project/orderflow-metrics/)
|
|
6
|
+
[](https://pypi.org/project/orderflow-metrics/)
|
|
7
|
+
[](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"}
|