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
|
@@ -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
|
package/dist/vpin.js.map
ADDED
|
@@ -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
|
+
}
|
package/src/classify.ts
ADDED
|
@@ -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
|
+
}
|