Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
dc415a77fd | ||
|
|
e385734275 |
+7
-1
@@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
|
|
||||||
## [Unreleased]
|
## [Unreleased]
|
||||||
|
|
||||||
|
## [0.7.0] - 2026-06-08
|
||||||
|
- **Hasbrouck Information Share** — variance-ratio proxy for each venue's share of price discovery (Hasbrouck information share) (`HasbrouckInformationShare`).
|
||||||
|
- **PIN** — probability of informed trading from rolling buy/sell imbalance (EKOP single-window estimator) (`Pin`).
|
||||||
|
- **Trade-Sign Autocorrelation** — lag-1 autocorrelation of the signed trade aggressor (order-flow persistence) (`TradeSignAutocorrelation`).
|
||||||
|
|
||||||
## [0.6.9] - 2026-06-08
|
## [0.6.9] - 2026-06-08
|
||||||
- **Tristar** — a three-doji star reversal: three consecutive dojis with the middle gapped above (bearish) or below (bullish) its neighbours (`Tristar`).
|
- **Tristar** — a three-doji star reversal: three consecutive dojis with the middle gapped above (bearish) or below (bullish) its neighbours (`Tristar`).
|
||||||
- **Harami Cross** — a Harami whose second candle is a contained doji, a stronger reversal than a plain Harami (`HaramiCross`).
|
- **Harami Cross** — a Harami whose second candle is a contained doji, a stronger reversal than a plain Harami (`HaramiCross`).
|
||||||
@@ -1377,7 +1382,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||||||
optional Binance live feed.
|
optional Binance live feed.
|
||||||
- Bindings for Python, Node.js, and WebAssembly.
|
- Bindings for Python, Node.js, and WebAssembly.
|
||||||
|
|
||||||
[Unreleased]: https://github.com/wickra-lib/wickra/compare/v0.6.9...HEAD
|
[Unreleased]: https://github.com/wickra-lib/wickra/compare/v0.7.0...HEAD
|
||||||
|
[0.7.0]: https://github.com/wickra-lib/wickra/compare/v0.6.9...v0.7.0
|
||||||
[0.6.9]: https://github.com/wickra-lib/wickra/compare/v0.6.8...v0.6.9
|
[0.6.9]: https://github.com/wickra-lib/wickra/compare/v0.6.8...v0.6.9
|
||||||
[0.6.8]: https://github.com/wickra-lib/wickra/compare/v0.6.7...v0.6.8
|
[0.6.8]: https://github.com/wickra-lib/wickra/compare/v0.6.7...v0.6.8
|
||||||
[0.6.7]: https://github.com/wickra-lib/wickra/compare/v0.6.6...v0.6.7
|
[0.6.7]: https://github.com/wickra-lib/wickra/compare/v0.6.6...v0.6.7
|
||||||
|
|||||||
Generated
+8
-8
@@ -1944,7 +1944,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wickra"
|
name = "wickra"
|
||||||
version = "0.6.9"
|
version = "0.7.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"approx",
|
"approx",
|
||||||
"criterion",
|
"criterion",
|
||||||
@@ -1955,7 +1955,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wickra-bench"
|
name = "wickra-bench"
|
||||||
version = "0.6.9"
|
version = "0.7.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"criterion",
|
"criterion",
|
||||||
"kand",
|
"kand",
|
||||||
@@ -1967,7 +1967,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wickra-core"
|
name = "wickra-core"
|
||||||
version = "0.6.9"
|
version = "0.7.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"approx",
|
"approx",
|
||||||
"proptest",
|
"proptest",
|
||||||
@@ -1977,7 +1977,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wickra-data"
|
name = "wickra-data"
|
||||||
version = "0.6.9"
|
version = "0.7.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"approx",
|
"approx",
|
||||||
"csv",
|
"csv",
|
||||||
@@ -1994,7 +1994,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wickra-examples"
|
name = "wickra-examples"
|
||||||
version = "0.6.9"
|
version = "0.7.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"tokio",
|
"tokio",
|
||||||
@@ -2004,7 +2004,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wickra-node"
|
name = "wickra-node"
|
||||||
version = "0.6.9"
|
version = "0.7.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"napi",
|
"napi",
|
||||||
"napi-build",
|
"napi-build",
|
||||||
@@ -2014,7 +2014,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wickra-python"
|
name = "wickra-python"
|
||||||
version = "0.6.9"
|
version = "0.7.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"numpy",
|
"numpy",
|
||||||
"pyo3",
|
"pyo3",
|
||||||
@@ -2023,7 +2023,7 @@ dependencies = [
|
|||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "wickra-wasm"
|
name = "wickra-wasm"
|
||||||
version = "0.6.9"
|
version = "0.7.0"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"console_error_panic_hook",
|
"console_error_panic_hook",
|
||||||
"js-sys",
|
"js-sys",
|
||||||
|
|||||||
+2
-2
@@ -13,7 +13,7 @@ members = [
|
|||||||
exclude = ["fuzz"]
|
exclude = ["fuzz"]
|
||||||
|
|
||||||
[workspace.package]
|
[workspace.package]
|
||||||
version = "0.6.9"
|
version = "0.7.0"
|
||||||
authors = ["kingchenc <support@wickra.org>"]
|
authors = ["kingchenc <support@wickra.org>"]
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
rust-version = "1.86"
|
rust-version = "1.86"
|
||||||
@@ -25,7 +25,7 @@ keywords = ["finance", "trading", "indicators", "technical-analysis", "ta"]
|
|||||||
categories = ["finance", "mathematics", "science"]
|
categories = ["finance", "mathematics", "science"]
|
||||||
|
|
||||||
[workspace.dependencies]
|
[workspace.dependencies]
|
||||||
wickra-core = { path = "crates/wickra-core", version = "0.6.9" }
|
wickra-core = { path = "crates/wickra-core", version = "0.7.0" }
|
||||||
|
|
||||||
thiserror = "2"
|
thiserror = "2"
|
||||||
rayon = "1.10"
|
rayon = "1.10"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<p align="center">
|
<p align="center">
|
||||||
<a href="https://wickra.org"><img src="https://raw.githubusercontent.com/wickra-lib/.github/main/profile/wickra-banner.webp?v=485" alt="Wickra — streaming-first technical indicators" width="100%"></a>
|
<a href="https://wickra.org"><img src="https://raw.githubusercontent.com/wickra-lib/.github/main/profile/wickra-banner.webp?v=488" alt="Wickra — streaming-first technical indicators" width="100%"></a>
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
[](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml)
|
[](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml)
|
||||||
@@ -48,7 +48,7 @@ Full documentation lives at **[docs.wickra.org](https://docs.wickra.org)**:
|
|||||||
[Node](https://docs.wickra.org/Quickstart-Node),
|
[Node](https://docs.wickra.org/Quickstart-Node),
|
||||||
[WASM](https://docs.wickra.org/Quickstart-WASM).
|
[WASM](https://docs.wickra.org/Quickstart-WASM).
|
||||||
- **Indicators** — a per-indicator deep dive (formula, parameters, warmup) for
|
- **Indicators** — a per-indicator deep dive (formula, parameters, warmup) for
|
||||||
every one of the 485 indicators; start at the
|
every one of the 488 indicators; start at the
|
||||||
[indicators overview](https://docs.wickra.org/Indicators-Overview).
|
[indicators overview](https://docs.wickra.org/Indicators-Overview).
|
||||||
- **Reference** — [warmup periods](https://docs.wickra.org/Warmup-Periods),
|
- **Reference** — [warmup periods](https://docs.wickra.org/Warmup-Periods),
|
||||||
[streaming vs batch](https://docs.wickra.org/Streaming-vs-Batch),
|
[streaming vs batch](https://docs.wickra.org/Streaming-vs-Batch),
|
||||||
@@ -66,7 +66,7 @@ an afterthought — **live, tick-by-tick data** — without giving up the breadt
|
|||||||
a full batch library, and without making you reimplement your indicators four
|
a full batch library, and without making you reimplement your indicators four
|
||||||
times to get there.
|
times to get there.
|
||||||
|
|
||||||
- **The biggest streaming-native catalogue, period.** 485 indicators across 24
|
- **The biggest streaming-native catalogue, period.** 488 indicators across 24
|
||||||
families — candlesticks, harmonic & chart patterns, market profile, market
|
families — candlesticks, harmonic & chart patterns, market profile, market
|
||||||
breadth, Renko/Kagi/Point&Figure bars, Ehlers DSP cycles, risk/performance
|
breadth, Renko/Kagi/Point&Figure bars, Ehlers DSP cycles, risk/performance
|
||||||
metrics — every single one updating in **O(1) per tick**. TA-Lib ships ~150 and
|
metrics — every single one updating in **O(1) per tick**. TA-Lib ships ~150 and
|
||||||
@@ -77,7 +77,7 @@ times to get there.
|
|||||||
- **Correct by construction, not by hope.** Every `update` validates its input,
|
- **Correct by construction, not by hope.** Every `update` validates its input,
|
||||||
runs a real warmup, and returns an `Option` so a single bad tick can't silently
|
runs a real warmup, and returns an `Option` so a single bad tick can't silently
|
||||||
poison state. `batch == streaming` is **bit-exact, fuzzed and 100 %-line-covered
|
poison state. `batch == streaming` is **bit-exact, fuzzed and 100 %-line-covered
|
||||||
for all 485 indicators**.
|
for all 488 indicators**.
|
||||||
- **Orders of magnitude faster where it counts.** In streaming Wickra is **11–56×**
|
- **Orders of magnitude faster where it counts.** In streaming Wickra is **11–56×**
|
||||||
faster than the only other incremental peer and **thousands of times** faster
|
faster than the only other incremental peer and **thousands of times** faster
|
||||||
than recompute-on-every-tick libraries. On batch it wins several rows outright
|
than recompute-on-every-tick libraries. On batch it wins several rows outright
|
||||||
@@ -95,7 +95,7 @@ Every other library forces one of those compromises. Wickra doesn't:
|
|||||||
|
|
||||||
| Library | Install | Streaming | Languages | Indicators | Active |
|
| Library | Install | Streaming | Languages | Indicators | Active |
|
||||||
|------------------|-------------|-------------|-----------------------------|-----------:|--------|
|
|------------------|-------------|-------------|-----------------------------|-----------:|--------|
|
||||||
| **★ Wickra**| **clean** | **yes, O(1)** | **Python · Node · WASM · Rust** | **485** | **yes** |
|
| **★ Wickra**| **clean** | **yes, O(1)** | **Python · Node · WASM · Rust** | **488** | **yes** |
|
||||||
| kand | clean | yes | Python · WASM · Rust | ~60 | yes |
|
| kand | clean | yes | Python · WASM · Rust | ~60 | yes |
|
||||||
| ta-rs | clean | yes | Rust only | ~30 | stale |
|
| ta-rs | clean | yes | Rust only | ~30 | stale |
|
||||||
| yata | clean | partial | Rust only | ~35 | yes |
|
| yata | clean | partial | Rust only | ~35 | yes |
|
||||||
@@ -128,7 +128,7 @@ Full tables (Rust + Python, streaming + batch) and how to reproduce them live in
|
|||||||
|
|
||||||
## Indicators
|
## Indicators
|
||||||
|
|
||||||
485 streaming-first indicators across twenty-four families. Every one passes the
|
488 streaming-first indicators across twenty-four families. Every one passes the
|
||||||
`batch == streaming` equivalence test, reference-value tests, and reset
|
`batch == streaming` equivalence test, reference-value tests, and reset
|
||||||
semantics tests. Each has a per-indicator deep dive (formula, parameters,
|
semantics tests. Each has a per-indicator deep dive (formula, parameters,
|
||||||
warmup) at [docs.wickra.org](https://docs.wickra.org/Indicators-Overview).
|
warmup) at [docs.wickra.org](https://docs.wickra.org/Indicators-Overview).
|
||||||
@@ -153,7 +153,7 @@ warmup) at [docs.wickra.org](https://docs.wickra.org/Indicators-Overview).
|
|||||||
| Chart Patterns | Double Top / Bottom, Triple Top / Bottom, Head and Shoulders, Triangle (asc/desc/sym), Wedge (rising/falling), Flag / Pennant, Rectangle / Range, Cup and Handle |
|
| Chart Patterns | Double Top / Bottom, Triple Top / Bottom, Head and Shoulders, Triangle (asc/desc/sym), Wedge (rising/falling), Flag / Pennant, Rectangle / Range, Cup and Handle |
|
||||||
| Harmonic Patterns | AB=CD, Gartley, Butterfly, Bat, Crab, Shark, Cypher, Three Drives |
|
| Harmonic Patterns | AB=CD, Gartley, Butterfly, Bat, Crab, Shark, Cypher, Three Drives |
|
||||||
| Fibonacci | Fibonacci Retracement, Fibonacci Extension, Fibonacci Projection, Auto-Fibonacci, Golden Pocket, Fibonacci Confluence, Fibonacci Fan, Fibonacci Arcs, Fibonacci Channel, Fibonacci Time Zones |
|
| Fibonacci | Fibonacci Retracement, Fibonacci Extension, Fibonacci Projection, Auto-Fibonacci, Golden Pocket, Fibonacci Confluence, Fibonacci Fan, Fibonacci Arcs, Fibonacci Channel, Fibonacci Time Zones |
|
||||||
| Microstructure | Order-Book Imbalance (Top-1 / Top-N / Full), Microprice, Quoted Spread, Depth Slope, Signed Volume, Cumulative Volume Delta, Trade Imbalance, Effective Spread, Realized Spread, Kyle's Lambda, Footprint, Order Flow Imbalance, VPIN, Amihud Illiquidity, Roll Measure |
|
| Microstructure | Order-Book Imbalance (Top-1 / Top-N / Full), Microprice, Quoted Spread, Depth Slope, Signed Volume, Cumulative Volume Delta, Trade Imbalance, Effective Spread, Realized Spread, Kyle's Lambda, Footprint, Order Flow Imbalance, VPIN, Amihud Illiquidity, Roll Measure, Trade-Sign Autocorrelation, Hasbrouck Information Share |
|
||||||
| Derivatives | Funding Rate, Funding Rate Mean, Funding Rate Z-Score, Funding Basis, Open-Interest Delta, OI / Price Divergence, OI-Weighted Price, Long/Short Ratio, Taker Buy/Sell Ratio, Liquidation Features, Term-Structure Basis, Calendar Spread |
|
| Derivatives | Funding Rate, Funding Rate Mean, Funding Rate Z-Score, Funding Basis, Open-Interest Delta, OI / Price Divergence, OI-Weighted Price, Long/Short Ratio, Taker Buy/Sell Ratio, Liquidation Features, Term-Structure Basis, Calendar Spread |
|
||||||
| Market Profile | Value Area (POC / VAH / VAL), Volume Profile (histogram), TPO Profile, Initial Balance, Opening Range |
|
| Market Profile | Value Area (POC / VAH / VAL), Volume Profile (histogram), TPO Profile, Initial Balance, Opening Range |
|
||||||
| Market Breadth | Advance/Decline Line, Advance/Decline Ratio, Advance/Decline Volume Line, McClellan Oscillator, McClellan Summation Index, TRIN / Arms Index, Breadth Thrust, New Highs - New Lows, High-Low Index, Percent Above Moving Average, Up/Down Volume Ratio, Bullish Percent Index, Cumulative Volume Index, Absolute Breadth Index, TICK Index |
|
| Market Breadth | Advance/Decline Line, Advance/Decline Ratio, Advance/Decline Volume Line, McClellan Oscillator, McClellan Summation Index, TRIN / Arms Index, Breadth Thrust, New Highs - New Lows, High-Low Index, Percent Above Moving Average, Up/Down Volume Ratio, Bullish Percent Index, Cumulative Volume Index, Absolute Breadth Index, TICK Index |
|
||||||
@@ -237,7 +237,7 @@ A Python live-trading example using the public `websockets` package lives at
|
|||||||
```
|
```
|
||||||
wickra/
|
wickra/
|
||||||
├── crates/
|
├── crates/
|
||||||
│ ├── wickra-core/ core engine + all 485 indicators
|
│ ├── wickra-core/ core engine + all 488 indicators
|
||||||
│ ├── wickra/ top-level facade crate (publishes on crates.io) + benches/
|
│ ├── wickra/ top-level facade crate (publishes on crates.io) + benches/
|
||||||
│ ├── wickra-data/ CSV reader, tick aggregator, live exchange feeds
|
│ ├── wickra-data/ CSV reader, tick aggregator, live exchange feeds
|
||||||
│ └── wickra-bench/ internal cross-library benchmark harness (not published)
|
│ └── wickra-bench/ internal cross-library benchmark harness (not published)
|
||||||
|
|||||||
@@ -667,6 +667,7 @@ const pairFactories = {
|
|||||||
GrangerCausality: () => new wickra.GrangerCausality(60, 1),
|
GrangerCausality: () => new wickra.GrangerCausality(60, 1),
|
||||||
SpreadAr1Coefficient: () => new wickra.SpreadAr1Coefficient(40),
|
SpreadAr1Coefficient: () => new wickra.SpreadAr1Coefficient(40),
|
||||||
KendallTau: () => new wickra.KendallTau(20),
|
KendallTau: () => new wickra.KendallTau(20),
|
||||||
|
HasbrouckInformationShare: () => new wickra.HasbrouckInformationShare(2),
|
||||||
};
|
};
|
||||||
|
|
||||||
for (const [name, make] of Object.entries(pairFactories)) {
|
for (const [name, make] of Object.entries(pairFactories)) {
|
||||||
@@ -1270,7 +1271,7 @@ test('vpin / amihud / roll reference + streaming matches batch', () => {
|
|||||||
const price = Array.from({ length: n }, (_, i) => 100 + Math.sin(i * 0.25) * 4);
|
const price = Array.from({ length: n }, (_, i) => 100 + Math.sin(i * 0.25) * 4);
|
||||||
const size = Array.from({ length: n }, (_, i) => 1 + (i % 5));
|
const size = Array.from({ length: n }, (_, i) => 1 + (i % 5));
|
||||||
const isBuy = Array.from({ length: n }, (_, i) => i % 2 === 0);
|
const isBuy = Array.from({ length: n }, (_, i) => i % 2 === 0);
|
||||||
for (const make of [() => new wickra.Vpin(8, 5), () => new wickra.AmihudIlliquidity(14), () => new wickra.RollMeasure(14)]) {
|
for (const make of [() => new wickra.Vpin(8, 5), () => new wickra.AmihudIlliquidity(14), () => new wickra.RollMeasure(14), () => new wickra.TradeSignAutocorrelation(10), () => new wickra.Pin(10)]) {
|
||||||
const batch = make().batch(price, size, isBuy);
|
const batch = make().batch(price, size, isBuy);
|
||||||
const streamer = make();
|
const streamer = make();
|
||||||
assert.equal(batch.length, n);
|
assert.equal(batch.length, n);
|
||||||
@@ -1279,6 +1280,16 @@ test('vpin / amihud / roll reference + streaming matches batch', () => {
|
|||||||
assert.ok((Number.isNaN(batch[i]) && s === null) || Math.abs(s - batch[i]) < 1e-9, `mismatch at ${i}`);
|
assert.ok((Number.isNaN(batch[i]) && s === null) || Math.abs(s - batch[i]) < 1e-9, `mismatch at ${i}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Trade-sign autocorrelation: alternating signs -> -1, all buys -> +1.
|
||||||
|
let tsac = null;
|
||||||
|
const tsacInd = new wickra.TradeSignAutocorrelation(10);
|
||||||
|
for (let i = 0; i < 20; i++) tsac = tsacInd.update(100, 1, i % 2 === 0);
|
||||||
|
assert.ok(Math.abs(tsac - -1.0) < 1e-12);
|
||||||
|
// PIN: one-sided flow -> 1, balanced flow -> 0.
|
||||||
|
let pin = null;
|
||||||
|
const pinInd = new wickra.Pin(10);
|
||||||
|
for (let i = 0; i < 20; i++) pin = pinInd.update(100, 1, true);
|
||||||
|
assert.ok(Math.abs(pin - 1.0) < 1e-12);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('price-impact indicators reference values', () => {
|
test('price-impact indicators reference values', () => {
|
||||||
|
|||||||
Vendored
+31
@@ -1449,6 +1449,19 @@ export declare class BetaNeutralSpread {
|
|||||||
isReady(): boolean
|
isReady(): boolean
|
||||||
warmupPeriod(): number
|
warmupPeriod(): number
|
||||||
}
|
}
|
||||||
|
export type HasbrouckInformationShareNode = HasbrouckInformationShare
|
||||||
|
export declare class HasbrouckInformationShare {
|
||||||
|
constructor(period: number)
|
||||||
|
update(x: number, y: number): number | null
|
||||||
|
/**
|
||||||
|
* Batch over two equally-sized arrays. Returns a length-`n` array
|
||||||
|
* with `NaN` for warmup positions.
|
||||||
|
*/
|
||||||
|
batch(x: Array<number>, y: Array<number>): Array<number>
|
||||||
|
reset(): void
|
||||||
|
isReady(): boolean
|
||||||
|
warmupPeriod(): number
|
||||||
|
}
|
||||||
export type PairSpreadZScoreNode = PairSpreadZScore
|
export type PairSpreadZScoreNode = PairSpreadZScore
|
||||||
/**
|
/**
|
||||||
* Pair spread z-score: two ctor params (`betaPeriod`, `zPeriod`), one `(a, b)`
|
* Pair spread z-score: two ctor params (`betaPeriod`, `zPeriod`), one `(a, b)`
|
||||||
@@ -4333,6 +4346,24 @@ export declare class TradeImbalance {
|
|||||||
isReady(): boolean
|
isReady(): boolean
|
||||||
warmupPeriod(): number
|
warmupPeriod(): number
|
||||||
}
|
}
|
||||||
|
export type TradeSignAutocorrelationNode = TradeSignAutocorrelation
|
||||||
|
export declare class TradeSignAutocorrelation {
|
||||||
|
constructor(period: number)
|
||||||
|
update(price: number, size: number, isBuy: boolean): number | null
|
||||||
|
batch(price: Array<number>, size: Array<number>, isBuy: Array<boolean>): Array<number>
|
||||||
|
reset(): void
|
||||||
|
isReady(): boolean
|
||||||
|
warmupPeriod(): number
|
||||||
|
}
|
||||||
|
export type PinNode = Pin
|
||||||
|
export declare class Pin {
|
||||||
|
constructor(window: number)
|
||||||
|
update(price: number, size: number, isBuy: boolean): number | null
|
||||||
|
batch(price: Array<number>, size: Array<number>, isBuy: Array<boolean>): Array<number>
|
||||||
|
reset(): void
|
||||||
|
isReady(): boolean
|
||||||
|
warmupPeriod(): number
|
||||||
|
}
|
||||||
export type OrderFlowImbalanceNode = OrderFlowImbalance
|
export type OrderFlowImbalanceNode = OrderFlowImbalance
|
||||||
export declare class OrderFlowImbalance {
|
export declare class OrderFlowImbalance {
|
||||||
constructor(period: number)
|
constructor(period: number)
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "wickra-darwin-arm64",
|
"name": "wickra-darwin-arm64",
|
||||||
"version": "0.6.9",
|
"version": "0.7.0",
|
||||||
"description": "Native binding for wickra (macOS Apple Silicon). Installed automatically as an optional dependency of wickra on matching platforms.",
|
"description": "Native binding for wickra (macOS Apple Silicon). Installed automatically as an optional dependency of wickra on matching platforms.",
|
||||||
"main": "wickra.darwin-arm64.node",
|
"main": "wickra.darwin-arm64.node",
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "wickra-darwin-x64",
|
"name": "wickra-darwin-x64",
|
||||||
"version": "0.6.9",
|
"version": "0.7.0",
|
||||||
"description": "Native binding for wickra (macOS Intel). Installed automatically as an optional dependency of wickra on matching platforms.",
|
"description": "Native binding for wickra (macOS Intel). Installed automatically as an optional dependency of wickra on matching platforms.",
|
||||||
"main": "wickra.darwin-x64.node",
|
"main": "wickra.darwin-x64.node",
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "wickra-linux-arm64-gnu",
|
"name": "wickra-linux-arm64-gnu",
|
||||||
"version": "0.6.9",
|
"version": "0.7.0",
|
||||||
"description": "Native binding for wickra (linux arm64 GNU). Installed automatically as an optional dependency of wickra on matching platforms.",
|
"description": "Native binding for wickra (linux arm64 GNU). Installed automatically as an optional dependency of wickra on matching platforms.",
|
||||||
"main": "wickra.linux-arm64-gnu.node",
|
"main": "wickra.linux-arm64-gnu.node",
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "wickra-linux-x64-gnu",
|
"name": "wickra-linux-x64-gnu",
|
||||||
"version": "0.6.9",
|
"version": "0.7.0",
|
||||||
"description": "Native binding for wickra (linux x64 GNU). Installed automatically as an optional dependency of wickra on matching platforms.",
|
"description": "Native binding for wickra (linux x64 GNU). Installed automatically as an optional dependency of wickra on matching platforms.",
|
||||||
"main": "wickra.linux-x64-gnu.node",
|
"main": "wickra.linux-x64-gnu.node",
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "wickra-win32-arm64-msvc",
|
"name": "wickra-win32-arm64-msvc",
|
||||||
"version": "0.6.9",
|
"version": "0.7.0",
|
||||||
"description": "Native binding for wickra (Windows arm64 MSVC). Installed automatically as an optional dependency of wickra on matching platforms.",
|
"description": "Native binding for wickra (Windows arm64 MSVC). Installed automatically as an optional dependency of wickra on matching platforms.",
|
||||||
"main": "wickra.win32-arm64-msvc.node",
|
"main": "wickra.win32-arm64-msvc.node",
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "wickra-win32-x64-msvc",
|
"name": "wickra-win32-x64-msvc",
|
||||||
"version": "0.6.9",
|
"version": "0.7.0",
|
||||||
"description": "Native binding for wickra (Windows x64 MSVC). Installed automatically as an optional dependency of wickra on matching platforms.",
|
"description": "Native binding for wickra (Windows x64 MSVC). Installed automatically as an optional dependency of wickra on matching platforms.",
|
||||||
"main": "wickra.win32-x64-msvc.node",
|
"main": "wickra.win32-x64-msvc.node",
|
||||||
"files": [
|
"files": [
|
||||||
|
|||||||
Generated
+20
-20
@@ -1,12 +1,12 @@
|
|||||||
{
|
{
|
||||||
"name": "wickra",
|
"name": "wickra",
|
||||||
"version": "0.6.9",
|
"version": "0.7.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "wickra",
|
"name": "wickra",
|
||||||
"version": "0.6.9",
|
"version": "0.7.0",
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@napi-rs/cli": "^2.18.0"
|
"@napi-rs/cli": "^2.18.0"
|
||||||
@@ -15,12 +15,12 @@
|
|||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"wickra-darwin-arm64": "0.6.9",
|
"wickra-darwin-arm64": "0.7.0",
|
||||||
"wickra-darwin-x64": "0.6.9",
|
"wickra-darwin-x64": "0.7.0",
|
||||||
"wickra-linux-arm64-gnu": "0.6.9",
|
"wickra-linux-arm64-gnu": "0.7.0",
|
||||||
"wickra-linux-x64-gnu": "0.6.9",
|
"wickra-linux-x64-gnu": "0.7.0",
|
||||||
"wickra-win32-arm64-msvc": "0.6.9",
|
"wickra-win32-arm64-msvc": "0.7.0",
|
||||||
"wickra-win32-x64-msvc": "0.6.9"
|
"wickra-win32-x64-msvc": "0.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@napi-rs/cli": {
|
"node_modules/@napi-rs/cli": {
|
||||||
@@ -41,8 +41,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/wickra-darwin-arm64": {
|
"node_modules/wickra-darwin-arm64": {
|
||||||
"version": "0.6.9",
|
"version": "0.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/wickra-darwin-arm64/-/wickra-darwin-arm64-0.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/wickra-darwin-arm64/-/wickra-darwin-arm64-0.7.0.tgz",
|
||||||
"integrity": "sha512-4eZiBR/yGUdr4nzhEUFy2i69XgNx64iI2ax/LPamsThgylC0KpHOZKK19QzJ2d9KbK4C8nMjME5FLuR+4GNEwQ==",
|
"integrity": "sha512-4eZiBR/yGUdr4nzhEUFy2i69XgNx64iI2ax/LPamsThgylC0KpHOZKK19QzJ2d9KbK4C8nMjME5FLuR+4GNEwQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
@@ -57,8 +57,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/wickra-darwin-x64": {
|
"node_modules/wickra-darwin-x64": {
|
||||||
"version": "0.6.9",
|
"version": "0.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/wickra-darwin-x64/-/wickra-darwin-x64-0.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/wickra-darwin-x64/-/wickra-darwin-x64-0.7.0.tgz",
|
||||||
"integrity": "sha512-6hf8zI3QPjTFp4zCpmgUwDvNtu6jHqNUHKD5e55POo0CgA52HkpyxSPtVm8TGTIZDI7kPjlbOdBM8CJ76mmXwA==",
|
"integrity": "sha512-6hf8zI3QPjTFp4zCpmgUwDvNtu6jHqNUHKD5e55POo0CgA52HkpyxSPtVm8TGTIZDI7kPjlbOdBM8CJ76mmXwA==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
@@ -73,8 +73,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/wickra-linux-arm64-gnu": {
|
"node_modules/wickra-linux-arm64-gnu": {
|
||||||
"version": "0.6.9",
|
"version": "0.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/wickra-linux-arm64-gnu/-/wickra-linux-arm64-gnu-0.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/wickra-linux-arm64-gnu/-/wickra-linux-arm64-gnu-0.7.0.tgz",
|
||||||
"integrity": "sha512-kSe6y0xBMSiqdPLXNjwop5WZdHtvdBNKSEBCwZ4hFq33p4apW25/wrlzv9/oDuyD4kuPabJEhCCnFOplh58CUg==",
|
"integrity": "sha512-kSe6y0xBMSiqdPLXNjwop5WZdHtvdBNKSEBCwZ4hFq33p4apW25/wrlzv9/oDuyD4kuPabJEhCCnFOplh58CUg==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
@@ -89,8 +89,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/wickra-linux-x64-gnu": {
|
"node_modules/wickra-linux-x64-gnu": {
|
||||||
"version": "0.6.9",
|
"version": "0.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/wickra-linux-x64-gnu/-/wickra-linux-x64-gnu-0.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/wickra-linux-x64-gnu/-/wickra-linux-x64-gnu-0.7.0.tgz",
|
||||||
"integrity": "sha512-tWBWS4qz7hxM4xnpFb59bhf6TaLwXq0Z3jEa/2l7r8PiHA94g8r8S53NRMiT+4yiL5hSWe/nUiC/YXdRrhEZ4g==",
|
"integrity": "sha512-tWBWS4qz7hxM4xnpFb59bhf6TaLwXq0Z3jEa/2l7r8PiHA94g8r8S53NRMiT+4yiL5hSWe/nUiC/YXdRrhEZ4g==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
@@ -105,8 +105,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/wickra-win32-arm64-msvc": {
|
"node_modules/wickra-win32-arm64-msvc": {
|
||||||
"version": "0.6.9",
|
"version": "0.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/wickra-win32-arm64-msvc/-/wickra-win32-arm64-msvc-0.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/wickra-win32-arm64-msvc/-/wickra-win32-arm64-msvc-0.7.0.tgz",
|
||||||
"integrity": "sha512-EXIckHxAtF75PUGDKRzXyqMe9ldP0JjSdu68WFN6iJfp+McYrGu6h40TEJlQ/oUEIoPqiZB/xhVyo/el5Lg7zw==",
|
"integrity": "sha512-EXIckHxAtF75PUGDKRzXyqMe9ldP0JjSdu68WFN6iJfp+McYrGu6h40TEJlQ/oUEIoPqiZB/xhVyo/el5Lg7zw==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"arm64"
|
"arm64"
|
||||||
@@ -121,8 +121,8 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/wickra-win32-x64-msvc": {
|
"node_modules/wickra-win32-x64-msvc": {
|
||||||
"version": "0.6.9",
|
"version": "0.7.0",
|
||||||
"resolved": "https://registry.npmjs.org/wickra-win32-x64-msvc/-/wickra-win32-x64-msvc-0.6.9.tgz",
|
"resolved": "https://registry.npmjs.org/wickra-win32-x64-msvc/-/wickra-win32-x64-msvc-0.7.0.tgz",
|
||||||
"integrity": "sha512-Yfsqq1Xwp6hdxMyLze411vNdo7BDwI6+lPSe7A9XdqyPecNDbtKwYLpsal2r8EHbNzqM+R8XnuRtUaEQS5VlUQ==",
|
"integrity": "sha512-Yfsqq1Xwp6hdxMyLze411vNdo7BDwI6+lPSe7A9XdqyPecNDbtKwYLpsal2r8EHbNzqM+R8XnuRtUaEQS5VlUQ==",
|
||||||
"cpu": [
|
"cpu": [
|
||||||
"x64"
|
"x64"
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "wickra",
|
"name": "wickra",
|
||||||
"version": "0.6.9",
|
"version": "0.7.0",
|
||||||
"description": "Streaming-first technical indicators: incremental, fast, install-free. Node bindings powered by Rust.",
|
"description": "Streaming-first technical indicators: incremental, fast, install-free. Node bindings powered by Rust.",
|
||||||
"author": "kingchenc <support@wickra.org>",
|
"author": "kingchenc <support@wickra.org>",
|
||||||
"main": "index.js",
|
"main": "index.js",
|
||||||
@@ -47,12 +47,12 @@
|
|||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"wickra-linux-x64-gnu": "0.6.9",
|
"wickra-linux-x64-gnu": "0.7.0",
|
||||||
"wickra-linux-arm64-gnu": "0.6.9",
|
"wickra-linux-arm64-gnu": "0.7.0",
|
||||||
"wickra-darwin-x64": "0.6.9",
|
"wickra-darwin-x64": "0.7.0",
|
||||||
"wickra-darwin-arm64": "0.6.9",
|
"wickra-darwin-arm64": "0.7.0",
|
||||||
"wickra-win32-x64-msvc": "0.6.9",
|
"wickra-win32-x64-msvc": "0.7.0",
|
||||||
"wickra-win32-arm64-msvc": "0.6.9"
|
"wickra-win32-arm64-msvc": "0.7.0"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "napi build --platform --release",
|
"build": "napi build --platform --release",
|
||||||
|
|||||||
@@ -884,6 +884,11 @@ node_pair_indicator!(
|
|||||||
"BetaNeutralSpread",
|
"BetaNeutralSpread",
|
||||||
wc::BetaNeutralSpread
|
wc::BetaNeutralSpread
|
||||||
);
|
);
|
||||||
|
node_pair_indicator!(
|
||||||
|
HasbrouckInformationShareNode,
|
||||||
|
"HasbrouckInformationShare",
|
||||||
|
wc::HasbrouckInformationShare
|
||||||
|
);
|
||||||
|
|
||||||
// ============================== PairSpreadZScore ==============================
|
// ============================== PairSpreadZScore ==============================
|
||||||
|
|
||||||
@@ -13739,6 +13744,108 @@ impl TradeImbalanceNode {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trade-sign autocorrelation carries a `period` parameter, so it is hand-written.
|
||||||
|
#[napi(js_name = "TradeSignAutocorrelation")]
|
||||||
|
pub struct TradeSignAutocorrelationNode {
|
||||||
|
inner: wc::TradeSignAutocorrelation,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
impl TradeSignAutocorrelationNode {
|
||||||
|
#[napi(constructor)]
|
||||||
|
pub fn new(period: u32) -> napi::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
inner: wc::TradeSignAutocorrelation::new(period as usize).map_err(map_err)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
#[napi]
|
||||||
|
pub fn update(&mut self, price: f64, size: f64, is_buy: bool) -> napi::Result<Option<f64>> {
|
||||||
|
Ok(self.inner.update(build_trade(price, size, is_buy)?))
|
||||||
|
}
|
||||||
|
#[napi]
|
||||||
|
pub fn batch(
|
||||||
|
&mut self,
|
||||||
|
price: Vec<f64>,
|
||||||
|
size: Vec<f64>,
|
||||||
|
is_buy: Vec<bool>,
|
||||||
|
) -> napi::Result<Vec<f64>> {
|
||||||
|
if price.len() != size.len() || size.len() != is_buy.len() {
|
||||||
|
return Err(NapiError::from_reason(
|
||||||
|
"price, size, is_buy must be equal length".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let mut out = Vec::with_capacity(price.len());
|
||||||
|
for i in 0..price.len() {
|
||||||
|
let trade = build_trade(price[i], size[i], is_buy[i])?;
|
||||||
|
out.push(self.inner.update(trade).unwrap_or(f64::NAN));
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
#[napi]
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.inner.reset();
|
||||||
|
}
|
||||||
|
#[napi(js_name = "isReady")]
|
||||||
|
pub fn is_ready(&self) -> bool {
|
||||||
|
self.inner.is_ready()
|
||||||
|
}
|
||||||
|
#[napi(js_name = "warmupPeriod")]
|
||||||
|
pub fn warmup_period(&self) -> u32 {
|
||||||
|
self.inner.warmup_period() as u32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PIN carries a `window` parameter, so it is hand-written.
|
||||||
|
#[napi(js_name = "Pin")]
|
||||||
|
pub struct PinNode {
|
||||||
|
inner: wc::Pin,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[napi]
|
||||||
|
impl PinNode {
|
||||||
|
#[napi(constructor)]
|
||||||
|
pub fn new(window: u32) -> napi::Result<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
inner: wc::Pin::new(window as usize).map_err(map_err)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
#[napi]
|
||||||
|
pub fn update(&mut self, price: f64, size: f64, is_buy: bool) -> napi::Result<Option<f64>> {
|
||||||
|
Ok(self.inner.update(build_trade(price, size, is_buy)?))
|
||||||
|
}
|
||||||
|
#[napi]
|
||||||
|
pub fn batch(
|
||||||
|
&mut self,
|
||||||
|
price: Vec<f64>,
|
||||||
|
size: Vec<f64>,
|
||||||
|
is_buy: Vec<bool>,
|
||||||
|
) -> napi::Result<Vec<f64>> {
|
||||||
|
if price.len() != size.len() || size.len() != is_buy.len() {
|
||||||
|
return Err(NapiError::from_reason(
|
||||||
|
"price, size, is_buy must be equal length".to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let mut out = Vec::with_capacity(price.len());
|
||||||
|
for i in 0..price.len() {
|
||||||
|
let trade = build_trade(price[i], size[i], is_buy[i])?;
|
||||||
|
out.push(self.inner.update(trade).unwrap_or(f64::NAN));
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
#[napi]
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.inner.reset();
|
||||||
|
}
|
||||||
|
#[napi(js_name = "isReady")]
|
||||||
|
pub fn is_ready(&self) -> bool {
|
||||||
|
self.inner.is_ready()
|
||||||
|
}
|
||||||
|
#[napi(js_name = "warmupPeriod")]
|
||||||
|
pub fn warmup_period(&self) -> u32 {
|
||||||
|
self.inner.warmup_period() as u32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Order Flow Imbalance: order-book input with a `period` parameter.
|
// Order Flow Imbalance: order-book input with a `period` parameter.
|
||||||
#[napi(js_name = "OrderFlowImbalance")]
|
#[napi(js_name = "OrderFlowImbalance")]
|
||||||
pub struct OrderFlowImbalanceNode {
|
pub struct OrderFlowImbalanceNode {
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
|||||||
|
|
||||||
[project]
|
[project]
|
||||||
name = "wickra"
|
name = "wickra"
|
||||||
version = "0.6.9"
|
version = "0.7.0"
|
||||||
description = "Streaming-first technical indicators: incremental, fast, install-free."
|
description = "Streaming-first technical indicators: incremental, fast, install-free."
|
||||||
readme = "README.md"
|
readme = "README.md"
|
||||||
license = "MIT OR Apache-2.0"
|
license = "MIT OR Apache-2.0"
|
||||||
|
|||||||
@@ -464,6 +464,8 @@ from ._wickra import (
|
|||||||
QuotedSpread,
|
QuotedSpread,
|
||||||
DepthSlope,
|
DepthSlope,
|
||||||
# Microstructure: trade flow
|
# Microstructure: trade flow
|
||||||
|
Pin,
|
||||||
|
TradeSignAutocorrelation,
|
||||||
RollMeasure,
|
RollMeasure,
|
||||||
AmihudIlliquidity,
|
AmihudIlliquidity,
|
||||||
Vpin,
|
Vpin,
|
||||||
@@ -471,6 +473,7 @@ from ._wickra import (
|
|||||||
CumulativeVolumeDelta,
|
CumulativeVolumeDelta,
|
||||||
TradeImbalance,
|
TradeImbalance,
|
||||||
# Microstructure: price impact
|
# Microstructure: price impact
|
||||||
|
HasbrouckInformationShare,
|
||||||
EffectiveSpread,
|
EffectiveSpread,
|
||||||
RealizedSpread,
|
RealizedSpread,
|
||||||
KylesLambda,
|
KylesLambda,
|
||||||
@@ -979,6 +982,8 @@ __all__ = [
|
|||||||
"QuotedSpread",
|
"QuotedSpread",
|
||||||
"DepthSlope",
|
"DepthSlope",
|
||||||
# Microstructure: trade flow
|
# Microstructure: trade flow
|
||||||
|
"Pin",
|
||||||
|
"TradeSignAutocorrelation",
|
||||||
"RollMeasure",
|
"RollMeasure",
|
||||||
"AmihudIlliquidity",
|
"AmihudIlliquidity",
|
||||||
"Vpin",
|
"Vpin",
|
||||||
@@ -986,6 +991,7 @@ __all__ = [
|
|||||||
"CumulativeVolumeDelta",
|
"CumulativeVolumeDelta",
|
||||||
"TradeImbalance",
|
"TradeImbalance",
|
||||||
# Microstructure: price impact
|
# Microstructure: price impact
|
||||||
|
"HasbrouckInformationShare",
|
||||||
"EffectiveSpread",
|
"EffectiveSpread",
|
||||||
"RealizedSpread",
|
"RealizedSpread",
|
||||||
"KylesLambda",
|
"KylesLambda",
|
||||||
|
|||||||
@@ -16980,6 +16980,70 @@ impl PyRollingCorrelation {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ========================= HasbrouckInformationShare =========================
|
||||||
|
|
||||||
|
#[pyclass(
|
||||||
|
name = "HasbrouckInformationShare",
|
||||||
|
module = "wickra._wickra",
|
||||||
|
skip_from_py_object
|
||||||
|
)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct PyHasbrouckInformationShare {
|
||||||
|
inner: wc::HasbrouckInformationShare,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyHasbrouckInformationShare {
|
||||||
|
#[new]
|
||||||
|
#[pyo3(signature = (period=20))]
|
||||||
|
fn new(period: usize) -> PyResult<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
inner: wc::HasbrouckInformationShare::new(period).map_err(map_err)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn update(&mut self, a: f64, b: f64) -> Option<f64> {
|
||||||
|
self.inner.update((a, b))
|
||||||
|
}
|
||||||
|
/// Batch over two equally-sized numpy arrays: `a` and `b`.
|
||||||
|
fn batch<'py>(
|
||||||
|
&mut self,
|
||||||
|
py: Python<'py>,
|
||||||
|
a: PyReadonlyArray1<'py, f64>,
|
||||||
|
b: PyReadonlyArray1<'py, f64>,
|
||||||
|
) -> PyResult<Bound<'py, PyArray1<f64>>> {
|
||||||
|
let xs = a
|
||||||
|
.as_slice()
|
||||||
|
.map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?;
|
||||||
|
let ys = b
|
||||||
|
.as_slice()
|
||||||
|
.map_err(|_| PyValueError::new_err(NON_CONTIGUOUS))?;
|
||||||
|
if xs.len() != ys.len() {
|
||||||
|
return Err(PyValueError::new_err("a and b must be equal length"));
|
||||||
|
}
|
||||||
|
let mut out = Vec::with_capacity(xs.len());
|
||||||
|
for i in 0..xs.len() {
|
||||||
|
out.push(self.inner.update((xs[i], ys[i])).unwrap_or(f64::NAN));
|
||||||
|
}
|
||||||
|
Ok(out.into_pyarray(py))
|
||||||
|
}
|
||||||
|
#[getter]
|
||||||
|
fn period(&self) -> usize {
|
||||||
|
self.inner.period()
|
||||||
|
}
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.inner.reset();
|
||||||
|
}
|
||||||
|
fn is_ready(&self) -> bool {
|
||||||
|
self.inner.is_ready()
|
||||||
|
}
|
||||||
|
fn warmup_period(&self) -> usize {
|
||||||
|
self.inner.warmup_period()
|
||||||
|
}
|
||||||
|
fn __repr__(&self) -> String {
|
||||||
|
format!("HasbrouckInformationShare(period={})", self.inner.period())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ============================== RollingCovariance ==============================
|
// ============================== RollingCovariance ==============================
|
||||||
|
|
||||||
#[pyclass(
|
#[pyclass(
|
||||||
@@ -18652,6 +18716,112 @@ impl PyTradeImbalance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trade-sign autocorrelation carries a `period` parameter, so it is hand-written.
|
||||||
|
#[pyclass(
|
||||||
|
name = "TradeSignAutocorrelation",
|
||||||
|
module = "wickra._wickra",
|
||||||
|
skip_from_py_object
|
||||||
|
)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct PyTradeSignAutocorrelation {
|
||||||
|
inner: wc::TradeSignAutocorrelation,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyTradeSignAutocorrelation {
|
||||||
|
#[new]
|
||||||
|
fn new(period: usize) -> PyResult<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
inner: wc::TradeSignAutocorrelation::new(period).map_err(map_err)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn update(&mut self, price: f64, size: f64, is_buy: bool) -> PyResult<Option<f64>> {
|
||||||
|
Ok(self.inner.update(build_trade(price, size, is_buy)?))
|
||||||
|
}
|
||||||
|
fn batch<'py>(
|
||||||
|
&mut self,
|
||||||
|
py: Python<'py>,
|
||||||
|
price: Vec<f64>,
|
||||||
|
size: Vec<f64>,
|
||||||
|
is_buy: Vec<bool>,
|
||||||
|
) -> PyResult<Bound<'py, PyArray1<f64>>> {
|
||||||
|
if price.len() != size.len() || size.len() != is_buy.len() {
|
||||||
|
return Err(PyValueError::new_err(
|
||||||
|
"price, size, is_buy must be equal length",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let mut out = Vec::with_capacity(price.len());
|
||||||
|
for i in 0..price.len() {
|
||||||
|
let trade = build_trade(price[i], size[i], is_buy[i])?;
|
||||||
|
out.push(self.inner.update(trade).unwrap_or(f64::NAN));
|
||||||
|
}
|
||||||
|
Ok(out.into_pyarray(py))
|
||||||
|
}
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.inner.reset();
|
||||||
|
}
|
||||||
|
fn is_ready(&self) -> bool {
|
||||||
|
self.inner.is_ready()
|
||||||
|
}
|
||||||
|
fn warmup_period(&self) -> usize {
|
||||||
|
self.inner.warmup_period()
|
||||||
|
}
|
||||||
|
fn __repr__(&self) -> String {
|
||||||
|
format!("TradeSignAutocorrelation(period={})", self.inner.period())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PIN carries a `window` parameter, so it is hand-written.
|
||||||
|
#[pyclass(name = "Pin", module = "wickra._wickra", skip_from_py_object)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct PyPin {
|
||||||
|
inner: wc::Pin,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[pymethods]
|
||||||
|
impl PyPin {
|
||||||
|
#[new]
|
||||||
|
fn new(window: usize) -> PyResult<Self> {
|
||||||
|
Ok(Self {
|
||||||
|
inner: wc::Pin::new(window).map_err(map_err)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn update(&mut self, price: f64, size: f64, is_buy: bool) -> PyResult<Option<f64>> {
|
||||||
|
Ok(self.inner.update(build_trade(price, size, is_buy)?))
|
||||||
|
}
|
||||||
|
fn batch<'py>(
|
||||||
|
&mut self,
|
||||||
|
py: Python<'py>,
|
||||||
|
price: Vec<f64>,
|
||||||
|
size: Vec<f64>,
|
||||||
|
is_buy: Vec<bool>,
|
||||||
|
) -> PyResult<Bound<'py, PyArray1<f64>>> {
|
||||||
|
if price.len() != size.len() || size.len() != is_buy.len() {
|
||||||
|
return Err(PyValueError::new_err(
|
||||||
|
"price, size, is_buy must be equal length",
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let mut out = Vec::with_capacity(price.len());
|
||||||
|
for i in 0..price.len() {
|
||||||
|
let trade = build_trade(price[i], size[i], is_buy[i])?;
|
||||||
|
out.push(self.inner.update(trade).unwrap_or(f64::NAN));
|
||||||
|
}
|
||||||
|
Ok(out.into_pyarray(py))
|
||||||
|
}
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.inner.reset();
|
||||||
|
}
|
||||||
|
fn is_ready(&self) -> bool {
|
||||||
|
self.inner.is_ready()
|
||||||
|
}
|
||||||
|
fn warmup_period(&self) -> usize {
|
||||||
|
self.inner.warmup_period()
|
||||||
|
}
|
||||||
|
fn __repr__(&self) -> String {
|
||||||
|
format!("Pin(window={})", self.inner.window())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Order Flow Imbalance carries a `period` parameter and an order-book input,
|
// Order Flow Imbalance carries a `period` parameter and an order-book input,
|
||||||
// so it is hand-written.
|
// so it is hand-written.
|
||||||
#[pyclass(
|
#[pyclass(
|
||||||
@@ -24743,6 +24913,7 @@ fn _wickra(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|||||||
m.add_class::<PyCointegration>()?;
|
m.add_class::<PyCointegration>()?;
|
||||||
m.add_class::<PyRelativeStrengthAB>()?;
|
m.add_class::<PyRelativeStrengthAB>()?;
|
||||||
m.add_class::<PyRollingCorrelation>()?;
|
m.add_class::<PyRollingCorrelation>()?;
|
||||||
|
m.add_class::<PyHasbrouckInformationShare>()?;
|
||||||
m.add_class::<PyRollingCovariance>()?;
|
m.add_class::<PyRollingCovariance>()?;
|
||||||
m.add_class::<PyOuHalfLife>()?;
|
m.add_class::<PyOuHalfLife>()?;
|
||||||
m.add_class::<PySpreadHurst>()?;
|
m.add_class::<PySpreadHurst>()?;
|
||||||
@@ -24833,6 +25004,8 @@ fn _wickra(_py: Python<'_>, m: &Bound<'_, PyModule>) -> PyResult<()> {
|
|||||||
m.add_class::<PySignedVolume>()?;
|
m.add_class::<PySignedVolume>()?;
|
||||||
m.add_class::<PyCumulativeVolumeDelta>()?;
|
m.add_class::<PyCumulativeVolumeDelta>()?;
|
||||||
m.add_class::<PyTradeImbalance>()?;
|
m.add_class::<PyTradeImbalance>()?;
|
||||||
|
m.add_class::<PyTradeSignAutocorrelation>()?;
|
||||||
|
m.add_class::<PyPin>()?;
|
||||||
m.add_class::<PyOrderFlowImbalance>()?;
|
m.add_class::<PyOrderFlowImbalance>()?;
|
||||||
m.add_class::<PyVpin>()?;
|
m.add_class::<PyVpin>()?;
|
||||||
m.add_class::<PyAmihudIlliquidity>()?;
|
m.add_class::<PyAmihudIlliquidity>()?;
|
||||||
|
|||||||
@@ -217,6 +217,7 @@ def test_scalar_streaming_matches_batch(cls, args, sine_prices):
|
|||||||
# --- Two-series (asset, benchmark) indicators -----------------------------
|
# --- Two-series (asset, benchmark) indicators -----------------------------
|
||||||
|
|
||||||
PAIR = [
|
PAIR = [
|
||||||
|
(ta.HasbrouckInformationShare, (2,)),
|
||||||
(ta.KendallTau, (20,)),
|
(ta.KendallTau, (20,)),
|
||||||
(ta.SpreadAr1Coefficient, (40,)),
|
(ta.SpreadAr1Coefficient, (40,)),
|
||||||
(ta.GrangerCausality, (60, 1)),
|
(ta.GrangerCausality, (60, 1)),
|
||||||
@@ -3323,6 +3324,13 @@ def test_tower_top_bottom_reference():
|
|||||||
assert t.update((110.0, 110.1, 99.9, 100.0, 1.0, 2)) == pytest.approx(-1.0)
|
assert t.update((110.0, 110.1, 99.9, 100.0, 1.0, 2)) == pytest.approx(-1.0)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def test_hasbrouck_information_share_reference():
|
||||||
|
t = ta.HasbrouckInformationShare(2)
|
||||||
|
assert t.update(7.0, 9.0) is None
|
||||||
|
assert t.update(7.0, 9.0) is None
|
||||||
|
assert t.update(7.0, 9.0) == pytest.approx(0.5)
|
||||||
|
|
||||||
# --- Lifecycle ------------------------------------------------------------
|
# --- Lifecycle ------------------------------------------------------------
|
||||||
|
|
||||||
|
|
||||||
@@ -3663,6 +3671,8 @@ def test_tradeflow_indicators_streaming_equals_batch():
|
|||||||
lambda: ta.Vpin(8.0, 5),
|
lambda: ta.Vpin(8.0, 5),
|
||||||
lambda: ta.AmihudIlliquidity(14),
|
lambda: ta.AmihudIlliquidity(14),
|
||||||
lambda: ta.RollMeasure(14),
|
lambda: ta.RollMeasure(14),
|
||||||
|
lambda: ta.TradeSignAutocorrelation(10),
|
||||||
|
lambda: ta.Pin(10),
|
||||||
):
|
):
|
||||||
batch = make().batch(price, size, is_buy)
|
batch = make().batch(price, size, is_buy)
|
||||||
streamer = make()
|
streamer = make()
|
||||||
@@ -3674,6 +3684,34 @@ def test_tradeflow_indicators_streaming_equals_batch():
|
|||||||
assert _eq_nan(batch, streamed)
|
assert _eq_nan(batch, streamed)
|
||||||
|
|
||||||
|
|
||||||
|
def test_trade_sign_autocorrelation_reference():
|
||||||
|
# Perfectly alternating aggressor signs -> lag-1 autocorrelation -1.
|
||||||
|
t = ta.TradeSignAutocorrelation(10)
|
||||||
|
last = None
|
||||||
|
for i in range(20):
|
||||||
|
last = t.update(100.0, 1.0, i % 2 == 0)
|
||||||
|
assert last == pytest.approx(-1.0)
|
||||||
|
# All buys -> perfectly persistent flow -> +1.
|
||||||
|
t2 = ta.TradeSignAutocorrelation(10)
|
||||||
|
for _ in range(20):
|
||||||
|
last2 = t2.update(100.0, 1.0, True)
|
||||||
|
assert last2 == pytest.approx(1.0)
|
||||||
|
|
||||||
|
|
||||||
|
def test_pin_reference():
|
||||||
|
# One-sided flow (all buys) -> maximally informed -> PIN 1.
|
||||||
|
p = ta.Pin(10)
|
||||||
|
last = None
|
||||||
|
for _ in range(20):
|
||||||
|
last = p.update(100.0, 1.0, True)
|
||||||
|
assert last == pytest.approx(1.0)
|
||||||
|
# Balanced flow -> uninformed -> PIN 0.
|
||||||
|
p2 = ta.Pin(10)
|
||||||
|
for i in range(20):
|
||||||
|
last2 = p2.update(100.0, 1.0, i % 2 == 0)
|
||||||
|
assert last2 == pytest.approx(0.0)
|
||||||
|
|
||||||
|
|
||||||
def test_price_impact_indicators_streaming_equals_batch():
|
def test_price_impact_indicators_streaming_equals_batch():
|
||||||
n = 40
|
n = 40
|
||||||
mid = np.array([100.0 + 0.5 * math.sin(i * 0.4) for i in range(n)], dtype=np.float64)
|
mid = np.array([100.0 + 0.5 * math.sin(i * 0.4) for i in range(n)], dtype=np.float64)
|
||||||
|
|||||||
@@ -563,6 +563,11 @@ wasm_pair_indicator!(
|
|||||||
"BetaNeutralSpread",
|
"BetaNeutralSpread",
|
||||||
wc::BetaNeutralSpread
|
wc::BetaNeutralSpread
|
||||||
);
|
);
|
||||||
|
wasm_pair_indicator!(
|
||||||
|
WasmHasbrouckInformationShare,
|
||||||
|
"HasbrouckInformationShare",
|
||||||
|
wc::HasbrouckInformationShare
|
||||||
|
);
|
||||||
|
|
||||||
// ---------- PairSpreadZScore (two params) ----------
|
// ---------- PairSpreadZScore (two params) ----------
|
||||||
|
|
||||||
@@ -9279,6 +9284,66 @@ impl WasmTradeImbalance {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Trade-sign autocorrelation carries a `period` parameter, so it is hand-written.
|
||||||
|
#[wasm_bindgen(js_name = TradeSignAutocorrelation)]
|
||||||
|
pub struct WasmTradeSignAutocorrelation {
|
||||||
|
inner: wc::TradeSignAutocorrelation,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_class = TradeSignAutocorrelation)]
|
||||||
|
impl WasmTradeSignAutocorrelation {
|
||||||
|
#[wasm_bindgen(constructor)]
|
||||||
|
pub fn new(period: usize) -> Result<WasmTradeSignAutocorrelation, JsError> {
|
||||||
|
Ok(Self {
|
||||||
|
inner: wc::TradeSignAutocorrelation::new(period).map_err(map_err)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn update(&mut self, price: f64, size: f64, is_buy: bool) -> Result<Option<f64>, JsError> {
|
||||||
|
Ok(self.inner.update(build_trade(price, size, is_buy)?))
|
||||||
|
}
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.inner.reset();
|
||||||
|
}
|
||||||
|
#[wasm_bindgen(js_name = isReady)]
|
||||||
|
pub fn is_ready(&self) -> bool {
|
||||||
|
self.inner.is_ready()
|
||||||
|
}
|
||||||
|
#[wasm_bindgen(js_name = warmupPeriod)]
|
||||||
|
pub fn warmup_period(&self) -> usize {
|
||||||
|
self.inner.warmup_period()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// PIN carries a `window` parameter, so it is hand-written.
|
||||||
|
#[wasm_bindgen(js_name = Pin)]
|
||||||
|
pub struct WasmPin {
|
||||||
|
inner: wc::Pin,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[wasm_bindgen(js_class = Pin)]
|
||||||
|
impl WasmPin {
|
||||||
|
#[wasm_bindgen(constructor)]
|
||||||
|
pub fn new(window: usize) -> Result<WasmPin, JsError> {
|
||||||
|
Ok(Self {
|
||||||
|
inner: wc::Pin::new(window).map_err(map_err)?,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub fn update(&mut self, price: f64, size: f64, is_buy: bool) -> Result<Option<f64>, JsError> {
|
||||||
|
Ok(self.inner.update(build_trade(price, size, is_buy)?))
|
||||||
|
}
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.inner.reset();
|
||||||
|
}
|
||||||
|
#[wasm_bindgen(js_name = isReady)]
|
||||||
|
pub fn is_ready(&self) -> bool {
|
||||||
|
self.inner.is_ready()
|
||||||
|
}
|
||||||
|
#[wasm_bindgen(js_name = warmupPeriod)]
|
||||||
|
pub fn warmup_period(&self) -> usize {
|
||||||
|
self.inner.warmup_period()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Order Flow Imbalance: order-book input with a `period` parameter.
|
// Order Flow Imbalance: order-book input with a `period` parameter.
|
||||||
#[wasm_bindgen(js_name = OrderFlowImbalance)]
|
#[wasm_bindgen(js_name = OrderFlowImbalance)]
|
||||||
pub struct WasmOrderFlowImbalance {
|
pub struct WasmOrderFlowImbalance {
|
||||||
|
|||||||
@@ -0,0 +1,251 @@
|
|||||||
|
//! Hasbrouck Information Share — each venue's contribution to price discovery.
|
||||||
|
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
use crate::error::{Error, Result};
|
||||||
|
use crate::traits::Indicator;
|
||||||
|
|
||||||
|
/// Hasbrouck Information Share — the share of price-discovery attributable to the
|
||||||
|
/// **first** of two synchronised price series (e.g. the same asset on two venues).
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// rx_t = x_t − x_{t−1}, ry_t = y_t − y_{t−1} (one-step price changes)
|
||||||
|
/// IS_x = var(rx) / ( var(rx) + var(ry) ) over the window, ∈ [0, 1]
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// When the same instrument trades on several venues, Joel Hasbrouck's information
|
||||||
|
/// share measures how much each venue contributes to the common efficient price.
|
||||||
|
/// The venue whose innovations carry more of the variance leads price discovery.
|
||||||
|
/// This streaming form uses the **variance-ratio proxy**: the fraction of total
|
||||||
|
/// return variance contributed by series `x`. A reading above `0.5` means venue
|
||||||
|
/// `x` is the price leader; below `0.5`, the follower. (The full Hasbrouck measure
|
||||||
|
/// estimates a vector error-correction model and reports an upper/lower bound from
|
||||||
|
/// the Cholesky ordering; this proxy captures the leading idea without the VECM.)
|
||||||
|
///
|
||||||
|
/// The output is in `[0, 1]`; if both series are flat it reports the neutral `0.5`.
|
||||||
|
/// The first value lands after `period + 1` inputs. Each `update` is O(1).
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use wickra_core::{Indicator, HasbrouckInformationShare};
|
||||||
|
///
|
||||||
|
/// let mut indicator = HasbrouckInformationShare::new(20).unwrap();
|
||||||
|
/// let mut last = None;
|
||||||
|
/// for i in 0..40 {
|
||||||
|
/// // Venue x moves a lot, venue y barely moves -> x leads.
|
||||||
|
/// let x = (f64::from(i) * 0.5).sin() * 10.0;
|
||||||
|
/// let y = (f64::from(i) * 0.5).sin() * 1.0;
|
||||||
|
/// last = indicator.update((x, y));
|
||||||
|
/// }
|
||||||
|
/// assert!(last.unwrap() > 0.8);
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct HasbrouckInformationShare {
|
||||||
|
period: usize,
|
||||||
|
prev: Option<(f64, f64)>,
|
||||||
|
window: VecDeque<(f64, f64)>,
|
||||||
|
sum_x: f64,
|
||||||
|
sum_y: f64,
|
||||||
|
sum_xx: f64,
|
||||||
|
sum_yy: f64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl HasbrouckInformationShare {
|
||||||
|
/// Construct a Hasbrouck information share over `period` return pairs.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`Error::InvalidPeriod`] if `period < 2` (variance needs two
|
||||||
|
/// returns).
|
||||||
|
pub fn new(period: usize) -> Result<Self> {
|
||||||
|
if period < 2 {
|
||||||
|
return Err(Error::InvalidPeriod {
|
||||||
|
message: "information share needs period >= 2",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
period,
|
||||||
|
prev: None,
|
||||||
|
window: VecDeque::with_capacity(period),
|
||||||
|
sum_x: 0.0,
|
||||||
|
sum_y: 0.0,
|
||||||
|
sum_xx: 0.0,
|
||||||
|
sum_yy: 0.0,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configured window of return pairs.
|
||||||
|
pub const fn period(&self) -> usize {
|
||||||
|
self.period
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Indicator for HasbrouckInformationShare {
|
||||||
|
type Input = (f64, f64);
|
||||||
|
type Output = f64;
|
||||||
|
|
||||||
|
fn update(&mut self, input: (f64, f64)) -> Option<f64> {
|
||||||
|
let (x, y) = input;
|
||||||
|
let Some((px, py)) = self.prev else {
|
||||||
|
self.prev = Some((x, y));
|
||||||
|
return None;
|
||||||
|
};
|
||||||
|
self.prev = Some((x, y));
|
||||||
|
let (rx, ry) = (x - px, y - py);
|
||||||
|
if self.window.len() == self.period {
|
||||||
|
let (ox, oy) = self.window.pop_front().expect("non-empty");
|
||||||
|
self.sum_x -= ox;
|
||||||
|
self.sum_y -= oy;
|
||||||
|
self.sum_xx -= ox * ox;
|
||||||
|
self.sum_yy -= oy * oy;
|
||||||
|
}
|
||||||
|
self.window.push_back((rx, ry));
|
||||||
|
self.sum_x += rx;
|
||||||
|
self.sum_y += ry;
|
||||||
|
self.sum_xx += rx * rx;
|
||||||
|
self.sum_yy += ry * ry;
|
||||||
|
if self.window.len() < self.period {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let n = self.period as f64;
|
||||||
|
let var_x = (self.sum_xx / n - (self.sum_x / n).powi(2)).max(0.0);
|
||||||
|
let var_y = (self.sum_yy / n - (self.sum_y / n).powi(2)).max(0.0);
|
||||||
|
let total = var_x + var_y;
|
||||||
|
Some(if total > 0.0 { var_x / total } else { 0.5 })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.prev = None;
|
||||||
|
self.window.clear();
|
||||||
|
self.sum_x = 0.0;
|
||||||
|
self.sum_y = 0.0;
|
||||||
|
self.sum_xx = 0.0;
|
||||||
|
self.sum_yy = 0.0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn warmup_period(&self) -> usize {
|
||||||
|
self.period + 1
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_ready(&self) -> bool {
|
||||||
|
self.window.len() == self.period
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"HasbrouckInformationShare"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::traits::BatchExt;
|
||||||
|
use approx::assert_relative_eq;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_period_below_two() {
|
||||||
|
assert!(matches!(
|
||||||
|
HasbrouckInformationShare::new(1),
|
||||||
|
Err(Error::InvalidPeriod { .. })
|
||||||
|
));
|
||||||
|
assert!(HasbrouckInformationShare::new(2).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn accessors_and_metadata() {
|
||||||
|
let h = HasbrouckInformationShare::new(20).unwrap();
|
||||||
|
assert_eq!(h.period(), 20);
|
||||||
|
assert_eq!(h.warmup_period(), 21);
|
||||||
|
assert_eq!(h.name(), "HasbrouckInformationShare");
|
||||||
|
assert!(!h.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn warmup_needs_period_plus_one() {
|
||||||
|
let mut h = HasbrouckInformationShare::new(3).unwrap();
|
||||||
|
assert_eq!(h.update((1.0, 1.0)), None);
|
||||||
|
assert_eq!(h.update((2.0, 2.0)), None);
|
||||||
|
assert_eq!(h.update((3.0, 2.5)), None);
|
||||||
|
assert!(h.update((4.0, 3.0)).is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn loud_venue_leads() {
|
||||||
|
// x is far more volatile than y -> x holds nearly all the share.
|
||||||
|
let pairs: Vec<(f64, f64)> = (0..40)
|
||||||
|
.map(|i| {
|
||||||
|
(
|
||||||
|
(f64::from(i) * 0.5).sin() * 10.0,
|
||||||
|
(f64::from(i) * 0.5).sin() * 1.0,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let last = HasbrouckInformationShare::new(20)
|
||||||
|
.unwrap()
|
||||||
|
.batch(&pairs)
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.last()
|
||||||
|
.unwrap();
|
||||||
|
assert!(last > 0.8, "the loud venue should lead, got {last}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn equal_venues_split_evenly() {
|
||||||
|
// Independent but equal-variance moves -> share near 0.5.
|
||||||
|
let pairs: Vec<(f64, f64)> = (0..200)
|
||||||
|
.map(|i| {
|
||||||
|
(
|
||||||
|
(f64::from(i) * 0.5).sin() * 5.0,
|
||||||
|
(f64::from(i) * 0.5).cos() * 5.0,
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
for v in HasbrouckInformationShare::new(40)
|
||||||
|
.unwrap()
|
||||||
|
.batch(&pairs)
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
{
|
||||||
|
assert!((0.0..=1.0).contains(&v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn flat_series_is_half() {
|
||||||
|
let pairs: Vec<(f64, f64)> = (0..20).map(|_| (7.0, 9.0)).collect();
|
||||||
|
let last = HasbrouckInformationShare::new(5)
|
||||||
|
.unwrap()
|
||||||
|
.batch(&pairs)
|
||||||
|
.into_iter()
|
||||||
|
.flatten()
|
||||||
|
.last()
|
||||||
|
.unwrap();
|
||||||
|
assert_relative_eq!(last, 0.5, epsilon = 1e-12);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reset_clears_state() {
|
||||||
|
let mut h = HasbrouckInformationShare::new(4).unwrap();
|
||||||
|
h.batch(&[(1.0, 1.0), (2.0, 2.0), (3.0, 3.0), (4.0, 4.0), (5.0, 5.0)]);
|
||||||
|
assert!(h.is_ready());
|
||||||
|
h.reset();
|
||||||
|
assert!(!h.is_ready());
|
||||||
|
assert_eq!(h.update((1.0, 1.0)), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn batch_equals_streaming() {
|
||||||
|
let pairs: Vec<(f64, f64)> = (0..120)
|
||||||
|
.map(|i| {
|
||||||
|
let t = f64::from(i);
|
||||||
|
(t.sin() * 5.0, (t * 0.5).cos() * 3.0)
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
let batch = HasbrouckInformationShare::new(20).unwrap().batch(&pairs);
|
||||||
|
let mut h = HasbrouckInformationShare::new(20).unwrap();
|
||||||
|
let streamed: Vec<_> = pairs.iter().map(|p| h.update(*p)).collect();
|
||||||
|
assert_eq!(batch, streamed);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -174,6 +174,7 @@ mod hammer;
|
|||||||
mod hanging_man;
|
mod hanging_man;
|
||||||
mod harami;
|
mod harami;
|
||||||
mod harami_cross;
|
mod harami_cross;
|
||||||
|
mod hasbrouck_information_share;
|
||||||
mod head_and_shoulders;
|
mod head_and_shoulders;
|
||||||
mod heikin_ashi;
|
mod heikin_ashi;
|
||||||
mod heikin_ashi_oscillator;
|
mod heikin_ashi_oscillator;
|
||||||
@@ -296,6 +297,7 @@ mod percent_b;
|
|||||||
mod percentage_trailing_stop;
|
mod percentage_trailing_stop;
|
||||||
mod pgo;
|
mod pgo;
|
||||||
mod piercing_dark_cloud;
|
mod piercing_dark_cloud;
|
||||||
|
mod pin;
|
||||||
mod pivot_reversal;
|
mod pivot_reversal;
|
||||||
mod plus_di;
|
mod plus_di;
|
||||||
mod plus_dm;
|
mod plus_dm;
|
||||||
@@ -423,6 +425,7 @@ mod time_of_day_return_profile;
|
|||||||
mod tower_top_bottom;
|
mod tower_top_bottom;
|
||||||
mod tpo_profile;
|
mod tpo_profile;
|
||||||
mod trade_imbalance;
|
mod trade_imbalance;
|
||||||
|
mod trade_sign_autocorrelation;
|
||||||
mod trade_volume_index;
|
mod trade_volume_index;
|
||||||
mod trend_label;
|
mod trend_label;
|
||||||
mod trend_strength_index;
|
mod trend_strength_index;
|
||||||
@@ -659,6 +662,7 @@ pub use hammer::Hammer;
|
|||||||
pub use hanging_man::HangingMan;
|
pub use hanging_man::HangingMan;
|
||||||
pub use harami::Harami;
|
pub use harami::Harami;
|
||||||
pub use harami_cross::HaramiCross;
|
pub use harami_cross::HaramiCross;
|
||||||
|
pub use hasbrouck_information_share::HasbrouckInformationShare;
|
||||||
pub use head_and_shoulders::HeadAndShoulders;
|
pub use head_and_shoulders::HeadAndShoulders;
|
||||||
pub use heikin_ashi::{HeikinAshi, HeikinAshiOutput};
|
pub use heikin_ashi::{HeikinAshi, HeikinAshiOutput};
|
||||||
pub use heikin_ashi_oscillator::HeikinAshiOscillator;
|
pub use heikin_ashi_oscillator::HeikinAshiOscillator;
|
||||||
@@ -781,6 +785,7 @@ pub use percent_b::PercentB;
|
|||||||
pub use percentage_trailing_stop::PercentageTrailingStop;
|
pub use percentage_trailing_stop::PercentageTrailingStop;
|
||||||
pub use pgo::Pgo;
|
pub use pgo::Pgo;
|
||||||
pub use piercing_dark_cloud::PiercingDarkCloud;
|
pub use piercing_dark_cloud::PiercingDarkCloud;
|
||||||
|
pub use pin::Pin;
|
||||||
pub use pivot_reversal::PivotReversal;
|
pub use pivot_reversal::PivotReversal;
|
||||||
pub use plus_di::PlusDi;
|
pub use plus_di::PlusDi;
|
||||||
pub use plus_dm::PlusDm;
|
pub use plus_dm::PlusDm;
|
||||||
@@ -908,6 +913,7 @@ pub use time_of_day_return_profile::{TimeOfDayReturnProfile, TimeOfDayReturnProf
|
|||||||
pub use tower_top_bottom::TowerTopBottom;
|
pub use tower_top_bottom::TowerTopBottom;
|
||||||
pub use tpo_profile::{TpoProfile, TpoProfileOutput};
|
pub use tpo_profile::{TpoProfile, TpoProfileOutput};
|
||||||
pub use trade_imbalance::TradeImbalance;
|
pub use trade_imbalance::TradeImbalance;
|
||||||
|
pub use trade_sign_autocorrelation::TradeSignAutocorrelation;
|
||||||
pub use trade_volume_index::TradeVolumeIndex;
|
pub use trade_volume_index::TradeVolumeIndex;
|
||||||
pub use trend_label::TrendLabel;
|
pub use trend_label::TrendLabel;
|
||||||
pub use trend_strength_index::TrendStrengthIndex;
|
pub use trend_strength_index::TrendStrengthIndex;
|
||||||
@@ -1452,6 +1458,9 @@ pub const FAMILIES: &[(&str, &[&str])] = &[
|
|||||||
"Vpin",
|
"Vpin",
|
||||||
"AmihudIlliquidity",
|
"AmihudIlliquidity",
|
||||||
"RollMeasure",
|
"RollMeasure",
|
||||||
|
"TradeSignAutocorrelation",
|
||||||
|
"Pin",
|
||||||
|
"HasbrouckInformationShare",
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
(
|
(
|
||||||
@@ -1615,6 +1624,6 @@ mod family_tests {
|
|||||||
// the actual indicator count is the early-warning signal that an
|
// the actual indicator count is the early-warning signal that an
|
||||||
// indicator was added without being assigned a family.
|
// indicator was added without being assigned a family.
|
||||||
let total: usize = FAMILIES.iter().map(|(_, ns)| ns.len()).sum();
|
let total: usize = FAMILIES.iter().map(|(_, ns)| ns.len()).sum();
|
||||||
assert_eq!(total, 485, "FAMILIES total drifted from indicator count");
|
assert_eq!(total, 488, "FAMILIES total drifted from indicator count");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,223 @@
|
|||||||
|
//! PIN — Probability of Informed Trading (single-window EKOP estimate).
|
||||||
|
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
use crate::error::{Error, Result};
|
||||||
|
use crate::microstructure::Trade;
|
||||||
|
use crate::traits::Indicator;
|
||||||
|
|
||||||
|
/// PIN — the **Probability of Informed Trading**, estimated from the buy/sell order
|
||||||
|
/// imbalance over a rolling window of trades.
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// over the last `window` trades: B = buys, S = sells (B + S = window)
|
||||||
|
/// PIN ≈ |B − S| / (B + S) ∈ [0, 1]
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The Easley-Kiefer-O'Hara-Paperman (EKOP) model splits order flow into an
|
||||||
|
/// uninformed component (balanced buys and sells, rate `ε` per side) and an
|
||||||
|
/// informed component that trades one-directionally when private information
|
||||||
|
/// arrives (rate `μ`, probability `α`). The probability that any given trade is
|
||||||
|
/// information-motivated is `PIN = αμ / (αμ + 2ε)`. Estimated over a single window,
|
||||||
|
/// the informed flow shows up as the **net imbalance** `|B − S|` and the uninformed
|
||||||
|
/// flow as the balanced remainder, giving the moment estimator above. A high PIN
|
||||||
|
/// flags a one-sided, likely-informed market; a low PIN flags balanced, uninformed
|
||||||
|
/// flow.
|
||||||
|
///
|
||||||
|
/// This is distinct from [`Vpin`](crate::Vpin), the volume-synchronised variant
|
||||||
|
/// that buckets by volume and uses bulk-volume classification; here trades are
|
||||||
|
/// counted in event time and classified by their tagged aggressor side. The full
|
||||||
|
/// PIN is fit by maximum likelihood over many periods — this single-window
|
||||||
|
/// estimator is the streaming moment approximation. The output is in `[0, 1]`; the
|
||||||
|
/// first value lands after `window` trades.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use wickra_core::{Indicator, Pin, Side, Trade};
|
||||||
|
///
|
||||||
|
/// let mut indicator = Pin::new(20).unwrap();
|
||||||
|
/// let mut last = None;
|
||||||
|
/// for i in 0..40 {
|
||||||
|
/// // All buys -> maximally one-sided -> PIN 1.
|
||||||
|
/// last = indicator.update(Trade::new(100.0, 1.0, Side::Buy, i).unwrap());
|
||||||
|
/// }
|
||||||
|
/// assert!((last.unwrap() - 1.0).abs() < 1e-9);
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct Pin {
|
||||||
|
window: usize,
|
||||||
|
sides: VecDeque<f64>,
|
||||||
|
buy_count: usize,
|
||||||
|
last: Option<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Pin {
|
||||||
|
/// Construct a PIN estimator over `window` trades.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`Error::PeriodZero`] if `window == 0`.
|
||||||
|
pub fn new(window: usize) -> Result<Self> {
|
||||||
|
if window == 0 {
|
||||||
|
return Err(Error::PeriodZero);
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
window,
|
||||||
|
sides: VecDeque::with_capacity(window),
|
||||||
|
buy_count: 0,
|
||||||
|
last: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configured window of trades.
|
||||||
|
pub const fn window(&self) -> usize {
|
||||||
|
self.window
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Current value if available.
|
||||||
|
pub const fn value(&self) -> Option<f64> {
|
||||||
|
self.last
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Indicator for Pin {
|
||||||
|
type Input = Trade;
|
||||||
|
type Output = f64;
|
||||||
|
|
||||||
|
fn update(&mut self, trade: Trade) -> Option<f64> {
|
||||||
|
let is_buy = trade.side.sign() > 0.0;
|
||||||
|
if self.sides.len() == self.window {
|
||||||
|
let old = self.sides.pop_front().expect("non-empty");
|
||||||
|
if old > 0.0 {
|
||||||
|
self.buy_count -= 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.sides.push_back(if is_buy { 1.0 } else { 0.0 });
|
||||||
|
if is_buy {
|
||||||
|
self.buy_count += 1;
|
||||||
|
}
|
||||||
|
if self.sides.len() < self.window {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
// The window is full and `window >= 1` (zero is rejected at
|
||||||
|
// construction), so the trade count is always positive — `|B - S| / N`
|
||||||
|
// needs no zero guard.
|
||||||
|
let buys = self.buy_count as f64;
|
||||||
|
let sells = self.window as f64 - buys;
|
||||||
|
let total = self.window as f64;
|
||||||
|
let pin = (buys - sells).abs() / total;
|
||||||
|
self.last = Some(pin);
|
||||||
|
Some(pin)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.sides.clear();
|
||||||
|
self.buy_count = 0;
|
||||||
|
self.last = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn warmup_period(&self) -> usize {
|
||||||
|
self.window
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_ready(&self) -> bool {
|
||||||
|
self.last.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"PIN"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::microstructure::Side;
|
||||||
|
use crate::traits::BatchExt;
|
||||||
|
use approx::assert_relative_eq;
|
||||||
|
|
||||||
|
fn buy() -> Trade {
|
||||||
|
Trade::new_unchecked(100.0, 1.0, Side::Buy, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sell() -> Trade {
|
||||||
|
Trade::new_unchecked(100.0, 1.0, Side::Sell, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_zero_window() {
|
||||||
|
assert!(matches!(Pin::new(0), Err(Error::PeriodZero)));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn accessors_and_metadata() {
|
||||||
|
let p = Pin::new(20).unwrap();
|
||||||
|
assert_eq!(p.window(), 20);
|
||||||
|
assert_eq!(p.warmup_period(), 20);
|
||||||
|
assert_eq!(p.name(), "PIN");
|
||||||
|
assert!(!p.is_ready());
|
||||||
|
assert_eq!(p.value(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn first_emission_at_warmup_period() {
|
||||||
|
let mut p = Pin::new(4).unwrap();
|
||||||
|
let out = p.batch(&[buy(), buy(), buy(), buy(), buy()]);
|
||||||
|
for v in out.iter().take(3) {
|
||||||
|
assert!(v.is_none());
|
||||||
|
}
|
||||||
|
assert!(out[3].is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn one_sided_flow_is_one() {
|
||||||
|
let mut p = Pin::new(10).unwrap();
|
||||||
|
let trades: Vec<Trade> = (0..20).map(|_| buy()).collect();
|
||||||
|
let last = p.batch(&trades).into_iter().flatten().last().unwrap();
|
||||||
|
assert_relative_eq!(last, 1.0, epsilon = 1e-12);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn balanced_flow_is_zero() {
|
||||||
|
let mut p = Pin::new(10).unwrap();
|
||||||
|
let trades: Vec<Trade> = (0..20)
|
||||||
|
.map(|i| if i % 2 == 0 { buy() } else { sell() })
|
||||||
|
.collect();
|
||||||
|
let last = p.batch(&trades).into_iter().flatten().last().unwrap();
|
||||||
|
assert_relative_eq!(last, 0.0, epsilon = 1e-12);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn output_in_range() {
|
||||||
|
let mut p = Pin::new(16).unwrap();
|
||||||
|
let trades: Vec<Trade> = (0..200)
|
||||||
|
.map(|i| if (i * 5 % 13) < 8 { buy() } else { sell() })
|
||||||
|
.collect();
|
||||||
|
for v in p.batch(&trades).into_iter().flatten() {
|
||||||
|
assert!((0.0..=1.0).contains(&v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reset_clears_state() {
|
||||||
|
let mut p = Pin::new(4).unwrap();
|
||||||
|
p.batch(&[buy(), buy(), sell(), buy()]);
|
||||||
|
assert!(p.is_ready());
|
||||||
|
p.reset();
|
||||||
|
assert!(!p.is_ready());
|
||||||
|
assert_eq!(p.value(), None);
|
||||||
|
assert_eq!(p.update(buy()), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn batch_equals_streaming() {
|
||||||
|
let trades: Vec<Trade> = (0..120)
|
||||||
|
.map(|i| if i % 3 == 0 { sell() } else { buy() })
|
||||||
|
.collect();
|
||||||
|
let batch = Pin::new(16).unwrap().batch(&trades);
|
||||||
|
let mut b = Pin::new(16).unwrap();
|
||||||
|
let streamed: Vec<_> = trades.iter().map(|x| b.update(*x)).collect();
|
||||||
|
assert_eq!(batch, streamed);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
//! Trade-Sign Autocorrelation — lag-1 persistence of the trade-aggressor side.
|
||||||
|
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
|
||||||
|
use crate::error::{Error, Result};
|
||||||
|
use crate::microstructure::Trade;
|
||||||
|
use crate::traits::Indicator;
|
||||||
|
|
||||||
|
/// Trade-Sign Autocorrelation — the lag-1 autocorrelation of the **trade sign**
|
||||||
|
/// (`+1` buy, `−1` sell), measuring how strongly signed order flow persists.
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// s_t = +1 if the trade is a buy, −1 if a sell
|
||||||
|
/// ρ1 = mean over the window of ( s_t · s_{t−1} ) ∈ [−1, +1]
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// In real markets trade signs are strongly **positively** autocorrelated: a buy
|
||||||
|
/// tends to be followed by another buy (and a sell by a sell), because large
|
||||||
|
/// parent orders are split into many child trades and because of order-splitting
|
||||||
|
/// and herding. A high reading therefore indicates persistent directional pressure
|
||||||
|
/// — a footprint of informed or algorithmic execution — while a reading near zero
|
||||||
|
/// signals balanced, uninformed flow and a negative reading signals alternating
|
||||||
|
/// (bid-ask bounce) flow.
|
||||||
|
///
|
||||||
|
/// The output is the mean product of consecutive signs, bounded in `[−1, +1]`. The
|
||||||
|
/// first value lands after `period` trades. Each `update` is O(`period`).
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use wickra_core::{Indicator, Side, Trade, TradeSignAutocorrelation};
|
||||||
|
///
|
||||||
|
/// let mut indicator = TradeSignAutocorrelation::new(20).unwrap();
|
||||||
|
/// let mut last = None;
|
||||||
|
/// for i in 0..40 {
|
||||||
|
/// let side = if i % 2 == 0 { Side::Buy } else { Side::Sell };
|
||||||
|
/// last = indicator.update(Trade::new(100.0, 1.0, side, i).unwrap());
|
||||||
|
/// }
|
||||||
|
/// // Perfectly alternating signs -> autocorrelation -1.
|
||||||
|
/// assert!((last.unwrap() + 1.0).abs() < 1e-9);
|
||||||
|
/// ```
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TradeSignAutocorrelation {
|
||||||
|
period: usize,
|
||||||
|
signs: VecDeque<f64>,
|
||||||
|
last: Option<f64>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TradeSignAutocorrelation {
|
||||||
|
/// Construct a trade-sign autocorrelation over `period` trades.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns [`Error::InvalidPeriod`] if `period < 2` (a lag-1 product needs two
|
||||||
|
/// trades).
|
||||||
|
pub fn new(period: usize) -> Result<Self> {
|
||||||
|
if period < 2 {
|
||||||
|
return Err(Error::InvalidPeriod {
|
||||||
|
message: "trade-sign autocorrelation needs period >= 2",
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(Self {
|
||||||
|
period,
|
||||||
|
signs: VecDeque::with_capacity(period),
|
||||||
|
last: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Configured window of trades.
|
||||||
|
pub const fn period(&self) -> usize {
|
||||||
|
self.period
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Current value if available.
|
||||||
|
pub const fn value(&self) -> Option<f64> {
|
||||||
|
self.last
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Indicator for TradeSignAutocorrelation {
|
||||||
|
type Input = Trade;
|
||||||
|
type Output = f64;
|
||||||
|
|
||||||
|
fn update(&mut self, trade: Trade) -> Option<f64> {
|
||||||
|
if self.signs.len() == self.period {
|
||||||
|
self.signs.pop_front();
|
||||||
|
}
|
||||||
|
self.signs.push_back(trade.side.sign());
|
||||||
|
if self.signs.len() < self.period {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
let mut product_sum = 0.0;
|
||||||
|
let mut prev: Option<f64> = None;
|
||||||
|
for &s in &self.signs {
|
||||||
|
if let Some(p) = prev {
|
||||||
|
product_sum += s * p;
|
||||||
|
}
|
||||||
|
prev = Some(s);
|
||||||
|
}
|
||||||
|
let rho = product_sum / (self.period as f64 - 1.0);
|
||||||
|
self.last = Some(rho);
|
||||||
|
Some(rho)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reset(&mut self) {
|
||||||
|
self.signs.clear();
|
||||||
|
self.last = None;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn warmup_period(&self) -> usize {
|
||||||
|
self.period
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_ready(&self) -> bool {
|
||||||
|
self.last.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn name(&self) -> &'static str {
|
||||||
|
"TradeSignAutocorrelation"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::microstructure::Side;
|
||||||
|
use crate::traits::BatchExt;
|
||||||
|
use approx::assert_relative_eq;
|
||||||
|
|
||||||
|
fn buy() -> Trade {
|
||||||
|
Trade::new_unchecked(100.0, 1.0, Side::Buy, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sell() -> Trade {
|
||||||
|
Trade::new_unchecked(100.0, 1.0, Side::Sell, 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn rejects_period_below_two() {
|
||||||
|
assert!(matches!(
|
||||||
|
TradeSignAutocorrelation::new(1),
|
||||||
|
Err(Error::InvalidPeriod { .. })
|
||||||
|
));
|
||||||
|
assert!(TradeSignAutocorrelation::new(2).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn accessors_and_metadata() {
|
||||||
|
let t = TradeSignAutocorrelation::new(20).unwrap();
|
||||||
|
assert_eq!(t.period(), 20);
|
||||||
|
assert_eq!(t.warmup_period(), 20);
|
||||||
|
assert_eq!(t.name(), "TradeSignAutocorrelation");
|
||||||
|
assert!(!t.is_ready());
|
||||||
|
assert_eq!(t.value(), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn first_emission_at_warmup_period() {
|
||||||
|
let mut t = TradeSignAutocorrelation::new(4).unwrap();
|
||||||
|
let out = t.batch(&[buy(), buy(), buy(), buy(), buy()]);
|
||||||
|
for v in out.iter().take(3) {
|
||||||
|
assert!(v.is_none());
|
||||||
|
}
|
||||||
|
assert!(out[3].is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn persistent_flow_is_one() {
|
||||||
|
let mut t = TradeSignAutocorrelation::new(10).unwrap();
|
||||||
|
let trades: Vec<Trade> = (0..20).map(|_| buy()).collect();
|
||||||
|
let last = t.batch(&trades).into_iter().flatten().last().unwrap();
|
||||||
|
assert_relative_eq!(last, 1.0, epsilon = 1e-12);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn alternating_flow_is_minus_one() {
|
||||||
|
let mut t = TradeSignAutocorrelation::new(10).unwrap();
|
||||||
|
let trades: Vec<Trade> = (0..20)
|
||||||
|
.map(|i| if i % 2 == 0 { buy() } else { sell() })
|
||||||
|
.collect();
|
||||||
|
let last = t.batch(&trades).into_iter().flatten().last().unwrap();
|
||||||
|
assert_relative_eq!(last, -1.0, epsilon = 1e-12);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn output_in_range() {
|
||||||
|
let mut t = TradeSignAutocorrelation::new(16).unwrap();
|
||||||
|
let trades: Vec<Trade> = (0..200)
|
||||||
|
.map(|i| if (i * 7 % 13) < 6 { buy() } else { sell() })
|
||||||
|
.collect();
|
||||||
|
for v in t.batch(&trades).into_iter().flatten() {
|
||||||
|
assert!((-1.0..=1.0).contains(&v));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn reset_clears_state() {
|
||||||
|
let mut t = TradeSignAutocorrelation::new(4).unwrap();
|
||||||
|
t.batch(&[buy(), buy(), buy(), buy()]);
|
||||||
|
assert!(t.is_ready());
|
||||||
|
t.reset();
|
||||||
|
assert!(!t.is_ready());
|
||||||
|
assert_eq!(t.value(), None);
|
||||||
|
assert_eq!(t.update(buy()), None);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn batch_equals_streaming() {
|
||||||
|
let trades: Vec<Trade> = (0..120)
|
||||||
|
.map(|i| if i % 3 == 0 { sell() } else { buy() })
|
||||||
|
.collect();
|
||||||
|
let batch = TradeSignAutocorrelation::new(16).unwrap().batch(&trades);
|
||||||
|
let mut b = TradeSignAutocorrelation::new(16).unwrap();
|
||||||
|
let streamed: Vec<_> = trades.iter().map(|x| b.update(*x)).collect();
|
||||||
|
assert_eq!(batch, streamed);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -91,12 +91,12 @@ pub use indicators::{
|
|||||||
FundingRateMean, FundingRateZScore, GainLossRatio, GapSideBySideWhite, Garch11,
|
FundingRateMean, FundingRateZScore, GainLossRatio, GapSideBySideWhite, Garch11,
|
||||||
GarmanKlassVolatility, Gartley, GatorOscillator, GatorOscillatorOutput, GeneralizedDema,
|
GarmanKlassVolatility, Gartley, GatorOscillator, GatorOscillatorOutput, GeneralizedDema,
|
||||||
GeometricMa, GoldenPocket, GoldenPocketOutput, GrangerCausality, GravestoneDoji, Hammer,
|
GeometricMa, GoldenPocket, GoldenPocketOutput, GrangerCausality, GravestoneDoji, Hammer,
|
||||||
HangingMan, Harami, HaramiCross, HeadAndShoulders, HeikinAshi, HeikinAshiOscillator,
|
HangingMan, Harami, HaramiCross, HasbrouckInformationShare, HeadAndShoulders, HeikinAshi,
|
||||||
HeikinAshiOutput, HiLoActivator, HighLowIndex, HighLowRange, HighWave, HighpassFilter, Hikkake,
|
HeikinAshiOscillator, HeikinAshiOutput, HiLoActivator, HighLowIndex, HighLowRange, HighWave,
|
||||||
HikkakeModified, HilbertDominantCycle, HistoricalVolatility, Hma, HoltWinters, HomingPigeon,
|
HighpassFilter, Hikkake, HikkakeModified, HilbertDominantCycle, HistoricalVolatility, Hma,
|
||||||
HtDcPhase, HtPhasor, HtPhasorOutput, HtTrendMode, HurstChannel, HurstChannelOutput,
|
HoltWinters, HomingPigeon, HtDcPhase, HtPhasor, HtPhasorOutput, HtTrendMode, HurstChannel,
|
||||||
HurstExponent, Ichimoku, IchimokuOutput, IdenticalThreeCrows, InNeck, Inertia,
|
HurstChannelOutput, HurstExponent, Ichimoku, IchimokuOutput, IdenticalThreeCrows, InNeck,
|
||||||
InformationRatio, InitialBalance, InitialBalanceOutput, InstantaneousTrendline,
|
Inertia, InformationRatio, InitialBalance, InitialBalanceOutput, InstantaneousTrendline,
|
||||||
IntradayIntensity, IntradayMomentumIndex, IntradayVolatilityProfile,
|
IntradayIntensity, IntradayMomentumIndex, IntradayVolatilityProfile,
|
||||||
IntradayVolatilityProfileOutput, InverseFisherTransform, InvertedHammer, JarqueBera, Jma,
|
IntradayVolatilityProfileOutput, InverseFisherTransform, InvertedHammer, JarqueBera, Jma,
|
||||||
JumpIndicator, KagiBars, KalmanHedgeRatio, KalmanHedgeRatioOutput, Kama, KaseDevStop,
|
JumpIndicator, KagiBars, KalmanHedgeRatio, KalmanHedgeRatioOutput, Kama, KaseDevStop,
|
||||||
@@ -117,7 +117,7 @@ pub use indicators::{
|
|||||||
OrderBookImbalanceTopN, OrderFlowImbalance, OuHalfLife, OvernightGap, OvernightIntradayReturn,
|
OrderBookImbalanceTopN, OrderFlowImbalance, OuHalfLife, OvernightGap, OvernightIntradayReturn,
|
||||||
OvernightIntradayReturnOutput, PainIndex, PairSpreadZScore, PairwiseBeta, ParkinsonVolatility,
|
OvernightIntradayReturnOutput, PainIndex, PairSpreadZScore, PairwiseBeta, ParkinsonVolatility,
|
||||||
PearsonCorrelation, PercentAboveMa, PercentB, PercentageTrailingStop, Pgo, PiercingDarkCloud,
|
PearsonCorrelation, PercentAboveMa, PercentB, PercentageTrailingStop, Pgo, PiercingDarkCloud,
|
||||||
PivotReversal, PlusDi, PlusDm, Pmo, PointAndFigureBars, PolarizedFractalEfficiency, Ppo,
|
Pin, PivotReversal, PlusDi, PlusDm, Pmo, PointAndFigureBars, PolarizedFractalEfficiency, Ppo,
|
||||||
PpoHistogram, ProfitFactor, ProjectionBands, ProjectionBandsOutput, ProjectionOscillator, Psar,
|
PpoHistogram, ProfitFactor, ProjectionBands, ProjectionBandsOutput, ProjectionOscillator, Psar,
|
||||||
Pvi, Qqe, QqeOutput, Qstick, QuartileBands, QuartileBandsOutput, QuotedSpread, RSquared,
|
Pvi, Qqe, QqeOutput, Qstick, QuartileBands, QuartileBandsOutput, QuotedSpread, RSquared,
|
||||||
RealizedSpread, RealizedVolatility, RecoveryFactor, RectangleRange, Reflex, RegimeLabel,
|
RealizedSpread, RealizedVolatility, RecoveryFactor, RectangleRange, Reflex, RegimeLabel,
|
||||||
@@ -140,20 +140,21 @@ pub use indicators::{
|
|||||||
TdSequentialOutput, TdSetup, TdTrap, Tema, TermStructureBasis, ThreeDrives, ThreeInside,
|
TdSequentialOutput, TdSetup, TdTrap, Tema, TermStructureBasis, ThreeDrives, ThreeInside,
|
||||||
ThreeLineBreak, ThreeLineStrike, ThreeOutside, ThreeSoldiersOrCrows, ThreeStarsInSouth,
|
ThreeLineBreak, ThreeLineStrike, ThreeOutside, ThreeSoldiersOrCrows, ThreeStarsInSouth,
|
||||||
Thrusting, TickIndex, Tii, TimeBasedStop, TimeOfDayReturnProfile, TimeOfDayReturnProfileOutput,
|
Thrusting, TickIndex, Tii, TimeBasedStop, TimeOfDayReturnProfile, TimeOfDayReturnProfileOutput,
|
||||||
TowerTopBottom, TpoProfile, TpoProfileOutput, TradeImbalance, TradeVolumeIndex, TrendLabel,
|
TowerTopBottom, TpoProfile, TpoProfileOutput, TradeImbalance, TradeSignAutocorrelation,
|
||||||
TrendStrengthIndex, Trendflex, TreynorRatio, Triangle, Trima, Trin, TripleTopBottom, Tristar,
|
TradeVolumeIndex, TrendLabel, TrendStrengthIndex, Trendflex, TreynorRatio, Triangle, Trima,
|
||||||
Trix, TrueRange, Tsf, TsfOscillator, Tsi, Tsv, TtmSqueeze, TtmSqueezeOutput, TtmTrend,
|
Trin, TripleTopBottom, Tristar, Trix, TrueRange, Tsf, TsfOscillator, Tsi, Tsv, TtmSqueeze,
|
||||||
TurnOfMonth, Tweezer, TwiggsMoneyFlow, TwoCrows, TypicalPrice, UlcerIndex, UltimateOscillator,
|
TtmSqueezeOutput, TtmTrend, TurnOfMonth, Tweezer, TwiggsMoneyFlow, TwoCrows, TypicalPrice,
|
||||||
UniqueThreeRiver, UniversalOscillator, UpDownVolumeRatio, UpsideGapThreeMethods,
|
UlcerIndex, UltimateOscillator, UniqueThreeRiver, UniversalOscillator, UpDownVolumeRatio,
|
||||||
UpsideGapTwoCrows, ValueArea, ValueAreaOutput, ValueAtRisk, Variance, VarianceRatio,
|
UpsideGapThreeMethods, UpsideGapTwoCrows, ValueArea, ValueAreaOutput, ValueAtRisk, Variance,
|
||||||
VerticalHorizontalFilter, Vidya, VolatilityCone, VolatilityConeOutput, VolatilityOfVolatility,
|
VarianceRatio, VerticalHorizontalFilter, Vidya, VolatilityCone, VolatilityConeOutput,
|
||||||
VolatilityRatio, VoltyStop, VolumeByTimeProfile, VolumeByTimeProfileOutput, VolumeOscillator,
|
VolatilityOfVolatility, VolatilityRatio, VoltyStop, VolumeByTimeProfile,
|
||||||
VolumePriceTrend, VolumeProfile, VolumeProfileOutput, VolumeRsi, VolumeWeightedMacd,
|
VolumeByTimeProfileOutput, VolumeOscillator, VolumePriceTrend, VolumeProfile,
|
||||||
VolumeWeightedMacdOutput, VolumeWeightedSr, VolumeWeightedSrOutput, Vortex, VortexOutput, Vpin,
|
VolumeProfileOutput, VolumeRsi, VolumeWeightedMacd, VolumeWeightedMacdOutput, VolumeWeightedSr,
|
||||||
Vwap, VwapStdDevBands, VwapStdDevBandsOutput, Vwma, Vzo, Wad, WavePm, WaveTrend,
|
VolumeWeightedSrOutput, Vortex, VortexOutput, Vpin, Vwap, VwapStdDevBands,
|
||||||
WaveTrendOutput, Wedge, WeightedClose, WickRatio, WilliamsFractals, WilliamsFractalsOutput,
|
VwapStdDevBandsOutput, Vwma, Vzo, Wad, WavePm, WaveTrend, WaveTrendOutput, Wedge,
|
||||||
WilliamsR, WinRate, Wma, WoodiePivots, WoodiePivotsOutput, YangZhangVolatility, YoyoExit,
|
WeightedClose, WickRatio, WilliamsFractals, WilliamsFractalsOutput, WilliamsR, WinRate, Wma,
|
||||||
ZScore, ZeroLagMacd, ZeroLagMacdOutput, ZigZag, ZigZagOutput, Zlema, FAMILIES, T3,
|
WoodiePivots, WoodiePivotsOutput, YangZhangVolatility, YoyoExit, ZScore, ZeroLagMacd,
|
||||||
|
ZeroLagMacdOutput, ZigZag, ZigZagOutput, Zlema, FAMILIES, T3,
|
||||||
};
|
};
|
||||||
// `FootprintLevel` is a row element of `FootprintOutput`, re-exported on its own
|
// `FootprintLevel` is a row element of `FootprintOutput`, re-exported on its own
|
||||||
// line so the indicator-count tooling (which scans the braced block above and
|
// line so the indicator-count tooling (which scans the braced block above and
|
||||||
|
|||||||
+1
-1
@@ -8,7 +8,7 @@ That includes:
|
|||||||
[Python](https://docs.wickra.org/Quickstart-Python),
|
[Python](https://docs.wickra.org/Quickstart-Python),
|
||||||
[Node](https://docs.wickra.org/Quickstart-Node), and
|
[Node](https://docs.wickra.org/Quickstart-Node), and
|
||||||
[WASM](https://docs.wickra.org/Quickstart-WASM).
|
[WASM](https://docs.wickra.org/Quickstart-WASM).
|
||||||
- A per-indicator deep dive for every one of the **485 indicators** across
|
- A per-indicator deep dive for every one of the **488 indicators** across
|
||||||
the sixteen families (Moving Averages, Momentum Oscillators, Trend &
|
the sixteen families (Moving Averages, Momentum Oscillators, Trend &
|
||||||
Directional, Price Oscillators, Volatility & Bands, Bands & Channels,
|
Directional, Price Oscillators, Volatility & Bands, Bands & Channels,
|
||||||
Trailing Stops, Volume, Price Statistics, Ehlers / Cycle DSP, Pivots &
|
Trailing Stops, Volume, Price Statistics, Ehlers / Cycle DSP, Pivots &
|
||||||
|
|||||||
Generated
+7
-7
@@ -17,7 +17,7 @@
|
|||||||
},
|
},
|
||||||
"../../bindings/node": {
|
"../../bindings/node": {
|
||||||
"name": "wickra",
|
"name": "wickra",
|
||||||
"version": "0.6.9",
|
"version": "0.7.0",
|
||||||
"license": "MIT OR Apache-2.0",
|
"license": "MIT OR Apache-2.0",
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@napi-rs/cli": "^2.18.0"
|
"@napi-rs/cli": "^2.18.0"
|
||||||
@@ -26,12 +26,12 @@
|
|||||||
"node": ">= 18"
|
"node": ">= 18"
|
||||||
},
|
},
|
||||||
"optionalDependencies": {
|
"optionalDependencies": {
|
||||||
"wickra-darwin-arm64": "0.6.9",
|
"wickra-darwin-arm64": "0.7.0",
|
||||||
"wickra-darwin-x64": "0.6.9",
|
"wickra-darwin-x64": "0.7.0",
|
||||||
"wickra-linux-arm64-gnu": "0.6.9",
|
"wickra-linux-arm64-gnu": "0.7.0",
|
||||||
"wickra-linux-x64-gnu": "0.6.9",
|
"wickra-linux-x64-gnu": "0.7.0",
|
||||||
"wickra-win32-arm64-msvc": "0.6.9",
|
"wickra-win32-arm64-msvc": "0.7.0",
|
||||||
"wickra-win32-x64-msvc": "0.6.9"
|
"wickra-win32-x64-msvc": "0.7.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/wickra": {
|
"node_modules/wickra": {
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
//! panic.
|
//! panic.
|
||||||
|
|
||||||
use libfuzzer_sys::fuzz_target;
|
use libfuzzer_sys::fuzz_target;
|
||||||
use wickra_core::{Alpha, BatchExt, BetaNeutralSpread, Cointegration, DistanceSsd, GrangerCausality, Indicator, InformationRatio, KalmanHedgeRatio, KendallTau, LeadLagCrossCorrelation, OuHalfLife, PairSpreadZScore, PairwiseBeta, RelativeStrengthAB, RollingCorrelation, RollingCovariance, SpreadAr1Coefficient, SpreadBollingerBands, SpreadHurst, TreynorRatio, VarianceRatio};
|
use wickra_core::{Alpha, BatchExt, BetaNeutralSpread, Cointegration, DistanceSsd, GrangerCausality, HasbrouckInformationShare, Indicator, InformationRatio, KalmanHedgeRatio, KendallTau, LeadLagCrossCorrelation, OuHalfLife, PairSpreadZScore, PairwiseBeta, RelativeStrengthAB, RollingCorrelation, RollingCovariance, SpreadAr1Coefficient, SpreadBollingerBands, SpreadHurst, TreynorRatio, VarianceRatio};
|
||||||
|
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
fn drive<I>(make: impl Fn() -> I, data: &[(f64, f64)])
|
fn drive<I>(make: impl Fn() -> I, data: &[(f64, f64)])
|
||||||
@@ -48,6 +48,7 @@ fuzz_target!(|data: &[u8]| {
|
|||||||
drive(|| GrangerCausality::new(60, 1).unwrap(), &pairs);
|
drive(|| GrangerCausality::new(60, 1).unwrap(), &pairs);
|
||||||
drive(|| SpreadAr1Coefficient::new(40).unwrap(), &pairs);
|
drive(|| SpreadAr1Coefficient::new(40).unwrap(), &pairs);
|
||||||
drive(|| KendallTau::new(20).unwrap(), &pairs);
|
drive(|| KendallTau::new(20).unwrap(), &pairs);
|
||||||
|
drive(|| HasbrouckInformationShare::new(2).unwrap(), &pairs);
|
||||||
|
|
||||||
// Struct-output pair indicator: drive update + batch directly (the generic
|
// Struct-output pair indicator: drive update + batch directly (the generic
|
||||||
// `drive` above only covers `Output = f64`).
|
// `drive` above only covers `Output = f64`).
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
//! would reject — the indicators must never panic, streaming or batched.
|
//! would reject — the indicators must never panic, streaming or batched.
|
||||||
|
|
||||||
use libfuzzer_sys::fuzz_target;
|
use libfuzzer_sys::fuzz_target;
|
||||||
use wickra_core::{AmihudIlliquidity, BatchExt, CumulativeVolumeDelta, Footprint, Indicator, RollMeasure, Side, SignedVolume, Trade, TradeImbalance, Vpin};
|
use wickra_core::{AmihudIlliquidity, BatchExt, CumulativeVolumeDelta, Footprint, Indicator, Pin, RollMeasure, Side, SignedVolume, Trade, TradeImbalance, TradeSignAutocorrelation, Vpin};
|
||||||
|
|
||||||
#[inline(never)]
|
#[inline(never)]
|
||||||
fn drive<I>(make: impl Fn() -> I, trades: &[Trade])
|
fn drive<I>(make: impl Fn() -> I, trades: &[Trade])
|
||||||
@@ -43,6 +43,8 @@ fuzz_target!(|data: &[u8]| {
|
|||||||
drive(|| Vpin::new(8.0, 5).unwrap(), &trades);
|
drive(|| Vpin::new(8.0, 5).unwrap(), &trades);
|
||||||
drive(|| AmihudIlliquidity::new(20).unwrap(), &trades);
|
drive(|| AmihudIlliquidity::new(20).unwrap(), &trades);
|
||||||
drive(|| RollMeasure::new(20).unwrap(), &trades);
|
drive(|| RollMeasure::new(20).unwrap(), &trades);
|
||||||
|
drive(|| TradeSignAutocorrelation::new(20).unwrap(), &trades);
|
||||||
|
drive(|| Pin::new(20).unwrap(), &trades);
|
||||||
|
|
||||||
// Footprint emits a variable-length `FootprintOutput` rather than an `f64`,
|
// Footprint emits a variable-length `FootprintOutput` rather than an `f64`,
|
||||||
// so it is driven directly rather than through the scalar-output helper.
|
// so it is driven directly rather than through the scalar-output helper.
|
||||||
|
|||||||
Reference in New Issue
Block a user