Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 5f0677d62d | |||
| ce792cf8b8 | |||
| 120b6ac265 | |||
| 4f708d410d | |||
| de1112ea91 | |||
| 82d7479011 |
@@ -32,3 +32,10 @@ bindings/r/src/Makevars.in text eol=lf
|
||||
bindings/r/src/Makevars.win text eol=lf
|
||||
bindings/r/src/wickra.c text eol=lf
|
||||
|
||||
# Golden fixtures are replayed byte-for-byte by every binding's parity test. Pin
|
||||
# them to LF so a Windows `core.autocrlf=true` checkout doesn't rewrite them as
|
||||
# CRLF — which silently broke the Node reader (`Number('inf\r')` is NaN, and a
|
||||
# blank "no-bar" row gained a stray `\r`), failing only on Windows runners. The
|
||||
# tolerant readers (Python `splitlines`/`float`) hid the same hazard.
|
||||
testdata/golden/** text eol=lf
|
||||
|
||||
|
||||
@@ -566,6 +566,12 @@ jobs:
|
||||
test -f bindings/wasm/pkg/wickra_wasm_bg.wasm
|
||||
test -f bindings/wasm/pkg/wickra_wasm.d.ts
|
||||
|
||||
- name: Build WASM package (nodejs target) for the golden suite
|
||||
run: wasm-pack build bindings/wasm --target nodejs --release --out-dir pkg
|
||||
|
||||
- name: Golden parity — all 514 indicators vs the Rust reference
|
||||
run: node --test bindings/wasm/tests/golden.test.js
|
||||
|
||||
node:
|
||||
name: Node ${{ matrix.node-version }} on ${{ matrix.os }}
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
+1
-1
@@ -227,7 +227,7 @@ A handful of indicators need care beyond naive accumulation:
|
||||
A typical full-stack call sequence for a Python live-trading example:
|
||||
|
||||
```
|
||||
[ Python: live_trading.py ]
|
||||
[ Python: live_binance.py ]
|
||||
│
|
||||
▼
|
||||
[ binance.AsyncClient WebSocket ] ──── wickra_data live feed ───┐
|
||||
|
||||
+58
-1
@@ -7,6 +7,62 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
## [0.9.2] - 2026-06-15
|
||||
|
||||
### Added
|
||||
- **Cross-language golden parity for all 514 indicators across all 10 languages.**
|
||||
A new `gen_golden` reference emits a deterministic OHLCV input series plus the
|
||||
Rust output of every one of the 514 indicators to `testdata/golden/`. Each
|
||||
binding now replays that shared input and is checked **bit-for-bit against the
|
||||
Rust reference**, covering every archetype (scalar, multi-output, pairwise,
|
||||
derivatives-tick, cross-section, order-book, trade, profile, alt-chart bars,
|
||||
footprint):
|
||||
- Python, Node.js, Java and R via reflection-driven runners.
|
||||
- Go, C# and C/C++ via generated dispatch (`golden_all_test.go`,
|
||||
`GoldenAllTests.g.cs`, `examples/c/golden_test.c` compiled as both C and C++).
|
||||
- WASM via a `node --test` runner over the nodejs-target build.
|
||||
- CI now runs the WASM golden suite; the C/C++ golden tests run as `ctest`
|
||||
targets in the existing C-ABI job, and the Python/Node/Go/C#/Java/R suites pick
|
||||
up their golden runners automatically.
|
||||
- **README:** a "verified across 10 languages" badge (linking to the FAQ that
|
||||
explains the cross-language golden parity) and a per-binding throughput table so
|
||||
readers can pick a binding by its streaming FFI cost.
|
||||
|
||||
### Fixed
|
||||
- **Java binding marshalled C ABI `bool` parameters incorrectly.** The
|
||||
cross-section state flags (`newHigh`, `newLow`, `aboveMa`, `onBuySignal`) were
|
||||
allocated as `JAVA_DOUBLE` arrays and passed to `const bool*` parameters, so the
|
||||
native side read the low byte of each 8-byte double and saw every flag as
|
||||
`false` (affecting e.g. `NewHighsNewLows`, `HighLowIndex`, `BullishPercentIndex`,
|
||||
`PercentAboveMa`). They are now packed into a real `bool` buffer. `MacdExt`'s
|
||||
`MaType` arguments are now passed as `byte` to match the `uint8_t` downcall.
|
||||
- **R binding marshalled C ABI `bool` flags incorrectly.** `(bool *)REAL(x)`
|
||||
reinterpreted the 8-byte doubles as 1-byte bools across the 15 cross-section
|
||||
update wrappers, reading every flag as `false`; the flags are now converted into
|
||||
a real C `bool` buffer.
|
||||
- C# binding: added the `#nullable enable` directive the generated
|
||||
`Indicators.g.cs` requires, clearing four `CS8669` warnings.
|
||||
|
||||
### Changed
|
||||
- Renamed the `live_trading` examples to `live_binance` across the Python, Node.js,
|
||||
WASM and C examples — they poll Binance market data, they do not place trades.
|
||||
- **Breaking — de-duplicated four indicators that computed identically to another
|
||||
one.** Each is now its own distinct, correctly-defined indicator (the catalogue
|
||||
stays at the same count):
|
||||
- `AverageDrawdown` now reports the mean of the maximum depths of the distinct
|
||||
drawdown episodes in the window (previously the per-bar mean under-water
|
||||
fraction, which equalled `PainIndex`).
|
||||
- `IntradayIntensity` now reports the raw per-bar Bostian intensity
|
||||
`volume * (2*close − high − low) / (high − low)` (previously a cumulative line
|
||||
that equalled the A/D Line `Adl`; its normalized form is `Cmf`).
|
||||
- `AwesomeOscillatorHistogram` now reports the AO momentum
|
||||
`AO[t] − AO[t−lookback]`; its third parameter is the momentum `lookback`
|
||||
(default 1) instead of an SMA period (the old `AO − SMA(AO, n)` equalled
|
||||
`AcceleratorOscillator`).
|
||||
- `AdOscillator` is now the Williams **A/D Oscillator** (`WAD − SMA(WAD, 13)`),
|
||||
distinct from the cumulative Williams A/D line `Wad`. Its native (Python /
|
||||
Node.js / WASM) alias is renamed **`WilliamsAD` → `ADOSC`**.
|
||||
|
||||
## [0.9.1] - 2026-06-14
|
||||
|
||||
### Added
|
||||
@@ -1663,7 +1719,8 @@ public API changes.
|
||||
optional Binance live feed.
|
||||
- Bindings for Python, Node.js, and WebAssembly.
|
||||
|
||||
[Unreleased]: https://github.com/wickra-lib/wickra/compare/v0.9.1...HEAD
|
||||
[Unreleased]: https://github.com/wickra-lib/wickra/compare/v0.9.2...HEAD
|
||||
[0.9.2]: https://github.com/wickra-lib/wickra/compare/v0.9.1...v0.9.2
|
||||
[0.9.1]: https://github.com/wickra-lib/wickra/compare/v0.9.0...v0.9.1
|
||||
[0.9.0]: https://github.com/wickra-lib/wickra/compare/v0.8.9...v0.9.0
|
||||
[0.8.9]: https://github.com/wickra-lib/wickra/compare/v0.8.8...v0.8.9
|
||||
|
||||
Generated
+9
-9
@@ -1943,7 +1943,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra"
|
||||
version = "0.9.1"
|
||||
version = "0.9.2"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"criterion",
|
||||
@@ -1954,7 +1954,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra-bench"
|
||||
version = "0.9.1"
|
||||
version = "0.9.2"
|
||||
dependencies = [
|
||||
"criterion",
|
||||
"kand",
|
||||
@@ -1966,14 +1966,14 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra-c"
|
||||
version = "0.9.1"
|
||||
version = "0.9.2"
|
||||
dependencies = [
|
||||
"wickra-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "wickra-core"
|
||||
version = "0.9.1"
|
||||
version = "0.9.2"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"proptest",
|
||||
@@ -1983,7 +1983,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra-data"
|
||||
version = "0.9.1"
|
||||
version = "0.9.2"
|
||||
dependencies = [
|
||||
"approx",
|
||||
"csv",
|
||||
@@ -2000,7 +2000,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra-examples"
|
||||
version = "0.9.1"
|
||||
version = "0.9.2"
|
||||
dependencies = [
|
||||
"serde_json",
|
||||
"tokio",
|
||||
@@ -2010,7 +2010,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra-node"
|
||||
version = "0.9.1"
|
||||
version = "0.9.2"
|
||||
dependencies = [
|
||||
"napi",
|
||||
"napi-build",
|
||||
@@ -2020,7 +2020,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra-python"
|
||||
version = "0.9.1"
|
||||
version = "0.9.2"
|
||||
dependencies = [
|
||||
"numpy",
|
||||
"pyo3",
|
||||
@@ -2029,7 +2029,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "wickra-wasm"
|
||||
version = "0.9.1"
|
||||
version = "0.9.2"
|
||||
dependencies = [
|
||||
"console_error_panic_hook",
|
||||
"js-sys",
|
||||
|
||||
+2
-2
@@ -14,7 +14,7 @@ members = [
|
||||
exclude = ["fuzz"]
|
||||
|
||||
[workspace.package]
|
||||
version = "0.9.1"
|
||||
version = "0.9.2"
|
||||
authors = ["kingchenc <support@wickra.org>"]
|
||||
edition = "2021"
|
||||
rust-version = "1.86"
|
||||
@@ -26,7 +26,7 @@ keywords = ["finance", "trading", "indicators", "technical-analysis", "ta"]
|
||||
categories = ["finance", "mathematics", "science"]
|
||||
|
||||
[workspace.dependencies]
|
||||
wickra-core = { path = "crates/wickra-core", version = "0.9.1" }
|
||||
wickra-core = { path = "crates/wickra-core", version = "0.9.2" }
|
||||
|
||||
thiserror = "2"
|
||||
rayon = "1.10"
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
[](https://www.bestpractices.dev/projects/13094)
|
||||
[](https://github.com/wickra-lib/wickra/attestations)
|
||||
[](https://docs.wickra.org)
|
||||
[](https://docs.wickra.org/FAQ#do-all-the-language-bindings-compute-the-same-values)
|
||||
|
||||
**Streaming-first technical indicators. Install with `pip install wickra` — no system dependencies.**
|
||||
|
||||
@@ -53,6 +54,7 @@ Full documentation lives at **[docs.wickra.org](https://docs.wickra.org)**:
|
||||
[Node](https://docs.wickra.org/Quickstart-Node),
|
||||
[WASM](https://docs.wickra.org/Quickstart-WASM),
|
||||
[C](https://docs.wickra.org/Quickstart-C),
|
||||
[C++](https://docs.wickra.org/Quickstart-C),
|
||||
[C#](https://docs.wickra.org/Quickstart-CSharp),
|
||||
[Go](https://docs.wickra.org/Quickstart-Go),
|
||||
[Java](https://docs.wickra.org/Quickstart-Java),
|
||||
@@ -89,6 +91,11 @@ times to get there.
|
||||
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
|
||||
for all 514 indicators**.
|
||||
- **Identical across every language — proven, not promised.** All 514 indicators
|
||||
are replayed through **all 10 languages** (Rust · Python · Node.js · WASM · C ·
|
||||
C++ · C# · Go · Java · R) and checked **bit-for-bit against the Rust reference**
|
||||
via shared golden fixtures in CI. The math is verifiably the same everywhere —
|
||||
this very check caught and fixed two real cross-language marshalling bugs.
|
||||
- **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
|
||||
than recompute-on-every-tick libraries. On batch it wins several rows outright
|
||||
@@ -137,12 +144,38 @@ elsewhere for `None`-warmup, NaN-safety and bit-exact `batch == streaming`.
|
||||
Full tables (Rust + Python, streaming + batch) and how to reproduce them live in
|
||||
**[BENCHMARKS.md](BENCHMARKS.md)**.
|
||||
|
||||
### Pick your language with eyes open — per-binding throughput
|
||||
|
||||
Every binding calls the **same** Rust core, so this is **not** a speed claim — it
|
||||
is the raw cost of crossing each language's FFI boundary (`SMA(20)`, 200 000 bars,
|
||||
Ryzen 9 9950X, million updates/sec). **Batch is near-core everywhere; streaming is
|
||||
where the boundary shows** — so if you stream tick-by-tick, the table tells you
|
||||
which binding keeps up and which to avoid for hot loops.
|
||||
|
||||
| Language | streaming (Mupd/s) | batch (Mupd/s) |
|
||||
|-----------------|-------------------:|---------------:|
|
||||
| Rust (no FFI) | 391 | 500 |
|
||||
| C / C++ | 383 | 330 |
|
||||
| C# | 337 | 244 |
|
||||
| Python | 33 | 488 |
|
||||
| Java | 28 | 175 |
|
||||
| Go | 24 | 400 |
|
||||
| WASM | 19 | 167 |
|
||||
| Node.js | 17 | 10 |
|
||||
| R | 0.1 | 193 |
|
||||
|
||||
All ten share one verified implementation (see the verification badge above), so
|
||||
the *numbers* differ but the *values* are bit-for-bit identical. Methodology and
|
||||
the per-indicator breakdown are in [BENCHMARKS.md](BENCHMARKS.md#3-per-binding-throughput--the-cost-of-the-boundary).
|
||||
|
||||
## Indicators
|
||||
|
||||
514 streaming-first indicators across twenty-four families. Every one passes the
|
||||
`batch == streaming` equivalence test, reference-value tests, and reset
|
||||
semantics tests. Each has a per-indicator deep dive (formula, parameters,
|
||||
warmup) at [docs.wickra.org](https://docs.wickra.org/Indicators-Overview).
|
||||
semantics tests — and is replayed through **all 10 languages** and checked
|
||||
bit-for-bit against the Rust reference (golden fixtures, in CI). Each has a
|
||||
per-indicator deep dive (formula, parameters, warmup) at
|
||||
[docs.wickra.org](https://docs.wickra.org/Indicators-Overview).
|
||||
|
||||
| Family | Indicators |
|
||||
|--------|-----------|
|
||||
@@ -153,7 +186,7 @@ warmup) at [docs.wickra.org](https://docs.wickra.org/Indicators-Overview).
|
||||
| Volatility & Bands | ATR, Bollinger Bands, Keltner Channels, Donchian Channels, NATR, StdDev, Ulcer Index, Historical Volatility, Bollinger Bandwidth, %B, True Range, Chaikin Volatility, RVI (Relative Volatility Index), Parkinson Volatility, Garman-Klass Volatility, Rogers-Satchell Volatility, Yang-Zhang Volatility, Volatility Cone |
|
||||
| Bands & Channels | MA Envelope, Acceleration Bands, STARC Bands, ATR Bands, Hurst Channel, LinReg Channel, Standard Error Bands, Double Bollinger Bands, TTM Squeeze, Fractal Chaos Bands, VWAP StdDev Bands, Quartile Bands, Bomar Bands, Median Channel, Projection Bands, Projection Oscillator |
|
||||
| Trailing Stops | Parabolic SAR, Parabolic SAR Extended (SAREXT), SuperTrend, Chandelier Exit, Chande Kroll Stop, ATR Trailing Stop, HiLo Activator, Volty Stop, Yo-Yo Exit, Donchian Channel Stop, Percentage Trailing Stop, Step Trailing Stop, Renko Trailing Stop, Kase DevStop, Elder SafeZone, ATR Ratchet, NRTR, Time-Based Stop, Modified MA Stop |
|
||||
| Volume | OBV, VWAP (cumulative + rolling), ADL, Volume-Price Trend, Chaikin Money Flow, Chaikin Oscillator, Force Index, Ease of Movement, Klinger Volume Oscillator, Volume Oscillator, NVI, PVI, Williams A/D, Anchored VWAP, Demand Index, TSV, VZO, Market Facilitation Index, Volume RSI, Williams Accumulation/Distribution, Twiggs Money Flow, Trade Volume Index, Intraday Intensity Index, Better Volume, Volume-Weighted MACD |
|
||||
| Volume | OBV, VWAP (cumulative + rolling), ADL, Volume-Price Trend, Chaikin Money Flow, Chaikin Oscillator, Force Index, Ease of Movement, Klinger Volume Oscillator, Volume Oscillator, NVI, PVI, Williams A/D Oscillator, Anchored VWAP, Demand Index, TSV, VZO, Market Facilitation Index, Volume RSI, Williams Accumulation/Distribution, Twiggs Money Flow, Trade Volume Index, Intraday Intensity, Better Volume, Volume-Weighted MACD |
|
||||
| Price Statistics | Typical Price, Median Price, Weighted Close, Linear Regression, Linear Regression Slope, Z-Score, Linear Regression Angle, Variance, Coefficient of Variation, Skewness, Kurtosis, Standard Error, Detrended StdDev, R², Median Absolute Deviation, Autocorrelation, Hurst Exponent, Pearson Correlation, Beta, Pairwise Beta, Pair Spread Z-Score, Lead-Lag Cross-Correlation, Cointegration, Relative Strength A-vs-B, Spearman Correlation, Mid Price, Mid Point, Average Price, Linear Regression Intercept, Time Series Forecast, Rolling Correlation, Rolling Covariance, OU Half-Life, Spread Hurst, Distance SSD, Beta-Neutral Spread, Variance Ratio, Granger Causality, Kalman Hedge Ratio, Spread Bollinger Bands, Spread AR(1) Coefficient, Jarque-Bera, Rolling Min-Max Scaler, Shannon Entropy, Sample Entropy, Kendall Tau |
|
||||
| Ehlers / Cycle (DSP) | MAMA, FAMA, Fisher Transform, Inverse Fisher Transform, SuperSmoother, Hilbert Dominant Cycle, Hilbert Phasor, Hilbert DC Phase, Hilbert Trend Mode, Sine Wave, Decycler, Decycler Oscillator, Roofing Filter, Center of Gravity, Cybernetic Cycle, Adaptive Cycle, Empirical Mode Decomposition, Ehlers Stochastic, Instantaneous Trendline, Highpass Filter, Reflex, Trendflex, Correlation Trend Indicator, Adaptive RSI, Universal Oscillator, Adaptive CCI, Bandpass Filter, Even Better Sinewave, Autocorrelation Periodogram |
|
||||
| Pivots & S/R | Classic Pivots, Fibonacci Pivots, Camarilla, Woodie Pivots, DeMark Pivots, Williams Fractals, ZigZag, Central Pivot Range, Murrey Math Lines, Andrews Pitchfork, Volume-Weighted Support/Resistance, Pivot Reversal |
|
||||
@@ -270,8 +303,8 @@ while let Some(event) = stream.next_event().await? {
|
||||
}
|
||||
```
|
||||
|
||||
A Python live-trading example using the public `websockets` package lives at
|
||||
`examples/python/live_trading.py`.
|
||||
A Python live Binance feed example using the public `websockets` package lives at
|
||||
`examples/python/live_binance.py`.
|
||||
|
||||
## Project layout
|
||||
|
||||
@@ -294,8 +327,8 @@ wickra/
|
||||
├── examples/ examples/README.md indexes every language
|
||||
│ ├── data/ real BTCUSDT OHLCV datasets, one per timeframe
|
||||
│ ├── rust/ Rust workspace member (`wickra-examples`)
|
||||
│ ├── python/ backtest, live trading, parallel assets, multi-tf
|
||||
│ ├── node/ streaming, backtest, live trading (load `wickra`)
|
||||
│ ├── python/ backtest, live Binance feed, parallel assets, multi-tf
|
||||
│ ├── node/ streaming, backtest, live Binance feed (load `wickra`)
|
||||
│ ├── wasm/ browser demo for `wickra-wasm`
|
||||
│ ├── c/ C smoke + streaming, C++ RAII wrapper
|
||||
│ ├── csharp/ streaming, backtest, strategies (load `Wickra`)
|
||||
@@ -381,11 +414,15 @@ Every layer is covered; run the suites with the commands in
|
||||
- `bindings/java`: JUnit cases covering one indicator per FFI archetype
|
||||
(scalar/batch, multi-output, bars, profile, array input) plus batch equivalence.
|
||||
|
||||
The four C-ABI bindings (C#, Go, Java, R) additionally replay a shared,
|
||||
language-neutral golden fixture (`testdata/golden/*.csv`, generated by
|
||||
`cargo run -p wickra-examples --bin gen_golden`) and assert exact parity with the
|
||||
Rust reference outputs across every archetype (SMA, EMA, RSI, ATR, MACD, ADX,
|
||||
Beta), catching FFI wiring bugs the math-only core tests cannot see.
|
||||
On top of those per-binding tests, **all 10 languages** (Rust, Python, Node.js,
|
||||
WASM, C, C++, C#, Go, Java, R) replay a shared, language-neutral golden fixture
|
||||
(`testdata/golden/*.csv`, generated by
|
||||
`cargo run -p wickra-examples --bin gen_golden`) and assert **bit-for-bit parity
|
||||
with the Rust reference for every one of the 514 indicators** across every
|
||||
archetype (scalar, multi-output, pairwise, derivatives-tick, cross-section,
|
||||
order-book, trade, profile, alt-chart bars, footprint). This catches FFI wiring
|
||||
bugs the math-only core tests cannot see — it has already found and fixed real
|
||||
cross-language marshalling bugs in the Java and R bindings.
|
||||
|
||||
## Contributing
|
||||
|
||||
|
||||
+3
-3
@@ -2,13 +2,13 @@
|
||||
|
||||
## Supported versions
|
||||
|
||||
Wickra is pre-1.0. Security fixes are applied to the latest released `0.9.1`
|
||||
Wickra is pre-1.0. Security fixes are applied to the latest released `0.9.2`
|
||||
version only; please upgrade to the newest release before reporting an issue.
|
||||
|
||||
| Version | Supported |
|
||||
| --- | --- |
|
||||
| 0.9.1 (latest) | :white_check_mark: |
|
||||
| < 0.9.1 | :x: |
|
||||
| 0.9.2 (latest) | :white_check_mark: |
|
||||
| < 0.9.2 | :x: |
|
||||
|
||||
## Reporting a vulnerability
|
||||
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -159,4 +159,58 @@ public class GoldenTests
|
||||
AssertClose(got.Value.Adx, Cell(e[2]), i, "adx.adx");
|
||||
}
|
||||
}
|
||||
|
||||
// --- the four de-duplicated indicators ------------------------------------
|
||||
|
||||
[Fact]
|
||||
public void Candle_AdOscillator_MatchesGolden()
|
||||
{
|
||||
var input = Input();
|
||||
var expected = ReadCsv("ad_oscillator");
|
||||
using var ad = new AdOscillator();
|
||||
for (var i = 0; i < input.Length; i++)
|
||||
{
|
||||
var (o, h, l, c, v) = (input[i][0], input[i][1], input[i][2], input[i][3], input[i][4]);
|
||||
AssertClose(ad.Update(o, h, l, c, v, i), Cell(expected[i][0]), i, "ad_oscillator");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Candle_IntradayIntensity_MatchesGolden()
|
||||
{
|
||||
var input = Input();
|
||||
var expected = ReadCsv("intraday_intensity");
|
||||
using var ii = new IntradayIntensity();
|
||||
for (var i = 0; i < input.Length; i++)
|
||||
{
|
||||
var (o, h, l, c, v) = (input[i][0], input[i][1], input[i][2], input[i][3], input[i][4]);
|
||||
AssertClose(ii.Update(o, h, l, c, v, i), Cell(expected[i][0]), i, "intraday_intensity");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Candle_AwesomeOscillatorHistogram_MatchesGolden()
|
||||
{
|
||||
var input = Input();
|
||||
var expected = ReadCsv("awesome_oscillator_histogram");
|
||||
using var aoh = new AwesomeOscillatorHistogram(5, 34, 1);
|
||||
for (var i = 0; i < input.Length; i++)
|
||||
{
|
||||
var (o, h, l, c, v) = (input[i][0], input[i][1], input[i][2], input[i][3], input[i][4]);
|
||||
AssertClose(aoh.Update(o, h, l, c, v, i), Cell(expected[i][0]), i, "awesome_oscillator_histogram");
|
||||
}
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void Scalar_AverageDrawdown_MatchesGolden()
|
||||
{
|
||||
var input = Input();
|
||||
var expected = ReadCsv("average_drawdown");
|
||||
using var avg = new AverageDrawdown(20);
|
||||
for (var i = 0; i < input.Length; i++)
|
||||
{
|
||||
// generator fed the close column as the equity-curve sample.
|
||||
AssertClose(avg.Update(input[i][3]), Cell(expected[i][0]), i, "average_drawdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// <auto-generated />
|
||||
// Generated from bindings/c/include/wickra.h. Do not edit by hand.
|
||||
#nullable enable
|
||||
using System.Runtime.InteropServices;
|
||||
|
||||
namespace Wickra;
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
|
||||
<!-- NuGet package metadata -->
|
||||
<PackageId>Wickra</PackageId>
|
||||
<Version>0.9.1</Version>
|
||||
<Version>0.9.2</Version>
|
||||
<Authors>kingchenc</Authors>
|
||||
<Description>High-performance streaming technical-analysis indicators (514 indicators) for .NET, backed by the native Rust core via the Wickra C ABI.</Description>
|
||||
<PackageLicenseExpression>MIT OR Apache-2.0</PackageLicenseExpression>
|
||||
|
||||
@@ -23,6 +23,13 @@ internal static class WickraNative
|
||||
// Any exported symbol works as a fingerprint; sma_new exists in every build.
|
||||
private const string SentinelSymbol = "wickra_sma_new";
|
||||
|
||||
// CA2255 warns against [ModuleInitializer] in libraries, but registering the
|
||||
// native-library resolver before any P/Invoke runs is exactly the advanced
|
||||
// scenario the attribute exists for: a static constructor would run too late
|
||||
// (only on first access to this type), letting the default resolver fail first.
|
||||
[System.Diagnostics.CodeAnalysis.SuppressMessage(
|
||||
"Usage", "CA2255:The 'ModuleInitializer' attribute should not be used in libraries",
|
||||
Justification = "The DllImport resolver must be registered before the first P/Invoke; a static constructor would run too late.")]
|
||||
[ModuleInitializer]
|
||||
internal static void Register()
|
||||
{
|
||||
|
||||
@@ -0,0 +1,300 @@
|
||||
"""Generate Wickra.Tests/GoldenAllTests.g.cs: a value-parity test that replays
|
||||
the shared golden input through every one of the 514 C# indicators and checks
|
||||
output bit-for-bit against the Rust reference fixtures g_<Canonical>.csv.
|
||||
|
||||
Run from repo root: python bindings/csharp/gen_golden_test.py
|
||||
"""
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
G = os.path.join(ROOT, "testdata", "golden")
|
||||
GEN = open(os.path.join(ROOT, "bindings", "csharp", "Wickra", "Generated", "Indicators.g.cs"), encoding="utf-8").read()
|
||||
|
||||
# C# constructor parameter types per class.
|
||||
ctor_types = {}
|
||||
cur = None
|
||||
for line in GEN.splitlines():
|
||||
m = re.match(r"public sealed class (\w+)", line)
|
||||
if m:
|
||||
cur = m.group(1)
|
||||
continue
|
||||
if cur:
|
||||
cm = re.match(r"\s*public %s\(([^)]*)\)" % re.escape(cur), line)
|
||||
if cm:
|
||||
ps = cm.group(1).strip()
|
||||
types = [p.strip().rsplit(" ", 1)[0].strip() for p in ps.split(",")] if ps else []
|
||||
ctor_types[cur] = types
|
||||
cur = None
|
||||
|
||||
# Unified archetype + params, keyed by canonical (== C# class name).
|
||||
spec = {}
|
||||
for e in json.load(open(os.path.join(G, "scalar_manifest.json"))):
|
||||
arch = {"f64": "scalar_f64", "Candle": "scalar_candle", "(f64, f64)": "pairwise"}[e["input"]]
|
||||
spec[e["canonical"]] = {"arch": arch, "params": e["params"]}
|
||||
for e in json.load(open(os.path.join(G, "multi_manifest.json"))):
|
||||
arch = {"f64": "multi_f64", "Candle": "multi_candle", "(f64, f64)": "multi_pairwise"}[e["input"]]
|
||||
spec[e["canonical"]] = {"arch": arch, "params": e["params"], "n": e["n"]}
|
||||
ex = json.load(open(os.path.join(G, "exotic_manifest.json")))
|
||||
for e in ex["deriv"]:
|
||||
spec[e["canonical"]] = {"arch": "deriv_multi" if "n" in e else "deriv", "params": e["params"], "n": e.get("n")}
|
||||
for e in ex["cross"]:
|
||||
spec[e["canonical"]] = {"arch": "cross", "params": e["params"]}
|
||||
for e in ex["trade"]:
|
||||
spec[e["canonical"]] = {"arch": "trade", "params": e["params"]}
|
||||
for e in ex["trademid"]:
|
||||
spec[e["canonical"]] = {"arch": "trademid", "params": e["params"]}
|
||||
for e in ex["ob"]:
|
||||
spec[e["canonical"]] = {"arch": "ob", "params": e["params"]}
|
||||
for e in json.load(open(os.path.join(G, "profile_manifest.json"))):
|
||||
spec[e["canonical"]] = {"arch": "profile_" + e["kind"], "params": e["params"], "width": e["width"]}
|
||||
for e in json.load(open(os.path.join(G, "bars_manifest.json"))):
|
||||
arch = "footprint" if e["canonical"] == "Footprint" else "bars_" + e["feed"]
|
||||
spec[e["canonical"]] = {"arch": arch, "params": e["params"]}
|
||||
|
||||
canons = sorted(os.path.basename(f)[2:-4] for f in glob.glob(os.path.join(G, "g_*.csv")))
|
||||
|
||||
|
||||
def lit(value, cstype):
|
||||
if cstype == "int":
|
||||
return str(int(round(value)))
|
||||
if cstype == "uint":
|
||||
return f"{int(round(value))}u"
|
||||
if cstype == "byte":
|
||||
return f"(byte){int(round(value))}"
|
||||
f = float(value)
|
||||
s = repr(f)
|
||||
return s if ("." in s or "e" in s or "E" in s) else s + ".0"
|
||||
|
||||
|
||||
def ctor_call(canon):
|
||||
types = ctor_types.get(canon, [])
|
||||
vals = spec[canon]["params"]
|
||||
args = ", ".join(lit(v, t) for v, t in zip(vals, types))
|
||||
return f"new Wickra.{canon}({args})"
|
||||
|
||||
|
||||
def block(canon):
|
||||
s = spec[canon]
|
||||
a = s["arch"]
|
||||
L = [f" [Fact]", f" public void Golden_{canon}()", " {"]
|
||||
L.append(f" using var ind = {ctor_call(canon)};")
|
||||
L.append(" var got = new List<double[]>();")
|
||||
L.append(" for (var i = 0; i < Rows.Length; i++)")
|
||||
L.append(" {")
|
||||
L.append(" var r = Rows[i];")
|
||||
if a == "scalar_f64":
|
||||
L.append(" got.Add(new[] { ind.Update(r[3]) });")
|
||||
elif a == "pairwise":
|
||||
L.append(" got.Add(new[] { ind.Update(r[3], r[0]) });")
|
||||
elif a == "scalar_candle":
|
||||
L.append(" got.Add(new[] { ind.Update(r[0], r[1], r[2], r[3], r[4], i) });")
|
||||
elif a == "trade":
|
||||
L.append(" got.Add(new[] { ind.Update(r[3], r[4], r[3] >= r[0], i) });")
|
||||
elif a == "trademid":
|
||||
L.append(" got.Add(new[] { ind.Update(r[3], r[4], r[3] >= r[0], i, (r[1] + r[2]) / 2) });")
|
||||
elif a == "ob":
|
||||
L.append(" var (bp, bs, ap, asz) = ObLists(r);")
|
||||
L.append(" got.Add(new[] { ind.Update(bp, bs, ap, asz) });")
|
||||
elif a == "deriv":
|
||||
L.append(" var d = DerivFields(r);")
|
||||
L.append(" got.Add(new[] { ind.Update(d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], i) });")
|
||||
elif a == "deriv_multi":
|
||||
L.append(" var d = DerivFields(r);")
|
||||
L.append(" got.Add(FlattenNullable(ind.Update(d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], i), %d));" % s["n"])
|
||||
elif a == "cross":
|
||||
L.append(" var (ch, vo, nh, nl, am, ob) = CrossLists(r);")
|
||||
L.append(" got.Add(new[] { ind.Update(ch, vo, nh, nl, am, ob, i) });")
|
||||
elif a == "multi_f64":
|
||||
L.append(" got.Add(FlattenNullable(ind.Update(r[3]), %d));" % s["n"])
|
||||
elif a == "multi_pairwise":
|
||||
L.append(" got.Add(FlattenNullable(ind.Update(r[3], r[0]), %d));" % s["n"])
|
||||
elif a == "multi_candle":
|
||||
L.append(" got.Add(FlattenNullable(ind.Update(r[0], r[1], r[2], r[3], r[4], i), %d));" % s["n"])
|
||||
elif a == "profile_bins":
|
||||
L.append(" var bins = ind.Update(r[0], r[1], r[2], r[3], r[4], i);")
|
||||
L.append(" got.Add(bins ?? NanRow(%d));" % s["width"])
|
||||
elif a == "profile_pricebins":
|
||||
L.append(" got.Add(FlattenNullable(ind.Update(r[0], r[1], r[2], r[3], r[4], i), %d));" % s["width"])
|
||||
elif a == "bars_close":
|
||||
L.append(" got.Add(FlattenBars(ind.Update(r[3], r[3], r[3], r[3], 1.0, 0)));")
|
||||
elif a == "bars_candle4":
|
||||
L.append(" got.Add(FlattenBars(ind.Update(r[0], r[1], r[2], r[3], 1.0, 0)));")
|
||||
elif a == "bars_candle5":
|
||||
L.append(" got.Add(FlattenBars(ind.Update(r[0], r[1], r[2], r[3], r[4], 0)));")
|
||||
elif a == "footprint":
|
||||
L.append(" got.Add(FlattenBars(ind.Update(r[3], r[4], r[3] >= r[0], i)));")
|
||||
else:
|
||||
raise SystemExit("arch " + a)
|
||||
L.append(" }")
|
||||
L.append(f' Compare("{canon}", got);')
|
||||
L.append(" }")
|
||||
return "\n".join(L)
|
||||
|
||||
|
||||
HEADER = '''// <auto-generated>
|
||||
// Generated by gen_golden_test.py. DO NOT EDIT.
|
||||
//
|
||||
// Value-parity for every one of the 514 C# indicators: the shared golden input
|
||||
// is replayed through each one and checked bit-for-bit against the Rust
|
||||
// reference fixtures testdata/golden/g_<Canonical>.csv. Multi-output, profile
|
||||
// and bar shapes are flattened by reflection so one comparator covers all
|
||||
// archetypes. Regenerate with: python bindings/csharp/gen_golden_test.py
|
||||
// </auto-generated>
|
||||
#nullable enable
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using Xunit;
|
||||
|
||||
namespace Wickra.Tests;
|
||||
|
||||
public class GoldenAllTests
|
||||
{
|
||||
private const double Tol = 1e-6;
|
||||
|
||||
private static readonly double[][] Rows = LoadInput();
|
||||
|
||||
private static string GoldenDir([System.Runtime.CompilerServices.CallerFilePath] string file = "") =>
|
||||
Path.GetFullPath(Path.Combine(Path.GetDirectoryName(file)!, "..", "..", "..", "testdata", "golden"));
|
||||
|
||||
private static double Cell(string s) =>
|
||||
s == "nan" ? double.NaN
|
||||
: s == "inf" ? double.PositiveInfinity
|
||||
: s == "-inf" ? double.NegativeInfinity
|
||||
: double.Parse(s, CultureInfo.InvariantCulture);
|
||||
|
||||
private static double[][] LoadInput()
|
||||
{
|
||||
var lines = File.ReadAllLines(Path.Combine(GoldenDir(), "input.csv"));
|
||||
return lines.Skip(1).Where(l => l.Length > 0)
|
||||
.Select(l => l.Split(',').Select(x => double.Parse(x, CultureInfo.InvariantCulture)).ToArray())
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
// Keep blank lines (a candle on which no bar closed) so rows stay aligned.
|
||||
private static double[]?[] ReadFixture(string name)
|
||||
{
|
||||
var lines = File.ReadAllLines(Path.Combine(GoldenDir(), "g_" + name + ".csv"));
|
||||
return lines.Skip(1).Select(l => l.Length == 0 ? Array.Empty<double>() : l.Split(',').Select(Cell).ToArray()).ToArray();
|
||||
}
|
||||
|
||||
private static double[] NanRow(int n)
|
||||
{
|
||||
var r = new double[n];
|
||||
for (var i = 0; i < n; i++) r[i] = double.NaN;
|
||||
return r;
|
||||
}
|
||||
|
||||
private static double[] FlattenStruct(object o)
|
||||
{
|
||||
var props = o.GetType()
|
||||
.GetProperties(BindingFlags.Public | BindingFlags.Instance)
|
||||
.OrderBy(p => p.MetadataToken);
|
||||
var list = new List<double>();
|
||||
foreach (var p in props)
|
||||
{
|
||||
var v = p.GetValue(o);
|
||||
switch (v)
|
||||
{
|
||||
case double d: list.Add(d); break;
|
||||
case float f: list.Add(f); break;
|
||||
case long l: list.Add(l); break;
|
||||
case int n: list.Add(n); break;
|
||||
case double[] arr: list.AddRange(arr); break;
|
||||
}
|
||||
}
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
private static double[] FlattenNullable<T>(T? value, int width) where T : struct =>
|
||||
value.HasValue ? FlattenStruct(value.Value) : NanRow(width);
|
||||
|
||||
private static double[] FlattenBars<T>(T[] bars)
|
||||
{
|
||||
var list = new List<double>();
|
||||
foreach (var bar in bars) list.AddRange(FlattenStruct(bar!));
|
||||
return list.ToArray();
|
||||
}
|
||||
|
||||
private static double[] DerivFields(double[] r)
|
||||
{
|
||||
double o = r[0], h = r[1], l = r[2], c = r[3], v = r[4];
|
||||
return new[]
|
||||
{
|
||||
(c - o) / c * 0.01, c, c - 0.5, c + 1.0, v * 10.0, v * 0.6, v * 0.4,
|
||||
v * 0.55, v * 0.45, h - c, c - l,
|
||||
};
|
||||
}
|
||||
|
||||
private static (double[], double[], bool[], bool[], bool[], bool[]) CrossLists(double[] r)
|
||||
{
|
||||
double o = r[0], c = r[3], v = r[4];
|
||||
var change = new double[5];
|
||||
var volume = new double[5];
|
||||
var nh = new bool[5];
|
||||
var nl = new bool[5];
|
||||
var am = new bool[5];
|
||||
var ob = new bool[5];
|
||||
for (var j = 0; j < 5; j++)
|
||||
{
|
||||
change[j] = (c - o) + j;
|
||||
volume[j] = v + j * 10.0;
|
||||
nh[j] = j % 2 == 0;
|
||||
nl[j] = j % 3 == 0;
|
||||
am[j] = j % 2 == 0;
|
||||
ob[j] = j % 3 == 0;
|
||||
}
|
||||
return (change, volume, nh, nl, am, ob);
|
||||
}
|
||||
|
||||
private static (double[], double[], double[], double[]) ObLists(double[] r)
|
||||
{
|
||||
double c = r[3], v = r[4];
|
||||
var bp = new double[5];
|
||||
var bs = new double[5];
|
||||
var ap = new double[5];
|
||||
var asz = new double[5];
|
||||
for (var k = 0; k < 5; k++)
|
||||
{
|
||||
var kf = k + 1;
|
||||
bp[k] = c - 0.1 * kf;
|
||||
bs[k] = v / kf;
|
||||
ap[k] = c + 0.1 * kf;
|
||||
asz[k] = v * 0.9 / kf;
|
||||
}
|
||||
return (bp, bs, ap, asz);
|
||||
}
|
||||
|
||||
private static void Compare(string name, List<double[]> got)
|
||||
{
|
||||
var exp = ReadFixture(name);
|
||||
Assert.True(exp.Length == got.Count, $"{name}: {exp.Length} fixture rows vs {got.Count} computed");
|
||||
for (var i = 0; i < exp.Length; i++)
|
||||
{
|
||||
var want = exp[i]!;
|
||||
var g = got[i];
|
||||
Assert.True(want.Length == g.Length, $"{name} row {i}: arity {g.Length} vs {want.Length}");
|
||||
for (var k = 0; k < want.Length; k++)
|
||||
{
|
||||
var w = want[k];
|
||||
if (double.IsNaN(w)) { Assert.True(double.IsNaN(g[k]), $"{name} row {i} col {k}: want NaN got {g[k]}"); continue; }
|
||||
if (double.IsInfinity(w)) { Assert.True(double.IsInfinity(g[k]) && Math.Sign(g[k]) == Math.Sign(w), $"{name} row {i} col {k}: want {w} got {g[k]}"); continue; }
|
||||
var tol = Tol * Math.Max(1.0, Math.Abs(w));
|
||||
Assert.True(Math.Abs(g[k] - w) <= tol, $"{name} row {i} col {k}: got {g[k]} want {w}");
|
||||
}
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
out = [HEADER]
|
||||
for canon in canons:
|
||||
out.append(block(canon))
|
||||
out.append("}")
|
||||
open(os.path.join(ROOT, "bindings", "csharp", "Wickra.Tests", "GoldenAllTests.g.cs"), "w", encoding="utf-8").write("\n".join(out) + "\n")
|
||||
print("generated GoldenAllTests.g.cs with", len(canons), "indicators")
|
||||
@@ -0,0 +1,337 @@
|
||||
"""Generate golden_all_test.go: a value-parity test that replays the shared
|
||||
golden input through every one of the 514 Go indicators and checks output
|
||||
bit-for-bit against the Rust-generated g_<Canonical>.csv fixtures.
|
||||
|
||||
Run from repo root: python bindings/go/gen_golden_test.py
|
||||
"""
|
||||
import glob
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
G = os.path.join(ROOT, "testdata", "golden")
|
||||
GEN = open(os.path.join(ROOT, "bindings", "go", "indicators_gen.go"), encoding="utf-8").read()
|
||||
|
||||
# Go constructor parameter types, keyed by canonical (== Go type name).
|
||||
ctor_types = {}
|
||||
for m in re.finditer(r"func New(\w+)\(([^)]*)\)\s*\(\*\w+, error\)", GEN):
|
||||
name, ps = m.group(1), m.group(2).strip()
|
||||
types = []
|
||||
if ps:
|
||||
for p in ps.split(","):
|
||||
p = p.strip()
|
||||
_, _, ty = p.partition(" ")
|
||||
types.append(ty.strip())
|
||||
ctor_types[name] = types
|
||||
|
||||
# Unified archetype + params, keyed by canonical.
|
||||
spec = {} # canon -> dict(arch, params, width?, n?)
|
||||
scal = json.load(open(os.path.join(G, "scalar_manifest.json")))
|
||||
for e in scal:
|
||||
inp = e["input"]
|
||||
arch = {"f64": "scalar_f64", "Candle": "scalar_candle", "(f64, f64)": "pairwise"}[inp]
|
||||
spec[e["canonical"]] = {"arch": arch, "params": e["params"]}
|
||||
for e in json.load(open(os.path.join(G, "multi_manifest.json"))):
|
||||
inp = e["input"]
|
||||
arch = {"f64": "multi_f64", "Candle": "multi_candle", "(f64, f64)": "multi_pairwise"}[inp]
|
||||
spec[e["canonical"]] = {"arch": arch, "params": e["params"], "n": e["n"]}
|
||||
ex = json.load(open(os.path.join(G, "exotic_manifest.json")))
|
||||
for e in ex["deriv"]:
|
||||
spec[e["canonical"]] = {"arch": "deriv_multi" if "n" in e else "deriv", "params": e["params"], "n": e.get("n")}
|
||||
for e in ex["cross"]:
|
||||
spec[e["canonical"]] = {"arch": "cross", "params": e["params"]}
|
||||
for e in ex["trade"]:
|
||||
spec[e["canonical"]] = {"arch": "trade", "params": e["params"]}
|
||||
for e in ex["trademid"]:
|
||||
spec[e["canonical"]] = {"arch": "trademid", "params": e["params"]}
|
||||
for e in ex["ob"]:
|
||||
spec[e["canonical"]] = {"arch": "ob", "params": e["params"]}
|
||||
for e in json.load(open(os.path.join(G, "profile_manifest.json"))):
|
||||
spec[e["canonical"]] = {"arch": "profile_" + e["kind"], "params": e["params"], "width": e["width"]}
|
||||
for e in json.load(open(os.path.join(G, "bars_manifest.json"))):
|
||||
arch = "footprint" if e["canonical"] == "Footprint" else "bars_" + e["feed"]
|
||||
spec[e["canonical"]] = {"arch": arch, "params": e["params"]}
|
||||
|
||||
canons = sorted(os.path.basename(f)[2:-4] for f in glob.glob(os.path.join(G, "g_*.csv")))
|
||||
|
||||
|
||||
def go_param(value, gotype):
|
||||
intlike = gotype in ("int", "int32", "int64", "uint", "uintptr", "usize")
|
||||
if intlike:
|
||||
return str(int(round(value)))
|
||||
# float64
|
||||
return repr(float(value)) if "." in repr(float(value)) or "e" in repr(float(value)) else f"{float(value)}"
|
||||
|
||||
|
||||
def ctor_call(canon):
|
||||
types = ctor_types.get(canon, [])
|
||||
vals = spec[canon]["params"]
|
||||
args = ", ".join(go_param(v, t) for v, t in zip(vals, types))
|
||||
return f"New{canon}({args})"
|
||||
|
||||
|
||||
# Update-call expression + output handling per archetype.
|
||||
def block(canon):
|
||||
s = spec[canon]
|
||||
a = s["arch"]
|
||||
ctor = ctor_call(canon)
|
||||
lines = [f'\tt.Run("{canon}", func(t *testing.T) {{']
|
||||
lines.append(f"\t\tind, err := {ctor}")
|
||||
lines.append('\t\tif err != nil {')
|
||||
lines.append(f'\t\t\tt.Fatalf("new {canon}: %v", err)')
|
||||
lines.append("\t\t}")
|
||||
lines.append("\t\tgot := make([][]float64, len(rows))")
|
||||
lines.append("\t\tfor i, r := range rows {")
|
||||
if a == "scalar_f64":
|
||||
upd = "ind.Update(r[3])"
|
||||
lines.append(f"\t\t\tgot[i] = []float64{{{upd}}}")
|
||||
elif a == "pairwise":
|
||||
lines.append("\t\t\tgot[i] = []float64{ind.Update(r[3], r[0])}")
|
||||
elif a == "scalar_candle":
|
||||
lines.append("\t\t\tgot[i] = []float64{ind.Update(r[0], r[1], r[2], r[3], r[4], int64(i))}")
|
||||
elif a == "trade":
|
||||
lines.append("\t\t\tgot[i] = []float64{ind.Update(r[3], r[4], r[3] >= r[0], int64(i))}")
|
||||
elif a == "trademid":
|
||||
lines.append("\t\t\tgot[i] = []float64{ind.Update(r[3], r[4], r[3] >= r[0], int64(i), (r[1]+r[2])/2)}")
|
||||
elif a == "ob":
|
||||
lines.append("\t\t\tbp, bs, ap, as_ := obLists(r)")
|
||||
lines.append("\t\t\tgot[i] = []float64{ind.Update(bp, bs, ap, as_)}")
|
||||
elif a == "deriv":
|
||||
lines.append("\t\t\td := derivFields(r)")
|
||||
lines.append("\t\t\tgot[i] = []float64{ind.Update(d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], int64(i))}")
|
||||
elif a == "deriv_multi":
|
||||
lines.append("\t\t\td := derivFields(r)")
|
||||
lines.append("\t\t\tout, ok := ind.Update(d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], int64(i))")
|
||||
lines.append(f"\t\t\tgot[i] = reflectRow(out, ok, {s['n']})")
|
||||
elif a == "cross":
|
||||
lines.append("\t\t\tch, vo, nh, nl, am, ob_ := crossLists(r)")
|
||||
lines.append("\t\t\tgot[i] = []float64{ind.Update(ch, vo, nh, nl, am, ob_, int64(i))}")
|
||||
elif a in ("multi_f64",):
|
||||
lines.append("\t\t\tout, ok := ind.Update(r[3])")
|
||||
lines.append(f"\t\t\tgot[i] = reflectRow(out, ok, {s['n']})")
|
||||
elif a == "multi_pairwise":
|
||||
lines.append("\t\t\tout, ok := ind.Update(r[3], r[0])")
|
||||
lines.append(f"\t\t\tgot[i] = reflectRow(out, ok, {s['n']})")
|
||||
elif a == "multi_candle":
|
||||
lines.append("\t\t\tout, ok := ind.Update(r[0], r[1], r[2], r[3], r[4], int64(i))")
|
||||
lines.append(f"\t\t\tgot[i] = reflectRow(out, ok, {s['n']})")
|
||||
elif a == "profile_bins":
|
||||
lines.append("\t\t\tbins, ok := ind.Update(r[0], r[1], r[2], r[3], r[4], int64(i))")
|
||||
lines.append(f"\t\t\tif ok {{ got[i] = bins }} else {{ got[i] = nanRow({s['width']}) }}")
|
||||
elif a == "profile_pricebins":
|
||||
lines.append("\t\t\tout, ok := ind.Update(r[0], r[1], r[2], r[3], r[4], int64(i))")
|
||||
lines.append(f"\t\t\tgot[i] = reflectRow(out, ok, {s['width']})")
|
||||
elif a == "bars_close":
|
||||
lines.append("\t\t\tgot[i] = flattenBars(ind.Update(r[3], r[3], r[3], r[3], 1.0, 0))")
|
||||
elif a == "bars_candle4":
|
||||
lines.append("\t\t\tgot[i] = flattenBars(ind.Update(r[0], r[1], r[2], r[3], 1.0, 0))")
|
||||
elif a == "bars_candle5":
|
||||
lines.append("\t\t\tgot[i] = flattenBars(ind.Update(r[0], r[1], r[2], r[3], r[4], 0))")
|
||||
elif a == "footprint":
|
||||
lines.append("\t\t\tgot[i] = flattenBars(ind.Update(r[3], r[4], r[3] >= r[0], int64(i)))")
|
||||
else:
|
||||
raise SystemExit("unknown arch " + a)
|
||||
lines.append("\t\t}")
|
||||
lines.append(f'\t\tcompareGolden(t, "{canon}", got)')
|
||||
lines.append("\t})")
|
||||
return "\n".join(lines)
|
||||
|
||||
|
||||
HEADER = '''// Code generated by gen_golden_test.py. DO NOT EDIT.
|
||||
//
|
||||
// Value-parity for every one of the 514 Go indicators: the shared golden input
|
||||
// is replayed through each one and checked bit-for-bit against the Rust
|
||||
// reference fixtures testdata/golden/g_<Canonical>.csv. Multi-output, profile
|
||||
// and bar shapes are flattened by reflection so a single comparator covers all
|
||||
// archetypes. Regenerate with: python bindings/go/gen_golden_test.py
|
||||
|
||||
package wickra
|
||||
|
||||
import (
|
||||
\t"bufio"
|
||||
\t"math"
|
||||
\t"os"
|
||||
\t"reflect"
|
||||
\t"strings"
|
||||
\t"testing"
|
||||
)
|
||||
|
||||
// readGoldenRaw keeps blank lines (a candle on which no bar closed) so bar rows
|
||||
// stay aligned to the input; non-bar fixtures contain no blank lines.
|
||||
func readGoldenRaw(t *testing.T, name string) [][]string {
|
||||
\tt.Helper()
|
||||
\tf, err := os.Open("../../testdata/golden/" + name + ".csv")
|
||||
\tif err != nil {
|
||||
\t\tt.Fatalf("open %s: %v", name, err)
|
||||
\t}
|
||||
\tdefer f.Close()
|
||||
\tvar rows [][]string
|
||||
\tsc := bufio.NewScanner(f)
|
||||
\tsc.Buffer(make([]byte, 0, 1024*1024), 1024*1024)
|
||||
\tfirst := true
|
||||
\tfor sc.Scan() {
|
||||
\t\tline := sc.Text()
|
||||
\t\tif first {
|
||||
\t\t\tfirst = false
|
||||
\t\t\tcontinue
|
||||
\t\t}
|
||||
\t\tif line == "" {
|
||||
\t\t\trows = append(rows, []string{})
|
||||
\t\t\tcontinue
|
||||
\t\t}
|
||||
\t\trows = append(rows, strings.Split(line, ","))
|
||||
\t}
|
||||
\treturn rows
|
||||
}
|
||||
|
||||
func nanRow(n int) []float64 {
|
||||
\tr := make([]float64, n)
|
||||
\tfor i := range r {
|
||||
\t\tr[i] = math.NaN()
|
||||
\t}
|
||||
\treturn r
|
||||
}
|
||||
|
||||
func reflectRow(out any, ok bool, width int) []float64 {
|
||||
\tif !ok {
|
||||
\t\treturn nanRow(width)
|
||||
\t}
|
||||
\tv := reflect.ValueOf(out)
|
||||
\trow := make([]float64, 0, width)
|
||||
\tfor k := 0; k < v.NumField(); k++ {
|
||||
\t\trow = appendField(row, v.Field(k))
|
||||
\t}
|
||||
\treturn row
|
||||
}
|
||||
|
||||
func appendField(row []float64, f reflect.Value) []float64 {
|
||||
\tswitch f.Kind() {
|
||||
\tcase reflect.Float64, reflect.Float32:
|
||||
\t\treturn append(row, f.Float())
|
||||
\tcase reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
|
||||
\t\treturn append(row, float64(f.Int()))
|
||||
\tcase reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr:
|
||||
\t\treturn append(row, float64(f.Uint()))
|
||||
\tcase reflect.Slice:
|
||||
\t\tfor j := 0; j < f.Len(); j++ {
|
||||
\t\t\trow = appendField(row, f.Index(j))
|
||||
\t\t}
|
||||
\t\treturn row
|
||||
\tdefault:
|
||||
\t\treturn row
|
||||
\t}
|
||||
}
|
||||
|
||||
func flattenBars(bars any) []float64 {
|
||||
\tv := reflect.ValueOf(bars)
|
||||
\trow := []float64{}
|
||||
\tfor i := 0; i < v.Len(); i++ {
|
||||
\t\tbar := v.Index(i)
|
||||
\t\tfor k := 0; k < bar.NumField(); k++ {
|
||||
\t\t\trow = appendField(row, bar.Field(k))
|
||||
\t\t}
|
||||
\t}
|
||||
\treturn row
|
||||
}
|
||||
|
||||
// Synthetic feeds derived from one OHLCV row, identical to gen_golden's Rust
|
||||
// construction (DerivativesTick / CrossSection / OrderBook).
|
||||
func derivFields(r []float64) [11]float64 {
|
||||
\to, h, l, c, v := r[0], r[1], r[2], r[3], r[4]
|
||||
\treturn [11]float64{
|
||||
\t\t(c - o) / c * 0.01, // funding_rate
|
||||
\t\tc, // mark_price
|
||||
\t\tc - 0.5, // index_price
|
||||
\t\tc + 1.0, // futures_price
|
||||
\t\tv * 10.0, // open_interest
|
||||
\t\tv * 0.6, // long_size
|
||||
\t\tv * 0.4, // short_size
|
||||
\t\tv * 0.55, // taker_buy_volume
|
||||
\t\tv * 0.45, // taker_sell_volume
|
||||
\t\th - c, // long_liquidation
|
||||
\t\tc - l, // short_liquidation
|
||||
\t}
|
||||
}
|
||||
|
||||
func crossLists(r []float64) ([]float64, []float64, []bool, []bool, []bool, []bool) {
|
||||
\to, c, v := r[0], r[3], r[4]
|
||||
\tchange := make([]float64, 5)
|
||||
\tvolume := make([]float64, 5)
|
||||
\tnewHigh := make([]bool, 5)
|
||||
\tnewLow := make([]bool, 5)
|
||||
\taboveMa := make([]bool, 5)
|
||||
\tonBuy := make([]bool, 5)
|
||||
\tfor j := 0; j < 5; j++ {
|
||||
\t\tjf := float64(j)
|
||||
\t\tchange[j] = (c - o) + jf
|
||||
\t\tvolume[j] = v + jf*10.0
|
||||
\t\tnewHigh[j] = j%2 == 0
|
||||
\t\tnewLow[j] = j%3 == 0
|
||||
\t\taboveMa[j] = j%2 == 0
|
||||
\t\tonBuy[j] = j%3 == 0
|
||||
\t}
|
||||
\treturn change, volume, newHigh, newLow, aboveMa, onBuy
|
||||
}
|
||||
|
||||
func obLists(r []float64) ([]float64, []float64, []float64, []float64) {
|
||||
\tc, v := r[3], r[4]
|
||||
\tbidPx := make([]float64, 5)
|
||||
\tbidSz := make([]float64, 5)
|
||||
\taskPx := make([]float64, 5)
|
||||
\taskSz := make([]float64, 5)
|
||||
\tfor k := 0; k < 5; k++ {
|
||||
\t\tkf := float64(k + 1)
|
||||
\t\tbidPx[k] = c - 0.1*kf
|
||||
\t\tbidSz[k] = v / kf
|
||||
\t\taskPx[k] = c + 0.1*kf
|
||||
\t\taskSz[k] = v * 0.9 / kf
|
||||
\t}
|
||||
\treturn bidPx, bidSz, askPx, askSz
|
||||
}
|
||||
|
||||
func compareGolden(t *testing.T, name string, got [][]float64) {
|
||||
\tt.Helper()
|
||||
\texp := readGoldenRaw(t, "g_"+name)
|
||||
\tif len(exp) != len(got) {
|
||||
\t\tt.Fatalf("%s: %d fixture rows vs %d computed", name, len(exp), len(got))
|
||||
\t}
|
||||
\tfor i := range exp {
|
||||
\t\tif len(exp[i]) != len(got[i]) {
|
||||
\t\t\tt.Fatalf("%s row %d: arity %d vs %d", name, i, len(got[i]), len(exp[i]))
|
||||
\t\t}
|
||||
\t\tfor k := range exp[i] {
|
||||
\t\t\twant := goldenCell(exp[i][k])
|
||||
\t\t\tg := got[i][k]
|
||||
\t\t\tif math.IsNaN(want) {
|
||||
\t\t\t\tif !math.IsNaN(g) {
|
||||
\t\t\t\t\tt.Fatalf("%s row %d col %d: want NaN got %v", name, i, k, g)
|
||||
\t\t\t\t}
|
||||
\t\t\t\tcontinue
|
||||
\t\t\t}
|
||||
\t\t\tif math.IsInf(want, 0) {
|
||||
\t\t\t\tif !math.IsInf(g, 0) || (g > 0) != (want > 0) {
|
||||
\t\t\t\t\tt.Fatalf("%s row %d col %d: want %v got %v", name, i, k, want, g)
|
||||
\t\t\t\t}
|
||||
\t\t\t\tcontinue
|
||||
\t\t\t}
|
||||
\t\t\ttol := goldenTol * math.Max(1.0, math.Abs(want))
|
||||
\t\t\tif math.Abs(g-want) > tol {
|
||||
\t\t\t\tt.Fatalf("%s row %d col %d: got %v want %v", name, i, k, g, want)
|
||||
\t\t\t}
|
||||
\t\t}
|
||||
\t}
|
||||
}
|
||||
|
||||
func TestGoldenAll(t *testing.T) {
|
||||
\trows := goldenInput(t)
|
||||
'''
|
||||
|
||||
# bars need blank-line-preserving fixture reads; reuse readGolden but it skips
|
||||
# blanks. We need a raw reader for bars and input.
|
||||
out = [HEADER]
|
||||
for canon in canons:
|
||||
out.append(block(canon))
|
||||
out.append("}")
|
||||
open(os.path.join(ROOT, "bindings", "go", "golden_all_test.go"), "w", encoding="utf-8").write("\n".join(out) + "\n")
|
||||
print("generated golden_all_test.go with", len(canons), "indicators")
|
||||
File diff suppressed because it is too large
Load Diff
@@ -189,3 +189,62 @@ func TestGoldenAdx(t *testing.T) {
|
||||
assertGoldenClose(t, out.Adx, goldenCell(exp[i][2]), i, "adx.adx")
|
||||
}
|
||||
}
|
||||
|
||||
// The four de-duplicated indicators: pin their corrected definitions against
|
||||
// the Rust reference so the Go FFI stays bit-identical.
|
||||
|
||||
func TestGoldenAdOscillator(t *testing.T) {
|
||||
input := goldenInput(t)
|
||||
exp := readGolden(t, "ad_oscillator")
|
||||
ad, err := NewAdOscillator()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ad.Close()
|
||||
for i := range input {
|
||||
got := ad.Update(input[i][0], input[i][1], input[i][2], input[i][3], input[i][4], int64(i))
|
||||
assertGoldenClose(t, got, goldenCell(exp[i][0]), i, "ad_oscillator")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoldenIntradayIntensity(t *testing.T) {
|
||||
input := goldenInput(t)
|
||||
exp := readGolden(t, "intraday_intensity")
|
||||
ii, err := NewIntradayIntensity()
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer ii.Close()
|
||||
for i := range input {
|
||||
got := ii.Update(input[i][0], input[i][1], input[i][2], input[i][3], input[i][4], int64(i))
|
||||
assertGoldenClose(t, got, goldenCell(exp[i][0]), i, "intraday_intensity")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoldenAwesomeOscillatorHistogram(t *testing.T) {
|
||||
input := goldenInput(t)
|
||||
exp := readGolden(t, "awesome_oscillator_histogram")
|
||||
aoh, err := NewAwesomeOscillatorHistogram(5, 34, 1)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer aoh.Close()
|
||||
for i := range input {
|
||||
got := aoh.Update(input[i][0], input[i][1], input[i][2], input[i][3], input[i][4], int64(i))
|
||||
assertGoldenClose(t, got, goldenCell(exp[i][0]), i, "awesome_oscillator_histogram")
|
||||
}
|
||||
}
|
||||
|
||||
func TestGoldenAverageDrawdown(t *testing.T) {
|
||||
input := goldenInput(t)
|
||||
exp := readGolden(t, "average_drawdown")
|
||||
avg, err := NewAverageDrawdown(20)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
defer avg.Close()
|
||||
for i := range input {
|
||||
// generator fed the close column as the equity-curve sample.
|
||||
assertGoldenClose(t, avg.Update(input[i][3]), goldenCell(exp[i][0]), i, "average_drawdown")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -30,14 +30,14 @@ Maven:
|
||||
<dependency>
|
||||
<groupId>org.wickra</groupId>
|
||||
<artifactId>wickra</artifactId>
|
||||
<version>0.9.1</version>
|
||||
<version>0.9.2</version>
|
||||
</dependency>
|
||||
```
|
||||
|
||||
Gradle:
|
||||
|
||||
```kotlin
|
||||
implementation("org.wickra:wickra:0.9.1")
|
||||
implementation("org.wickra:wickra:0.9.2")
|
||||
```
|
||||
|
||||
The native library ships prebuilt per platform (Linux, macOS, Windows — x64 and
|
||||
|
||||
@@ -0,0 +1,279 @@
|
||||
"""Generate src/test/java/org/wickra/GoldenAllTest.java: a reflection-driven
|
||||
value-parity test that replays the shared golden input through every one of the
|
||||
514 Java indicators and checks output bit-for-bit against the Rust reference
|
||||
fixtures g_<Canonical>.csv. The per-indicator spec is embedded so the test has
|
||||
no JSON dependency; a single reflective runner covers all archetypes.
|
||||
|
||||
Run from repo root: python bindings/java/gen_golden_test.py
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
|
||||
ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
MAN = json.load(open(os.path.join(ROOT, "testdata", "golden", "golden_manifest.json")))
|
||||
|
||||
|
||||
def params_lit(ps):
|
||||
if not ps:
|
||||
return "new double[]{}"
|
||||
return "new double[]{" + ", ".join(repr(float(p)) for p in ps) + "}"
|
||||
|
||||
|
||||
specs = []
|
||||
for e in MAN:
|
||||
n = e.get("n", e.get("width", 0))
|
||||
specs.append(f' new Spec("{e["canonical"]}", "{e["arch"]}", {params_lit(e["params"])}, {n}),')
|
||||
specs_block = "\n".join(specs)
|
||||
|
||||
TEMPLATE = '''// Code generated by gen_golden_test.py. DO NOT EDIT.
|
||||
package org.wickra;
|
||||
|
||||
import org.junit.jupiter.api.DynamicTest;
|
||||
import org.junit.jupiter.api.TestFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.RecordComponent;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
|
||||
|
||||
/**
|
||||
* Reflection-driven value parity for the whole 514-indicator catalogue: each
|
||||
* indicator is reconstructed by its class name, fed the synthetic stream derived
|
||||
* from the shared golden input (identical to gen_golden's Rust construction) and
|
||||
* checked bit-for-bit against testdata/golden/g_<Canonical>.csv. One runner
|
||||
* flattens scalar, multi-output records, profiles and bar arrays by reflection.
|
||||
*/
|
||||
class GoldenAllTest {
|
||||
private static final double TOL = 1e-6;
|
||||
|
||||
private record Spec(String canonical, String arch, double[] params, int width) {}
|
||||
|
||||
private static final Spec[] SPECS = {
|
||||
%SPECS%
|
||||
};
|
||||
|
||||
private static Path goldenDir() {
|
||||
java.io.File d = new java.io.File("").getAbsoluteFile();
|
||||
while (d != null) {
|
||||
java.io.File g = new java.io.File(d, "testdata/golden");
|
||||
if (g.isDirectory()) {
|
||||
return g.toPath();
|
||||
}
|
||||
d = d.getParentFile();
|
||||
}
|
||||
throw new IllegalStateException("testdata/golden not found");
|
||||
}
|
||||
|
||||
private static double cell(String s) {
|
||||
return switch (s) {
|
||||
case "nan" -> Double.NaN;
|
||||
case "inf" -> Double.POSITIVE_INFINITY;
|
||||
case "-inf" -> Double.NEGATIVE_INFINITY;
|
||||
default -> Double.parseDouble(s);
|
||||
};
|
||||
}
|
||||
|
||||
private static double[][] input() throws IOException {
|
||||
List<String> lines = Files.readAllLines(goldenDir().resolve("input.csv"));
|
||||
List<double[]> rows = new ArrayList<>();
|
||||
for (int i = 1; i < lines.size(); i++) {
|
||||
if (lines.get(i).isEmpty()) continue;
|
||||
String[] p = lines.get(i).split(",");
|
||||
double[] r = new double[p.length];
|
||||
for (int j = 0; j < p.length; j++) r[j] = Double.parseDouble(p[j]);
|
||||
rows.add(r);
|
||||
}
|
||||
return rows.toArray(new double[0][]);
|
||||
}
|
||||
|
||||
// Keep blank lines (a candle on which no bar closed) so rows stay aligned.
|
||||
private static double[][] fixture(String name) throws IOException {
|
||||
List<String> lines = Files.readAllLines(goldenDir().resolve("g_" + name + ".csv"));
|
||||
List<double[]> rows = new ArrayList<>();
|
||||
for (int i = 1; i < lines.size(); i++) {
|
||||
String ln = lines.get(i);
|
||||
if (ln.isEmpty()) {
|
||||
rows.add(new double[0]);
|
||||
continue;
|
||||
}
|
||||
String[] p = ln.split(",");
|
||||
double[] r = new double[p.length];
|
||||
for (int j = 0; j < p.length; j++) r[j] = cell(p[j]);
|
||||
rows.add(r);
|
||||
}
|
||||
return rows.toArray(new double[0][]);
|
||||
}
|
||||
|
||||
private static double[] nanRow(int n) {
|
||||
double[] r = new double[n];
|
||||
java.util.Arrays.fill(r, Double.NaN);
|
||||
return r;
|
||||
}
|
||||
|
||||
private static double[] derivFields(double[] r) {
|
||||
double o = r[0], h = r[1], l = r[2], c = r[3], v = r[4];
|
||||
return new double[]{
|
||||
(c - o) / c * 0.01, c, c - 0.5, c + 1.0, v * 10.0, v * 0.6, v * 0.4,
|
||||
v * 0.55, v * 0.45, h - c, c - l,
|
||||
};
|
||||
}
|
||||
|
||||
private static double[] crossList(double[] r, int which) {
|
||||
double o = r[0], c = r[3], v = r[4];
|
||||
double[] out = new double[5];
|
||||
for (int j = 0; j < 5; j++) {
|
||||
out[j] = switch (which) {
|
||||
case 0 -> (c - o) + j;
|
||||
case 1 -> v + j * 10.0;
|
||||
case 2 -> j % 2 == 0 ? 1.0 : 0.0; // newHigh
|
||||
case 3 -> j % 3 == 0 ? 1.0 : 0.0; // newLow
|
||||
case 4 -> j % 2 == 0 ? 1.0 : 0.0; // aboveMa
|
||||
default -> j % 3 == 0 ? 1.0 : 0.0; // onBuySignal
|
||||
};
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static double[] obList(double[] r, int which) {
|
||||
double c = r[3], v = r[4];
|
||||
double[] out = new double[5];
|
||||
for (int k = 0; k < 5; k++) {
|
||||
double kf = k + 1;
|
||||
out[k] = switch (which) {
|
||||
case 0 -> c - 0.1 * kf;
|
||||
case 1 -> v / kf;
|
||||
case 2 -> c + 0.1 * kf;
|
||||
default -> v * 0.9 / kf;
|
||||
};
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static double[] flattenRecord(Object o) throws Exception {
|
||||
RecordComponent[] rc = o.getClass().getRecordComponents();
|
||||
List<Double> list = new ArrayList<>();
|
||||
for (RecordComponent c : rc) {
|
||||
Object v = c.getAccessor().invoke(o);
|
||||
if (v instanceof double[] arr) {
|
||||
for (double d : arr) list.add(d);
|
||||
} else if (v instanceof Number num) {
|
||||
list.add(num.doubleValue());
|
||||
}
|
||||
}
|
||||
double[] out = new double[list.size()];
|
||||
for (int i = 0; i < out.length; i++) out[i] = list.get(i);
|
||||
return out;
|
||||
}
|
||||
|
||||
private static double[] flattenArray(Object arr) throws Exception {
|
||||
int len = Array.getLength(arr);
|
||||
List<Double> list = new ArrayList<>();
|
||||
for (int i = 0; i < len; i++) {
|
||||
for (double d : flattenRecord(Array.get(arr, i))) list.add(d);
|
||||
}
|
||||
double[] out = new double[list.size()];
|
||||
for (int i = 0; i < out.length; i++) out[i] = list.get(i);
|
||||
return out;
|
||||
}
|
||||
|
||||
private static Object construct(Spec s) throws Exception {
|
||||
Class<?> cls = Class.forName("org.wickra." + s.canonical());
|
||||
Constructor<?> ctor = cls.getConstructors()[0];
|
||||
Class<?>[] pt = ctor.getParameterTypes();
|
||||
Object[] args = new Object[pt.length];
|
||||
for (int i = 0; i < pt.length; i++) {
|
||||
double v = s.params()[i];
|
||||
if (pt[i] == int.class) args[i] = (int) Math.round(v);
|
||||
else if (pt[i] == long.class) args[i] = (long) Math.round(v);
|
||||
else args[i] = v;
|
||||
}
|
||||
return ctor.newInstance(args);
|
||||
}
|
||||
|
||||
private static Method updateMethod(Object ind) {
|
||||
for (Method m : ind.getClass().getMethods()) {
|
||||
if (m.getName().equals("update")) return m;
|
||||
}
|
||||
throw new IllegalStateException("no update on " + ind.getClass());
|
||||
}
|
||||
|
||||
private static double[] row(Spec s, Object ind, Method upd, double[] r, int i) throws Exception {
|
||||
double o = r[0], h = r[1], l = r[2], c = r[3], v = r[4];
|
||||
Object res = switch (s.arch()) {
|
||||
case "scalar_f64", "multi_f64" -> upd.invoke(ind, c);
|
||||
case "pairwise", "multi_pairwise" -> upd.invoke(ind, c, o);
|
||||
case "scalar_candle", "multi_candle", "profile_bins", "profile_pricebins" ->
|
||||
upd.invoke(ind, o, h, l, c, v, (long) i);
|
||||
case "trade" -> upd.invoke(ind, c, v, c >= o, (long) i);
|
||||
case "trademid" -> upd.invoke(ind, c, v, c >= o, (long) i, (h + l) / 2);
|
||||
case "ob" -> upd.invoke(ind, obList(r, 0), obList(r, 1), obList(r, 2), obList(r, 3));
|
||||
case "cross" -> upd.invoke(ind, crossList(r, 0), crossList(r, 1), crossList(r, 2),
|
||||
crossList(r, 3), crossList(r, 4), crossList(r, 5), (long) i);
|
||||
case "deriv", "deriv_multi" -> {
|
||||
double[] d = derivFields(r);
|
||||
yield upd.invoke(ind, d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], (long) i);
|
||||
}
|
||||
case "bars_close" -> upd.invoke(ind, c, c, c, c, 1.0, 0L);
|
||||
case "bars_candle4" -> upd.invoke(ind, o, h, l, c, 1.0, 0L);
|
||||
case "bars_candle5" -> upd.invoke(ind, o, h, l, c, v, 0L);
|
||||
case "footprint" -> upd.invoke(ind, c, v, c >= o, (long) i);
|
||||
default -> throw new IllegalStateException("arch " + s.arch());
|
||||
};
|
||||
return switch (s.arch()) {
|
||||
case "scalar_f64", "scalar_candle", "pairwise", "trade", "trademid", "ob", "cross", "deriv" ->
|
||||
new double[]{((Number) res).doubleValue()};
|
||||
case "multi_f64", "multi_candle", "multi_pairwise", "deriv_multi", "profile_pricebins" ->
|
||||
res == null ? nanRow(s.width()) : flattenRecord(res);
|
||||
case "profile_bins" -> res == null ? nanRow(s.width()) : (double[]) res;
|
||||
default -> flattenArray(res); // bars_*, footprint
|
||||
};
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
List<DynamicTest> golden() throws Exception {
|
||||
double[][] rows = input();
|
||||
List<DynamicTest> tests = new ArrayList<>();
|
||||
for (Spec s : SPECS) {
|
||||
tests.add(dynamicTest(s.canonical(), () -> {
|
||||
Object ind = construct(s);
|
||||
Method upd = updateMethod(ind);
|
||||
double[][] exp = fixture(s.canonical());
|
||||
assertTrue(exp.length == rows.length, s.canonical() + ": row count " + exp.length + " vs " + rows.length);
|
||||
for (int i = 0; i < rows.length; i++) {
|
||||
double[] got = row(s, ind, upd, rows[i], i);
|
||||
double[] want = exp[i];
|
||||
assertTrue(got.length == want.length,
|
||||
s.canonical() + " row " + i + ": arity " + got.length + " vs " + want.length);
|
||||
for (int k = 0; k < want.length; k++) {
|
||||
double w = want[k], g = got[k];
|
||||
if (Double.isNaN(w)) {
|
||||
assertTrue(Double.isNaN(g), s.canonical() + " row " + i + " col " + k + ": want NaN got " + g);
|
||||
} else if (Double.isInfinite(w)) {
|
||||
assertTrue(Double.isInfinite(g) && Math.signum(g) == Math.signum(w),
|
||||
s.canonical() + " row " + i + " col " + k + ": want " + w + " got " + g);
|
||||
} else {
|
||||
double tol = TOL * Math.max(1.0, Math.abs(w));
|
||||
assertTrue(Math.abs(g - w) <= tol,
|
||||
s.canonical() + " row " + i + " col " + k + ": got " + g + " want " + w);
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
return tests;
|
||||
}
|
||||
}
|
||||
'''
|
||||
|
||||
out = TEMPLATE.replace("%SPECS%", specs_block)
|
||||
dest = os.path.join(ROOT, "bindings", "java", "src", "test", "java", "org", "wickra", "GoldenAllTest.java")
|
||||
open(dest, "w", encoding="utf-8").write(out)
|
||||
print("generated GoldenAllTest.java with", len(MAN), "indicators")
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>org.wickra</groupId>
|
||||
<artifactId>wickra</artifactId>
|
||||
<version>0.9.1</version>
|
||||
<version>0.9.2</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>Wickra</name>
|
||||
|
||||
@@ -47,10 +47,10 @@ public final class AbsoluteBreadthIndex implements AutoCloseable {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment changeSeg = a.allocateFrom(JAVA_DOUBLE, change);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment newHighSeg = a.allocateFrom(JAVA_DOUBLE, newHigh);
|
||||
MemorySegment newLowSeg = a.allocateFrom(JAVA_DOUBLE, newLow);
|
||||
MemorySegment aboveMaSeg = a.allocateFrom(JAVA_DOUBLE, aboveMa);
|
||||
MemorySegment onBuySignalSeg = a.allocateFrom(JAVA_DOUBLE, onBuySignal);
|
||||
MemorySegment newHighSeg = WickraNative.boolSegment(a, newHigh);
|
||||
MemorySegment newLowSeg = WickraNative.boolSegment(a, newLow);
|
||||
MemorySegment aboveMaSeg = WickraNative.boolSegment(a, aboveMa);
|
||||
MemorySegment onBuySignalSeg = WickraNative.boolSegment(a, onBuySignal);
|
||||
return (double) NativeMethods.WICKRA_ABSOLUTE_BREADTH_INDEX_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
|
||||
@@ -47,10 +47,10 @@ public final class AdVolumeLine implements AutoCloseable {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment changeSeg = a.allocateFrom(JAVA_DOUBLE, change);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment newHighSeg = a.allocateFrom(JAVA_DOUBLE, newHigh);
|
||||
MemorySegment newLowSeg = a.allocateFrom(JAVA_DOUBLE, newLow);
|
||||
MemorySegment aboveMaSeg = a.allocateFrom(JAVA_DOUBLE, aboveMa);
|
||||
MemorySegment onBuySignalSeg = a.allocateFrom(JAVA_DOUBLE, onBuySignal);
|
||||
MemorySegment newHighSeg = WickraNative.boolSegment(a, newHigh);
|
||||
MemorySegment newLowSeg = WickraNative.boolSegment(a, newLow);
|
||||
MemorySegment aboveMaSeg = WickraNative.boolSegment(a, aboveMa);
|
||||
MemorySegment onBuySignalSeg = WickraNative.boolSegment(a, onBuySignal);
|
||||
return (double) NativeMethods.WICKRA_AD_VOLUME_LINE_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
|
||||
@@ -47,10 +47,10 @@ public final class AdvanceDecline implements AutoCloseable {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment changeSeg = a.allocateFrom(JAVA_DOUBLE, change);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment newHighSeg = a.allocateFrom(JAVA_DOUBLE, newHigh);
|
||||
MemorySegment newLowSeg = a.allocateFrom(JAVA_DOUBLE, newLow);
|
||||
MemorySegment aboveMaSeg = a.allocateFrom(JAVA_DOUBLE, aboveMa);
|
||||
MemorySegment onBuySignalSeg = a.allocateFrom(JAVA_DOUBLE, onBuySignal);
|
||||
MemorySegment newHighSeg = WickraNative.boolSegment(a, newHigh);
|
||||
MemorySegment newLowSeg = WickraNative.boolSegment(a, newLow);
|
||||
MemorySegment aboveMaSeg = WickraNative.boolSegment(a, aboveMa);
|
||||
MemorySegment onBuySignalSeg = WickraNative.boolSegment(a, onBuySignal);
|
||||
return (double) NativeMethods.WICKRA_ADVANCE_DECLINE_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
|
||||
@@ -47,10 +47,10 @@ public final class AdvanceDeclineRatio implements AutoCloseable {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment changeSeg = a.allocateFrom(JAVA_DOUBLE, change);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment newHighSeg = a.allocateFrom(JAVA_DOUBLE, newHigh);
|
||||
MemorySegment newLowSeg = a.allocateFrom(JAVA_DOUBLE, newLow);
|
||||
MemorySegment aboveMaSeg = a.allocateFrom(JAVA_DOUBLE, aboveMa);
|
||||
MemorySegment onBuySignalSeg = a.allocateFrom(JAVA_DOUBLE, onBuySignal);
|
||||
MemorySegment newHighSeg = WickraNative.boolSegment(a, newHigh);
|
||||
MemorySegment newLowSeg = WickraNative.boolSegment(a, newLow);
|
||||
MemorySegment aboveMaSeg = WickraNative.boolSegment(a, aboveMa);
|
||||
MemorySegment onBuySignalSeg = WickraNative.boolSegment(a, onBuySignal);
|
||||
return (double) NativeMethods.WICKRA_ADVANCE_DECLINE_RATIO_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
|
||||
@@ -50,10 +50,10 @@ public final class BreadthThrust implements AutoCloseable {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment changeSeg = a.allocateFrom(JAVA_DOUBLE, change);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment newHighSeg = a.allocateFrom(JAVA_DOUBLE, newHigh);
|
||||
MemorySegment newLowSeg = a.allocateFrom(JAVA_DOUBLE, newLow);
|
||||
MemorySegment aboveMaSeg = a.allocateFrom(JAVA_DOUBLE, aboveMa);
|
||||
MemorySegment onBuySignalSeg = a.allocateFrom(JAVA_DOUBLE, onBuySignal);
|
||||
MemorySegment newHighSeg = WickraNative.boolSegment(a, newHigh);
|
||||
MemorySegment newLowSeg = WickraNative.boolSegment(a, newLow);
|
||||
MemorySegment aboveMaSeg = WickraNative.boolSegment(a, aboveMa);
|
||||
MemorySegment onBuySignalSeg = WickraNative.boolSegment(a, onBuySignal);
|
||||
return (double) NativeMethods.WICKRA_BREADTH_THRUST_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
|
||||
@@ -47,10 +47,10 @@ public final class BullishPercentIndex implements AutoCloseable {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment changeSeg = a.allocateFrom(JAVA_DOUBLE, change);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment newHighSeg = a.allocateFrom(JAVA_DOUBLE, newHigh);
|
||||
MemorySegment newLowSeg = a.allocateFrom(JAVA_DOUBLE, newLow);
|
||||
MemorySegment aboveMaSeg = a.allocateFrom(JAVA_DOUBLE, aboveMa);
|
||||
MemorySegment onBuySignalSeg = a.allocateFrom(JAVA_DOUBLE, onBuySignal);
|
||||
MemorySegment newHighSeg = WickraNative.boolSegment(a, newHigh);
|
||||
MemorySegment newLowSeg = WickraNative.boolSegment(a, newLow);
|
||||
MemorySegment aboveMaSeg = WickraNative.boolSegment(a, aboveMa);
|
||||
MemorySegment onBuySignalSeg = WickraNative.boolSegment(a, onBuySignal);
|
||||
return (double) NativeMethods.WICKRA_BULLISH_PERCENT_INDEX_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
|
||||
@@ -47,10 +47,10 @@ public final class CumulativeVolumeIndex implements AutoCloseable {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment changeSeg = a.allocateFrom(JAVA_DOUBLE, change);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment newHighSeg = a.allocateFrom(JAVA_DOUBLE, newHigh);
|
||||
MemorySegment newLowSeg = a.allocateFrom(JAVA_DOUBLE, newLow);
|
||||
MemorySegment aboveMaSeg = a.allocateFrom(JAVA_DOUBLE, aboveMa);
|
||||
MemorySegment onBuySignalSeg = a.allocateFrom(JAVA_DOUBLE, onBuySignal);
|
||||
MemorySegment newHighSeg = WickraNative.boolSegment(a, newHigh);
|
||||
MemorySegment newLowSeg = WickraNative.boolSegment(a, newLow);
|
||||
MemorySegment aboveMaSeg = WickraNative.boolSegment(a, aboveMa);
|
||||
MemorySegment onBuySignalSeg = WickraNative.boolSegment(a, onBuySignal);
|
||||
return (double) NativeMethods.WICKRA_CUMULATIVE_VOLUME_INDEX_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
|
||||
@@ -50,10 +50,10 @@ public final class HighLowIndex implements AutoCloseable {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment changeSeg = a.allocateFrom(JAVA_DOUBLE, change);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment newHighSeg = a.allocateFrom(JAVA_DOUBLE, newHigh);
|
||||
MemorySegment newLowSeg = a.allocateFrom(JAVA_DOUBLE, newLow);
|
||||
MemorySegment aboveMaSeg = a.allocateFrom(JAVA_DOUBLE, aboveMa);
|
||||
MemorySegment onBuySignalSeg = a.allocateFrom(JAVA_DOUBLE, onBuySignal);
|
||||
MemorySegment newHighSeg = WickraNative.boolSegment(a, newHigh);
|
||||
MemorySegment newLowSeg = WickraNative.boolSegment(a, newLow);
|
||||
MemorySegment aboveMaSeg = WickraNative.boolSegment(a, aboveMa);
|
||||
MemorySegment onBuySignalSeg = WickraNative.boolSegment(a, onBuySignal);
|
||||
return (double) NativeMethods.WICKRA_HIGH_LOW_INDEX_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
|
||||
@@ -25,7 +25,7 @@ public final class MacdExt implements AutoCloseable {
|
||||
}
|
||||
MemorySegment h;
|
||||
try {
|
||||
h = (MemorySegment) NativeMethods.WICKRA_MACD_EXT_NEW.invokeExact((long) fast, fastType, (long) slow, slowType, (long) signal, signalType);
|
||||
h = (MemorySegment) NativeMethods.WICKRA_MACD_EXT_NEW.invokeExact((long) fast, (byte) fastType, (long) slow, (byte) slowType, (long) signal, (byte) signalType);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
}
|
||||
|
||||
@@ -47,10 +47,10 @@ public final class McClellanOscillator implements AutoCloseable {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment changeSeg = a.allocateFrom(JAVA_DOUBLE, change);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment newHighSeg = a.allocateFrom(JAVA_DOUBLE, newHigh);
|
||||
MemorySegment newLowSeg = a.allocateFrom(JAVA_DOUBLE, newLow);
|
||||
MemorySegment aboveMaSeg = a.allocateFrom(JAVA_DOUBLE, aboveMa);
|
||||
MemorySegment onBuySignalSeg = a.allocateFrom(JAVA_DOUBLE, onBuySignal);
|
||||
MemorySegment newHighSeg = WickraNative.boolSegment(a, newHigh);
|
||||
MemorySegment newLowSeg = WickraNative.boolSegment(a, newLow);
|
||||
MemorySegment aboveMaSeg = WickraNative.boolSegment(a, aboveMa);
|
||||
MemorySegment onBuySignalSeg = WickraNative.boolSegment(a, onBuySignal);
|
||||
return (double) NativeMethods.WICKRA_MC_CLELLAN_OSCILLATOR_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
|
||||
@@ -47,10 +47,10 @@ public final class McClellanSummationIndex implements AutoCloseable {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment changeSeg = a.allocateFrom(JAVA_DOUBLE, change);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment newHighSeg = a.allocateFrom(JAVA_DOUBLE, newHigh);
|
||||
MemorySegment newLowSeg = a.allocateFrom(JAVA_DOUBLE, newLow);
|
||||
MemorySegment aboveMaSeg = a.allocateFrom(JAVA_DOUBLE, aboveMa);
|
||||
MemorySegment onBuySignalSeg = a.allocateFrom(JAVA_DOUBLE, onBuySignal);
|
||||
MemorySegment newHighSeg = WickraNative.boolSegment(a, newHigh);
|
||||
MemorySegment newLowSeg = WickraNative.boolSegment(a, newLow);
|
||||
MemorySegment aboveMaSeg = WickraNative.boolSegment(a, aboveMa);
|
||||
MemorySegment onBuySignalSeg = WickraNative.boolSegment(a, onBuySignal);
|
||||
return (double) NativeMethods.WICKRA_MC_CLELLAN_SUMMATION_INDEX_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
|
||||
@@ -47,10 +47,10 @@ public final class NewHighsNewLows implements AutoCloseable {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment changeSeg = a.allocateFrom(JAVA_DOUBLE, change);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment newHighSeg = a.allocateFrom(JAVA_DOUBLE, newHigh);
|
||||
MemorySegment newLowSeg = a.allocateFrom(JAVA_DOUBLE, newLow);
|
||||
MemorySegment aboveMaSeg = a.allocateFrom(JAVA_DOUBLE, aboveMa);
|
||||
MemorySegment onBuySignalSeg = a.allocateFrom(JAVA_DOUBLE, onBuySignal);
|
||||
MemorySegment newHighSeg = WickraNative.boolSegment(a, newHigh);
|
||||
MemorySegment newLowSeg = WickraNative.boolSegment(a, newLow);
|
||||
MemorySegment aboveMaSeg = WickraNative.boolSegment(a, aboveMa);
|
||||
MemorySegment onBuySignalSeg = WickraNative.boolSegment(a, onBuySignal);
|
||||
return (double) NativeMethods.WICKRA_NEW_HIGHS_NEW_LOWS_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
|
||||
@@ -47,10 +47,10 @@ public final class PercentAboveMa implements AutoCloseable {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment changeSeg = a.allocateFrom(JAVA_DOUBLE, change);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment newHighSeg = a.allocateFrom(JAVA_DOUBLE, newHigh);
|
||||
MemorySegment newLowSeg = a.allocateFrom(JAVA_DOUBLE, newLow);
|
||||
MemorySegment aboveMaSeg = a.allocateFrom(JAVA_DOUBLE, aboveMa);
|
||||
MemorySegment onBuySignalSeg = a.allocateFrom(JAVA_DOUBLE, onBuySignal);
|
||||
MemorySegment newHighSeg = WickraNative.boolSegment(a, newHigh);
|
||||
MemorySegment newLowSeg = WickraNative.boolSegment(a, newLow);
|
||||
MemorySegment aboveMaSeg = WickraNative.boolSegment(a, aboveMa);
|
||||
MemorySegment onBuySignalSeg = WickraNative.boolSegment(a, onBuySignal);
|
||||
return (double) NativeMethods.WICKRA_PERCENT_ABOVE_MA_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
|
||||
@@ -47,10 +47,10 @@ public final class TickIndex implements AutoCloseable {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment changeSeg = a.allocateFrom(JAVA_DOUBLE, change);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment newHighSeg = a.allocateFrom(JAVA_DOUBLE, newHigh);
|
||||
MemorySegment newLowSeg = a.allocateFrom(JAVA_DOUBLE, newLow);
|
||||
MemorySegment aboveMaSeg = a.allocateFrom(JAVA_DOUBLE, aboveMa);
|
||||
MemorySegment onBuySignalSeg = a.allocateFrom(JAVA_DOUBLE, onBuySignal);
|
||||
MemorySegment newHighSeg = WickraNative.boolSegment(a, newHigh);
|
||||
MemorySegment newLowSeg = WickraNative.boolSegment(a, newLow);
|
||||
MemorySegment aboveMaSeg = WickraNative.boolSegment(a, aboveMa);
|
||||
MemorySegment onBuySignalSeg = WickraNative.boolSegment(a, onBuySignal);
|
||||
return (double) NativeMethods.WICKRA_TICK_INDEX_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
|
||||
@@ -47,10 +47,10 @@ public final class Trin implements AutoCloseable {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment changeSeg = a.allocateFrom(JAVA_DOUBLE, change);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment newHighSeg = a.allocateFrom(JAVA_DOUBLE, newHigh);
|
||||
MemorySegment newLowSeg = a.allocateFrom(JAVA_DOUBLE, newLow);
|
||||
MemorySegment aboveMaSeg = a.allocateFrom(JAVA_DOUBLE, aboveMa);
|
||||
MemorySegment onBuySignalSeg = a.allocateFrom(JAVA_DOUBLE, onBuySignal);
|
||||
MemorySegment newHighSeg = WickraNative.boolSegment(a, newHigh);
|
||||
MemorySegment newLowSeg = WickraNative.boolSegment(a, newLow);
|
||||
MemorySegment aboveMaSeg = WickraNative.boolSegment(a, aboveMa);
|
||||
MemorySegment onBuySignalSeg = WickraNative.boolSegment(a, onBuySignal);
|
||||
return (double) NativeMethods.WICKRA_TRIN_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
|
||||
@@ -47,10 +47,10 @@ public final class UpDownVolumeRatio implements AutoCloseable {
|
||||
try (Arena a = Arena.ofConfined()) {
|
||||
MemorySegment changeSeg = a.allocateFrom(JAVA_DOUBLE, change);
|
||||
MemorySegment volumeSeg = a.allocateFrom(JAVA_DOUBLE, volume);
|
||||
MemorySegment newHighSeg = a.allocateFrom(JAVA_DOUBLE, newHigh);
|
||||
MemorySegment newLowSeg = a.allocateFrom(JAVA_DOUBLE, newLow);
|
||||
MemorySegment aboveMaSeg = a.allocateFrom(JAVA_DOUBLE, aboveMa);
|
||||
MemorySegment onBuySignalSeg = a.allocateFrom(JAVA_DOUBLE, onBuySignal);
|
||||
MemorySegment newHighSeg = WickraNative.boolSegment(a, newHigh);
|
||||
MemorySegment newLowSeg = WickraNative.boolSegment(a, newLow);
|
||||
MemorySegment aboveMaSeg = WickraNative.boolSegment(a, aboveMa);
|
||||
MemorySegment onBuySignalSeg = WickraNative.boolSegment(a, onBuySignal);
|
||||
return (double) NativeMethods.WICKRA_UP_DOWN_VOLUME_RATIO_UPDATE.invokeExact(handle, changeSeg, volumeSeg, newHighSeg, newLowSeg, aboveMaSeg, onBuySignalSeg, (long) change.length, timestamp);
|
||||
} catch (Throwable t) {
|
||||
throw WickraNative.rethrow(t);
|
||||
|
||||
@@ -58,6 +58,20 @@ public final class WickraNative {
|
||||
return CLEANER.register(owner, new FreeAction(handle, free));
|
||||
}
|
||||
|
||||
/**
|
||||
* Allocate a C {@code bool*} buffer (one byte per element) from flag values
|
||||
* supplied as doubles, treating any non-zero value as {@code true}. The C
|
||||
* ABI takes the cross-section state flags as {@code const bool*}, so they
|
||||
* must be one byte each rather than eight-byte doubles.
|
||||
*/
|
||||
public static MemorySegment boolSegment(Arena arena, double[] flags) {
|
||||
byte[] bytes = new byte[flags.length];
|
||||
for (int i = 0; i < flags.length; i++) {
|
||||
bytes[i] = (byte) (flags[i] != 0.0 ? 1 : 0);
|
||||
}
|
||||
return arena.allocateFrom(java.lang.foreign.ValueLayout.JAVA_BYTE, bytes);
|
||||
}
|
||||
|
||||
/** Re-throw a {@link MethodHandle#invokeExact} {@link Throwable} as an unchecked exception. */
|
||||
public static RuntimeException rethrow(Throwable t) {
|
||||
if (t instanceof RuntimeException re) {
|
||||
|
||||
@@ -0,0 +1,759 @@
|
||||
// Code generated by gen_golden_test.py. DO NOT EDIT.
|
||||
package org.wickra;
|
||||
|
||||
import org.junit.jupiter.api.DynamicTest;
|
||||
import org.junit.jupiter.api.TestFactory;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.Array;
|
||||
import java.lang.reflect.Constructor;
|
||||
import java.lang.reflect.Method;
|
||||
import java.lang.reflect.RecordComponent;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.DynamicTest.dynamicTest;
|
||||
|
||||
/**
|
||||
* Reflection-driven value parity for the whole 514-indicator catalogue: each
|
||||
* indicator is reconstructed by its class name, fed the synthetic stream derived
|
||||
* from the shared golden input (identical to gen_golden's Rust construction) and
|
||||
* checked bit-for-bit against testdata/golden/g_<Canonical>.csv. One runner
|
||||
* flattens scalar, multi-output records, profiles and bar arrays by reflection.
|
||||
*/
|
||||
class GoldenAllTest {
|
||||
private static final double TOL = 1e-6;
|
||||
|
||||
private record Spec(String canonical, String arch, double[] params, int width) {}
|
||||
|
||||
private static final Spec[] SPECS = {
|
||||
new Spec("AbandonedBaby", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("Abcd", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("AbsoluteBreadthIndex", "cross", new double[]{}, 0),
|
||||
new Spec("AccelerationBands", "multi_candle", new double[]{14.0, 2.0}, 3),
|
||||
new Spec("AcceleratorOscillator", "scalar_candle", new double[]{3.0, 7.0, 14.0}, 0),
|
||||
new Spec("AdOscillator", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("AdVolumeLine", "cross", new double[]{}, 0),
|
||||
new Spec("AdaptiveCci", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("AdaptiveCycle", "scalar_f64", new double[]{}, 0),
|
||||
new Spec("AdaptiveLaguerreFilter", "scalar_f64", new double[]{20.0}, 0),
|
||||
new Spec("AdaptiveRsi", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("Adl", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("AdvanceBlock", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("AdvanceDecline", "cross", new double[]{}, 0),
|
||||
new Spec("AdvanceDeclineRatio", "cross", new double[]{}, 0),
|
||||
new Spec("Adx", "multi_candle", new double[]{14.0}, 3),
|
||||
new Spec("Adxr", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("Alligator", "multi_candle", new double[]{3.0, 7.0, 14.0}, 3),
|
||||
new Spec("Alma", "scalar_f64", new double[]{9.0, 0.85, 6.0}, 0),
|
||||
new Spec("Alpha", "pairwise", new double[]{14.0, 2.0}, 0),
|
||||
new Spec("AmihudIlliquidity", "trade", new double[]{20.0}, 0),
|
||||
new Spec("AnchoredRsi", "scalar_f64", new double[]{}, 0),
|
||||
new Spec("AnchoredVwap", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("AndrewsPitchfork", "multi_candle", new double[]{14.0}, 3),
|
||||
new Spec("Apo", "scalar_f64", new double[]{3.0, 7.0}, 0),
|
||||
new Spec("Aroon", "multi_candle", new double[]{14.0}, 2),
|
||||
new Spec("AroonOscillator", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("Atr", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("AtrBands", "multi_candle", new double[]{14.0, 2.0}, 3),
|
||||
new Spec("AtrRatchet", "multi_candle", new double[]{14.0, 2.0, 0.5}, 2),
|
||||
new Spec("AtrTrailingStop", "scalar_candle", new double[]{14.0, 2.0}, 0),
|
||||
new Spec("AutoFib", "multi_candle", new double[]{}, 7),
|
||||
new Spec("Autocorrelation", "scalar_f64", new double[]{10.0, 1.0}, 0),
|
||||
new Spec("AutocorrelationPeriodogram", "scalar_f64", new double[]{10.0, 48.0}, 0),
|
||||
new Spec("AverageDailyRange", "scalar_candle", new double[]{14.0, 0.0}, 0),
|
||||
new Spec("AverageDrawdown", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("AvgPrice", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("AwesomeOscillator", "scalar_candle", new double[]{3.0, 7.0}, 0),
|
||||
new Spec("AwesomeOscillatorHistogram", "scalar_candle", new double[]{3.0, 7.0, 14.0}, 0),
|
||||
new Spec("BalanceOfPower", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("BandpassFilter", "scalar_f64", new double[]{20.0, 0.3}, 0),
|
||||
new Spec("Bat", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("BeltHold", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("Beta", "pairwise", new double[]{14.0}, 0),
|
||||
new Spec("BetaNeutralSpread", "pairwise", new double[]{14.0}, 0),
|
||||
new Spec("BetterVolume", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("BipowerVariation", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("BodySizePct", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("BollingerBands", "multi_f64", new double[]{20.0, 2.0}, 4),
|
||||
new Spec("BollingerBandwidth", "scalar_f64", new double[]{14.0, 2.0}, 0),
|
||||
new Spec("BomarBands", "multi_f64", new double[]{4.0, 0.85}, 3),
|
||||
new Spec("BreadthThrust", "cross", new double[]{10.0}, 0),
|
||||
new Spec("Breakaway", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("BullishPercentIndex", "cross", new double[]{}, 0),
|
||||
new Spec("BurkeRatio", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("Butterfly", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("CalendarSpread", "deriv", new double[]{}, 0),
|
||||
new Spec("CalmarRatio", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("Camarilla", "multi_candle", new double[]{}, 9),
|
||||
new Spec("CandleVolume", "multi_candle", new double[]{14.0}, 2),
|
||||
new Spec("Cci", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("CenterOfGravity", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("CentralPivotRange", "multi_candle", new double[]{}, 3),
|
||||
new Spec("Cfo", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("ChaikinMoneyFlow", "scalar_candle", new double[]{20.0}, 0),
|
||||
new Spec("ChaikinOscillator", "scalar_candle", new double[]{3.0, 7.0}, 0),
|
||||
new Spec("ChaikinVolatility", "scalar_candle", new double[]{3.0, 7.0}, 0),
|
||||
new Spec("ChandeKrollStop", "multi_candle", new double[]{3.0, 2.0, 7.0}, 2),
|
||||
new Spec("ChandelierExit", "multi_candle", new double[]{14.0, 2.0}, 2),
|
||||
new Spec("ChoppinessIndex", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("ClassicPivots", "multi_candle", new double[]{}, 7),
|
||||
new Spec("CloseVsOpen", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("ClosingMarubozu", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("Cmo", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("CoefficientOfVariation", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("Cointegration", "multi_pairwise", new double[]{40.0, 1.0}, 3),
|
||||
new Spec("CommonSenseRatio", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("CompositeProfile", "multi_candle", new double[]{20.0, 24.0, 0.7}, 3),
|
||||
new Spec("ConcealingBabySwallow", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("ConditionalValueAtRisk", "scalar_f64", new double[]{20.0, 0.95}, 0),
|
||||
new Spec("ConnorsRsi", "scalar_f64", new double[]{3.0, 7.0, 14.0}, 0),
|
||||
new Spec("Coppock", "scalar_f64", new double[]{3.0, 7.0, 14.0}, 0),
|
||||
new Spec("CorrelationTrendIndicator", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("Counterattack", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("Crab", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("CumulativeVolumeDelta", "trade", new double[]{}, 0),
|
||||
new Spec("CumulativeVolumeIndex", "cross", new double[]{}, 0),
|
||||
new Spec("CupAndHandle", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("CyberneticCycle", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("Cypher", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("DayOfWeekProfile", "profile_bins", new double[]{0.0}, 7),
|
||||
new Spec("Decycler", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("DecyclerOscillator", "scalar_f64", new double[]{3.0, 7.0}, 0),
|
||||
new Spec("Dema", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("DemandIndex", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("DemarkPivots", "multi_candle", new double[]{}, 3),
|
||||
new Spec("DepthSlope", "ob", new double[]{}, 0),
|
||||
new Spec("DerivativeOscillator", "scalar_f64", new double[]{3.0, 7.0, 14.0, 28.0}, 0),
|
||||
new Spec("DetrendedStdDev", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("DisparityIndex", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("DistanceSsd", "pairwise", new double[]{14.0}, 0),
|
||||
new Spec("Doji", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("DojiStar", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("DollarBars", "bars_candle5", new double[]{50000.0}, 0),
|
||||
new Spec("Donchian", "multi_candle", new double[]{14.0}, 3),
|
||||
new Spec("DonchianStop", "multi_candle", new double[]{14.0}, 2),
|
||||
new Spec("DoubleBollinger", "multi_f64", new double[]{20.0, 1.0, 2.0}, 5),
|
||||
new Spec("DoubleTopBottom", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("DownsideGapThreeMethods", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("Dpo", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("DragonflyDoji", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("DrawdownDuration", "scalar_f64", new double[]{}, 0),
|
||||
new Spec("DumplingTop", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("Dx", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("DynamicMomentumIndex", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("EaseOfMovement", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("EffectiveSpread", "trademid", new double[]{}, 0),
|
||||
new Spec("EhlersStochastic", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("Ehma", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("ElderImpulse", "scalar_f64", new double[]{3.0, 7.0, 14.0, 28.0}, 0),
|
||||
new Spec("ElderRay", "multi_candle", new double[]{14.0}, 2),
|
||||
new Spec("ElderSafeZone", "multi_candle", new double[]{10.0, 2.0}, 2),
|
||||
new Spec("Ema", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("EmpiricalModeDecomposition", "scalar_f64", new double[]{20.0, 0.1}, 0),
|
||||
new Spec("Engulfing", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("Equivolume", "multi_candle", new double[]{14.0}, 2),
|
||||
new Spec("EstimatedLeverageRatio", "deriv", new double[]{}, 0),
|
||||
new Spec("EvenBetterSinewave", "scalar_f64", new double[]{40.0, 10.0}, 0),
|
||||
new Spec("EveningDojiStar", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("Evwma", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("EwmaVolatility", "scalar_f64", new double[]{0.94}, 0),
|
||||
new Spec("Expectancy", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("FallingThreeMethods", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("Fama", "scalar_f64", new double[]{0.5, 0.05}, 0),
|
||||
new Spec("FibArcs", "multi_candle", new double[]{}, 3),
|
||||
new Spec("FibChannel", "multi_candle", new double[]{}, 4),
|
||||
new Spec("FibConfluence", "multi_candle", new double[]{}, 2),
|
||||
new Spec("FibExtension", "multi_candle", new double[]{}, 5),
|
||||
new Spec("FibFan", "multi_candle", new double[]{}, 3),
|
||||
new Spec("FibProjection", "multi_candle", new double[]{}, 4),
|
||||
new Spec("FibRetracement", "multi_candle", new double[]{}, 7),
|
||||
new Spec("FibTimeZones", "multi_candle", new double[]{}, 2),
|
||||
new Spec("FibonacciPivots", "multi_candle", new double[]{}, 7),
|
||||
new Spec("FisherRsi", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("FisherTransform", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("FlagPennant", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("Footprint", "footprint", new double[]{1.0}, 0),
|
||||
new Spec("ForceIndex", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("FractalChaosBands", "multi_candle", new double[]{14.0}, 2),
|
||||
new Spec("Frama", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("FryPanBottom", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("FundingBasis", "deriv", new double[]{}, 0),
|
||||
new Spec("FundingImpliedApr", "deriv", new double[]{1095.0}, 0),
|
||||
new Spec("FundingRate", "deriv", new double[]{}, 0),
|
||||
new Spec("FundingRateMean", "deriv", new double[]{20.0}, 0),
|
||||
new Spec("FundingRateZScore", "deriv", new double[]{20.0}, 0),
|
||||
new Spec("GainLossRatio", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("GainToPainRatio", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("GapSideBySideWhite", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("Garch11", "scalar_f64", new double[]{2e-06, 0.1, 0.88}, 0),
|
||||
new Spec("GarmanKlassVolatility", "scalar_candle", new double[]{20.0, 252.0}, 0),
|
||||
new Spec("Gartley", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("GatorOscillator", "multi_candle", new double[]{3.0, 7.0, 14.0}, 2),
|
||||
new Spec("GeneralizedDema", "scalar_f64", new double[]{5.0, 0.7}, 0),
|
||||
new Spec("GeometricMa", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("GoldenPocket", "multi_candle", new double[]{}, 3),
|
||||
new Spec("GrangerCausality", "pairwise", new double[]{60.0, 1.0}, 0),
|
||||
new Spec("GravestoneDoji", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("Hammer", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("HangingMan", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("Harami", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("HaramiCross", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("HasbrouckInformationShare", "pairwise", new double[]{14.0}, 0),
|
||||
new Spec("HeadAndShoulders", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("HeikinAshi", "multi_candle", new double[]{}, 4),
|
||||
new Spec("HeikinAshiOscillator", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("HiLoActivator", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("HighLowIndex", "cross", new double[]{10.0}, 0),
|
||||
new Spec("HighLowRange", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("HighLowVolumeNodes", "multi_candle", new double[]{3.0, 7.0}, 2),
|
||||
new Spec("HighWave", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("HighpassFilter", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("Hikkake", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("HikkakeModified", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("HilbertDominantCycle", "scalar_f64", new double[]{}, 0),
|
||||
new Spec("HistoricalVolatility", "scalar_f64", new double[]{3.0, 7.0}, 0),
|
||||
new Spec("Hma", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("HoltWinters", "scalar_f64", new double[]{0.5, 0.1}, 0),
|
||||
new Spec("HomingPigeon", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("HtDcPhase", "scalar_f64", new double[]{}, 0),
|
||||
new Spec("HtPhasor", "multi_f64", new double[]{}, 2),
|
||||
new Spec("HtTrendMode", "scalar_f64", new double[]{}, 0),
|
||||
new Spec("HurstChannel", "multi_candle", new double[]{14.0, 2.0}, 3),
|
||||
new Spec("HurstExponent", "scalar_f64", new double[]{100.0, 4.0}, 0),
|
||||
new Spec("Ichimoku", "multi_candle", new double[]{9.0, 26.0, 52.0, 26.0}, 5),
|
||||
new Spec("IdenticalThreeCrows", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("ImbalanceBars", "bars_candle4", new double[]{5.0}, 0),
|
||||
new Spec("InNeck", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("Inertia", "scalar_candle", new double[]{3.0, 7.0}, 0),
|
||||
new Spec("InformationRatio", "pairwise", new double[]{14.0}, 0),
|
||||
new Spec("InitialBalance", "multi_candle", new double[]{14.0}, 2),
|
||||
new Spec("InstantaneousTrendline", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("IntradayIntensity", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("IntradayMomentumIndex", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("IntradayVolatilityProfile", "profile_bins", new double[]{24.0, 0.0}, 24),
|
||||
new Spec("InverseFisherTransform", "scalar_f64", new double[]{2.0}, 0),
|
||||
new Spec("InvertedHammer", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("JarqueBera", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("Jma", "scalar_f64", new double[]{7.0, 0.0, 2.0}, 0),
|
||||
new Spec("JumpIndicator", "scalar_f64", new double[]{14.0, 2.0}, 0),
|
||||
new Spec("KRatio", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("KagiBars", "bars_close", new double[]{2.0}, 0),
|
||||
new Spec("KalmanHedgeRatio", "multi_pairwise", new double[]{0.01, 0.001}, 3),
|
||||
new Spec("Kama", "scalar_f64", new double[]{3.0, 7.0, 14.0}, 0),
|
||||
new Spec("KaseDevStop", "multi_candle", new double[]{14.0, 2.0}, 2),
|
||||
new Spec("KasePermissionStochastic", "multi_candle", new double[]{3.0, 7.0}, 2),
|
||||
new Spec("KellyCriterion", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("Keltner", "multi_candle", new double[]{3.0, 7.0, 2.0}, 3),
|
||||
new Spec("KendallTau", "pairwise", new double[]{14.0}, 0),
|
||||
new Spec("Kicking", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("KickingByLength", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("Kst", "multi_f64", new double[]{3.0, 7.0, 14.0, 28.0, 35.0, 42.0, 56.0, 63.0, 70.0}, 2),
|
||||
new Spec("Kurtosis", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("Kvo", "scalar_candle", new double[]{3.0, 7.0}, 0),
|
||||
new Spec("KylesLambda", "trademid", new double[]{20.0}, 0),
|
||||
new Spec("LadderBottom", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("LaguerreRsi", "scalar_f64", new double[]{0.5}, 0),
|
||||
new Spec("LeadLagCrossCorrelation", "multi_pairwise", new double[]{20.0, 10.0}, 2),
|
||||
new Spec("LinRegAngle", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("LinRegChannel", "multi_f64", new double[]{14.0, 2.0}, 3),
|
||||
new Spec("LinRegIntercept", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("LinRegSlope", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("LinearRegression", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("LiquidationFeatures", "deriv_multi", new double[]{}, 5),
|
||||
new Spec("LogReturn", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("LongLeggedDoji", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("LongLine", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("LongShortRatio", "deriv", new double[]{}, 0),
|
||||
new Spec("M2Measure", "scalar_f64", new double[]{14.0, 2.0, 0.5}, 0),
|
||||
new Spec("MaEnvelope", "multi_f64", new double[]{14.0, 2.0}, 3),
|
||||
new Spec("MacdExt", "multi_f64", new double[]{12.0, 0.0, 26.0, 0.0, 9.0, 0.0}, 3),
|
||||
new Spec("MacdFix", "multi_f64", new double[]{9.0}, 3),
|
||||
new Spec("MacdHistogram", "scalar_f64", new double[]{3.0, 7.0, 14.0}, 0),
|
||||
new Spec("MacdIndicator", "multi_f64", new double[]{12.0, 26.0, 9.0}, 3),
|
||||
new Spec("Mama", "multi_f64", new double[]{0.5, 0.05}, 2),
|
||||
new Spec("MarketFacilitationIndex", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("MartinRatio", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("Marubozu", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("MassIndex", "scalar_candle", new double[]{3.0, 7.0}, 0),
|
||||
new Spec("MatHold", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("MatchingLow", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("MaxDrawdown", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("McClellanOscillator", "cross", new double[]{}, 0),
|
||||
new Spec("McClellanSummationIndex", "cross", new double[]{}, 0),
|
||||
new Spec("McGinleyDynamic", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("MedianAbsoluteDeviation", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("MedianChannel", "multi_f64", new double[]{14.0, 2.0}, 3),
|
||||
new Spec("MedianMa", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("MedianPrice", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("Mfi", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("Microprice", "ob", new double[]{}, 0),
|
||||
new Spec("MidPoint", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("MidPrice", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("MinusDi", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("MinusDm", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("ModifiedMaStop", "multi_candle", new double[]{14.0}, 2),
|
||||
new Spec("Mom", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("MorningDojiStar", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("MorningEveningStar", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("MurreyMathLines", "multi_candle", new double[]{14.0}, 9),
|
||||
new Spec("NakedPoc", "scalar_candle", new double[]{3.0, 7.0}, 0),
|
||||
new Spec("Natr", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("NewHighsNewLows", "cross", new double[]{}, 0),
|
||||
new Spec("NewPriceLines", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("Nrtr", "multi_candle", new double[]{2.0}, 2),
|
||||
new Spec("Nvi", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("OIPriceDivergence", "deriv", new double[]{20.0}, 0),
|
||||
new Spec("OIWeighted", "deriv", new double[]{}, 0),
|
||||
new Spec("Obv", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("OiToVolumeRatio", "deriv", new double[]{}, 0),
|
||||
new Spec("OmegaRatio", "scalar_f64", new double[]{14.0, 2.0}, 0),
|
||||
new Spec("OnNeck", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("OpenInterestDelta", "deriv", new double[]{}, 0),
|
||||
new Spec("OpenInterestMomentum", "deriv", new double[]{10.0}, 0),
|
||||
new Spec("OpeningMarubozu", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("OpeningRange", "multi_candle", new double[]{14.0}, 3),
|
||||
new Spec("OrderBookImbalanceFull", "ob", new double[]{}, 0),
|
||||
new Spec("OrderBookImbalanceTop1", "ob", new double[]{}, 0),
|
||||
new Spec("OrderBookImbalanceTopN", "ob", new double[]{5.0}, 0),
|
||||
new Spec("OrderFlowImbalance", "ob", new double[]{20.0}, 0),
|
||||
new Spec("OuHalfLife", "pairwise", new double[]{14.0}, 0),
|
||||
new Spec("OvernightGap", "scalar_candle", new double[]{0.0}, 0),
|
||||
new Spec("OvernightIntradayReturn", "multi_candle", new double[]{14.0}, 2),
|
||||
new Spec("PainIndex", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("PairSpreadZScore", "pairwise", new double[]{20.0, 20.0}, 0),
|
||||
new Spec("PairwiseBeta", "pairwise", new double[]{14.0}, 0),
|
||||
new Spec("ParkinsonVolatility", "scalar_candle", new double[]{20.0, 252.0}, 0),
|
||||
new Spec("PearsonCorrelation", "pairwise", new double[]{14.0}, 0),
|
||||
new Spec("PercentAboveMa", "cross", new double[]{}, 0),
|
||||
new Spec("PercentB", "scalar_f64", new double[]{14.0, 2.0}, 0),
|
||||
new Spec("PercentageTrailingStop", "scalar_f64", new double[]{2.0}, 0),
|
||||
new Spec("PerpetualPremiumIndex", "deriv", new double[]{}, 0),
|
||||
new Spec("Pgo", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("PiercingDarkCloud", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("Pin", "trade", new double[]{20.0}, 0),
|
||||
new Spec("PivotReversal", "scalar_candle", new double[]{3.0, 7.0}, 0),
|
||||
new Spec("PlusDi", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("PlusDm", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("Pmo", "scalar_f64", new double[]{3.0, 7.0}, 0),
|
||||
new Spec("PointAndFigureBars", "bars_close", new double[]{2.0, 3.0}, 0),
|
||||
new Spec("PolarizedFractalEfficiency", "scalar_f64", new double[]{10.0, 5.0}, 0),
|
||||
new Spec("Ppo", "scalar_f64", new double[]{3.0, 7.0}, 0),
|
||||
new Spec("PpoHistogram", "scalar_f64", new double[]{3.0, 7.0, 14.0}, 0),
|
||||
new Spec("ProfileShape", "scalar_candle", new double[]{3.0, 7.0}, 0),
|
||||
new Spec("ProfitFactor", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("ProjectionBands", "multi_candle", new double[]{14.0}, 3),
|
||||
new Spec("ProjectionOscillator", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("Psar", "scalar_candle", new double[]{0.02, 0.02, 0.2}, 0),
|
||||
new Spec("Pvi", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("Qqe", "multi_f64", new double[]{3.0, 7.0, 2.0}, 2),
|
||||
new Spec("Qstick", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("QuartileBands", "multi_f64", new double[]{14.0}, 3),
|
||||
new Spec("QuotedSpread", "ob", new double[]{}, 0),
|
||||
new Spec("RSquared", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("RangeBars", "bars_close", new double[]{2.0}, 0),
|
||||
new Spec("RealizedSpread", "trademid", new double[]{20.0}, 0),
|
||||
new Spec("RealizedVolatility", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("RecoveryFactor", "scalar_f64", new double[]{}, 0),
|
||||
new Spec("RectangleRange", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("Reflex", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("RegimeLabel", "scalar_f64", new double[]{3.0, 7.0}, 0),
|
||||
new Spec("RelativeStrengthAB", "multi_pairwise", new double[]{14.0, 14.0}, 3),
|
||||
new Spec("RenkoBars", "bars_close", new double[]{2.0}, 0),
|
||||
new Spec("RenkoTrailingStop", "scalar_f64", new double[]{2.0}, 0),
|
||||
new Spec("RickshawMan", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("RisingThreeMethods", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("Rmi", "scalar_f64", new double[]{3.0, 7.0}, 0),
|
||||
new Spec("Roc", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("Rocp", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("Rocr", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("Rocr100", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("RogersSatchellVolatility", "scalar_candle", new double[]{20.0, 252.0}, 0),
|
||||
new Spec("RollMeasure", "trade", new double[]{20.0}, 0),
|
||||
new Spec("RollingCorrelation", "pairwise", new double[]{14.0}, 0),
|
||||
new Spec("RollingCovariance", "pairwise", new double[]{14.0}, 0),
|
||||
new Spec("RollingIqr", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("RollingMinMaxScaler", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("RollingPercentileRank", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("RollingQuantile", "scalar_f64", new double[]{20.0, 0.5}, 0),
|
||||
new Spec("RollingVwap", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("RoofingFilter", "scalar_f64", new double[]{3.0, 7.0}, 0),
|
||||
new Spec("Rsi", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("Rsx", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("RunBars", "bars_candle4", new double[]{3.0}, 0),
|
||||
new Spec("Rvi", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("RviVolatility", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("Rwi", "multi_candle", new double[]{14.0}, 2),
|
||||
new Spec("SampleEntropy", "scalar_f64", new double[]{20.0, 2.0, 0.2}, 0),
|
||||
new Spec("SarExt", "scalar_candle", new double[]{2.0, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5, 0.5}, 0),
|
||||
new Spec("SeasonalZScore", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("SeparatingLines", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("SessionHighLow", "multi_candle", new double[]{14.0}, 2),
|
||||
new Spec("SessionRange", "multi_candle", new double[]{14.0}, 3),
|
||||
new Spec("SessionVwap", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("ShannonEntropy", "scalar_f64", new double[]{3.0, 7.0}, 0),
|
||||
new Spec("Shark", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("SharpeRatio", "scalar_f64", new double[]{14.0, 2.0}, 0),
|
||||
new Spec("ShootingStar", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("ShortLine", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("SignedVolume", "trade", new double[]{}, 0),
|
||||
new Spec("SineWave", "scalar_f64", new double[]{}, 0),
|
||||
new Spec("SineWeightedMa", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("SinglePrints", "scalar_candle", new double[]{3.0, 7.0}, 0),
|
||||
new Spec("Skewness", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("Sma", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("Smi", "scalar_candle", new double[]{3.0, 7.0, 14.0}, 0),
|
||||
new Spec("Smma", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("SmoothedHeikinAshi", "multi_candle", new double[]{14.0}, 4),
|
||||
new Spec("SortinoRatio", "scalar_f64", new double[]{14.0, 2.0}, 0),
|
||||
new Spec("SpearmanCorrelation", "pairwise", new double[]{14.0}, 0),
|
||||
new Spec("SpinningTop", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("SpreadAr1Coefficient", "pairwise", new double[]{14.0}, 0),
|
||||
new Spec("SpreadBollingerBands", "multi_pairwise", new double[]{14.0, 2.0}, 4),
|
||||
new Spec("SpreadHurst", "pairwise", new double[]{14.0}, 0),
|
||||
new Spec("StalledPattern", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("StandardError", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("StandardErrorBands", "multi_f64", new double[]{14.0, 2.0}, 3),
|
||||
new Spec("StarcBands", "multi_candle", new double[]{3.0, 7.0, 2.0}, 3),
|
||||
new Spec("Stc", "scalar_f64", new double[]{10.0, 23.0, 10.0, 0.5}, 0),
|
||||
new Spec("StdDev", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("StepTrailingStop", "scalar_f64", new double[]{2.0}, 0),
|
||||
new Spec("SterlingRatio", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("StickSandwich", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("StochRsi", "scalar_f64", new double[]{3.0, 7.0}, 0),
|
||||
new Spec("Stochastic", "multi_candle", new double[]{3.0, 7.0}, 2),
|
||||
new Spec("StochasticCci", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("SuperSmoother", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("SuperTrend", "multi_candle", new double[]{14.0, 2.0}, 2),
|
||||
new Spec("T3", "scalar_f64", new double[]{5.0, 0.7}, 0),
|
||||
new Spec("TailRatio", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("TakerBuySellRatio", "deriv", new double[]{}, 0),
|
||||
new Spec("Takuri", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("TasukiGap", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("TdCamouflage", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("TdClop", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("TdClopwin", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("TdCombo", "scalar_candle", new double[]{3.0, 7.0, 14.0, 28.0}, 0),
|
||||
new Spec("TdCountdown", "scalar_candle", new double[]{3.0, 7.0, 14.0, 28.0}, 0),
|
||||
new Spec("TdDWave", "scalar_candle", new double[]{2.0}, 0),
|
||||
new Spec("TdDeMarker", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("TdDifferential", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("TdLines", "multi_candle", new double[]{3.0, 7.0}, 2),
|
||||
new Spec("TdMovingAverage", "multi_candle", new double[]{3.0, 7.0}, 2),
|
||||
new Spec("TdOpen", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("TdPressure", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("TdPropulsion", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("TdRangeProjection", "multi_candle", new double[]{}, 2),
|
||||
new Spec("TdRei", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("TdRiskLevel", "multi_candle", new double[]{3.0, 7.0}, 2),
|
||||
new Spec("TdSequential", "multi_candle", new double[]{3.0, 7.0, 14.0, 28.0}, 3),
|
||||
new Spec("TdSetup", "scalar_candle", new double[]{3.0, 7.0}, 0),
|
||||
new Spec("TdTrap", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("Tema", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("TermStructureBasis", "deriv", new double[]{}, 0),
|
||||
new Spec("ThreeDrives", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("ThreeInside", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("ThreeLineBreak", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("ThreeLineBreakBars", "bars_close", new double[]{3.0}, 0),
|
||||
new Spec("ThreeLineStrike", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("ThreeOutside", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("ThreeSoldiersOrCrows", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("ThreeStarsInSouth", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("Thrusting", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("TickBars", "bars_candle5", new double[]{2.0}, 0),
|
||||
new Spec("TickIndex", "cross", new double[]{}, 0),
|
||||
new Spec("Tii", "scalar_f64", new double[]{3.0, 7.0}, 0),
|
||||
new Spec("TimeBasedStop", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("TimeOfDayReturnProfile", "profile_bins", new double[]{24.0, 0.0}, 24),
|
||||
new Spec("TowerTopBottom", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("TpoProfile", "profile_pricebins", new double[]{30.0, 50.0}, 52),
|
||||
new Spec("TradeImbalance", "trade", new double[]{20.0}, 0),
|
||||
new Spec("TradeSignAutocorrelation", "trade", new double[]{20.0}, 0),
|
||||
new Spec("TradeVolumeIndex", "scalar_candle", new double[]{2.0}, 0),
|
||||
new Spec("TrendLabel", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("TrendStrengthIndex", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("Trendflex", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("TreynorRatio", "pairwise", new double[]{14.0, 2.0}, 0),
|
||||
new Spec("Triangle", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("Trima", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("Trin", "cross", new double[]{}, 0),
|
||||
new Spec("TripleTopBottom", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("Tristar", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("Trix", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("TrueRange", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("Tsf", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("TsfOscillator", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("Tsi", "scalar_f64", new double[]{3.0, 7.0}, 0),
|
||||
new Spec("Tsv", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("TtmSqueeze", "multi_candle", new double[]{14.0, 2.0, 0.5}, 2),
|
||||
new Spec("TtmTrend", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("TurnOfMonth", "scalar_candle", new double[]{3.0, 3.0, 0.0}, 0),
|
||||
new Spec("Tweezer", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("TwiggsMoneyFlow", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("TwoCrows", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("TypicalPrice", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("UlcerIndex", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("UltimateOscillator", "scalar_candle", new double[]{3.0, 7.0, 14.0}, 0),
|
||||
new Spec("UniqueThreeRiver", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("UniversalOscillator", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("UpDownVolumeRatio", "cross", new double[]{}, 0),
|
||||
new Spec("UpsideGapThreeMethods", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("UpsideGapTwoCrows", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("UpsidePotentialRatio", "scalar_f64", new double[]{14.0, 2.0}, 0),
|
||||
new Spec("ValueArea", "multi_candle", new double[]{20.0, 50.0, 0.7}, 3),
|
||||
new Spec("ValueAtRisk", "scalar_f64", new double[]{20.0, 0.95}, 0),
|
||||
new Spec("Variance", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("VarianceRatio", "pairwise", new double[]{60.0, 2.0}, 0),
|
||||
new Spec("VerticalHorizontalFilter", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("Vidya", "scalar_f64", new double[]{3.0, 7.0}, 0),
|
||||
new Spec("VolatilityCone", "multi_candle", new double[]{3.0, 7.0}, 5),
|
||||
new Spec("VolatilityOfVolatility", "scalar_f64", new double[]{3.0, 7.0}, 0),
|
||||
new Spec("VolatilityRatio", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("VoltyStop", "scalar_candle", new double[]{14.0, 2.0}, 0),
|
||||
new Spec("VolumeBars", "bars_candle5", new double[]{500.0}, 0),
|
||||
new Spec("VolumeByTimeProfile", "profile_bins", new double[]{24.0, 0.0}, 24),
|
||||
new Spec("VolumeOscillator", "scalar_candle", new double[]{3.0, 7.0}, 0),
|
||||
new Spec("VolumePriceTrend", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("VolumeProfile", "profile_pricebins", new double[]{20.0, 50.0}, 52),
|
||||
new Spec("VolumeRsi", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("VolumeWeightedMacd", "multi_candle", new double[]{3.0, 7.0, 14.0}, 3),
|
||||
new Spec("VolumeWeightedSr", "multi_candle", new double[]{14.0}, 2),
|
||||
new Spec("Vortex", "multi_candle", new double[]{14.0}, 2),
|
||||
new Spec("Vpin", "trade", new double[]{5000.0, 10.0}, 0),
|
||||
new Spec("Vwap", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("VwapStdDevBands", "multi_candle", new double[]{2.0}, 4),
|
||||
new Spec("Vwma", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("Vzo", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("Wad", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("WavePm", "scalar_f64", new double[]{3.0, 7.0}, 0),
|
||||
new Spec("WaveTrend", "multi_candle", new double[]{3.0, 7.0, 14.0}, 2),
|
||||
new Spec("Wedge", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("WeightedClose", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("WickRatio", "scalar_candle", new double[]{}, 0),
|
||||
new Spec("WilliamsFractals", "multi_candle", new double[]{}, 2),
|
||||
new Spec("WilliamsR", "scalar_candle", new double[]{14.0}, 0),
|
||||
new Spec("WinRate", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("Wma", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("WoodiePivots", "multi_candle", new double[]{}, 5),
|
||||
new Spec("YangZhangVolatility", "scalar_candle", new double[]{20.0, 252.0}, 0),
|
||||
new Spec("YoyoExit", "scalar_candle", new double[]{14.0, 2.0}, 0),
|
||||
new Spec("ZScore", "scalar_f64", new double[]{14.0}, 0),
|
||||
new Spec("ZeroLagMacd", "multi_f64", new double[]{3.0, 7.0, 14.0}, 3),
|
||||
new Spec("ZigZag", "multi_candle", new double[]{0.02}, 2),
|
||||
new Spec("Zlema", "scalar_f64", new double[]{14.0}, 0),
|
||||
};
|
||||
|
||||
private static Path goldenDir() {
|
||||
java.io.File d = new java.io.File("").getAbsoluteFile();
|
||||
while (d != null) {
|
||||
java.io.File g = new java.io.File(d, "testdata/golden");
|
||||
if (g.isDirectory()) {
|
||||
return g.toPath();
|
||||
}
|
||||
d = d.getParentFile();
|
||||
}
|
||||
throw new IllegalStateException("testdata/golden not found");
|
||||
}
|
||||
|
||||
private static double cell(String s) {
|
||||
return switch (s) {
|
||||
case "nan" -> Double.NaN;
|
||||
case "inf" -> Double.POSITIVE_INFINITY;
|
||||
case "-inf" -> Double.NEGATIVE_INFINITY;
|
||||
default -> Double.parseDouble(s);
|
||||
};
|
||||
}
|
||||
|
||||
private static double[][] input() throws IOException {
|
||||
List<String> lines = Files.readAllLines(goldenDir().resolve("input.csv"));
|
||||
List<double[]> rows = new ArrayList<>();
|
||||
for (int i = 1; i < lines.size(); i++) {
|
||||
if (lines.get(i).isEmpty()) continue;
|
||||
String[] p = lines.get(i).split(",");
|
||||
double[] r = new double[p.length];
|
||||
for (int j = 0; j < p.length; j++) r[j] = Double.parseDouble(p[j]);
|
||||
rows.add(r);
|
||||
}
|
||||
return rows.toArray(new double[0][]);
|
||||
}
|
||||
|
||||
// Keep blank lines (a candle on which no bar closed) so rows stay aligned.
|
||||
private static double[][] fixture(String name) throws IOException {
|
||||
List<String> lines = Files.readAllLines(goldenDir().resolve("g_" + name + ".csv"));
|
||||
List<double[]> rows = new ArrayList<>();
|
||||
for (int i = 1; i < lines.size(); i++) {
|
||||
String ln = lines.get(i);
|
||||
if (ln.isEmpty()) {
|
||||
rows.add(new double[0]);
|
||||
continue;
|
||||
}
|
||||
String[] p = ln.split(",");
|
||||
double[] r = new double[p.length];
|
||||
for (int j = 0; j < p.length; j++) r[j] = cell(p[j]);
|
||||
rows.add(r);
|
||||
}
|
||||
return rows.toArray(new double[0][]);
|
||||
}
|
||||
|
||||
private static double[] nanRow(int n) {
|
||||
double[] r = new double[n];
|
||||
java.util.Arrays.fill(r, Double.NaN);
|
||||
return r;
|
||||
}
|
||||
|
||||
private static double[] derivFields(double[] r) {
|
||||
double o = r[0], h = r[1], l = r[2], c = r[3], v = r[4];
|
||||
return new double[]{
|
||||
(c - o) / c * 0.01, c, c - 0.5, c + 1.0, v * 10.0, v * 0.6, v * 0.4,
|
||||
v * 0.55, v * 0.45, h - c, c - l,
|
||||
};
|
||||
}
|
||||
|
||||
private static double[] crossList(double[] r, int which) {
|
||||
double o = r[0], c = r[3], v = r[4];
|
||||
double[] out = new double[5];
|
||||
for (int j = 0; j < 5; j++) {
|
||||
out[j] = switch (which) {
|
||||
case 0 -> (c - o) + j;
|
||||
case 1 -> v + j * 10.0;
|
||||
case 2 -> j % 2 == 0 ? 1.0 : 0.0; // newHigh
|
||||
case 3 -> j % 3 == 0 ? 1.0 : 0.0; // newLow
|
||||
case 4 -> j % 2 == 0 ? 1.0 : 0.0; // aboveMa
|
||||
default -> j % 3 == 0 ? 1.0 : 0.0; // onBuySignal
|
||||
};
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static double[] obList(double[] r, int which) {
|
||||
double c = r[3], v = r[4];
|
||||
double[] out = new double[5];
|
||||
for (int k = 0; k < 5; k++) {
|
||||
double kf = k + 1;
|
||||
out[k] = switch (which) {
|
||||
case 0 -> c - 0.1 * kf;
|
||||
case 1 -> v / kf;
|
||||
case 2 -> c + 0.1 * kf;
|
||||
default -> v * 0.9 / kf;
|
||||
};
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
private static double[] flattenRecord(Object o) throws Exception {
|
||||
RecordComponent[] rc = o.getClass().getRecordComponents();
|
||||
List<Double> list = new ArrayList<>();
|
||||
for (RecordComponent c : rc) {
|
||||
Object v = c.getAccessor().invoke(o);
|
||||
if (v instanceof double[] arr) {
|
||||
for (double d : arr) list.add(d);
|
||||
} else if (v instanceof Number num) {
|
||||
list.add(num.doubleValue());
|
||||
}
|
||||
}
|
||||
double[] out = new double[list.size()];
|
||||
for (int i = 0; i < out.length; i++) out[i] = list.get(i);
|
||||
return out;
|
||||
}
|
||||
|
||||
private static double[] flattenArray(Object arr) throws Exception {
|
||||
int len = Array.getLength(arr);
|
||||
List<Double> list = new ArrayList<>();
|
||||
for (int i = 0; i < len; i++) {
|
||||
for (double d : flattenRecord(Array.get(arr, i))) list.add(d);
|
||||
}
|
||||
double[] out = new double[list.size()];
|
||||
for (int i = 0; i < out.length; i++) out[i] = list.get(i);
|
||||
return out;
|
||||
}
|
||||
|
||||
private static Object construct(Spec s) throws Exception {
|
||||
Class<?> cls = Class.forName("org.wickra." + s.canonical());
|
||||
Constructor<?> ctor = cls.getConstructors()[0];
|
||||
Class<?>[] pt = ctor.getParameterTypes();
|
||||
Object[] args = new Object[pt.length];
|
||||
for (int i = 0; i < pt.length; i++) {
|
||||
double v = s.params()[i];
|
||||
if (pt[i] == int.class) args[i] = (int) Math.round(v);
|
||||
else if (pt[i] == long.class) args[i] = (long) Math.round(v);
|
||||
else args[i] = v;
|
||||
}
|
||||
return ctor.newInstance(args);
|
||||
}
|
||||
|
||||
private static Method updateMethod(Object ind) {
|
||||
for (Method m : ind.getClass().getMethods()) {
|
||||
if (m.getName().equals("update")) return m;
|
||||
}
|
||||
throw new IllegalStateException("no update on " + ind.getClass());
|
||||
}
|
||||
|
||||
private static double[] row(Spec s, Object ind, Method upd, double[] r, int i) throws Exception {
|
||||
double o = r[0], h = r[1], l = r[2], c = r[3], v = r[4];
|
||||
Object res = switch (s.arch()) {
|
||||
case "scalar_f64", "multi_f64" -> upd.invoke(ind, c);
|
||||
case "pairwise", "multi_pairwise" -> upd.invoke(ind, c, o);
|
||||
case "scalar_candle", "multi_candle", "profile_bins", "profile_pricebins" ->
|
||||
upd.invoke(ind, o, h, l, c, v, (long) i);
|
||||
case "trade" -> upd.invoke(ind, c, v, c >= o, (long) i);
|
||||
case "trademid" -> upd.invoke(ind, c, v, c >= o, (long) i, (h + l) / 2);
|
||||
case "ob" -> upd.invoke(ind, obList(r, 0), obList(r, 1), obList(r, 2), obList(r, 3));
|
||||
case "cross" -> upd.invoke(ind, crossList(r, 0), crossList(r, 1), crossList(r, 2),
|
||||
crossList(r, 3), crossList(r, 4), crossList(r, 5), (long) i);
|
||||
case "deriv", "deriv_multi" -> {
|
||||
double[] d = derivFields(r);
|
||||
yield upd.invoke(ind, d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7], d[8], d[9], d[10], (long) i);
|
||||
}
|
||||
case "bars_close" -> upd.invoke(ind, c, c, c, c, 1.0, 0L);
|
||||
case "bars_candle4" -> upd.invoke(ind, o, h, l, c, 1.0, 0L);
|
||||
case "bars_candle5" -> upd.invoke(ind, o, h, l, c, v, 0L);
|
||||
case "footprint" -> upd.invoke(ind, c, v, c >= o, (long) i);
|
||||
default -> throw new IllegalStateException("arch " + s.arch());
|
||||
};
|
||||
return switch (s.arch()) {
|
||||
case "scalar_f64", "scalar_candle", "pairwise", "trade", "trademid", "ob", "cross", "deriv" ->
|
||||
new double[]{((Number) res).doubleValue()};
|
||||
case "multi_f64", "multi_candle", "multi_pairwise", "deriv_multi", "profile_pricebins" ->
|
||||
res == null ? nanRow(s.width()) : flattenRecord(res);
|
||||
case "profile_bins" -> res == null ? nanRow(s.width()) : (double[]) res;
|
||||
default -> flattenArray(res); // bars_*, footprint
|
||||
};
|
||||
}
|
||||
|
||||
@TestFactory
|
||||
List<DynamicTest> golden() throws Exception {
|
||||
double[][] rows = input();
|
||||
List<DynamicTest> tests = new ArrayList<>();
|
||||
for (Spec s : SPECS) {
|
||||
tests.add(dynamicTest(s.canonical(), () -> {
|
||||
Object ind = construct(s);
|
||||
Method upd = updateMethod(ind);
|
||||
double[][] exp = fixture(s.canonical());
|
||||
assertTrue(exp.length == rows.length, s.canonical() + ": row count " + exp.length + " vs " + rows.length);
|
||||
for (int i = 0; i < rows.length; i++) {
|
||||
double[] got = row(s, ind, upd, rows[i], i);
|
||||
double[] want = exp[i];
|
||||
assertTrue(got.length == want.length,
|
||||
s.canonical() + " row " + i + ": arity " + got.length + " vs " + want.length);
|
||||
for (int k = 0; k < want.length; k++) {
|
||||
double w = want[k], g = got[k];
|
||||
if (Double.isNaN(w)) {
|
||||
assertTrue(Double.isNaN(g), s.canonical() + " row " + i + " col " + k + ": want NaN got " + g);
|
||||
} else if (Double.isInfinite(w)) {
|
||||
assertTrue(Double.isInfinite(g) && Math.signum(g) == Math.signum(w),
|
||||
s.canonical() + " row " + i + " col " + k + ": want " + w + " got " + g);
|
||||
} else {
|
||||
double tol = TOL * Math.max(1.0, Math.abs(w));
|
||||
assertTrue(Math.abs(g - w) <= tol,
|
||||
s.canonical() + " row " + i + " col " + k + ": got " + g + " want " + w);
|
||||
}
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
return tests;
|
||||
}
|
||||
}
|
||||
@@ -157,4 +157,51 @@ class GoldenTests {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// The four de-duplicated indicators, pinned against the Rust reference.
|
||||
|
||||
@Test
|
||||
void adOscillatorMatchesGolden() throws Exception {
|
||||
double[][] in = input();
|
||||
List<String[]> e = readCsv("ad_oscillator");
|
||||
try (AdOscillator ad = new AdOscillator()) {
|
||||
for (int i = 0; i < in.length; i++) {
|
||||
close(ad.update(in[i][0], in[i][1], in[i][2], in[i][3], in[i][4], i), cell(e.get(i)[0]), i, "ad_oscillator");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void intradayIntensityMatchesGolden() throws Exception {
|
||||
double[][] in = input();
|
||||
List<String[]> e = readCsv("intraday_intensity");
|
||||
try (IntradayIntensity ii = new IntradayIntensity()) {
|
||||
for (int i = 0; i < in.length; i++) {
|
||||
close(ii.update(in[i][0], in[i][1], in[i][2], in[i][3], in[i][4], i), cell(e.get(i)[0]), i, "intraday_intensity");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void awesomeOscillatorHistogramMatchesGolden() throws Exception {
|
||||
double[][] in = input();
|
||||
List<String[]> e = readCsv("awesome_oscillator_histogram");
|
||||
try (AwesomeOscillatorHistogram aoh = new AwesomeOscillatorHistogram(5, 34, 1)) {
|
||||
for (int i = 0; i < in.length; i++) {
|
||||
close(aoh.update(in[i][0], in[i][1], in[i][2], in[i][3], in[i][4], i), cell(e.get(i)[0]), i, "awesome_oscillator_histogram");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void averageDrawdownMatchesGolden() throws Exception {
|
||||
double[][] in = input();
|
||||
List<String[]> e = readCsv("average_drawdown");
|
||||
try (AverageDrawdown avg = new AverageDrawdown(20)) {
|
||||
for (int i = 0; i < in.length; i++) {
|
||||
// generator fed the close column as the equity-curve sample.
|
||||
close(avg.update(in[i][3]), cell(e.get(i)[0]), i, "average_drawdown");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
"""Build node_manifest.json: for every one of the 514 indicators, record the
|
||||
native class name, constructor parameter values, the ordered update argument
|
||||
names (parsed from index.d.ts, each flagged scalar/array) and how to read its
|
||||
output against the shared g_<Canonical>.csv fixtures. Run from repo root."""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
|
||||
DTS = os.path.join(ROOT, "bindings", "node", "index.d.ts")
|
||||
GOLDEN = os.path.join(ROOT, "testdata", "golden")
|
||||
|
||||
|
||||
def camel(snake):
|
||||
head, *rest = snake.split("_")
|
||||
return head + "".join(p[:1].upper() + p[1:] for p in rest)
|
||||
|
||||
|
||||
def parse_args(sig):
|
||||
out = []
|
||||
for p in sig.split(","):
|
||||
p = p.strip()
|
||||
if not p:
|
||||
continue
|
||||
name = p.split(":")[0].strip().rstrip("?")
|
||||
typ = p.split(":", 1)[1].strip() if ":" in p else ""
|
||||
out.append({"name": name, "array": "Array" in typ})
|
||||
return out
|
||||
|
||||
|
||||
dts = open(DTS, encoding="utf-8").read()
|
||||
node_upd = {}
|
||||
for name, body in re.findall(r"export declare class (\w+) \{(.*?)\n\}", dts, re.S):
|
||||
um = re.search(r"\bupdate\(([^)]*)\)", body)
|
||||
node_upd[name] = parse_args(um.group(1)) if um else []
|
||||
|
||||
# constructor params: reuse the values pinned in the Python-side manifests.
|
||||
params = {}
|
||||
for fn in ("scalar_manifest", "multi_manifest"):
|
||||
for e in json.load(open(os.path.join(GOLDEN, fn + ".json"))):
|
||||
params[e["native"]] = e["params"]
|
||||
for fam in json.load(open(os.path.join(GOLDEN, "exotic_manifest.json"))).values():
|
||||
for e in fam:
|
||||
params[e["native"]] = e["params"]
|
||||
for e in json.load(open(os.path.join(GOLDEN, "profile_manifest.json"))):
|
||||
params[e["native"]] = e["params"]
|
||||
for e in json.load(open(os.path.join(GOLDEN, "bars_manifest.json"))):
|
||||
params[e["native"]] = e["params"]
|
||||
|
||||
# the 11 hand-written archetypes (separate, non-g_ fixtures) keep their own test;
|
||||
# everything else is g_<Canonical>.csv. Output shape per native:
|
||||
multi_specs = {e["native"]: e for e in json.load(open(os.path.join(GOLDEN, "multi_manifest.json")))}
|
||||
deriv_multi = {e["native"]: e for e in json.load(open(os.path.join(GOLDEN, "exotic_manifest.json")))["deriv"] if "n" in e}
|
||||
profile_specs = {e["native"]: e for e in json.load(open(os.path.join(GOLDEN, "profile_manifest.json")))}
|
||||
bars_specs = {e["native"]: e for e in json.load(open(os.path.join(GOLDEN, "bars_manifest.json")))}
|
||||
|
||||
# bar output field order (camelCase), mirroring gen_golden's per-bar tuple.
|
||||
BAR_FIELDS = {
|
||||
"RenkoBars": ["open", "close", "direction"],
|
||||
"KagiBars": ["start", "end", "direction"],
|
||||
"PointAndFigureBars": ["direction", "high", "low"],
|
||||
"RangeBars": ["open", "close", "direction"],
|
||||
"ThreeLineBreakBars": ["open", "close", "direction"],
|
||||
"ImbalanceBars": ["open", "high", "low", "close", "imbalance", "direction"],
|
||||
"RunBars": ["open", "high", "low", "close", "length", "direction"],
|
||||
"DollarBars": ["open", "high", "low", "close", "volume", "dollar"],
|
||||
"TickBars": ["open", "high", "low", "close", "volume"],
|
||||
"VolumeBars": ["open", "high", "low", "close", "volume"],
|
||||
"Footprint": ["price", "bidVol", "askVol"],
|
||||
}
|
||||
|
||||
|
||||
def header_fields(canon):
|
||||
with open(os.path.join(GOLDEN, "g_" + canon + ".csv"), encoding="utf-8") as f:
|
||||
return f.readline().strip().split(",")
|
||||
|
||||
|
||||
# canonical -> native, gathered from every Python-side manifest. The g_ fixture
|
||||
# is named by canonical (the Rust struct); the Node class is the native alias.
|
||||
canon_native = {}
|
||||
for fn in ("scalar_manifest", "multi_manifest"):
|
||||
for e in json.load(open(os.path.join(GOLDEN, fn + ".json"))):
|
||||
canon_native[e["canonical"]] = e["native"]
|
||||
for fam in json.load(open(os.path.join(GOLDEN, "exotic_manifest.json"))).values():
|
||||
for e in fam:
|
||||
canon_native[e["canonical"]] = e["native"]
|
||||
for e in json.load(open(os.path.join(GOLDEN, "profile_manifest.json"))):
|
||||
canon_native[e["canonical"]] = e["native"]
|
||||
for e in json.load(open(os.path.join(GOLDEN, "bars_manifest.json"))):
|
||||
canon_native[e["canonical"]] = e["native"]
|
||||
|
||||
out = []
|
||||
for canon in sorted(canon_native):
|
||||
native = canon_native[canon]
|
||||
if native not in node_upd:
|
||||
raise SystemExit(f"node class for {canon} (native {native}) not found in index.d.ts")
|
||||
ctor = params.get(native, [])
|
||||
# EaseOfMovement's volume divisor is an optional Rust constructor argument
|
||||
# (default 1e8) but a required Node constructor parameter; pass it explicitly.
|
||||
if native == "EaseOfMovement":
|
||||
ctor = [ctor[0], 100000000.0]
|
||||
entry = {"canonical": canon, "native": native, "ctor": ctor, "args": node_upd[native]}
|
||||
if native in bars_specs:
|
||||
entry["out"] = "footprint" if native == "Footprint" else "bars"
|
||||
entry["fields"] = BAR_FIELDS[native]
|
||||
elif native in profile_specs:
|
||||
ps = profile_specs[native]
|
||||
entry["out"] = "profile_" + ps["kind"]
|
||||
entry["width"] = ps["width"]
|
||||
if ps["kind"] == "pricebins":
|
||||
entry["arrayField"] = "counts" if native == "TpoProfile" else "bins"
|
||||
elif native in deriv_multi or native in multi_specs:
|
||||
entry["out"] = "multi"
|
||||
entry["fields"] = [camel(c) for c in header_fields(canon)]
|
||||
else:
|
||||
entry["out"] = "scalar"
|
||||
out.append(entry)
|
||||
|
||||
json.dump(out, open(os.path.join(GOLDEN, "node_manifest.json"), "w"), indent=1)
|
||||
print("node_manifest entries:", len(out))
|
||||
from collections import Counter
|
||||
print(Counter(e["out"] for e in out))
|
||||
@@ -0,0 +1,195 @@
|
||||
// Generic golden-fixture parity for the Node binding.
|
||||
//
|
||||
// Every one of the 514 indicators is reconstructed from `node_manifest.json`
|
||||
// (native class, constructor params, ordered update args), fed the synthetic
|
||||
// stream derived from the shared `testdata/golden/input.csv` — the exact same
|
||||
// construction the Rust `gen_golden` binary uses — and checked bit-for-bit
|
||||
// against the Rust-generated `g_<Canonical>.csv`. This pins the Node FFI to the
|
||||
// Rust reference for the whole indicator catalogue, not just a few archetypes.
|
||||
//
|
||||
// cd bindings/node && npm run build && npm test
|
||||
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const wickra = require('..');
|
||||
|
||||
const GOLDEN = path.resolve(__dirname, '..', '..', '..', 'testdata', 'golden');
|
||||
|
||||
function cell(s) {
|
||||
if (s === 'nan') return NaN;
|
||||
if (s === 'inf') return Infinity;
|
||||
if (s === '-inf') return -Infinity;
|
||||
return Number(s);
|
||||
}
|
||||
|
||||
function readCsv(name) {
|
||||
// Split on \r?\n so a CRLF checkout (Windows core.autocrlf) parses identically
|
||||
// to LF — otherwise `cell('inf\r')` falls through to Number() and becomes NaN.
|
||||
const lines = fs.readFileSync(path.join(GOLDEN, name + '.csv'), 'utf8').split(/\r?\n/);
|
||||
lines.shift(); // header
|
||||
return lines.filter((l) => l.length > 0).map((l) => l.split(',').map(cell));
|
||||
}
|
||||
|
||||
// Bars keep blank lines (one row per candle, blank == no bar closed).
|
||||
function readBarRows(name) {
|
||||
const lines = fs.readFileSync(path.join(GOLDEN, name + '.csv'), 'utf8').split(/\r?\n/);
|
||||
lines.shift();
|
||||
// Drop only the single trailing-newline artifact, keeping legitimate blank
|
||||
// rows (a candle on which no bar closed) so rows stay aligned to the input.
|
||||
if (lines.length && lines[lines.length - 1] === '') lines.pop();
|
||||
return lines.map((l) => (l.length === 0 ? [] : l.split(',').map(cell)));
|
||||
}
|
||||
|
||||
const MANIFEST = JSON.parse(fs.readFileSync(path.join(GOLDEN, 'node_manifest.json'), 'utf8'));
|
||||
const ROWS = readCsv('input');
|
||||
|
||||
function derivFields(o, h, l, c, v) {
|
||||
return {
|
||||
fundingRate: ((c - o) / c) * 0.01,
|
||||
markPrice: c,
|
||||
indexPrice: c - 0.5,
|
||||
futuresPrice: c + 1.0,
|
||||
openInterest: v * 10.0,
|
||||
longSize: v * 0.6,
|
||||
shortSize: v * 0.4,
|
||||
takerBuyVolume: v * 0.55,
|
||||
takerSellVolume: v * 0.45,
|
||||
longLiquidation: h - c,
|
||||
shortLiquidation: c - l,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveArg(arg, o, h, l, c, v, i) {
|
||||
const name = arg.name;
|
||||
if (arg.array) {
|
||||
switch (name) {
|
||||
case 'change':
|
||||
return [0, 1, 2, 3, 4].map((j) => c - o + j);
|
||||
case 'volume':
|
||||
return [0, 1, 2, 3, 4].map((j) => v + j * 10.0);
|
||||
case 'newHigh':
|
||||
return [0, 1, 2, 3, 4].map((j) => j % 2 === 0);
|
||||
case 'newLow':
|
||||
return [0, 1, 2, 3, 4].map((j) => j % 3 === 0);
|
||||
case 'aboveMa':
|
||||
return [0, 1, 2, 3, 4].map((j) => j % 2 === 0);
|
||||
case 'onBuySignal':
|
||||
return [0, 1, 2, 3, 4].map((j) => j % 3 === 0);
|
||||
case 'bidPx':
|
||||
return [0, 1, 2, 3, 4].map((k) => c - 0.1 * (k + 1));
|
||||
case 'bidSz':
|
||||
return [0, 1, 2, 3, 4].map((k) => v / (k + 1));
|
||||
case 'askPx':
|
||||
return [0, 1, 2, 3, 4].map((k) => c + 0.1 * (k + 1));
|
||||
case 'askSz':
|
||||
return [0, 1, 2, 3, 4].map((k) => (v * 0.9) / (k + 1));
|
||||
default:
|
||||
throw new Error('unknown array arg ' + name);
|
||||
}
|
||||
}
|
||||
switch (name) {
|
||||
case 'value':
|
||||
case 'close':
|
||||
case 'price':
|
||||
case 'x':
|
||||
case 'a':
|
||||
case 'asset':
|
||||
return c;
|
||||
case 'y':
|
||||
case 'b':
|
||||
case 'benchmark':
|
||||
case 'open':
|
||||
return o;
|
||||
case 'high':
|
||||
return h;
|
||||
case 'low':
|
||||
return l;
|
||||
case 'volume':
|
||||
case 'size':
|
||||
return v;
|
||||
case 'timestamp':
|
||||
return i;
|
||||
case 'isBuy':
|
||||
return c >= o;
|
||||
case 'mid':
|
||||
return (h + l) / 2.0;
|
||||
default: {
|
||||
const d = derivFields(o, h, l, c, v);
|
||||
if (name in d) return d[name];
|
||||
throw new Error('unknown scalar arg ' + name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function closeEq(got, want, label) {
|
||||
if (Number.isNaN(want)) {
|
||||
assert.ok(Number.isNaN(got), `${label}: want NaN got ${got}`);
|
||||
return;
|
||||
}
|
||||
if (!Number.isFinite(want)) {
|
||||
assert.ok(got === want, `${label}: want ${want} got ${got}`);
|
||||
return;
|
||||
}
|
||||
const tol = 1e-6 * Math.max(1.0, Math.abs(want));
|
||||
assert.ok(Math.abs(got - want) <= tol, `${label}: got ${got} want ${want}`);
|
||||
}
|
||||
|
||||
for (const spec of MANIFEST) {
|
||||
test(`golden: ${spec.canonical}`, () => {
|
||||
const Cls = wickra[spec.native];
|
||||
assert.ok(Cls, `missing Node class ${spec.native}`);
|
||||
const ind = new Cls(...spec.ctor);
|
||||
const isBars = spec.out === 'bars' || spec.out === 'footprint';
|
||||
const expected = isBars ? readBarRows('g_' + spec.canonical) : readCsv('g_' + spec.canonical);
|
||||
|
||||
for (let i = 0; i < ROWS.length; i++) {
|
||||
const [o, h, l, c, v] = ROWS[i];
|
||||
const args = spec.args.map((a) => resolveArg(a, o, h, l, c, v, i));
|
||||
const got = ind.update(...args);
|
||||
const want = expected[i];
|
||||
const label = `${spec.canonical} row ${i}`;
|
||||
|
||||
if (spec.out === 'scalar') {
|
||||
closeEq(got === null || got === undefined ? NaN : got, want[0], label);
|
||||
} else if (spec.out === 'multi') {
|
||||
if (got === null || got === undefined) {
|
||||
assert.ok(want.every(Number.isNaN), `${label}: want ${want} got null`);
|
||||
continue;
|
||||
}
|
||||
// napi serialises the output struct's fields in declaration order, which
|
||||
// matches the CSV column order — compare positionally to avoid relying on
|
||||
// the exact camelCase of each field name.
|
||||
const vals = Object.values(got);
|
||||
assert.equal(vals.length, want.length, `${label}: arity ${vals.length} vs ${want.length}`);
|
||||
vals.forEach((gv, k) => {
|
||||
closeEq(gv === null || gv === undefined ? NaN : gv, want[k], `${label} col ${k}`);
|
||||
});
|
||||
} else if (spec.out === 'profile_bins') {
|
||||
if (got === null || got === undefined) {
|
||||
assert.ok(want.every(Number.isNaN), `${label}: want all-NaN got null`);
|
||||
continue;
|
||||
}
|
||||
assert.equal(got.length, want.length, `${label}: width ${got.length} vs ${want.length}`);
|
||||
got.forEach((gv, k) => closeEq(gv, want[k], `${label} bin ${k}`));
|
||||
} else if (spec.out === 'profile_pricebins') {
|
||||
if (got === null || got === undefined) {
|
||||
assert.ok(want.every(Number.isNaN), `${label}: want all-NaN got null`);
|
||||
continue;
|
||||
}
|
||||
const flat = [got.priceLow, got.priceHigh, ...got[spec.arrayField]];
|
||||
assert.equal(flat.length, want.length, `${label}: width ${flat.length} vs ${want.length}`);
|
||||
flat.forEach((gv, k) => closeEq(gv, want[k], `${label} col ${k}`));
|
||||
} else {
|
||||
// bars / footprint: flatten array-of-objects in declared field order.
|
||||
const flat = [];
|
||||
for (const bar of got) {
|
||||
for (const f of spec.fields) flat.push(Number(bar[f]));
|
||||
}
|
||||
assert.equal(flat.length, want.length, `${label}: arity ${flat.length} vs ${want.length}`);
|
||||
flat.forEach((gv, k) => closeEq(gv, want[k], `${label} col ${k}`));
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -259,7 +259,7 @@ const candleScalar = {
|
||||
VolumeOscillator: { make: () => new wickra.VolumeOscillator(14, 28), step: (ind, i) => ind.update(volume[i]), batch: (ind) => ind.batch(volume) },
|
||||
NVI: { make: () => new wickra.NVI(), step: (ind, i) => ind.update(close[i], volume[i]), batch: (ind) => ind.batch(close, volume) },
|
||||
PVI: { make: () => new wickra.PVI(), step: (ind, i) => ind.update(close[i], volume[i]), batch: (ind) => ind.batch(close, volume) },
|
||||
WilliamsAD: { make: () => new wickra.WilliamsAD(), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
|
||||
ADOSC: { make: () => new wickra.ADOSC(), step: (ind, i) => ind.update(high[i], low[i], close[i]), batch: (ind) => ind.batch(high, low, close) },
|
||||
AnchoredVWAP: { make: () => new wickra.AnchoredVWAP(), step: (ind, i) => ind.update(high[i], low[i], close[i], volume[i]), batch: (ind) => ind.batch(high, low, close, volume) },
|
||||
DemandIndex: { make: () => new wickra.DemandIndex(10), step: (ind, i) => ind.update(high[i], low[i], close[i], volume[i]), batch: (ind) => ind.batch(high, low, close, volume) },
|
||||
TSV: { make: () => new wickra.TSV(18), step: (ind, i) => ind.update(close[i], volume[i]), batch: (ind) => ind.batch(close, volume) },
|
||||
@@ -1009,8 +1009,8 @@ test('AwesomeOscillatorHistogram on a flat median converges to zero', () => {
|
||||
Array(n).fill(11),
|
||||
Array(n).fill(9),
|
||||
);
|
||||
// warmup = 5 + 3 - 1 = 7.
|
||||
for (let i = 6; i < n; i++) assert.ok(Math.abs(out[i]) < 1e-12);
|
||||
// AO momentum; warmup = slow + lookback = 5 + 3 = 8.
|
||||
for (let i = 7; i < n; i++) assert.ok(Math.abs(out[i]) < 1e-12);
|
||||
});
|
||||
|
||||
test('STC on a flat series stays at zero', () => {
|
||||
|
||||
Vendored
+2
-2
@@ -2505,8 +2505,8 @@ export declare class KVO {
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type AdOscillatorNode = WilliamsAD
|
||||
export declare class WilliamsAD {
|
||||
export type AdOscillatorNode = ADOSC
|
||||
export declare class ADOSC {
|
||||
constructor()
|
||||
update(high: number, low: number, close: number): number | null
|
||||
batch(high: Array<number>, low: Array<number>, close: Array<number>): Array<number>
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wickra-darwin-arm64",
|
||||
"version": "0.9.1",
|
||||
"version": "0.9.2",
|
||||
"description": "Native binding for wickra (macOS Apple Silicon). Installed automatically as an optional dependency of wickra on matching platforms.",
|
||||
"main": "wickra.darwin-arm64.node",
|
||||
"files": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wickra-darwin-x64",
|
||||
"version": "0.9.1",
|
||||
"version": "0.9.2",
|
||||
"description": "Native binding for wickra (macOS Intel). Installed automatically as an optional dependency of wickra on matching platforms.",
|
||||
"main": "wickra.darwin-x64.node",
|
||||
"files": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wickra-linux-arm64-gnu",
|
||||
"version": "0.9.1",
|
||||
"version": "0.9.2",
|
||||
"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",
|
||||
"files": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wickra-linux-x64-gnu",
|
||||
"version": "0.9.1",
|
||||
"version": "0.9.2",
|
||||
"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",
|
||||
"files": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wickra-win32-arm64-msvc",
|
||||
"version": "0.9.1",
|
||||
"version": "0.9.2",
|
||||
"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",
|
||||
"files": [
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wickra-win32-x64-msvc",
|
||||
"version": "0.9.1",
|
||||
"version": "0.9.2",
|
||||
"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",
|
||||
"files": [
|
||||
|
||||
Generated
+20
-20
@@ -1,12 +1,12 @@
|
||||
{
|
||||
"name": "wickra",
|
||||
"version": "0.9.1",
|
||||
"version": "0.9.2",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "wickra",
|
||||
"version": "0.9.1",
|
||||
"version": "0.9.2",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "^2.18.0"
|
||||
@@ -15,12 +15,12 @@
|
||||
"node": ">= 20"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"wickra-darwin-arm64": "0.9.1",
|
||||
"wickra-darwin-x64": "0.9.1",
|
||||
"wickra-linux-arm64-gnu": "0.9.1",
|
||||
"wickra-linux-x64-gnu": "0.9.1",
|
||||
"wickra-win32-arm64-msvc": "0.9.1",
|
||||
"wickra-win32-x64-msvc": "0.9.1"
|
||||
"wickra-darwin-arm64": "0.9.2",
|
||||
"wickra-darwin-x64": "0.9.2",
|
||||
"wickra-linux-arm64-gnu": "0.9.2",
|
||||
"wickra-linux-x64-gnu": "0.9.2",
|
||||
"wickra-win32-arm64-msvc": "0.9.2",
|
||||
"wickra-win32-x64-msvc": "0.9.2"
|
||||
}
|
||||
},
|
||||
"node_modules/@napi-rs/cli": {
|
||||
@@ -41,8 +41,8 @@
|
||||
}
|
||||
},
|
||||
"node_modules/wickra-darwin-arm64": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/wickra-darwin-arm64/-/wickra-darwin-arm64-0.9.1.tgz",
|
||||
"version": "0.9.2",
|
||||
"resolved": "https://registry.npmjs.org/wickra-darwin-arm64/-/wickra-darwin-arm64-0.9.2.tgz",
|
||||
"integrity": "sha512-4eZiBR/yGUdr4nzhEUFy2i69XgNx64iI2ax/LPamsThgylC0KpHOZKK19QzJ2d9KbK4C8nMjME5FLuR+4GNEwQ==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
@@ -57,8 +57,8 @@
|
||||
}
|
||||
},
|
||||
"node_modules/wickra-darwin-x64": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/wickra-darwin-x64/-/wickra-darwin-x64-0.9.1.tgz",
|
||||
"version": "0.9.2",
|
||||
"resolved": "https://registry.npmjs.org/wickra-darwin-x64/-/wickra-darwin-x64-0.9.2.tgz",
|
||||
"integrity": "sha512-6hf8zI3QPjTFp4zCpmgUwDvNtu6jHqNUHKD5e55POo0CgA52HkpyxSPtVm8TGTIZDI7kPjlbOdBM8CJ76mmXwA==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
@@ -73,8 +73,8 @@
|
||||
}
|
||||
},
|
||||
"node_modules/wickra-linux-arm64-gnu": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/wickra-linux-arm64-gnu/-/wickra-linux-arm64-gnu-0.9.1.tgz",
|
||||
"version": "0.9.2",
|
||||
"resolved": "https://registry.npmjs.org/wickra-linux-arm64-gnu/-/wickra-linux-arm64-gnu-0.9.2.tgz",
|
||||
"integrity": "sha512-kSe6y0xBMSiqdPLXNjwop5WZdHtvdBNKSEBCwZ4hFq33p4apW25/wrlzv9/oDuyD4kuPabJEhCCnFOplh58CUg==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
@@ -89,8 +89,8 @@
|
||||
}
|
||||
},
|
||||
"node_modules/wickra-linux-x64-gnu": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/wickra-linux-x64-gnu/-/wickra-linux-x64-gnu-0.9.1.tgz",
|
||||
"version": "0.9.2",
|
||||
"resolved": "https://registry.npmjs.org/wickra-linux-x64-gnu/-/wickra-linux-x64-gnu-0.9.2.tgz",
|
||||
"integrity": "sha512-tWBWS4qz7hxM4xnpFb59bhf6TaLwXq0Z3jEa/2l7r8PiHA94g8r8S53NRMiT+4yiL5hSWe/nUiC/YXdRrhEZ4g==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
@@ -105,8 +105,8 @@
|
||||
}
|
||||
},
|
||||
"node_modules/wickra-win32-arm64-msvc": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/wickra-win32-arm64-msvc/-/wickra-win32-arm64-msvc-0.9.1.tgz",
|
||||
"version": "0.9.2",
|
||||
"resolved": "https://registry.npmjs.org/wickra-win32-arm64-msvc/-/wickra-win32-arm64-msvc-0.9.2.tgz",
|
||||
"integrity": "sha512-EXIckHxAtF75PUGDKRzXyqMe9ldP0JjSdu68WFN6iJfp+McYrGu6h40TEJlQ/oUEIoPqiZB/xhVyo/el5Lg7zw==",
|
||||
"cpu": [
|
||||
"arm64"
|
||||
@@ -121,8 +121,8 @@
|
||||
}
|
||||
},
|
||||
"node_modules/wickra-win32-x64-msvc": {
|
||||
"version": "0.9.1",
|
||||
"resolved": "https://registry.npmjs.org/wickra-win32-x64-msvc/-/wickra-win32-x64-msvc-0.9.1.tgz",
|
||||
"version": "0.9.2",
|
||||
"resolved": "https://registry.npmjs.org/wickra-win32-x64-msvc/-/wickra-win32-x64-msvc-0.9.2.tgz",
|
||||
"integrity": "sha512-Yfsqq1Xwp6hdxMyLze411vNdo7BDwI6+lPSe7A9XdqyPecNDbtKwYLpsal2r8EHbNzqM+R8XnuRtUaEQS5VlUQ==",
|
||||
"cpu": [
|
||||
"x64"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "wickra",
|
||||
"version": "0.9.1",
|
||||
"version": "0.9.2",
|
||||
"description": "Streaming-first technical indicators: incremental, fast, install-free. Node bindings powered by Rust.",
|
||||
"author": "kingchenc <support@wickra.org>",
|
||||
"main": "index.js",
|
||||
@@ -47,12 +47,12 @@
|
||||
"node": ">= 20"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"wickra-linux-x64-gnu": "0.9.1",
|
||||
"wickra-linux-arm64-gnu": "0.9.1",
|
||||
"wickra-darwin-x64": "0.9.1",
|
||||
"wickra-darwin-arm64": "0.9.1",
|
||||
"wickra-win32-x64-msvc": "0.9.1",
|
||||
"wickra-win32-arm64-msvc": "0.9.1"
|
||||
"wickra-linux-x64-gnu": "0.9.2",
|
||||
"wickra-linux-arm64-gnu": "0.9.2",
|
||||
"wickra-darwin-x64": "0.9.2",
|
||||
"wickra-darwin-arm64": "0.9.2",
|
||||
"wickra-win32-x64-msvc": "0.9.2",
|
||||
"wickra-win32-arm64-msvc": "0.9.2"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "napi build --platform --release",
|
||||
|
||||
@@ -5926,7 +5926,7 @@ impl KvoNode {
|
||||
|
||||
// ============================== Williams A/D ==============================
|
||||
|
||||
#[napi(js_name = "WilliamsAD")]
|
||||
#[napi(js_name = "ADOSC")]
|
||||
pub struct AdOscillatorNode {
|
||||
inner: wc::AdOscillator,
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ build-backend = "maturin"
|
||||
|
||||
[project]
|
||||
name = "wickra"
|
||||
version = "0.9.1"
|
||||
version = "0.9.2"
|
||||
description = "Streaming-first technical indicators: incremental, fast, install-free."
|
||||
readme = "README.md"
|
||||
license = "MIT OR Apache-2.0"
|
||||
|
||||
@@ -236,7 +236,7 @@ from ._wickra import (
|
||||
VolumeOscillator,
|
||||
NVI,
|
||||
PVI,
|
||||
WilliamsAD,
|
||||
ADOSC,
|
||||
AnchoredVWAP,
|
||||
DemandIndex,
|
||||
TSV,
|
||||
@@ -780,7 +780,7 @@ __all__ = [
|
||||
"VolumeOscillator",
|
||||
"NVI",
|
||||
"PVI",
|
||||
"WilliamsAD",
|
||||
"ADOSC",
|
||||
"AnchoredVWAP",
|
||||
"DemandIndex",
|
||||
"TSV",
|
||||
|
||||
@@ -9450,7 +9450,7 @@ impl PyKvo {
|
||||
|
||||
// ============================== Williams A/D Oscillator ==============================
|
||||
|
||||
#[pyclass(name = "WilliamsAD", module = "wickra._wickra", skip_from_py_object)]
|
||||
#[pyclass(name = "ADOSC", module = "wickra._wickra", skip_from_py_object)]
|
||||
#[derive(Clone)]
|
||||
struct PyAdOscillator {
|
||||
inner: wc::AdOscillator,
|
||||
@@ -9507,7 +9507,7 @@ impl PyAdOscillator {
|
||||
self.inner.warmup_period()
|
||||
}
|
||||
fn __repr__(&self) -> String {
|
||||
"WilliamsAD()".to_string()
|
||||
"ADOSC()".to_string()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,70 @@
|
||||
"""Generic golden replay for the alt-chart bar builders and the footprint.
|
||||
|
||||
Each builder turns one candle into 0..n completed bars, so the fixture stores
|
||||
one CSV line per input candle holding every bar flattened (an empty line means
|
||||
no bar closed on that candle). Close-driven builders (Renko, Kagi, P&F, Range,
|
||||
Three-Line-Break) receive a flat candle, mirroring the binding's `update(close)`.
|
||||
Values are checked bit-for-bit against the Rust-generated `g_<Canonical>.csv`.
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import wickra as ta
|
||||
|
||||
HERE = os.path.dirname(__file__)
|
||||
GOLDEN = os.path.normpath(os.path.join(HERE, "..", "..", "..", "testdata", "golden"))
|
||||
|
||||
|
||||
def _cell(s):
|
||||
return math.nan if s == "nan" else float(s)
|
||||
|
||||
|
||||
def _bar_rows(name):
|
||||
# Keep blank lines: one row per input candle, blank == no bar closed.
|
||||
with open(os.path.join(GOLDEN, name + ".csv")) as f:
|
||||
lines = f.read().splitlines()[1:]
|
||||
return [[] if not ln.strip() else [_cell(x) for x in ln.split(",")] for ln in lines]
|
||||
|
||||
|
||||
def _input():
|
||||
with open(os.path.join(GOLDEN, "input.csv")) as f:
|
||||
return [[float(x) for x in line.split(",")] for line in f.read().splitlines()[1:] if line.strip()]
|
||||
|
||||
|
||||
with open(os.path.join(GOLDEN, "bars_manifest.json")) as _mf:
|
||||
MANIFEST = json.load(_mf)
|
||||
ROWS = _input()
|
||||
|
||||
|
||||
def _flatten(bars):
|
||||
out = []
|
||||
for bar in bars:
|
||||
out.extend(float(x) for x in bar)
|
||||
return out
|
||||
|
||||
|
||||
@pytest.mark.parametrize("spec", MANIFEST, ids=[m["canonical"] for m in MANIFEST])
|
||||
def test_bars_match_golden(spec):
|
||||
ind = getattr(ta, spec["native"])(*spec["params"])
|
||||
expected = _bar_rows("g_" + spec["canonical"])
|
||||
feed = spec["feed"]
|
||||
assert len(expected) == len(ROWS), f"{spec['canonical']}: {len(expected)} rows vs {len(ROWS)} inputs"
|
||||
for i, (o, h, l, c, v) in enumerate(ROWS):
|
||||
if feed == "close":
|
||||
produced = ind.update(c)
|
||||
elif feed == "candle4":
|
||||
produced = ind.update(o, h, l, c)
|
||||
elif feed == "candle5":
|
||||
produced = ind.update(o, h, l, c, v)
|
||||
else: # trade footprint
|
||||
produced = ind.update(c, v, c >= o)
|
||||
got = _flatten(produced)
|
||||
want = expected[i]
|
||||
assert len(got) == len(want), f"{spec['canonical']} row {i}: arity {len(got)} vs {len(want)}"
|
||||
for gv, w in zip(got, want):
|
||||
if math.isnan(w):
|
||||
assert math.isnan(gv), f"{spec['canonical']} row {i}: want NaN got {gv}"
|
||||
else:
|
||||
assert abs(gv - w) <= 1e-6 * max(1.0, abs(w)), f"{spec['canonical']} row {i}: got {gv} want {w}"
|
||||
@@ -0,0 +1,128 @@
|
||||
"""Generic golden replay for the exotic-input families: DerivativesTick,
|
||||
CrossSection, Trade, TradeQuote and OrderBook indicators.
|
||||
|
||||
Each family feeds a synthetic stream deterministically derived from the shared
|
||||
`testdata/golden/input.csv` OHLCV rows — the exact same construction the Rust
|
||||
`gen_golden` binary uses — and every value is checked bit-for-bit against the
|
||||
Rust-generated `g_<Canonical>.csv`. This pins the Python FFI for indicators
|
||||
whose inputs cannot be expressed as a plain close/candle/pair stream.
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import wickra as ta
|
||||
|
||||
HERE = os.path.dirname(__file__)
|
||||
GOLDEN = os.path.normpath(os.path.join(HERE, "..", "..", "..", "testdata", "golden"))
|
||||
|
||||
|
||||
def _cell(s):
|
||||
return math.nan if s == "nan" else float(s)
|
||||
|
||||
|
||||
def _rows(name):
|
||||
with open(os.path.join(GOLDEN, name + ".csv")) as f:
|
||||
return [[_cell(x) for x in line.split(",")] for line in f.read().splitlines()[1:] if line.strip()]
|
||||
|
||||
|
||||
def _input():
|
||||
with open(os.path.join(GOLDEN, "input.csv")) as f:
|
||||
return [[float(x) for x in line.split(",")] for line in f.read().splitlines()[1:] if line.strip()]
|
||||
|
||||
|
||||
with open(os.path.join(GOLDEN, "exotic_manifest.json")) as _mf:
|
||||
MANIFEST = json.load(_mf)
|
||||
ROWS = _input()
|
||||
|
||||
|
||||
def _deriv_fields(o, h, l, c, v):
|
||||
return {
|
||||
"funding_rate": (c - o) / c * 0.01,
|
||||
"mark_price": c,
|
||||
"index_price": c - 0.5,
|
||||
"futures_price": c + 1.0,
|
||||
"open_interest": v * 10.0,
|
||||
"long_size": v * 0.6,
|
||||
"short_size": v * 0.4,
|
||||
"taker_buy_volume": v * 0.55,
|
||||
"taker_sell_volume": v * 0.45,
|
||||
"long_liquidation": h - c,
|
||||
"short_liquidation": c - l,
|
||||
}
|
||||
|
||||
|
||||
def _cross_lists(o, h, l, c, v):
|
||||
change = [(c - o) + j for j in range(5)]
|
||||
volume = [v + j * 10.0 for j in range(5)]
|
||||
new_high = [j % 2 == 0 for j in range(5)]
|
||||
new_low = [j % 3 == 0 for j in range(5)]
|
||||
above_ma = [j % 2 == 0 for j in range(5)]
|
||||
on_buy_signal = [j % 3 == 0 for j in range(5)]
|
||||
return change, volume, new_high, new_low, above_ma, on_buy_signal
|
||||
|
||||
|
||||
def _ob_lists(o, h, l, c, v):
|
||||
bid_px = [c - 0.1 * (k + 1) for k in range(5)]
|
||||
bid_sz = [v / (k + 1) for k in range(5)]
|
||||
ask_px = [c + 0.1 * (k + 1) for k in range(5)]
|
||||
ask_sz = [v * 0.9 / (k + 1) for k in range(5)]
|
||||
return bid_px, bid_sz, ask_px, ask_sz
|
||||
|
||||
|
||||
def _assert_scalar(got, want, canonical, i):
|
||||
got = math.nan if got is None else got
|
||||
if math.isnan(want):
|
||||
assert math.isnan(got), f"{canonical} row {i}: want NaN got {got}"
|
||||
elif math.isinf(want):
|
||||
assert math.isinf(got) and (got > 0) == (want > 0), f"{canonical} row {i}: got {got} want {want}"
|
||||
else:
|
||||
assert abs(got - want) <= 1e-6 * max(1.0, abs(want)), f"{canonical} row {i}: got {got} want {want}"
|
||||
|
||||
|
||||
def _specs(family):
|
||||
return [(family, s) for s in MANIFEST[family]]
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"family,spec",
|
||||
_specs("deriv") + _specs("cross") + _specs("trade") + _specs("trademid") + _specs("ob"),
|
||||
ids=[s["canonical"] for fam in ("deriv", "cross", "trade", "trademid", "ob") for s in MANIFEST[fam]],
|
||||
)
|
||||
def test_exotic_matches_golden(family, spec):
|
||||
ind = getattr(ta, spec["native"])(*spec["params"])
|
||||
expected = _rows("g_" + spec["canonical"])
|
||||
n = spec.get("n")
|
||||
for i, (o, h, l, c, v) in enumerate(ROWS):
|
||||
if family == "deriv":
|
||||
f = _deriv_fields(o, h, l, c, v)
|
||||
got = ind.update(*[f[a] for a in spec["args"]])
|
||||
elif family == "cross":
|
||||
change, volume, nh, nl, above_ma, on_buy = _cross_lists(o, h, l, c, v)
|
||||
extra = spec.get("extra")
|
||||
if extra == "above_ma":
|
||||
got = ind.update(change, volume, nh, nl, above_ma)
|
||||
elif extra == "on_buy_signal":
|
||||
got = ind.update(change, volume, nh, nl, on_buy)
|
||||
else:
|
||||
got = ind.update(change, volume, nh, nl)
|
||||
elif family == "trade":
|
||||
got = ind.update(c, v, c >= o)
|
||||
elif family == "trademid":
|
||||
got = ind.update(c, v, c >= o, (h + l) / 2.0)
|
||||
else: # ob
|
||||
bid_px, bid_sz, ask_px, ask_sz = _ob_lists(o, h, l, c, v)
|
||||
got = ind.update(bid_px, bid_sz, ask_px, ask_sz)
|
||||
|
||||
want = expected[i]
|
||||
if n: # multi-output (LiquidationFeatures)
|
||||
if got is None:
|
||||
assert all(math.isnan(w) for w in want), f"{spec['canonical']} row {i}: want {want} got None"
|
||||
continue
|
||||
vals = list(got)
|
||||
assert len(vals) == len(want), f"{spec['canonical']} row {i}: arity {len(vals)} vs {len(want)}"
|
||||
for gv, w in zip(vals, want):
|
||||
_assert_scalar(gv, w, spec["canonical"], i)
|
||||
else:
|
||||
_assert_scalar(got, want[0], spec["canonical"], i)
|
||||
@@ -0,0 +1,60 @@
|
||||
"""Generic golden replay for multi-output indicators: each entry in
|
||||
multi_manifest.json is reconstructed by its native name and every output field
|
||||
is checked against the Rust-generated g_<Canonical>.csv (one column per field).
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import wickra as ta
|
||||
|
||||
HERE = os.path.dirname(__file__)
|
||||
GOLDEN = os.path.normpath(os.path.join(HERE, "..", "..", "..", "testdata", "golden"))
|
||||
|
||||
|
||||
def _cell(s):
|
||||
return math.nan if s == "nan" else float(s)
|
||||
|
||||
|
||||
def _rows(name):
|
||||
with open(os.path.join(GOLDEN, name + ".csv")) as f:
|
||||
return [[_cell(x) for x in line.split(",")] for line in f.read().splitlines()[1:] if line.strip()]
|
||||
|
||||
|
||||
def _input():
|
||||
with open(os.path.join(GOLDEN, "input.csv")) as f:
|
||||
return [[float(x) for x in line.split(",")] for line in f.read().splitlines()[1:] if line.strip()]
|
||||
|
||||
|
||||
with open(os.path.join(GOLDEN, "multi_manifest.json")) as _mf:
|
||||
MANIFEST = json.load(_mf)
|
||||
ROWS = _input()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("spec", MANIFEST, ids=[m["canonical"] for m in MANIFEST])
|
||||
def test_multi_matches_golden(spec):
|
||||
ind = getattr(ta, spec["native"])(*spec["params"])
|
||||
expected = _rows("g_" + spec["canonical"])
|
||||
inp = spec["input"]
|
||||
for i, (o, h, l, c, v) in enumerate(ROWS):
|
||||
if inp == "f64":
|
||||
got = ind.update(c)
|
||||
elif inp == "Candle":
|
||||
got = ind.update((o, h, l, c, v, i))
|
||||
else:
|
||||
got = ind.update(c, o)
|
||||
want = expected[i]
|
||||
if got is None:
|
||||
assert all(math.isnan(w) for w in want), f"{spec['canonical']} row {i}: want {want} got None"
|
||||
continue
|
||||
vals = list(got)
|
||||
assert len(vals) == len(want), f"{spec['canonical']} row {i}: arity {len(vals)} vs {len(want)}"
|
||||
for gv, w in zip(vals, want):
|
||||
gv = math.nan if gv is None else gv
|
||||
if math.isnan(w):
|
||||
assert math.isnan(gv), f"{spec['canonical']} row {i}: want NaN got {gv}"
|
||||
elif math.isinf(w):
|
||||
assert math.isinf(gv) and (gv > 0) == (w > 0)
|
||||
else:
|
||||
assert abs(gv - w) <= 1e-6 * max(1.0, abs(w)), f"{spec['canonical']} row {i}: got {gv} want {w}"
|
||||
@@ -0,0 +1,61 @@
|
||||
"""Generic golden replay for the profile family: time/volume seasonality
|
||||
histograms (`bins`) and price-binned market profiles (`price_low, price_high,
|
||||
bins`). Each profile emits a fixed-width row once warm and `NaN`s during warmup.
|
||||
|
||||
The shared `testdata/golden/input.csv` candle series is replayed through the
|
||||
Python FFI and the flattened histogram is checked bit-for-bit against the
|
||||
Rust-generated `g_<Canonical>.csv`.
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
|
||||
import pytest
|
||||
import wickra as ta
|
||||
|
||||
HERE = os.path.dirname(__file__)
|
||||
GOLDEN = os.path.normpath(os.path.join(HERE, "..", "..", "..", "testdata", "golden"))
|
||||
|
||||
|
||||
def _cell(s):
|
||||
return math.nan if s == "nan" else float(s)
|
||||
|
||||
|
||||
def _rows(name):
|
||||
with open(os.path.join(GOLDEN, name + ".csv")) as f:
|
||||
return [[_cell(x) for x in line.split(",")] for line in f.read().splitlines()[1:] if line.strip()]
|
||||
|
||||
|
||||
def _input():
|
||||
with open(os.path.join(GOLDEN, "input.csv")) as f:
|
||||
return [[float(x) for x in line.split(",")] for line in f.read().splitlines()[1:] if line.strip()]
|
||||
|
||||
|
||||
with open(os.path.join(GOLDEN, "profile_manifest.json")) as _mf:
|
||||
MANIFEST = json.load(_mf)
|
||||
ROWS = _input()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("spec", MANIFEST, ids=[m["canonical"] for m in MANIFEST])
|
||||
def test_profile_matches_golden(spec):
|
||||
ind = getattr(ta, spec["native"])(*spec["params"])
|
||||
expected = _rows("g_" + spec["canonical"])
|
||||
width = spec["width"]
|
||||
for i, (o, h, l, c, v) in enumerate(ROWS):
|
||||
got = ind.update((o, h, l, c, v, i))
|
||||
want = expected[i]
|
||||
assert len(want) == width, f"{spec['canonical']} row {i}: fixture width {len(want)} != {width}"
|
||||
if got is None:
|
||||
assert all(math.isnan(w) for w in want), f"{spec['canonical']} row {i}: want {want} got None"
|
||||
continue
|
||||
if spec["kind"] == "pricebins":
|
||||
price_low, price_high, bins = got
|
||||
vals = [price_low, price_high, *list(bins)]
|
||||
else:
|
||||
vals = list(got)
|
||||
assert len(vals) == width, f"{spec['canonical']} row {i}: arity {len(vals)} != {width}"
|
||||
for gv, w in zip(vals, want):
|
||||
if math.isnan(w):
|
||||
assert math.isnan(gv), f"{spec['canonical']} row {i}: want NaN got {gv}"
|
||||
else:
|
||||
assert abs(gv - w) <= 1e-6 * max(1.0, abs(w)), f"{spec['canonical']} row {i}: got {gv} want {w}"
|
||||
@@ -0,0 +1,67 @@
|
||||
"""Generic golden replay: every scalar indicator in scalar_manifest.json is
|
||||
constructed by its native name with the recorded params, fed the shared golden
|
||||
input, and checked bit-for-bit against the Rust-generated g_<Canonical>.csv.
|
||||
|
||||
This ties the Python binding to the Rust reference for the whole scalar-output
|
||||
tranche (not just the seven archetype representatives). Fixtures + manifest are
|
||||
produced by `cargo run -p wickra-examples --bin gen_golden`.
|
||||
"""
|
||||
import json
|
||||
import math
|
||||
import os
|
||||
|
||||
import numpy as np
|
||||
import pytest
|
||||
import wickra as ta
|
||||
|
||||
HERE = os.path.dirname(__file__)
|
||||
GOLDEN = os.path.normpath(os.path.join(HERE, "..", "..", "..", "testdata", "golden"))
|
||||
|
||||
|
||||
def _cell(s):
|
||||
return math.nan if s == "nan" else float(s)
|
||||
|
||||
|
||||
def _load(name):
|
||||
with open(os.path.join(GOLDEN, name + ".csv")) as f:
|
||||
return [_cell(line.strip()) for line in f.read().splitlines()[1:] if line.strip()]
|
||||
|
||||
|
||||
def _input():
|
||||
rows = []
|
||||
with open(os.path.join(GOLDEN, "input.csv")) as f:
|
||||
for line in f.read().splitlines()[1:]:
|
||||
if line.strip():
|
||||
rows.append([float(x) for x in line.split(",")])
|
||||
return rows
|
||||
|
||||
|
||||
with open(os.path.join(GOLDEN, "scalar_manifest.json")) as _mf:
|
||||
MANIFEST = json.load(_mf)
|
||||
ROWS = _input()
|
||||
|
||||
|
||||
@pytest.mark.parametrize("spec", MANIFEST, ids=[m["canonical"] for m in MANIFEST])
|
||||
def test_scalar_matches_golden(spec):
|
||||
cls = getattr(ta, spec["native"])
|
||||
ind = cls(*spec["params"])
|
||||
expected = _load("g_" + spec["canonical"])
|
||||
inp = spec["input"]
|
||||
for i, (o, h, l, c, v) in enumerate(ROWS):
|
||||
if inp == "f64":
|
||||
got = ind.update(c)
|
||||
elif inp == "Candle":
|
||||
got = ind.update((o, h, l, c, v, i))
|
||||
else: # pairwise (f64, f64): generator fed (close, open)
|
||||
got = ind.update(c, o)
|
||||
want = expected[i]
|
||||
got = math.nan if got is None else got
|
||||
if math.isnan(want):
|
||||
assert math.isnan(got), f"{spec['canonical']} row {i}: want NaN got {got}"
|
||||
elif math.isinf(want):
|
||||
assert math.isinf(got) and (got > 0) == (want > 0), (
|
||||
f"{spec['canonical']} row {i}: got {got} want {want}"
|
||||
)
|
||||
else:
|
||||
tol = 1e-6 * max(1.0, abs(want))
|
||||
assert abs(got - want) <= tol, f"{spec['canonical']} row {i}: got {got} want {want}"
|
||||
@@ -258,9 +258,9 @@ def test_awesome_oscillator_histogram_flat_series_converges_to_zero():
|
||||
n = 50
|
||||
high = np.full(n, 11.0)
|
||||
low = np.full(n, 9.0)
|
||||
out = ta.AwesomeOscillatorHistogram(3, 5, 3).batch(high, low)
|
||||
# warmup = slow + sma - 1 = 5 + 3 - 1 = 7.
|
||||
np.testing.assert_allclose(out[6:], 0.0, atol=1e-12)
|
||||
out = ta.AwesomeOscillatorHistogram(3, 5, 3).batch(high, low) # AO momentum
|
||||
# warmup = slow + lookback = 5 + 3 = 8.
|
||||
np.testing.assert_allclose(out[7:], 0.0, atol=1e-12)
|
||||
|
||||
|
||||
def test_stc_constant_series_yields_zero():
|
||||
@@ -521,10 +521,10 @@ def test_calmar_ratio_known_path():
|
||||
|
||||
|
||||
def test_average_drawdown_known_window():
|
||||
# window [100, 120, 90, 110]: dd = 0, 0, 0.25, 10/120;
|
||||
# mean = (0.25 + 10/120) / 4.
|
||||
# window [100, 120, 90, 110]: one drawdown episode (peak 120, trough 90),
|
||||
# never recovering -> depth (120-90)/120 = 0.25; one episode -> AvgDD = 0.25.
|
||||
out = ta.AverageDrawdown(4).batch(np.array([100.0, 120.0, 90.0, 110.0]))
|
||||
expected = (0.25 + 10.0 / 120.0) / 4.0
|
||||
expected = 0.25
|
||||
assert math.isclose(out[-1], expected, rel_tol=1e-12)
|
||||
|
||||
|
||||
|
||||
@@ -634,8 +634,8 @@ CANDLE_SCALAR = {
|
||||
lambda: ta.PVI(),
|
||||
lambda ind, h, l, c, v: ind.batch(c, v),
|
||||
),
|
||||
"WilliamsAD": (
|
||||
lambda: ta.WilliamsAD(),
|
||||
"ADOSC": (
|
||||
lambda: ta.ADOSC(),
|
||||
lambda ind, h, l, c, v: ind.batch(h, l, c),
|
||||
),
|
||||
"AnchoredVWAP": (
|
||||
@@ -1762,7 +1762,7 @@ def test_wad_reference():
|
||||
# TR_l = min(10, 8) = 8 -> delta = 12 - 8 = 4. AD = 4.
|
||||
# bar 2: prev=12, today high=11, low=7, close=7 (down day).
|
||||
# TR_h = max(12, 11) = 12 -> delta = 7 - 12 = -5. AD = 4 - 5 = -1.
|
||||
ad = ta.WilliamsAD()
|
||||
ad = ta.Wad()
|
||||
high = np.array([11.0, 13.0, 11.0])
|
||||
low = np.array([9.0, 8.0, 7.0])
|
||||
close = np.array([10.0, 12.0, 7.0])
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
Package: wickra
|
||||
Type: Package
|
||||
Title: Streaming-First Technical Indicators
|
||||
Version: 0.9.1
|
||||
Version: 0.9.2
|
||||
Authors@R: person("Wickra contributors", role = c("aut", "cre"), email = "support@wickra.org")
|
||||
Description: R bindings for the Wickra technical-analysis library over its C ABI
|
||||
hub. Exposes 514 indicators, each an O(1) streaming state machine shared with
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
"""Generate tests/testthat/golden_specs.R: the per-indicator spec list (canonical
|
||||
name, archetype, ctor params, output width) consumed by test-golden-all.R, which
|
||||
replays all 514 indicators against the Rust reference fixtures g_<Canonical>.csv.
|
||||
|
||||
Run from repo root: python bindings/r/gen_golden_test.py
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
|
||||
ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
MAN = json.load(open(os.path.join(ROOT, "testdata", "golden", "golden_manifest.json")))
|
||||
|
||||
lines = [
|
||||
"# Code generated by gen_golden_test.py. DO NOT EDIT.",
|
||||
"# Per-indicator golden spec: canonical name, archetype, ctor params, output width.",
|
||||
"GOLDEN_SPECS <- list(",
|
||||
]
|
||||
for e in MAN:
|
||||
n = e.get("n", e.get("width", 0))
|
||||
params = ",".join(repr(float(p)) for p in e["params"])
|
||||
lines.append(f' list(canon="{e["canonical"]}", arch="{e["arch"]}", params=c({params}), width={n}L),')
|
||||
lines[-1] = lines[-1].rstrip(",")
|
||||
lines.append(")")
|
||||
|
||||
dest = os.path.join(ROOT, "bindings", "r", "tests", "testthat", "golden_specs.R")
|
||||
open(dest, "w", encoding="utf-8").write("\n".join(lines) + "\n")
|
||||
print("wrote golden_specs.R with", len(MAN), "specs")
|
||||
+30
-15
@@ -7,6 +7,21 @@
|
||||
#include <stddef.h>
|
||||
#include "wickra.h"
|
||||
|
||||
/* Convert an R numeric vector of flags (non-zero == TRUE) into a C `bool*`
|
||||
* buffer (one byte per element). The cross-section indicators take their state
|
||||
* flags as `const bool*`; casting `wk_bool_vec(x)` would reinterpret the
|
||||
* 8-byte doubles as 1-byte bools and read every flag as false. The buffer is
|
||||
* allocated with R_alloc, so it lives until the enclosing .Call returns. */
|
||||
static bool *wk_bool_vec(SEXP x) {
|
||||
R_xlen_t n = Rf_xlength(x);
|
||||
bool *out = (bool *)R_alloc(n, sizeof(bool));
|
||||
double *src = REAL(x);
|
||||
for (R_xlen_t i = 0; i < n; i++) {
|
||||
out[i] = src[i] != 0.0;
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
static void abandoned_baby_fin(SEXP e) {
|
||||
struct AbandonedBaby *h = (struct AbandonedBaby *)R_ExternalPtrAddr(e);
|
||||
if (h) wickra_abandoned_baby_free(h);
|
||||
@@ -104,7 +119,7 @@ SEXP wk_absolute_breadth_index_new(void) {
|
||||
}
|
||||
SEXP wk_absolute_breadth_index_update(SEXP e, SEXP a0, SEXP a1, SEXP a2, SEXP a3, SEXP a4, SEXP a5, SEXP a6) {
|
||||
struct AbsoluteBreadthIndex *h = (struct AbsoluteBreadthIndex *)R_ExternalPtrAddr(e);
|
||||
return Rf_ScalarReal(wickra_absolute_breadth_index_update(h, (double *)REAL(a0), (double *)REAL(a1), (bool *)REAL(a2), (bool *)REAL(a3), (bool *)REAL(a4), (bool *)REAL(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
return Rf_ScalarReal(wickra_absolute_breadth_index_update(h, (double *)REAL(a0), (double *)REAL(a1), wk_bool_vec(a2), wk_bool_vec(a3), wk_bool_vec(a4), wk_bool_vec(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
}
|
||||
SEXP wk_absolute_breadth_index_warmup_period(SEXP e) {
|
||||
struct AbsoluteBreadthIndex *h = (struct AbsoluteBreadthIndex *)R_ExternalPtrAddr(e);
|
||||
@@ -260,7 +275,7 @@ SEXP wk_ad_volume_line_new(void) {
|
||||
}
|
||||
SEXP wk_ad_volume_line_update(SEXP e, SEXP a0, SEXP a1, SEXP a2, SEXP a3, SEXP a4, SEXP a5, SEXP a6) {
|
||||
struct AdVolumeLine *h = (struct AdVolumeLine *)R_ExternalPtrAddr(e);
|
||||
return Rf_ScalarReal(wickra_ad_volume_line_update(h, (double *)REAL(a0), (double *)REAL(a1), (bool *)REAL(a2), (bool *)REAL(a3), (bool *)REAL(a4), (bool *)REAL(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
return Rf_ScalarReal(wickra_ad_volume_line_update(h, (double *)REAL(a0), (double *)REAL(a1), wk_bool_vec(a2), wk_bool_vec(a3), wk_bool_vec(a4), wk_bool_vec(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
}
|
||||
SEXP wk_ad_volume_line_warmup_period(SEXP e) {
|
||||
struct AdVolumeLine *h = (struct AdVolumeLine *)R_ExternalPtrAddr(e);
|
||||
@@ -531,7 +546,7 @@ SEXP wk_advance_decline_new(void) {
|
||||
}
|
||||
SEXP wk_advance_decline_update(SEXP e, SEXP a0, SEXP a1, SEXP a2, SEXP a3, SEXP a4, SEXP a5, SEXP a6) {
|
||||
struct AdvanceDecline *h = (struct AdvanceDecline *)R_ExternalPtrAddr(e);
|
||||
return Rf_ScalarReal(wickra_advance_decline_update(h, (double *)REAL(a0), (double *)REAL(a1), (bool *)REAL(a2), (bool *)REAL(a3), (bool *)REAL(a4), (bool *)REAL(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
return Rf_ScalarReal(wickra_advance_decline_update(h, (double *)REAL(a0), (double *)REAL(a1), wk_bool_vec(a2), wk_bool_vec(a3), wk_bool_vec(a4), wk_bool_vec(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
}
|
||||
SEXP wk_advance_decline_warmup_period(SEXP e) {
|
||||
struct AdvanceDecline *h = (struct AdvanceDecline *)R_ExternalPtrAddr(e);
|
||||
@@ -562,7 +577,7 @@ SEXP wk_advance_decline_ratio_new(void) {
|
||||
}
|
||||
SEXP wk_advance_decline_ratio_update(SEXP e, SEXP a0, SEXP a1, SEXP a2, SEXP a3, SEXP a4, SEXP a5, SEXP a6) {
|
||||
struct AdvanceDeclineRatio *h = (struct AdvanceDeclineRatio *)R_ExternalPtrAddr(e);
|
||||
return Rf_ScalarReal(wickra_advance_decline_ratio_update(h, (double *)REAL(a0), (double *)REAL(a1), (bool *)REAL(a2), (bool *)REAL(a3), (bool *)REAL(a4), (bool *)REAL(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
return Rf_ScalarReal(wickra_advance_decline_ratio_update(h, (double *)REAL(a0), (double *)REAL(a1), wk_bool_vec(a2), wk_bool_vec(a3), wk_bool_vec(a4), wk_bool_vec(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
}
|
||||
SEXP wk_advance_decline_ratio_warmup_period(SEXP e) {
|
||||
struct AdvanceDeclineRatio *h = (struct AdvanceDeclineRatio *)R_ExternalPtrAddr(e);
|
||||
@@ -2059,7 +2074,7 @@ SEXP wk_breadth_thrust_new(SEXP a0) {
|
||||
}
|
||||
SEXP wk_breadth_thrust_update(SEXP e, SEXP a0, SEXP a1, SEXP a2, SEXP a3, SEXP a4, SEXP a5, SEXP a6) {
|
||||
struct BreadthThrust *h = (struct BreadthThrust *)R_ExternalPtrAddr(e);
|
||||
return Rf_ScalarReal(wickra_breadth_thrust_update(h, (double *)REAL(a0), (double *)REAL(a1), (bool *)REAL(a2), (bool *)REAL(a3), (bool *)REAL(a4), (bool *)REAL(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
return Rf_ScalarReal(wickra_breadth_thrust_update(h, (double *)REAL(a0), (double *)REAL(a1), wk_bool_vec(a2), wk_bool_vec(a3), wk_bool_vec(a4), wk_bool_vec(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
}
|
||||
SEXP wk_breadth_thrust_warmup_period(SEXP e) {
|
||||
struct BreadthThrust *h = (struct BreadthThrust *)R_ExternalPtrAddr(e);
|
||||
@@ -2131,7 +2146,7 @@ SEXP wk_bullish_percent_index_new(void) {
|
||||
}
|
||||
SEXP wk_bullish_percent_index_update(SEXP e, SEXP a0, SEXP a1, SEXP a2, SEXP a3, SEXP a4, SEXP a5, SEXP a6) {
|
||||
struct BullishPercentIndex *h = (struct BullishPercentIndex *)R_ExternalPtrAddr(e);
|
||||
return Rf_ScalarReal(wickra_bullish_percent_index_update(h, (double *)REAL(a0), (double *)REAL(a1), (bool *)REAL(a2), (bool *)REAL(a3), (bool *)REAL(a4), (bool *)REAL(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
return Rf_ScalarReal(wickra_bullish_percent_index_update(h, (double *)REAL(a0), (double *)REAL(a1), wk_bool_vec(a2), wk_bool_vec(a3), wk_bool_vec(a4), wk_bool_vec(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
}
|
||||
SEXP wk_bullish_percent_index_warmup_period(SEXP e) {
|
||||
struct BullishPercentIndex *h = (struct BullishPercentIndex *)R_ExternalPtrAddr(e);
|
||||
@@ -3462,7 +3477,7 @@ SEXP wk_cumulative_volume_index_new(void) {
|
||||
}
|
||||
SEXP wk_cumulative_volume_index_update(SEXP e, SEXP a0, SEXP a1, SEXP a2, SEXP a3, SEXP a4, SEXP a5, SEXP a6) {
|
||||
struct CumulativeVolumeIndex *h = (struct CumulativeVolumeIndex *)R_ExternalPtrAddr(e);
|
||||
return Rf_ScalarReal(wickra_cumulative_volume_index_update(h, (double *)REAL(a0), (double *)REAL(a1), (bool *)REAL(a2), (bool *)REAL(a3), (bool *)REAL(a4), (bool *)REAL(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
return Rf_ScalarReal(wickra_cumulative_volume_index_update(h, (double *)REAL(a0), (double *)REAL(a1), wk_bool_vec(a2), wk_bool_vec(a3), wk_bool_vec(a4), wk_bool_vec(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
}
|
||||
SEXP wk_cumulative_volume_index_warmup_period(SEXP e) {
|
||||
struct CumulativeVolumeIndex *h = (struct CumulativeVolumeIndex *)R_ExternalPtrAddr(e);
|
||||
@@ -7104,7 +7119,7 @@ SEXP wk_high_low_index_new(SEXP a0) {
|
||||
}
|
||||
SEXP wk_high_low_index_update(SEXP e, SEXP a0, SEXP a1, SEXP a2, SEXP a3, SEXP a4, SEXP a5, SEXP a6) {
|
||||
struct HighLowIndex *h = (struct HighLowIndex *)R_ExternalPtrAddr(e);
|
||||
return Rf_ScalarReal(wickra_high_low_index_update(h, (double *)REAL(a0), (double *)REAL(a1), (bool *)REAL(a2), (bool *)REAL(a3), (bool *)REAL(a4), (bool *)REAL(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
return Rf_ScalarReal(wickra_high_low_index_update(h, (double *)REAL(a0), (double *)REAL(a1), wk_bool_vec(a2), wk_bool_vec(a3), wk_bool_vec(a4), wk_bool_vec(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
}
|
||||
SEXP wk_high_low_index_warmup_period(SEXP e) {
|
||||
struct HighLowIndex *h = (struct HighLowIndex *)R_ExternalPtrAddr(e);
|
||||
@@ -10118,7 +10133,7 @@ SEXP wk_mc_clellan_oscillator_new(void) {
|
||||
}
|
||||
SEXP wk_mc_clellan_oscillator_update(SEXP e, SEXP a0, SEXP a1, SEXP a2, SEXP a3, SEXP a4, SEXP a5, SEXP a6) {
|
||||
struct McClellanOscillator *h = (struct McClellanOscillator *)R_ExternalPtrAddr(e);
|
||||
return Rf_ScalarReal(wickra_mc_clellan_oscillator_update(h, (double *)REAL(a0), (double *)REAL(a1), (bool *)REAL(a2), (bool *)REAL(a3), (bool *)REAL(a4), (bool *)REAL(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
return Rf_ScalarReal(wickra_mc_clellan_oscillator_update(h, (double *)REAL(a0), (double *)REAL(a1), wk_bool_vec(a2), wk_bool_vec(a3), wk_bool_vec(a4), wk_bool_vec(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
}
|
||||
SEXP wk_mc_clellan_oscillator_warmup_period(SEXP e) {
|
||||
struct McClellanOscillator *h = (struct McClellanOscillator *)R_ExternalPtrAddr(e);
|
||||
@@ -10149,7 +10164,7 @@ SEXP wk_mc_clellan_summation_index_new(void) {
|
||||
}
|
||||
SEXP wk_mc_clellan_summation_index_update(SEXP e, SEXP a0, SEXP a1, SEXP a2, SEXP a3, SEXP a4, SEXP a5, SEXP a6) {
|
||||
struct McClellanSummationIndex *h = (struct McClellanSummationIndex *)R_ExternalPtrAddr(e);
|
||||
return Rf_ScalarReal(wickra_mc_clellan_summation_index_update(h, (double *)REAL(a0), (double *)REAL(a1), (bool *)REAL(a2), (bool *)REAL(a3), (bool *)REAL(a4), (bool *)REAL(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
return Rf_ScalarReal(wickra_mc_clellan_summation_index_update(h, (double *)REAL(a0), (double *)REAL(a1), wk_bool_vec(a2), wk_bool_vec(a3), wk_bool_vec(a4), wk_bool_vec(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
}
|
||||
SEXP wk_mc_clellan_summation_index_warmup_period(SEXP e) {
|
||||
struct McClellanSummationIndex *h = (struct McClellanSummationIndex *)R_ExternalPtrAddr(e);
|
||||
@@ -10914,7 +10929,7 @@ SEXP wk_new_highs_new_lows_new(void) {
|
||||
}
|
||||
SEXP wk_new_highs_new_lows_update(SEXP e, SEXP a0, SEXP a1, SEXP a2, SEXP a3, SEXP a4, SEXP a5, SEXP a6) {
|
||||
struct NewHighsNewLows *h = (struct NewHighsNewLows *)R_ExternalPtrAddr(e);
|
||||
return Rf_ScalarReal(wickra_new_highs_new_lows_update(h, (double *)REAL(a0), (double *)REAL(a1), (bool *)REAL(a2), (bool *)REAL(a3), (bool *)REAL(a4), (bool *)REAL(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
return Rf_ScalarReal(wickra_new_highs_new_lows_update(h, (double *)REAL(a0), (double *)REAL(a1), wk_bool_vec(a2), wk_bool_vec(a3), wk_bool_vec(a4), wk_bool_vec(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
}
|
||||
SEXP wk_new_highs_new_lows_warmup_period(SEXP e) {
|
||||
struct NewHighsNewLows *h = (struct NewHighsNewLows *)R_ExternalPtrAddr(e);
|
||||
@@ -11870,7 +11885,7 @@ SEXP wk_percent_above_ma_new(void) {
|
||||
}
|
||||
SEXP wk_percent_above_ma_update(SEXP e, SEXP a0, SEXP a1, SEXP a2, SEXP a3, SEXP a4, SEXP a5, SEXP a6) {
|
||||
struct PercentAboveMa *h = (struct PercentAboveMa *)R_ExternalPtrAddr(e);
|
||||
return Rf_ScalarReal(wickra_percent_above_ma_update(h, (double *)REAL(a0), (double *)REAL(a1), (bool *)REAL(a2), (bool *)REAL(a3), (bool *)REAL(a4), (bool *)REAL(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
return Rf_ScalarReal(wickra_percent_above_ma_update(h, (double *)REAL(a0), (double *)REAL(a1), wk_bool_vec(a2), wk_bool_vec(a3), wk_bool_vec(a4), wk_bool_vec(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
}
|
||||
SEXP wk_percent_above_ma_warmup_period(SEXP e) {
|
||||
struct PercentAboveMa *h = (struct PercentAboveMa *)R_ExternalPtrAddr(e);
|
||||
@@ -17276,7 +17291,7 @@ SEXP wk_tick_index_new(void) {
|
||||
}
|
||||
SEXP wk_tick_index_update(SEXP e, SEXP a0, SEXP a1, SEXP a2, SEXP a3, SEXP a4, SEXP a5, SEXP a6) {
|
||||
struct TickIndex *h = (struct TickIndex *)R_ExternalPtrAddr(e);
|
||||
return Rf_ScalarReal(wickra_tick_index_update(h, (double *)REAL(a0), (double *)REAL(a1), (bool *)REAL(a2), (bool *)REAL(a3), (bool *)REAL(a4), (bool *)REAL(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
return Rf_ScalarReal(wickra_tick_index_update(h, (double *)REAL(a0), (double *)REAL(a1), wk_bool_vec(a2), wk_bool_vec(a3), wk_bool_vec(a4), wk_bool_vec(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
}
|
||||
SEXP wk_tick_index_warmup_period(SEXP e) {
|
||||
struct TickIndex *h = (struct TickIndex *)R_ExternalPtrAddr(e);
|
||||
@@ -17853,7 +17868,7 @@ SEXP wk_trin_new(void) {
|
||||
}
|
||||
SEXP wk_trin_update(SEXP e, SEXP a0, SEXP a1, SEXP a2, SEXP a3, SEXP a4, SEXP a5, SEXP a6) {
|
||||
struct Trin *h = (struct Trin *)R_ExternalPtrAddr(e);
|
||||
return Rf_ScalarReal(wickra_trin_update(h, (double *)REAL(a0), (double *)REAL(a1), (bool *)REAL(a2), (bool *)REAL(a3), (bool *)REAL(a4), (bool *)REAL(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
return Rf_ScalarReal(wickra_trin_update(h, (double *)REAL(a0), (double *)REAL(a1), wk_bool_vec(a2), wk_bool_vec(a3), wk_bool_vec(a4), wk_bool_vec(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
}
|
||||
SEXP wk_trin_warmup_period(SEXP e) {
|
||||
struct Trin *h = (struct Trin *)R_ExternalPtrAddr(e);
|
||||
@@ -18651,7 +18666,7 @@ SEXP wk_up_down_volume_ratio_new(void) {
|
||||
}
|
||||
SEXP wk_up_down_volume_ratio_update(SEXP e, SEXP a0, SEXP a1, SEXP a2, SEXP a3, SEXP a4, SEXP a5, SEXP a6) {
|
||||
struct UpDownVolumeRatio *h = (struct UpDownVolumeRatio *)R_ExternalPtrAddr(e);
|
||||
return Rf_ScalarReal(wickra_up_down_volume_ratio_update(h, (double *)REAL(a0), (double *)REAL(a1), (bool *)REAL(a2), (bool *)REAL(a3), (bool *)REAL(a4), (bool *)REAL(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
return Rf_ScalarReal(wickra_up_down_volume_ratio_update(h, (double *)REAL(a0), (double *)REAL(a1), wk_bool_vec(a2), wk_bool_vec(a3), wk_bool_vec(a4), wk_bool_vec(a5), (uintptr_t)Rf_xlength(a0), (int64_t)Rf_asReal(a6)));
|
||||
}
|
||||
SEXP wk_up_down_volume_ratio_warmup_period(SEXP e) {
|
||||
struct UpDownVolumeRatio *h = (struct UpDownVolumeRatio *)R_ExternalPtrAddr(e);
|
||||
|
||||
@@ -0,0 +1,518 @@
|
||||
# Code generated by gen_golden_test.py. DO NOT EDIT.
|
||||
# Per-indicator golden spec: canonical name, archetype, ctor params, output width.
|
||||
GOLDEN_SPECS <- list(
|
||||
list(canon="AbandonedBaby", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="Abcd", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="AbsoluteBreadthIndex", arch="cross", params=c(), width=0L),
|
||||
list(canon="AccelerationBands", arch="multi_candle", params=c(14.0,2.0), width=3L),
|
||||
list(canon="AcceleratorOscillator", arch="scalar_candle", params=c(3.0,7.0,14.0), width=0L),
|
||||
list(canon="AdOscillator", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="AdVolumeLine", arch="cross", params=c(), width=0L),
|
||||
list(canon="AdaptiveCci", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="AdaptiveCycle", arch="scalar_f64", params=c(), width=0L),
|
||||
list(canon="AdaptiveLaguerreFilter", arch="scalar_f64", params=c(20.0), width=0L),
|
||||
list(canon="AdaptiveRsi", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="Adl", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="AdvanceBlock", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="AdvanceDecline", arch="cross", params=c(), width=0L),
|
||||
list(canon="AdvanceDeclineRatio", arch="cross", params=c(), width=0L),
|
||||
list(canon="Adx", arch="multi_candle", params=c(14.0), width=3L),
|
||||
list(canon="Adxr", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="Alligator", arch="multi_candle", params=c(3.0,7.0,14.0), width=3L),
|
||||
list(canon="Alma", arch="scalar_f64", params=c(9.0,0.85,6.0), width=0L),
|
||||
list(canon="Alpha", arch="pairwise", params=c(14.0,2.0), width=0L),
|
||||
list(canon="AmihudIlliquidity", arch="trade", params=c(20.0), width=0L),
|
||||
list(canon="AnchoredRsi", arch="scalar_f64", params=c(), width=0L),
|
||||
list(canon="AnchoredVwap", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="AndrewsPitchfork", arch="multi_candle", params=c(14.0), width=3L),
|
||||
list(canon="Apo", arch="scalar_f64", params=c(3.0,7.0), width=0L),
|
||||
list(canon="Aroon", arch="multi_candle", params=c(14.0), width=2L),
|
||||
list(canon="AroonOscillator", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="Atr", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="AtrBands", arch="multi_candle", params=c(14.0,2.0), width=3L),
|
||||
list(canon="AtrRatchet", arch="multi_candle", params=c(14.0,2.0,0.5), width=2L),
|
||||
list(canon="AtrTrailingStop", arch="scalar_candle", params=c(14.0,2.0), width=0L),
|
||||
list(canon="AutoFib", arch="multi_candle", params=c(), width=7L),
|
||||
list(canon="Autocorrelation", arch="scalar_f64", params=c(10.0,1.0), width=0L),
|
||||
list(canon="AutocorrelationPeriodogram", arch="scalar_f64", params=c(10.0,48.0), width=0L),
|
||||
list(canon="AverageDailyRange", arch="scalar_candle", params=c(14.0,0.0), width=0L),
|
||||
list(canon="AverageDrawdown", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="AvgPrice", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="AwesomeOscillator", arch="scalar_candle", params=c(3.0,7.0), width=0L),
|
||||
list(canon="AwesomeOscillatorHistogram", arch="scalar_candle", params=c(3.0,7.0,14.0), width=0L),
|
||||
list(canon="BalanceOfPower", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="BandpassFilter", arch="scalar_f64", params=c(20.0,0.3), width=0L),
|
||||
list(canon="Bat", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="BeltHold", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="Beta", arch="pairwise", params=c(14.0), width=0L),
|
||||
list(canon="BetaNeutralSpread", arch="pairwise", params=c(14.0), width=0L),
|
||||
list(canon="BetterVolume", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="BipowerVariation", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="BodySizePct", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="BollingerBands", arch="multi_f64", params=c(20.0,2.0), width=4L),
|
||||
list(canon="BollingerBandwidth", arch="scalar_f64", params=c(14.0,2.0), width=0L),
|
||||
list(canon="BomarBands", arch="multi_f64", params=c(4.0,0.85), width=3L),
|
||||
list(canon="BreadthThrust", arch="cross", params=c(10.0), width=0L),
|
||||
list(canon="Breakaway", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="BullishPercentIndex", arch="cross", params=c(), width=0L),
|
||||
list(canon="BurkeRatio", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="Butterfly", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="CalendarSpread", arch="deriv", params=c(), width=0L),
|
||||
list(canon="CalmarRatio", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="Camarilla", arch="multi_candle", params=c(), width=9L),
|
||||
list(canon="CandleVolume", arch="multi_candle", params=c(14.0), width=2L),
|
||||
list(canon="Cci", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="CenterOfGravity", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="CentralPivotRange", arch="multi_candle", params=c(), width=3L),
|
||||
list(canon="Cfo", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="ChaikinMoneyFlow", arch="scalar_candle", params=c(20.0), width=0L),
|
||||
list(canon="ChaikinOscillator", arch="scalar_candle", params=c(3.0,7.0), width=0L),
|
||||
list(canon="ChaikinVolatility", arch="scalar_candle", params=c(3.0,7.0), width=0L),
|
||||
list(canon="ChandeKrollStop", arch="multi_candle", params=c(3.0,2.0,7.0), width=2L),
|
||||
list(canon="ChandelierExit", arch="multi_candle", params=c(14.0,2.0), width=2L),
|
||||
list(canon="ChoppinessIndex", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="ClassicPivots", arch="multi_candle", params=c(), width=7L),
|
||||
list(canon="CloseVsOpen", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="ClosingMarubozu", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="Cmo", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="CoefficientOfVariation", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="Cointegration", arch="multi_pairwise", params=c(40.0,1.0), width=3L),
|
||||
list(canon="CommonSenseRatio", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="CompositeProfile", arch="multi_candle", params=c(20.0,24.0,0.7), width=3L),
|
||||
list(canon="ConcealingBabySwallow", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="ConditionalValueAtRisk", arch="scalar_f64", params=c(20.0,0.95), width=0L),
|
||||
list(canon="ConnorsRsi", arch="scalar_f64", params=c(3.0,7.0,14.0), width=0L),
|
||||
list(canon="Coppock", arch="scalar_f64", params=c(3.0,7.0,14.0), width=0L),
|
||||
list(canon="CorrelationTrendIndicator", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="Counterattack", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="Crab", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="CumulativeVolumeDelta", arch="trade", params=c(), width=0L),
|
||||
list(canon="CumulativeVolumeIndex", arch="cross", params=c(), width=0L),
|
||||
list(canon="CupAndHandle", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="CyberneticCycle", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="Cypher", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="DayOfWeekProfile", arch="profile_bins", params=c(0.0), width=7L),
|
||||
list(canon="Decycler", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="DecyclerOscillator", arch="scalar_f64", params=c(3.0,7.0), width=0L),
|
||||
list(canon="Dema", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="DemandIndex", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="DemarkPivots", arch="multi_candle", params=c(), width=3L),
|
||||
list(canon="DepthSlope", arch="ob", params=c(), width=0L),
|
||||
list(canon="DerivativeOscillator", arch="scalar_f64", params=c(3.0,7.0,14.0,28.0), width=0L),
|
||||
list(canon="DetrendedStdDev", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="DisparityIndex", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="DistanceSsd", arch="pairwise", params=c(14.0), width=0L),
|
||||
list(canon="Doji", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="DojiStar", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="DollarBars", arch="bars_candle5", params=c(50000.0), width=0L),
|
||||
list(canon="Donchian", arch="multi_candle", params=c(14.0), width=3L),
|
||||
list(canon="DonchianStop", arch="multi_candle", params=c(14.0), width=2L),
|
||||
list(canon="DoubleBollinger", arch="multi_f64", params=c(20.0,1.0,2.0), width=5L),
|
||||
list(canon="DoubleTopBottom", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="DownsideGapThreeMethods", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="Dpo", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="DragonflyDoji", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="DrawdownDuration", arch="scalar_f64", params=c(), width=0L),
|
||||
list(canon="DumplingTop", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="Dx", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="DynamicMomentumIndex", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="EaseOfMovement", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="EffectiveSpread", arch="trademid", params=c(), width=0L),
|
||||
list(canon="EhlersStochastic", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="Ehma", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="ElderImpulse", arch="scalar_f64", params=c(3.0,7.0,14.0,28.0), width=0L),
|
||||
list(canon="ElderRay", arch="multi_candle", params=c(14.0), width=2L),
|
||||
list(canon="ElderSafeZone", arch="multi_candle", params=c(10.0,2.0), width=2L),
|
||||
list(canon="Ema", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="EmpiricalModeDecomposition", arch="scalar_f64", params=c(20.0,0.1), width=0L),
|
||||
list(canon="Engulfing", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="Equivolume", arch="multi_candle", params=c(14.0), width=2L),
|
||||
list(canon="EstimatedLeverageRatio", arch="deriv", params=c(), width=0L),
|
||||
list(canon="EvenBetterSinewave", arch="scalar_f64", params=c(40.0,10.0), width=0L),
|
||||
list(canon="EveningDojiStar", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="Evwma", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="EwmaVolatility", arch="scalar_f64", params=c(0.94), width=0L),
|
||||
list(canon="Expectancy", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="FallingThreeMethods", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="Fama", arch="scalar_f64", params=c(0.5,0.05), width=0L),
|
||||
list(canon="FibArcs", arch="multi_candle", params=c(), width=3L),
|
||||
list(canon="FibChannel", arch="multi_candle", params=c(), width=4L),
|
||||
list(canon="FibConfluence", arch="multi_candle", params=c(), width=2L),
|
||||
list(canon="FibExtension", arch="multi_candle", params=c(), width=5L),
|
||||
list(canon="FibFan", arch="multi_candle", params=c(), width=3L),
|
||||
list(canon="FibProjection", arch="multi_candle", params=c(), width=4L),
|
||||
list(canon="FibRetracement", arch="multi_candle", params=c(), width=7L),
|
||||
list(canon="FibTimeZones", arch="multi_candle", params=c(), width=2L),
|
||||
list(canon="FibonacciPivots", arch="multi_candle", params=c(), width=7L),
|
||||
list(canon="FisherRsi", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="FisherTransform", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="FlagPennant", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="Footprint", arch="footprint", params=c(1.0), width=0L),
|
||||
list(canon="ForceIndex", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="FractalChaosBands", arch="multi_candle", params=c(14.0), width=2L),
|
||||
list(canon="Frama", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="FryPanBottom", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="FundingBasis", arch="deriv", params=c(), width=0L),
|
||||
list(canon="FundingImpliedApr", arch="deriv", params=c(1095.0), width=0L),
|
||||
list(canon="FundingRate", arch="deriv", params=c(), width=0L),
|
||||
list(canon="FundingRateMean", arch="deriv", params=c(20.0), width=0L),
|
||||
list(canon="FundingRateZScore", arch="deriv", params=c(20.0), width=0L),
|
||||
list(canon="GainLossRatio", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="GainToPainRatio", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="GapSideBySideWhite", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="Garch11", arch="scalar_f64", params=c(2e-06,0.1,0.88), width=0L),
|
||||
list(canon="GarmanKlassVolatility", arch="scalar_candle", params=c(20.0,252.0), width=0L),
|
||||
list(canon="Gartley", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="GatorOscillator", arch="multi_candle", params=c(3.0,7.0,14.0), width=2L),
|
||||
list(canon="GeneralizedDema", arch="scalar_f64", params=c(5.0,0.7), width=0L),
|
||||
list(canon="GeometricMa", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="GoldenPocket", arch="multi_candle", params=c(), width=3L),
|
||||
list(canon="GrangerCausality", arch="pairwise", params=c(60.0,1.0), width=0L),
|
||||
list(canon="GravestoneDoji", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="Hammer", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="HangingMan", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="Harami", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="HaramiCross", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="HasbrouckInformationShare", arch="pairwise", params=c(14.0), width=0L),
|
||||
list(canon="HeadAndShoulders", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="HeikinAshi", arch="multi_candle", params=c(), width=4L),
|
||||
list(canon="HeikinAshiOscillator", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="HiLoActivator", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="HighLowIndex", arch="cross", params=c(10.0), width=0L),
|
||||
list(canon="HighLowRange", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="HighLowVolumeNodes", arch="multi_candle", params=c(3.0,7.0), width=2L),
|
||||
list(canon="HighWave", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="HighpassFilter", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="Hikkake", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="HikkakeModified", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="HilbertDominantCycle", arch="scalar_f64", params=c(), width=0L),
|
||||
list(canon="HistoricalVolatility", arch="scalar_f64", params=c(3.0,7.0), width=0L),
|
||||
list(canon="Hma", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="HoltWinters", arch="scalar_f64", params=c(0.5,0.1), width=0L),
|
||||
list(canon="HomingPigeon", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="HtDcPhase", arch="scalar_f64", params=c(), width=0L),
|
||||
list(canon="HtPhasor", arch="multi_f64", params=c(), width=2L),
|
||||
list(canon="HtTrendMode", arch="scalar_f64", params=c(), width=0L),
|
||||
list(canon="HurstChannel", arch="multi_candle", params=c(14.0,2.0), width=3L),
|
||||
list(canon="HurstExponent", arch="scalar_f64", params=c(100.0,4.0), width=0L),
|
||||
list(canon="Ichimoku", arch="multi_candle", params=c(9.0,26.0,52.0,26.0), width=5L),
|
||||
list(canon="IdenticalThreeCrows", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="ImbalanceBars", arch="bars_candle4", params=c(5.0), width=0L),
|
||||
list(canon="InNeck", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="Inertia", arch="scalar_candle", params=c(3.0,7.0), width=0L),
|
||||
list(canon="InformationRatio", arch="pairwise", params=c(14.0), width=0L),
|
||||
list(canon="InitialBalance", arch="multi_candle", params=c(14.0), width=2L),
|
||||
list(canon="InstantaneousTrendline", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="IntradayIntensity", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="IntradayMomentumIndex", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="IntradayVolatilityProfile", arch="profile_bins", params=c(24.0,0.0), width=24L),
|
||||
list(canon="InverseFisherTransform", arch="scalar_f64", params=c(2.0), width=0L),
|
||||
list(canon="InvertedHammer", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="JarqueBera", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="Jma", arch="scalar_f64", params=c(7.0,0.0,2.0), width=0L),
|
||||
list(canon="JumpIndicator", arch="scalar_f64", params=c(14.0,2.0), width=0L),
|
||||
list(canon="KRatio", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="KagiBars", arch="bars_close", params=c(2.0), width=0L),
|
||||
list(canon="KalmanHedgeRatio", arch="multi_pairwise", params=c(0.01,0.001), width=3L),
|
||||
list(canon="Kama", arch="scalar_f64", params=c(3.0,7.0,14.0), width=0L),
|
||||
list(canon="KaseDevStop", arch="multi_candle", params=c(14.0,2.0), width=2L),
|
||||
list(canon="KasePermissionStochastic", arch="multi_candle", params=c(3.0,7.0), width=2L),
|
||||
list(canon="KellyCriterion", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="Keltner", arch="multi_candle", params=c(3.0,7.0,2.0), width=3L),
|
||||
list(canon="KendallTau", arch="pairwise", params=c(14.0), width=0L),
|
||||
list(canon="Kicking", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="KickingByLength", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="Kst", arch="multi_f64", params=c(3.0,7.0,14.0,28.0,35.0,42.0,56.0,63.0,70.0), width=2L),
|
||||
list(canon="Kurtosis", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="Kvo", arch="scalar_candle", params=c(3.0,7.0), width=0L),
|
||||
list(canon="KylesLambda", arch="trademid", params=c(20.0), width=0L),
|
||||
list(canon="LadderBottom", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="LaguerreRsi", arch="scalar_f64", params=c(0.5), width=0L),
|
||||
list(canon="LeadLagCrossCorrelation", arch="multi_pairwise", params=c(20.0,10.0), width=2L),
|
||||
list(canon="LinRegAngle", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="LinRegChannel", arch="multi_f64", params=c(14.0,2.0), width=3L),
|
||||
list(canon="LinRegIntercept", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="LinRegSlope", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="LinearRegression", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="LiquidationFeatures", arch="deriv_multi", params=c(), width=5L),
|
||||
list(canon="LogReturn", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="LongLeggedDoji", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="LongLine", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="LongShortRatio", arch="deriv", params=c(), width=0L),
|
||||
list(canon="M2Measure", arch="scalar_f64", params=c(14.0,2.0,0.5), width=0L),
|
||||
list(canon="MaEnvelope", arch="multi_f64", params=c(14.0,2.0), width=3L),
|
||||
list(canon="MacdExt", arch="multi_f64", params=c(12.0,0.0,26.0,0.0,9.0,0.0), width=3L),
|
||||
list(canon="MacdFix", arch="multi_f64", params=c(9.0), width=3L),
|
||||
list(canon="MacdHistogram", arch="scalar_f64", params=c(3.0,7.0,14.0), width=0L),
|
||||
list(canon="MacdIndicator", arch="multi_f64", params=c(12.0,26.0,9.0), width=3L),
|
||||
list(canon="Mama", arch="multi_f64", params=c(0.5,0.05), width=2L),
|
||||
list(canon="MarketFacilitationIndex", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="MartinRatio", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="Marubozu", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="MassIndex", arch="scalar_candle", params=c(3.0,7.0), width=0L),
|
||||
list(canon="MatHold", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="MatchingLow", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="MaxDrawdown", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="McClellanOscillator", arch="cross", params=c(), width=0L),
|
||||
list(canon="McClellanSummationIndex", arch="cross", params=c(), width=0L),
|
||||
list(canon="McGinleyDynamic", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="MedianAbsoluteDeviation", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="MedianChannel", arch="multi_f64", params=c(14.0,2.0), width=3L),
|
||||
list(canon="MedianMa", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="MedianPrice", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="Mfi", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="Microprice", arch="ob", params=c(), width=0L),
|
||||
list(canon="MidPoint", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="MidPrice", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="MinusDi", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="MinusDm", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="ModifiedMaStop", arch="multi_candle", params=c(14.0), width=2L),
|
||||
list(canon="Mom", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="MorningDojiStar", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="MorningEveningStar", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="MurreyMathLines", arch="multi_candle", params=c(14.0), width=9L),
|
||||
list(canon="NakedPoc", arch="scalar_candle", params=c(3.0,7.0), width=0L),
|
||||
list(canon="Natr", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="NewHighsNewLows", arch="cross", params=c(), width=0L),
|
||||
list(canon="NewPriceLines", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="Nrtr", arch="multi_candle", params=c(2.0), width=2L),
|
||||
list(canon="Nvi", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="OIPriceDivergence", arch="deriv", params=c(20.0), width=0L),
|
||||
list(canon="OIWeighted", arch="deriv", params=c(), width=0L),
|
||||
list(canon="Obv", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="OiToVolumeRatio", arch="deriv", params=c(), width=0L),
|
||||
list(canon="OmegaRatio", arch="scalar_f64", params=c(14.0,2.0), width=0L),
|
||||
list(canon="OnNeck", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="OpenInterestDelta", arch="deriv", params=c(), width=0L),
|
||||
list(canon="OpenInterestMomentum", arch="deriv", params=c(10.0), width=0L),
|
||||
list(canon="OpeningMarubozu", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="OpeningRange", arch="multi_candle", params=c(14.0), width=3L),
|
||||
list(canon="OrderBookImbalanceFull", arch="ob", params=c(), width=0L),
|
||||
list(canon="OrderBookImbalanceTop1", arch="ob", params=c(), width=0L),
|
||||
list(canon="OrderBookImbalanceTopN", arch="ob", params=c(5.0), width=0L),
|
||||
list(canon="OrderFlowImbalance", arch="ob", params=c(20.0), width=0L),
|
||||
list(canon="OuHalfLife", arch="pairwise", params=c(14.0), width=0L),
|
||||
list(canon="OvernightGap", arch="scalar_candle", params=c(0.0), width=0L),
|
||||
list(canon="OvernightIntradayReturn", arch="multi_candle", params=c(14.0), width=2L),
|
||||
list(canon="PainIndex", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="PairSpreadZScore", arch="pairwise", params=c(20.0,20.0), width=0L),
|
||||
list(canon="PairwiseBeta", arch="pairwise", params=c(14.0), width=0L),
|
||||
list(canon="ParkinsonVolatility", arch="scalar_candle", params=c(20.0,252.0), width=0L),
|
||||
list(canon="PearsonCorrelation", arch="pairwise", params=c(14.0), width=0L),
|
||||
list(canon="PercentAboveMa", arch="cross", params=c(), width=0L),
|
||||
list(canon="PercentB", arch="scalar_f64", params=c(14.0,2.0), width=0L),
|
||||
list(canon="PercentageTrailingStop", arch="scalar_f64", params=c(2.0), width=0L),
|
||||
list(canon="PerpetualPremiumIndex", arch="deriv", params=c(), width=0L),
|
||||
list(canon="Pgo", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="PiercingDarkCloud", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="Pin", arch="trade", params=c(20.0), width=0L),
|
||||
list(canon="PivotReversal", arch="scalar_candle", params=c(3.0,7.0), width=0L),
|
||||
list(canon="PlusDi", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="PlusDm", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="Pmo", arch="scalar_f64", params=c(3.0,7.0), width=0L),
|
||||
list(canon="PointAndFigureBars", arch="bars_close", params=c(2.0,3.0), width=0L),
|
||||
list(canon="PolarizedFractalEfficiency", arch="scalar_f64", params=c(10.0,5.0), width=0L),
|
||||
list(canon="Ppo", arch="scalar_f64", params=c(3.0,7.0), width=0L),
|
||||
list(canon="PpoHistogram", arch="scalar_f64", params=c(3.0,7.0,14.0), width=0L),
|
||||
list(canon="ProfileShape", arch="scalar_candle", params=c(3.0,7.0), width=0L),
|
||||
list(canon="ProfitFactor", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="ProjectionBands", arch="multi_candle", params=c(14.0), width=3L),
|
||||
list(canon="ProjectionOscillator", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="Psar", arch="scalar_candle", params=c(0.02,0.02,0.2), width=0L),
|
||||
list(canon="Pvi", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="Qqe", arch="multi_f64", params=c(3.0,7.0,2.0), width=2L),
|
||||
list(canon="Qstick", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="QuartileBands", arch="multi_f64", params=c(14.0), width=3L),
|
||||
list(canon="QuotedSpread", arch="ob", params=c(), width=0L),
|
||||
list(canon="RSquared", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="RangeBars", arch="bars_close", params=c(2.0), width=0L),
|
||||
list(canon="RealizedSpread", arch="trademid", params=c(20.0), width=0L),
|
||||
list(canon="RealizedVolatility", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="RecoveryFactor", arch="scalar_f64", params=c(), width=0L),
|
||||
list(canon="RectangleRange", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="Reflex", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="RegimeLabel", arch="scalar_f64", params=c(3.0,7.0), width=0L),
|
||||
list(canon="RelativeStrengthAB", arch="multi_pairwise", params=c(14.0,14.0), width=3L),
|
||||
list(canon="RenkoBars", arch="bars_close", params=c(2.0), width=0L),
|
||||
list(canon="RenkoTrailingStop", arch="scalar_f64", params=c(2.0), width=0L),
|
||||
list(canon="RickshawMan", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="RisingThreeMethods", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="Rmi", arch="scalar_f64", params=c(3.0,7.0), width=0L),
|
||||
list(canon="Roc", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="Rocp", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="Rocr", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="Rocr100", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="RogersSatchellVolatility", arch="scalar_candle", params=c(20.0,252.0), width=0L),
|
||||
list(canon="RollMeasure", arch="trade", params=c(20.0), width=0L),
|
||||
list(canon="RollingCorrelation", arch="pairwise", params=c(14.0), width=0L),
|
||||
list(canon="RollingCovariance", arch="pairwise", params=c(14.0), width=0L),
|
||||
list(canon="RollingIqr", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="RollingMinMaxScaler", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="RollingPercentileRank", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="RollingQuantile", arch="scalar_f64", params=c(20.0,0.5), width=0L),
|
||||
list(canon="RollingVwap", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="RoofingFilter", arch="scalar_f64", params=c(3.0,7.0), width=0L),
|
||||
list(canon="Rsi", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="Rsx", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="RunBars", arch="bars_candle4", params=c(3.0), width=0L),
|
||||
list(canon="Rvi", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="RviVolatility", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="Rwi", arch="multi_candle", params=c(14.0), width=2L),
|
||||
list(canon="SampleEntropy", arch="scalar_f64", params=c(20.0,2.0,0.2), width=0L),
|
||||
list(canon="SarExt", arch="scalar_candle", params=c(2.0,0.5,0.5,0.5,0.5,0.5,0.5,0.5), width=0L),
|
||||
list(canon="SeasonalZScore", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="SeparatingLines", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="SessionHighLow", arch="multi_candle", params=c(14.0), width=2L),
|
||||
list(canon="SessionRange", arch="multi_candle", params=c(14.0), width=3L),
|
||||
list(canon="SessionVwap", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="ShannonEntropy", arch="scalar_f64", params=c(3.0,7.0), width=0L),
|
||||
list(canon="Shark", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="SharpeRatio", arch="scalar_f64", params=c(14.0,2.0), width=0L),
|
||||
list(canon="ShootingStar", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="ShortLine", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="SignedVolume", arch="trade", params=c(), width=0L),
|
||||
list(canon="SineWave", arch="scalar_f64", params=c(), width=0L),
|
||||
list(canon="SineWeightedMa", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="SinglePrints", arch="scalar_candle", params=c(3.0,7.0), width=0L),
|
||||
list(canon="Skewness", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="Sma", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="Smi", arch="scalar_candle", params=c(3.0,7.0,14.0), width=0L),
|
||||
list(canon="Smma", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="SmoothedHeikinAshi", arch="multi_candle", params=c(14.0), width=4L),
|
||||
list(canon="SortinoRatio", arch="scalar_f64", params=c(14.0,2.0), width=0L),
|
||||
list(canon="SpearmanCorrelation", arch="pairwise", params=c(14.0), width=0L),
|
||||
list(canon="SpinningTop", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="SpreadAr1Coefficient", arch="pairwise", params=c(14.0), width=0L),
|
||||
list(canon="SpreadBollingerBands", arch="multi_pairwise", params=c(14.0,2.0), width=4L),
|
||||
list(canon="SpreadHurst", arch="pairwise", params=c(14.0), width=0L),
|
||||
list(canon="StalledPattern", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="StandardError", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="StandardErrorBands", arch="multi_f64", params=c(14.0,2.0), width=3L),
|
||||
list(canon="StarcBands", arch="multi_candle", params=c(3.0,7.0,2.0), width=3L),
|
||||
list(canon="Stc", arch="scalar_f64", params=c(10.0,23.0,10.0,0.5), width=0L),
|
||||
list(canon="StdDev", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="StepTrailingStop", arch="scalar_f64", params=c(2.0), width=0L),
|
||||
list(canon="SterlingRatio", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="StickSandwich", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="StochRsi", arch="scalar_f64", params=c(3.0,7.0), width=0L),
|
||||
list(canon="Stochastic", arch="multi_candle", params=c(3.0,7.0), width=2L),
|
||||
list(canon="StochasticCci", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="SuperSmoother", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="SuperTrend", arch="multi_candle", params=c(14.0,2.0), width=2L),
|
||||
list(canon="T3", arch="scalar_f64", params=c(5.0,0.7), width=0L),
|
||||
list(canon="TailRatio", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="TakerBuySellRatio", arch="deriv", params=c(), width=0L),
|
||||
list(canon="Takuri", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="TasukiGap", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="TdCamouflage", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="TdClop", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="TdClopwin", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="TdCombo", arch="scalar_candle", params=c(3.0,7.0,14.0,28.0), width=0L),
|
||||
list(canon="TdCountdown", arch="scalar_candle", params=c(3.0,7.0,14.0,28.0), width=0L),
|
||||
list(canon="TdDWave", arch="scalar_candle", params=c(2.0), width=0L),
|
||||
list(canon="TdDeMarker", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="TdDifferential", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="TdLines", arch="multi_candle", params=c(3.0,7.0), width=2L),
|
||||
list(canon="TdMovingAverage", arch="multi_candle", params=c(3.0,7.0), width=2L),
|
||||
list(canon="TdOpen", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="TdPressure", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="TdPropulsion", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="TdRangeProjection", arch="multi_candle", params=c(), width=2L),
|
||||
list(canon="TdRei", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="TdRiskLevel", arch="multi_candle", params=c(3.0,7.0), width=2L),
|
||||
list(canon="TdSequential", arch="multi_candle", params=c(3.0,7.0,14.0,28.0), width=3L),
|
||||
list(canon="TdSetup", arch="scalar_candle", params=c(3.0,7.0), width=0L),
|
||||
list(canon="TdTrap", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="Tema", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="TermStructureBasis", arch="deriv", params=c(), width=0L),
|
||||
list(canon="ThreeDrives", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="ThreeInside", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="ThreeLineBreak", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="ThreeLineBreakBars", arch="bars_close", params=c(3.0), width=0L),
|
||||
list(canon="ThreeLineStrike", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="ThreeOutside", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="ThreeSoldiersOrCrows", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="ThreeStarsInSouth", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="Thrusting", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="TickBars", arch="bars_candle5", params=c(2.0), width=0L),
|
||||
list(canon="TickIndex", arch="cross", params=c(), width=0L),
|
||||
list(canon="Tii", arch="scalar_f64", params=c(3.0,7.0), width=0L),
|
||||
list(canon="TimeBasedStop", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="TimeOfDayReturnProfile", arch="profile_bins", params=c(24.0,0.0), width=24L),
|
||||
list(canon="TowerTopBottom", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="TpoProfile", arch="profile_pricebins", params=c(30.0,50.0), width=52L),
|
||||
list(canon="TradeImbalance", arch="trade", params=c(20.0), width=0L),
|
||||
list(canon="TradeSignAutocorrelation", arch="trade", params=c(20.0), width=0L),
|
||||
list(canon="TradeVolumeIndex", arch="scalar_candle", params=c(2.0), width=0L),
|
||||
list(canon="TrendLabel", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="TrendStrengthIndex", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="Trendflex", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="TreynorRatio", arch="pairwise", params=c(14.0,2.0), width=0L),
|
||||
list(canon="Triangle", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="Trima", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="Trin", arch="cross", params=c(), width=0L),
|
||||
list(canon="TripleTopBottom", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="Tristar", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="Trix", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="TrueRange", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="Tsf", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="TsfOscillator", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="Tsi", arch="scalar_f64", params=c(3.0,7.0), width=0L),
|
||||
list(canon="Tsv", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="TtmSqueeze", arch="multi_candle", params=c(14.0,2.0,0.5), width=2L),
|
||||
list(canon="TtmTrend", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="TurnOfMonth", arch="scalar_candle", params=c(3.0,3.0,0.0), width=0L),
|
||||
list(canon="Tweezer", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="TwiggsMoneyFlow", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="TwoCrows", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="TypicalPrice", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="UlcerIndex", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="UltimateOscillator", arch="scalar_candle", params=c(3.0,7.0,14.0), width=0L),
|
||||
list(canon="UniqueThreeRiver", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="UniversalOscillator", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="UpDownVolumeRatio", arch="cross", params=c(), width=0L),
|
||||
list(canon="UpsideGapThreeMethods", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="UpsideGapTwoCrows", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="UpsidePotentialRatio", arch="scalar_f64", params=c(14.0,2.0), width=0L),
|
||||
list(canon="ValueArea", arch="multi_candle", params=c(20.0,50.0,0.7), width=3L),
|
||||
list(canon="ValueAtRisk", arch="scalar_f64", params=c(20.0,0.95), width=0L),
|
||||
list(canon="Variance", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="VarianceRatio", arch="pairwise", params=c(60.0,2.0), width=0L),
|
||||
list(canon="VerticalHorizontalFilter", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="Vidya", arch="scalar_f64", params=c(3.0,7.0), width=0L),
|
||||
list(canon="VolatilityCone", arch="multi_candle", params=c(3.0,7.0), width=5L),
|
||||
list(canon="VolatilityOfVolatility", arch="scalar_f64", params=c(3.0,7.0), width=0L),
|
||||
list(canon="VolatilityRatio", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="VoltyStop", arch="scalar_candle", params=c(14.0,2.0), width=0L),
|
||||
list(canon="VolumeBars", arch="bars_candle5", params=c(500.0), width=0L),
|
||||
list(canon="VolumeByTimeProfile", arch="profile_bins", params=c(24.0,0.0), width=24L),
|
||||
list(canon="VolumeOscillator", arch="scalar_candle", params=c(3.0,7.0), width=0L),
|
||||
list(canon="VolumePriceTrend", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="VolumeProfile", arch="profile_pricebins", params=c(20.0,50.0), width=52L),
|
||||
list(canon="VolumeRsi", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="VolumeWeightedMacd", arch="multi_candle", params=c(3.0,7.0,14.0), width=3L),
|
||||
list(canon="VolumeWeightedSr", arch="multi_candle", params=c(14.0), width=2L),
|
||||
list(canon="Vortex", arch="multi_candle", params=c(14.0), width=2L),
|
||||
list(canon="Vpin", arch="trade", params=c(5000.0,10.0), width=0L),
|
||||
list(canon="Vwap", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="VwapStdDevBands", arch="multi_candle", params=c(2.0), width=4L),
|
||||
list(canon="Vwma", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="Vzo", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="Wad", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="WavePm", arch="scalar_f64", params=c(3.0,7.0), width=0L),
|
||||
list(canon="WaveTrend", arch="multi_candle", params=c(3.0,7.0,14.0), width=2L),
|
||||
list(canon="Wedge", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="WeightedClose", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="WickRatio", arch="scalar_candle", params=c(), width=0L),
|
||||
list(canon="WilliamsFractals", arch="multi_candle", params=c(), width=2L),
|
||||
list(canon="WilliamsR", arch="scalar_candle", params=c(14.0), width=0L),
|
||||
list(canon="WinRate", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="Wma", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="WoodiePivots", arch="multi_candle", params=c(), width=5L),
|
||||
list(canon="YangZhangVolatility", arch="scalar_candle", params=c(20.0,252.0), width=0L),
|
||||
list(canon="YoyoExit", arch="scalar_candle", params=c(14.0,2.0), width=0L),
|
||||
list(canon="ZScore", arch="scalar_f64", params=c(14.0), width=0L),
|
||||
list(canon="ZeroLagMacd", arch="multi_f64", params=c(3.0,7.0,14.0), width=3L),
|
||||
list(canon="ZigZag", arch="multi_candle", params=c(0.02), width=2L),
|
||||
list(canon="Zlema", arch="scalar_f64", params=c(14.0), width=0L)
|
||||
)
|
||||
@@ -0,0 +1,121 @@
|
||||
# Generic golden-fixture parity for the whole 514-indicator catalogue: every
|
||||
# indicator is reconstructed by its constructor, fed the synthetic stream derived
|
||||
# from the shared testdata/golden input (identical to gen_golden's Rust
|
||||
# construction) and checked bit-for-bit against g_<Canonical>.csv. One reflective
|
||||
# runner flattens scalar, multi-output, profile and bar shapes.
|
||||
#
|
||||
# Like test-golden.R, the fixtures live at the repo root and are not bundled into
|
||||
# the standalone package, so packaged checks (r-universe / CRAN) skip; the parity
|
||||
# is enforced by the monorepo CI. Specs are generated by gen_golden_test.py.
|
||||
|
||||
find_golden_dir_all <- function() {
|
||||
d <- normalizePath(getwd(), winslash = "/", mustWork = FALSE)
|
||||
repeat {
|
||||
g <- file.path(d, "testdata", "golden")
|
||||
if (dir.exists(g)) return(g)
|
||||
parent <- dirname(d)
|
||||
if (identical(parent, d)) return(NULL)
|
||||
d <- parent
|
||||
}
|
||||
}
|
||||
|
||||
golden_dir_all <- find_golden_dir_all()
|
||||
|
||||
test_that("all 514 indicators match the Rust golden fixtures", {
|
||||
skip_if(is.null(golden_dir_all), "golden fixtures not bundled with the package")
|
||||
source(test_path("golden_specs.R"), local = TRUE)
|
||||
|
||||
gcell <- function(s) {
|
||||
if (s == "nan") NA_real_ else if (s == "inf") Inf else if (s == "-inf") -Inf else as.numeric(s)
|
||||
}
|
||||
read_rows <- function(name) {
|
||||
lines <- readLines(file.path(golden_dir_all, paste0(name, ".csv")))[-1]
|
||||
lapply(lines, function(l) {
|
||||
if (nchar(l) == 0) return(numeric(0))
|
||||
vapply(strsplit(l, ",", fixed = TRUE)[[1]], gcell, numeric(1), USE.NAMES = FALSE)
|
||||
})
|
||||
}
|
||||
input_rows <- lapply(
|
||||
readLines(file.path(golden_dir_all, "input.csv"))[-1],
|
||||
function(l) as.numeric(strsplit(l, ",", fixed = TRUE)[[1]])
|
||||
)
|
||||
|
||||
deriv_fields <- function(r) {
|
||||
o <- r[1]; h <- r[2]; l <- r[3]; c <- r[4]; v <- r[5]
|
||||
c((c - o) / c * 0.01, c, c - 0.5, c + 1.0, v * 10, v * 0.6, v * 0.4,
|
||||
v * 0.55, v * 0.45, h - c, c - l)
|
||||
}
|
||||
cross_lists <- function(r) {
|
||||
o <- r[1]; c <- r[4]; v <- r[5]; j <- 0:4
|
||||
list(change = (c - o) + j, volume = v + j * 10,
|
||||
newHigh = as.numeric(j %% 2 == 0), newLow = as.numeric(j %% 3 == 0),
|
||||
aboveMa = as.numeric(j %% 2 == 0), onBuy = as.numeric(j %% 3 == 0))
|
||||
}
|
||||
ob_lists <- function(r) {
|
||||
c <- r[4]; v <- r[5]; k <- 1:5
|
||||
list(bp = c - 0.1 * k, bs = v / k, ap = c + 0.1 * k, asz = v * 0.9 / k)
|
||||
}
|
||||
flatten <- function(o, arch, width) {
|
||||
if (arch %in% c("profile_bins")) {
|
||||
if (is.null(o) || length(o) == 0 || all(is.na(o))) return(rep(NA_real_, width))
|
||||
return(as.numeric(o))
|
||||
}
|
||||
if (arch == "profile_pricebins") {
|
||||
if (is.list(o)) return(c(o$price_low, o$price_high, as.numeric(o$values)))
|
||||
return(rep(NA_real_, width))
|
||||
}
|
||||
if (arch %in% c("bars_close", "bars_candle4", "bars_candle5", "footprint")) {
|
||||
if (is.null(o) || length(o) == 0) return(numeric(0))
|
||||
if (is.matrix(o)) return(as.numeric(t(o)))
|
||||
return(as.numeric(o))
|
||||
}
|
||||
as.numeric(o)
|
||||
}
|
||||
|
||||
compute <- function(spec, ind, r, i) {
|
||||
o <- r[1]; h <- r[2]; l <- r[3]; cl <- r[4]; v <- r[5]; ts <- as.integer(i - 1)
|
||||
out <- switch(spec$arch,
|
||||
scalar_f64 = update(ind, cl),
|
||||
multi_f64 = update(ind, cl),
|
||||
pairwise = , multi_pairwise = update(ind, cl, o),
|
||||
scalar_candle = , multi_candle = , profile_bins = , profile_pricebins =
|
||||
update(ind, o, h, l, cl, v, ts),
|
||||
trade = update(ind, cl, v, cl >= o, ts),
|
||||
trademid = update(ind, cl, v, cl >= o, ts, (h + l) / 2),
|
||||
ob = { L <- ob_lists(r); update(ind, L$bp, L$bs, L$ap, L$asz) },
|
||||
cross = { L <- cross_lists(r)
|
||||
update(ind, L$change, L$volume, L$newHigh, L$newLow, L$aboveMa, L$onBuy, ts) },
|
||||
deriv = , deriv_multi = { d <- deriv_fields(r)
|
||||
do.call(update, c(list(ind), as.list(d), list(ts))) },
|
||||
bars_close = update(ind, cl, cl, cl, cl, 1, 0L),
|
||||
bars_candle4 = update(ind, o, h, l, cl, 1, 0L),
|
||||
bars_candle5 = update(ind, o, h, l, cl, v, 0L),
|
||||
footprint = update(ind, cl, v, cl >= o, ts),
|
||||
stop("arch ", spec$arch)
|
||||
)
|
||||
flatten(out, spec$arch, spec$width)
|
||||
}
|
||||
|
||||
for (spec in GOLDEN_SPECS) {
|
||||
ind <- do.call(get(spec$canon), as.list(spec$params))
|
||||
exp <- read_rows(paste0("g_", spec$canon))
|
||||
for (i in seq_along(input_rows)) {
|
||||
got <- compute(spec, ind, input_rows[[i]], i)
|
||||
want <- exp[[i]]
|
||||
expect_equal(length(got), length(want),
|
||||
info = sprintf("%s row %d arity", spec$canon, i))
|
||||
for (k in seq_along(want)) {
|
||||
w <- want[k]; g <- got[k]
|
||||
if (is.na(w)) {
|
||||
expect_true(is.na(g), info = sprintf("%s row %d col %d: want NA", spec$canon, i, k))
|
||||
} else if (is.infinite(w)) {
|
||||
expect_true(is.infinite(g) && sign(g) == sign(w),
|
||||
info = sprintf("%s row %d col %d: want %g", spec$canon, i, k, w))
|
||||
} else {
|
||||
expect_lte(abs(g - w), 1e-6 * max(1, abs(w)),
|
||||
label = sprintf("%s row %d col %d (got %s want %g)", spec$canon, i, k, as.character(g), w))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -117,3 +117,37 @@ test_that("multi-output ADX matches golden", {
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
# The four de-duplicated indicators, pinned against the Rust reference.
|
||||
|
||||
test_that("de-duplicated candle indicators match golden", {
|
||||
skip_if_no_golden()
|
||||
golden_input <- read_golden_input()
|
||||
specs <- list(
|
||||
list("ad_oscillator", AdOscillator()),
|
||||
list("intraday_intensity", IntradayIntensity()),
|
||||
list("awesome_oscillator_histogram", AwesomeOscillatorHistogram(5, 34, 1))
|
||||
)
|
||||
for (spec in specs) {
|
||||
name <- spec[[1]]
|
||||
ind <- spec[[2]]
|
||||
exp <- read_golden(name)
|
||||
for (i in seq_len(nrow(golden_input))) {
|
||||
got <- update(ind, golden_input$open[i], golden_input$high[i], golden_input$low[i],
|
||||
golden_input$close[i], golden_input$volume[i], i - 1)
|
||||
expect_close(got, gcell(exp[i, 1]), i, name)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
test_that("AverageDrawdown matches golden", {
|
||||
skip_if_no_golden()
|
||||
golden_input <- read_golden_input()
|
||||
avg <- AverageDrawdown(20)
|
||||
exp <- read_golden("average_drawdown")
|
||||
for (i in seq_len(nrow(golden_input))) {
|
||||
# generator fed the close column as the equity-curve sample.
|
||||
got <- update(avg, golden_input$close[i])
|
||||
expect_close(got, gcell(exp[i, 1]), i, "average_drawdown")
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,201 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or Derivative
|
||||
Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2026 kingchenc and the Wickra contributors
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 kingchenc and the Wickra contributors
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,89 @@
|
||||
"""Generate wasm_manifest.json for the WASM golden replay: for every one of the
|
||||
514 indicators, record the JS class name, constructor params, the ordered update
|
||||
argument names (parsed from pkg/wickra_wasm.d.ts, each flagged array/bigint) and
|
||||
the output archetype (from golden_manifest.json). Run from repo root after
|
||||
`wasm-pack build --target nodejs`: python bindings/wasm/gen_golden_test.py
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
G = os.path.join(ROOT, "testdata", "golden")
|
||||
DTS = os.path.join(ROOT, "bindings", "wasm", "pkg", "wickra_wasm.d.ts")
|
||||
|
||||
# canonical -> native (JS class) and canonical -> (arch, n/width) from the manifests.
|
||||
native = {}
|
||||
arch = {}
|
||||
extra = {}
|
||||
for e in json.load(open(os.path.join(G, "scalar_manifest.json"))):
|
||||
native[e["canonical"]] = e["native"]
|
||||
arch[e["canonical"]] = {"f64": "scalar_f64", "Candle": "scalar_candle", "(f64, f64)": "pairwise"}[e["input"]]
|
||||
for e in json.load(open(os.path.join(G, "multi_manifest.json"))):
|
||||
native[e["canonical"]] = e["native"]
|
||||
arch[e["canonical"]] = {"f64": "multi_f64", "Candle": "multi_candle", "(f64, f64)": "multi_pairwise"}[e["input"]]
|
||||
extra[e["canonical"]] = {"n": e["n"]}
|
||||
ex = json.load(open(os.path.join(G, "exotic_manifest.json")))
|
||||
for e in ex["deriv"]:
|
||||
native[e["canonical"]] = e["native"]
|
||||
arch[e["canonical"]] = "deriv_multi" if "n" in e else "deriv"
|
||||
if "n" in e:
|
||||
extra[e["canonical"]] = {"n": e["n"]}
|
||||
for fam, a in (("cross", "cross"), ("trade", "trade"), ("trademid", "trademid"), ("ob", "ob")):
|
||||
for e in ex[fam]:
|
||||
native[e["canonical"]] = e["native"]
|
||||
arch[e["canonical"]] = a
|
||||
for e in json.load(open(os.path.join(G, "profile_manifest.json"))):
|
||||
native[e["canonical"]] = e["native"]
|
||||
arch[e["canonical"]] = "profile_" + e["kind"]
|
||||
extra[e["canonical"]] = {"width": e["width"], **({"arrayField": "counts" if e["canonical"] == "TpoProfile" else "bins"} if e["kind"] == "pricebins" else {})}
|
||||
for e in json.load(open(os.path.join(G, "bars_manifest.json"))):
|
||||
native[e["canonical"]] = e["native"]
|
||||
arch[e["canonical"]] = "footprint" if e["canonical"] == "Footprint" else "bars"
|
||||
|
||||
# params per canonical (constructor values) — same as the other bindings.
|
||||
params = {}
|
||||
for fn in ("scalar_manifest", "multi_manifest"):
|
||||
for e in json.load(open(os.path.join(G, fn + ".json"))):
|
||||
params[e["canonical"]] = e["params"]
|
||||
for fam in json.load(open(os.path.join(G, "exotic_manifest.json"))).values():
|
||||
for e in fam:
|
||||
params[e["canonical"]] = e["params"]
|
||||
for e in json.load(open(os.path.join(G, "profile_manifest.json"))):
|
||||
params[e["canonical"]] = e["params"]
|
||||
for e in json.load(open(os.path.join(G, "bars_manifest.json"))):
|
||||
params[e["canonical"]] = e["params"]
|
||||
|
||||
# parse update args per JS class from the wasm .d.ts
|
||||
dts = open(DTS, encoding="utf-8").read()
|
||||
cls_args = {}
|
||||
for m in re.finditer(r"export class (\w+) \{(.*?)\n\}", dts, re.S):
|
||||
name, body = m.group(1), m.group(2)
|
||||
um = re.search(r"\bupdate\(([^)]*)\)", body)
|
||||
args = []
|
||||
if um and um.group(1).strip():
|
||||
for p in um.group(1).split(","):
|
||||
p = p.strip()
|
||||
nm = p.split(":")[0].strip()
|
||||
typ = p.split(":", 1)[1].strip() if ":" in p else ""
|
||||
args.append({"name": nm, "array": "Array" in typ, "bigint": "bigint" in typ})
|
||||
cls_args[name] = args
|
||||
|
||||
out = []
|
||||
for canon in sorted(native):
|
||||
js = native[canon]
|
||||
if js not in cls_args:
|
||||
raise SystemExit(f"WASM class {js} (for {canon}) not in d.ts")
|
||||
ctor = params.get(canon, [])
|
||||
# EaseOfMovement's volume divisor is an optional Rust constructor argument
|
||||
# (default 1e8) but a required WASM constructor parameter; pass it explicitly.
|
||||
if canon == "EaseOfMovement":
|
||||
ctor = [ctor[0], 100000000.0]
|
||||
e = {"canonical": canon, "js": js, "ctor": ctor,
|
||||
"args": cls_args[js], "out": arch[canon]}
|
||||
e.update(extra.get(canon, {}))
|
||||
out.append(e)
|
||||
|
||||
json.dump(out, open(os.path.join(G, "wasm_manifest.json"), "w"), indent=1)
|
||||
from collections import Counter
|
||||
print("wasm_manifest:", len(out), dict(Counter(e["out"] for e in out)))
|
||||
@@ -3145,12 +3145,12 @@ impl WasmKvo {
|
||||
}
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_name = WilliamsAD)]
|
||||
#[wasm_bindgen(js_name = ADOSC)]
|
||||
pub struct WasmAdOscillator {
|
||||
inner: wc::AdOscillator,
|
||||
}
|
||||
|
||||
#[wasm_bindgen(js_class = WilliamsAD)]
|
||||
#[wasm_bindgen(js_class = ADOSC)]
|
||||
impl WasmAdOscillator {
|
||||
#[wasm_bindgen(constructor)]
|
||||
#[allow(clippy::new_without_default)]
|
||||
|
||||
@@ -0,0 +1,156 @@
|
||||
// Generic golden-fixture parity for the WASM (wasm-bindgen) binding, run under
|
||||
// Node's test runner against the nodejs-target build in ../pkg.
|
||||
//
|
||||
// Every one of the 514 indicators is reconstructed from wasm_manifest.json (JS
|
||||
// class, constructor params, ordered update args parsed from the generated
|
||||
// .d.ts), fed the synthetic stream derived from the shared golden input — the
|
||||
// same construction gen_golden uses — and checked bit-for-bit against the Rust
|
||||
// reference fixtures g_<Canonical>.csv.
|
||||
//
|
||||
// wasm-pack build --target nodejs --out-dir pkg
|
||||
// node --test tests/
|
||||
|
||||
const test = require('node:test');
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const W = require('../pkg/wickra_wasm.js');
|
||||
|
||||
const GOLDEN = path.resolve(__dirname, '..', '..', '..', 'testdata', 'golden');
|
||||
|
||||
function cell(s) {
|
||||
if (s === 'nan') return NaN;
|
||||
if (s === 'inf') return Infinity;
|
||||
if (s === '-inf') return -Infinity;
|
||||
return Number(s);
|
||||
}
|
||||
|
||||
function readCsv(name) {
|
||||
const lines = fs.readFileSync(path.join(GOLDEN, name + '.csv'), 'utf8').split('\n');
|
||||
lines.shift();
|
||||
return lines.filter((l) => l.length > 0).map((l) => l.split(',').map(cell));
|
||||
}
|
||||
|
||||
function readBarRows(name) {
|
||||
const lines = fs.readFileSync(path.join(GOLDEN, name + '.csv'), 'utf8').split('\n');
|
||||
lines.shift();
|
||||
if (lines.length && lines[lines.length - 1] === '') lines.pop();
|
||||
return lines.map((l) => (l.length === 0 ? [] : l.split(',').map(cell)));
|
||||
}
|
||||
|
||||
const MANIFEST = JSON.parse(fs.readFileSync(path.join(GOLDEN, 'wasm_manifest.json'), 'utf8'));
|
||||
const ROWS = readCsv('input');
|
||||
|
||||
function deriv(o, h, l, c, v) {
|
||||
return {
|
||||
funding_rate: ((c - o) / c) * 0.01,
|
||||
mark_price: c,
|
||||
index_price: c - 0.5,
|
||||
futures_price: c + 1.0,
|
||||
open_interest: v * 10.0,
|
||||
long_size: v * 0.6,
|
||||
short_size: v * 0.4,
|
||||
taker_buy_volume: v * 0.55,
|
||||
taker_sell_volume: v * 0.45,
|
||||
long_liquidation: h - c,
|
||||
short_liquidation: c - l,
|
||||
};
|
||||
}
|
||||
|
||||
function resolveArg(arg, o, h, l, c, v, i) {
|
||||
const n = arg.name;
|
||||
if (arg.array) {
|
||||
const j5 = [0, 1, 2, 3, 4];
|
||||
switch (n) {
|
||||
case 'change': return Float64Array.from(j5.map((j) => c - o + j));
|
||||
case 'volume': return Float64Array.from(j5.map((j) => v + j * 10.0));
|
||||
case 'new_high': return Float64Array.from(j5.map((j) => (j % 2 === 0 ? 1 : 0)));
|
||||
case 'new_low': return Float64Array.from(j5.map((j) => (j % 3 === 0 ? 1 : 0)));
|
||||
case 'above_ma': return Float64Array.from(j5.map((j) => (j % 2 === 0 ? 1 : 0)));
|
||||
case 'on_buy_signal': return Float64Array.from(j5.map((j) => (j % 3 === 0 ? 1 : 0)));
|
||||
case 'bid_px': return Float64Array.from(j5.map((k) => c - 0.1 * (k + 1)));
|
||||
case 'bid_sz': return Float64Array.from(j5.map((k) => v / (k + 1)));
|
||||
case 'ask_px': return Float64Array.from(j5.map((k) => c + 0.1 * (k + 1)));
|
||||
case 'ask_sz': return Float64Array.from(j5.map((k) => (v * 0.9) / (k + 1)));
|
||||
default: throw new Error('array arg ' + n);
|
||||
}
|
||||
}
|
||||
if (arg.bigint) return BigInt(i);
|
||||
switch (n) {
|
||||
case 'value': case 'close': case 'price': case 'x': case 'a': case 'asset': return c;
|
||||
case 'y': case 'b': case 'open': case 'benchmark': return o;
|
||||
case 'high': return h;
|
||||
case 'low': return l;
|
||||
case 'volume': case 'size': return v;
|
||||
case 'is_buy': return c >= o;
|
||||
case 'mid': return (h + l) / 2.0;
|
||||
case 'timestamp': return BigInt(i);
|
||||
default: {
|
||||
const d = deriv(o, h, l, c, v);
|
||||
if (n in d) return d[n];
|
||||
throw new Error('scalar arg ' + n);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Recursively flatten a WASM output (number, object with field props, typed/array
|
||||
// of either) into a flat number list. Returns null for warmup (null/undefined).
|
||||
function flat(v) {
|
||||
if (v === null || v === undefined) return null;
|
||||
if (typeof v === 'number' || typeof v === 'bigint') return [Number(v)];
|
||||
if (Array.isArray(v) || ArrayBuffer.isView(v)) {
|
||||
const out = [];
|
||||
for (const e of v) { const f = flat(e); if (f) out.push(...f); }
|
||||
return out;
|
||||
}
|
||||
if (typeof v === 'object') {
|
||||
const out = [];
|
||||
for (const val of Object.values(v)) { const f = flat(val); if (f) out.push(...f); }
|
||||
return out;
|
||||
}
|
||||
return [Number(v)];
|
||||
}
|
||||
|
||||
function nanRow(n) {
|
||||
return Array.from({ length: n }, () => NaN);
|
||||
}
|
||||
|
||||
function widthOf(spec) {
|
||||
if (spec.out.startsWith('multi') || spec.out === 'deriv_multi') return spec.n;
|
||||
if (spec.out.startsWith('profile')) return spec.width;
|
||||
return 1; // scalar archetypes
|
||||
}
|
||||
|
||||
function closeEq(got, want, label) {
|
||||
if (Number.isNaN(want)) { assert.ok(Number.isNaN(got), `${label}: want NaN got ${got}`); return; }
|
||||
if (!Number.isFinite(want)) { assert.ok(got === want, `${label}: want ${want} got ${got}`); return; }
|
||||
const tol = 1e-6 * Math.max(1.0, Math.abs(want));
|
||||
assert.ok(Math.abs(got - want) <= tol, `${label}: got ${got} want ${want}`);
|
||||
}
|
||||
|
||||
for (const spec of MANIFEST) {
|
||||
test(`wasm golden: ${spec.canonical}`, () => {
|
||||
const Cls = W[spec.js];
|
||||
assert.ok(Cls, `missing WASM class ${spec.js}`);
|
||||
const ind = new Cls(...spec.ctor);
|
||||
const isBars = spec.out === 'bars' || spec.out === 'footprint';
|
||||
const expected = isBars ? readBarRows('g_' + spec.canonical) : readCsv('g_' + spec.canonical);
|
||||
|
||||
for (let i = 0; i < ROWS.length; i++) {
|
||||
const [o, h, l, c, v] = ROWS[i];
|
||||
const args = spec.args.map((a) => resolveArg(a, o, h, l, c, v, i));
|
||||
const raw = ind.update(...args);
|
||||
const want = expected[i];
|
||||
const label = `${spec.canonical} row ${i}`;
|
||||
|
||||
let got = flat(raw);
|
||||
if (isBars) {
|
||||
if (got === null) got = [];
|
||||
} else if (got === null) {
|
||||
got = nanRow(widthOf(spec));
|
||||
}
|
||||
assert.equal(got.length, want.length, `${label}: arity ${got.length} vs ${want.length}`);
|
||||
for (let k = 0; k < want.length; k++) closeEq(got[k], want[k], `${label} col ${k}`);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -1,28 +1,35 @@
|
||||
//! Williams Accumulation/Distribution.
|
||||
//! Williams A/D Oscillator (ADOSC).
|
||||
|
||||
use crate::indicators::sma::Sma;
|
||||
use crate::ohlcv::Candle;
|
||||
use crate::traits::Indicator;
|
||||
|
||||
/// Larry Williams' Accumulation/Distribution — a cumulative volume-less price
|
||||
/// flow that classifies each bar as accumulation or distribution based on its
|
||||
/// close relative to the previous close, then sums the directional component.
|
||||
/// Smoothing window applied to the Williams A/D line to form the oscillator.
|
||||
const SIGNAL_PERIOD: usize = 13;
|
||||
|
||||
/// Williams **A/D Oscillator** — the volume-free Williams Accumulation/
|
||||
/// Distribution line measured against its own moving average, so it oscillates
|
||||
/// around zero instead of drifting like the cumulative line.
|
||||
///
|
||||
/// Williams' definition (1972) uses a *true* high/low that includes the prior
|
||||
/// close as an anchor — the same idea that motivates true range:
|
||||
/// The underlying line is Larry Williams' volume-less A/D (1972), which uses a
|
||||
/// *true* high/low anchored on the prior close; the oscillator subtracts its
|
||||
/// 13-bar simple moving average:
|
||||
///
|
||||
/// ```text
|
||||
/// TR_h_t = max(close_{t−1}, high_t)
|
||||
/// TR_l_t = min(close_{t−1}, low_t)
|
||||
/// AD_t = AD_{t−1} + (close_t − TR_l_t) if close_t > close_{t−1} (accumulation)
|
||||
/// AD_t = AD_{t−1} + (close_t − TR_h_t) if close_t < close_{t−1} (distribution)
|
||||
/// AD_t = AD_{t−1} if close_t == close_{t−1} (no change)
|
||||
/// WAD_t = WAD_{t−1} + (close_t − TR_l_t) if close_t > close_{t−1}
|
||||
/// WAD_t = WAD_{t−1} + (close_t − TR_h_t) if close_t < close_{t−1}
|
||||
/// WAD_t = WAD_{t−1} if close_t == close_{t−1}
|
||||
/// ADOSC_t = WAD_t − SMA(WAD, 13)_t
|
||||
/// ```
|
||||
///
|
||||
/// Unlike Chaikin's Accumulation/Distribution Line, the Williams A/D ignores
|
||||
/// volume entirely — Williams argued that the relative position of the close
|
||||
/// already encodes the day's "true" buying or selling pressure. The series is
|
||||
/// unbounded and used primarily for divergence analysis. The first candle only
|
||||
/// seeds the previous close; the first emission lands at bar 2.
|
||||
/// This is distinct from the raw cumulative line, which Wickra ships as
|
||||
/// [`Wad`](crate::Wad): `Wad` is the drifting line for divergence analysis,
|
||||
/// while this oscillator is its zero-centred, mean-reverting form (positive
|
||||
/// when accumulation is running ahead of its recent average, negative when
|
||||
/// distribution is). The first bar only seeds the previous close; the first
|
||||
/// oscillator value lands once the 13-bar average of the line is full.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@@ -39,30 +46,35 @@ use crate::traits::Indicator;
|
||||
/// }
|
||||
/// assert!(last.is_some());
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct AdOscillator {
|
||||
prev_close: Option<f64>,
|
||||
total: f64,
|
||||
has_emitted: bool,
|
||||
line: f64,
|
||||
signal: Sma,
|
||||
last: Option<f64>,
|
||||
}
|
||||
|
||||
impl Default for AdOscillator {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl AdOscillator {
|
||||
/// Construct a new Williams A/D starting at zero.
|
||||
pub const fn new() -> Self {
|
||||
/// Construct a new Williams A/D Oscillator.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
prev_close: None,
|
||||
total: 0.0,
|
||||
has_emitted: false,
|
||||
line: 0.0,
|
||||
signal: Sma::new(SIGNAL_PERIOD).expect("SIGNAL_PERIOD is non-zero"),
|
||||
last: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Current cumulative value if at least one emission has happened.
|
||||
/// Current oscillator value if available.
|
||||
pub const fn value(&self) -> Option<f64> {
|
||||
if self.has_emitted {
|
||||
Some(self.total)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
self.last
|
||||
}
|
||||
}
|
||||
|
||||
@@ -78,45 +90,47 @@ impl Indicator for AdOscillator {
|
||||
};
|
||||
let delta = if candle.close > prev {
|
||||
// Accumulation: distance from the true low.
|
||||
let tr_l = prev.min(candle.low);
|
||||
candle.close - tr_l
|
||||
candle.close - prev.min(candle.low)
|
||||
} else if candle.close < prev {
|
||||
// Distribution: distance from the true high (negative).
|
||||
let tr_h = prev.max(candle.high);
|
||||
candle.close - tr_h
|
||||
candle.close - prev.max(candle.high)
|
||||
} else {
|
||||
// Unchanged close contributes nothing.
|
||||
0.0
|
||||
};
|
||||
self.total += delta;
|
||||
self.line += delta;
|
||||
self.prev_close = Some(candle.close);
|
||||
self.has_emitted = true;
|
||||
Some(self.total)
|
||||
let signal = self.signal.update(self.line)?;
|
||||
let osc = self.line - signal;
|
||||
self.last = Some(osc);
|
||||
Some(osc)
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.prev_close = None;
|
||||
self.total = 0.0;
|
||||
self.has_emitted = false;
|
||||
self.line = 0.0;
|
||||
self.signal.reset();
|
||||
self.last = None;
|
||||
}
|
||||
|
||||
fn warmup_period(&self) -> usize {
|
||||
// One seed bar; the second bar is the first emission.
|
||||
2
|
||||
// One seed bar establishes the prior close; the line then feeds the
|
||||
// 13-bar signal SMA, which is full after `SIGNAL_PERIOD` line values.
|
||||
1 + SIGNAL_PERIOD
|
||||
}
|
||||
|
||||
fn is_ready(&self) -> bool {
|
||||
self.has_emitted
|
||||
self.last.is_some()
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
"WilliamsAD"
|
||||
"ADOSC"
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::indicators::wad::Wad;
|
||||
use crate::traits::BatchExt;
|
||||
use approx::assert_relative_eq;
|
||||
|
||||
@@ -127,94 +141,103 @@ mod tests {
|
||||
#[test]
|
||||
fn accessors_and_metadata() {
|
||||
let ad = AdOscillator::new();
|
||||
assert_eq!(ad.name(), "WilliamsAD");
|
||||
assert_eq!(ad.warmup_period(), 2);
|
||||
assert_eq!(ad.value(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn value_returns_total_after_first_emission() {
|
||||
let mut ad = AdOscillator::new();
|
||||
ad.update(c(10.0, 11.0, 9.0, 10.0, 0));
|
||||
let v = ad.update(c(11.0, 13.0, 8.0, 12.0, 1)).unwrap();
|
||||
assert_relative_eq!(ad.value().unwrap(), v, epsilon = 1e-12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn first_bar_only_seeds() {
|
||||
let mut ad = AdOscillator::new();
|
||||
assert_eq!(ad.update(c(10.0, 11.0, 9.0, 10.0, 0)), None);
|
||||
assert_eq!(ad.name(), "ADOSC");
|
||||
assert_eq!(ad.warmup_period(), 14);
|
||||
assert!(!ad.is_ready());
|
||||
assert_eq!(ad.value(), None);
|
||||
// `Default` matches `new`.
|
||||
assert_eq!(AdOscillator::default().warmup_period(), 14);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accumulation_adds_distance_from_true_low() {
|
||||
// prev close = 10, today low = 8, today close = 12 (up day).
|
||||
// TR_l = min(10, 8) = 8, delta = 12 - 8 = 4. AD = 0 + 4 = 4.
|
||||
fn seed_bar_returns_none() {
|
||||
let mut ad = AdOscillator::new();
|
||||
ad.update(c(10.0, 11.0, 9.0, 10.0, 0));
|
||||
let v = ad.update(c(11.0, 13.0, 8.0, 12.0, 1)).unwrap();
|
||||
assert_relative_eq!(v, 4.0, epsilon = 1e-12);
|
||||
assert_eq!(ad.update(c(100.0, 101.0, 99.0, 100.0, 0)), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn distribution_adds_distance_from_true_high() {
|
||||
// prev close = 10, today high = 11, today close = 7 (down day).
|
||||
// TR_h = max(10, 11) = 11, delta = 7 - 11 = -4. AD = -4.
|
||||
let mut ad = AdOscillator::new();
|
||||
ad.update(c(10.0, 11.0, 9.0, 10.0, 0));
|
||||
let v = ad.update(c(10.0, 11.0, 7.0, 7.0, 1)).unwrap();
|
||||
assert_relative_eq!(v, -4.0, epsilon = 1e-12);
|
||||
fn equals_wad_line_minus_its_sma() {
|
||||
// The oscillator is exactly the Williams A/D line minus its 13-SMA, so
|
||||
// it must match the standalone `Wad` line passed through an SMA(13).
|
||||
let candles: Vec<Candle> = (0..80_i64)
|
||||
.map(|i| {
|
||||
let base = 100.0 + (i as f64 * 0.3).sin() * 6.0;
|
||||
c(
|
||||
base,
|
||||
base + 2.0,
|
||||
base - 2.0,
|
||||
base + (i as f64 * 0.5).cos(),
|
||||
i,
|
||||
)
|
||||
})
|
||||
.collect();
|
||||
let osc = AdOscillator::new().batch(&candles);
|
||||
// Reconstruct: Wad line, then line − SMA(line, 13).
|
||||
let line = Wad::new().batch(&candles);
|
||||
let mut sma = Sma::new(SIGNAL_PERIOD).unwrap();
|
||||
let expected: Vec<Option<f64>> = line
|
||||
.iter()
|
||||
.map(|v| v.and_then(|l| sma.update(l).map(|s| l - s)))
|
||||
.collect();
|
||||
assert_eq!(osc, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn unchanged_close_keeps_total() {
|
||||
// close equals prev close -> no contribution.
|
||||
fn flat_market_oscillates_at_zero() {
|
||||
// A flat market never accumulates or distributes, so the line is
|
||||
// constant and the oscillator sits at zero once warm.
|
||||
let mut ad = AdOscillator::new();
|
||||
ad.update(c(10.0, 11.0, 9.0, 10.0, 0));
|
||||
let v = ad.update(c(10.0, 12.0, 8.0, 10.0, 1)).unwrap();
|
||||
assert_relative_eq!(v, 0.0, epsilon = 1e-12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constant_series_yields_zero() {
|
||||
// Every close equals the previous -> AD stays at zero forever.
|
||||
let candles: Vec<Candle> = (0..40).map(|i| c(10.0, 11.0, 9.0, 10.0, i)).collect();
|
||||
let mut ad = AdOscillator::new();
|
||||
for v in ad.batch(&candles).into_iter().flatten() {
|
||||
assert_relative_eq!(v, 0.0, epsilon = 1e-12);
|
||||
let candles: Vec<Candle> = (0..40).map(|i| c(50.0, 50.0, 50.0, 50.0, i)).collect();
|
||||
let out = ad.batch(&candles);
|
||||
for v in out.iter().skip(ad.warmup_period() - 1).flatten() {
|
||||
assert_relative_eq!(*v, 0.0, epsilon = 1e-12);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_equals_streaming() {
|
||||
let candles: Vec<Candle> = (0..80i64)
|
||||
fn warmup_emits_at_warmup_period() {
|
||||
let mut ad = AdOscillator::new();
|
||||
let candles: Vec<Candle> = (0..20)
|
||||
.map(|i| {
|
||||
let f = i as f64;
|
||||
let mid = 100.0 + (f * 0.3).sin() * 5.0;
|
||||
c(mid, mid + 2.0, mid - 2.0, mid + 0.5, i)
|
||||
let close = 100.0 + f64::from(i);
|
||||
c(close, close + 2.0, close - 2.0, close, i64::from(i))
|
||||
})
|
||||
.collect();
|
||||
let mut a = AdOscillator::new();
|
||||
let mut b = AdOscillator::new();
|
||||
assert_eq!(
|
||||
a.batch(&candles),
|
||||
candles.iter().map(|x| b.update(*x)).collect::<Vec<_>>()
|
||||
);
|
||||
let out = ad.batch(&candles);
|
||||
assert_eq!(ad.warmup_period(), 14);
|
||||
for v in out.iter().take(13) {
|
||||
assert!(v.is_none());
|
||||
}
|
||||
assert!(out[13].is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn reset_clears_state() {
|
||||
let mut ad = AdOscillator::new();
|
||||
ad.batch(&[
|
||||
c(10.0, 11.0, 9.0, 10.0, 0),
|
||||
c(10.0, 12.0, 9.0, 11.0, 1),
|
||||
c(11.0, 13.0, 10.0, 12.0, 2),
|
||||
]);
|
||||
let candles: Vec<Candle> = (0..30)
|
||||
.map(|i| {
|
||||
let close = 100.0 + f64::from(i);
|
||||
c(close, close + 2.0, close - 2.0, close, i64::from(i))
|
||||
})
|
||||
.collect();
|
||||
ad.batch(&candles);
|
||||
assert!(ad.is_ready());
|
||||
ad.reset();
|
||||
assert!(!ad.is_ready());
|
||||
assert_eq!(ad.value(), None);
|
||||
assert_eq!(ad.update(c(10.0, 11.0, 9.0, 10.0, 3)), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_equals_streaming() {
|
||||
let candles: Vec<Candle> = (0..100_i64)
|
||||
.map(|i| {
|
||||
let base = 100.0 + (i as f64 * 0.2).sin() * 5.0;
|
||||
c(base, base + 1.5, base - 1.5, base + 0.4, i)
|
||||
})
|
||||
.collect();
|
||||
let batch = AdOscillator::new().batch(&candles);
|
||||
let mut s = AdOscillator::new();
|
||||
let streamed: Vec<_> = candles.iter().map(|x| s.update(*x)).collect();
|
||||
assert_eq!(batch, streamed);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,19 +7,23 @@ use crate::traits::Indicator;
|
||||
|
||||
/// Rolling Average Drawdown.
|
||||
///
|
||||
/// Input is treated as an equity-curve sample. The indicator scans the
|
||||
/// trailing window of `period` values, tracks the running peak inside the
|
||||
/// window, and reports the **mean** of all bar-by-bar drawdowns (the average
|
||||
/// "pain" of being under water):
|
||||
/// Input is treated as an equity-curve sample. Over the trailing window of
|
||||
/// `period` values the indicator identifies each **distinct drawdown episode**
|
||||
/// — a stretch where equity is below the running peak — and reports the **mean
|
||||
/// of the episodes' maximum depths**:
|
||||
///
|
||||
/// ```text
|
||||
/// drawdown_t = (peak_t − equity_t) / peak_t (running peak inside window)
|
||||
/// AvgDD = mean(drawdown_t over window)
|
||||
/// episode opens when equity < running peak
|
||||
/// episode closes when equity reaches a new peak (full recovery)
|
||||
/// depth(episode) = (episode_peak − episode_trough) / episode_peak
|
||||
/// AvgDD = mean(depth over episodes in window) (0 if no drawdown)
|
||||
/// ```
|
||||
///
|
||||
/// Output is non-negative (a fraction; `0.05` ≈ 5 % average drawdown). This
|
||||
/// is the **Pain Index** under a different name — see [`crate::PainIndex`]
|
||||
/// for the same metric exposed under its conventional label.
|
||||
/// This is the conventional "average drawdown" (mean depth across separate
|
||||
/// drawdowns), which is distinct from the [`crate::PainIndex`] — the latter
|
||||
/// averages the under-water fraction at *every* bar, so a long shallow
|
||||
/// drawdown weighs more there than here. Output is a non-negative fraction
|
||||
/// (`0.05` ≈ 5 % mean episode depth).
|
||||
///
|
||||
/// Each `update` is O(period).
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -65,16 +69,40 @@ impl Indicator for AverageDrawdown {
|
||||
return None;
|
||||
}
|
||||
let mut peak = f64::NEG_INFINITY;
|
||||
let mut sum_dd = 0.0_f64;
|
||||
let mut sum_depth = 0.0_f64;
|
||||
let mut episodes = 0_u32;
|
||||
let mut in_dd = false;
|
||||
let mut episode_peak = 0.0_f64;
|
||||
let mut episode_trough = 0.0_f64;
|
||||
for &v in &self.window {
|
||||
if v > peak {
|
||||
if v >= peak {
|
||||
if in_dd {
|
||||
if episode_peak > 0.0 {
|
||||
sum_depth += (episode_peak - episode_trough) / episode_peak;
|
||||
episodes += 1;
|
||||
}
|
||||
in_dd = false;
|
||||
}
|
||||
peak = v;
|
||||
}
|
||||
if peak > 0.0 {
|
||||
sum_dd += (peak - v) / peak;
|
||||
} else if in_dd {
|
||||
if v < episode_trough {
|
||||
episode_trough = v;
|
||||
}
|
||||
} else {
|
||||
in_dd = true;
|
||||
episode_peak = peak;
|
||||
episode_trough = v;
|
||||
}
|
||||
}
|
||||
Some(sum_dd / self.period as f64)
|
||||
if in_dd && episode_peak > 0.0 {
|
||||
sum_depth += (episode_peak - episode_trough) / episode_peak;
|
||||
episodes += 1;
|
||||
}
|
||||
Some(if episodes == 0 {
|
||||
0.0
|
||||
} else {
|
||||
sum_depth / f64::from(episodes)
|
||||
})
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
@@ -124,13 +152,24 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn reference_value() {
|
||||
// window [100, 120, 90, 110]:
|
||||
// peaks: 100, 120, 120, 120; dd: 0, 0, (30/120)=.25, (10/120)=.0833...
|
||||
// avg = (.25 + .0833...) / 4 = .0833...
|
||||
// window [100, 120, 90, 110]: one drawdown episode, opened at 90 (peak
|
||||
// 120) and never recovering to 120 within the window. Its depth is
|
||||
// (120 - 90) / 120 = 0.25; 110 stays inside the same episode and does
|
||||
// not deepen the trough. One episode -> AvgDD = 0.25.
|
||||
let mut a = AverageDrawdown::new(4).unwrap();
|
||||
let out = a.batch(&[100.0, 120.0, 90.0, 110.0]);
|
||||
let expected = (0.25 + (10.0 / 120.0)) / 4.0;
|
||||
assert_relative_eq!(out[3].unwrap(), expected, epsilon = 1e-12);
|
||||
assert_relative_eq!(out[3].unwrap(), 0.25, epsilon = 1e-12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn averages_distinct_episodes() {
|
||||
// [100, 90, 100, 80, 100]: episode 1 troughs at 90 then recovers to 100
|
||||
// -> depth 0.10; episode 2 troughs at 80 then recovers -> depth 0.20.
|
||||
// Mean of the two episode depths = 0.15 (distinct from the Pain Index,
|
||||
// which would weight every under-water bar instead).
|
||||
let mut a = AverageDrawdown::new(5).unwrap();
|
||||
let out = a.batch(&[100.0, 90.0, 100.0, 80.0, 100.0]);
|
||||
assert_relative_eq!(out[4].unwrap(), 0.15, epsilon = 1e-12);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
//! Awesome Oscillator Histogram.
|
||||
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use crate::error::{Error, Result};
|
||||
use crate::indicators::awesome_oscillator::AwesomeOscillator;
|
||||
use crate::indicators::sma::Sma;
|
||||
use crate::ohlcv::Candle;
|
||||
use crate::traits::Indicator;
|
||||
|
||||
/// "Awesome Oscillator Histogram" — the difference between the Awesome
|
||||
/// Oscillator and its `sma_period`-bar `SMA`. Positive bars mean `AO` is
|
||||
/// trending up (bullish acceleration); negative bars mean `AO` is trending
|
||||
/// down (bearish acceleration).
|
||||
/// Awesome Oscillator Histogram — the bar-to-bar **momentum** of the Awesome
|
||||
/// Oscillator over a `lookback` window. This is the value behind the coloured
|
||||
/// histogram bars in Bill Williams' charts: each bar shows how much `AO` has
|
||||
/// changed, so positive values mean `AO` is rising (the histogram "greens up")
|
||||
/// and negative values mean it is falling.
|
||||
///
|
||||
/// ```text
|
||||
/// AO = SMA(median, fast) − SMA(median, slow)
|
||||
/// AOHist = AO − SMA(AO, sma_period)
|
||||
/// AO = SMA(median, fast) − SMA(median, slow)
|
||||
/// AOHist = AO_t − AO_{t−lookback}
|
||||
/// ```
|
||||
///
|
||||
/// With Williams' default `sma_period = 5`, this collapses to the existing
|
||||
/// `AcceleratorOscillator` for `fast = 5, slow = 34, sma_period = 5`; for any
|
||||
/// other parameterisation this is a more flexible variant.
|
||||
/// This is distinct from the two related indicators Wickra ships: the raw
|
||||
/// [`AwesomeOscillator`](crate::AwesomeOscillator) is `AO` itself, and the
|
||||
/// [`AcceleratorOscillator`](crate::AcceleratorOscillator) is `AO − SMA(AO, n)`.
|
||||
/// The histogram instead reports `AO`'s rate of change. The default `lookback`
|
||||
/// is `1` (the classic one-bar histogram delta).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@@ -38,17 +42,18 @@ use crate::traits::Indicator;
|
||||
pub struct AwesomeOscillatorHistogram {
|
||||
fast_period: usize,
|
||||
slow_period: usize,
|
||||
sma_period: usize,
|
||||
lookback: usize,
|
||||
ao: AwesomeOscillator,
|
||||
sma: Sma,
|
||||
history: VecDeque<f64>,
|
||||
emitted: bool,
|
||||
}
|
||||
|
||||
impl AwesomeOscillatorHistogram {
|
||||
/// # Errors
|
||||
/// - [`Error::PeriodZero`] if any period is zero.
|
||||
/// - [`Error::InvalidPeriod`] if `fast >= slow`.
|
||||
pub fn new(fast: usize, slow: usize, sma_period: usize) -> Result<Self> {
|
||||
if fast == 0 || slow == 0 || sma_period == 0 {
|
||||
pub fn new(fast: usize, slow: usize, lookback: usize) -> Result<Self> {
|
||||
if fast == 0 || slow == 0 || lookback == 0 {
|
||||
return Err(Error::PeriodZero);
|
||||
}
|
||||
if fast >= slow {
|
||||
@@ -59,20 +64,21 @@ impl AwesomeOscillatorHistogram {
|
||||
Ok(Self {
|
||||
fast_period: fast,
|
||||
slow_period: slow,
|
||||
sma_period,
|
||||
lookback,
|
||||
ao: AwesomeOscillator::new(fast, slow)?,
|
||||
sma: Sma::new(sma_period)?,
|
||||
history: VecDeque::with_capacity(lookback + 1),
|
||||
emitted: false,
|
||||
})
|
||||
}
|
||||
|
||||
/// Bill Williams' Accelerator-equivalent defaults `(5, 34, 5)`.
|
||||
/// Bill Williams' defaults with a one-bar histogram delta `(5, 34, 1)`.
|
||||
pub fn classic() -> Self {
|
||||
Self::new(5, 34, 5).expect("classic Awesome Oscillator Histogram parameters are valid")
|
||||
Self::new(5, 34, 1).expect("classic Awesome Oscillator Histogram parameters are valid")
|
||||
}
|
||||
|
||||
/// Configured `(fast_period, slow_period, sma_period)`.
|
||||
/// Configured `(fast_period, slow_period, lookback)`.
|
||||
pub const fn periods(&self) -> (usize, usize, usize) {
|
||||
(self.fast_period, self.slow_period, self.sma_period)
|
||||
(self.fast_period, self.slow_period, self.lookback)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -82,23 +88,29 @@ impl Indicator for AwesomeOscillatorHistogram {
|
||||
|
||||
fn update(&mut self, candle: Candle) -> Option<f64> {
|
||||
let ao = self.ao.update(candle)?;
|
||||
let sma = self.sma.update(ao)?;
|
||||
Some(ao - sma)
|
||||
self.history.push_back(ao);
|
||||
if self.history.len() <= self.lookback {
|
||||
return None;
|
||||
}
|
||||
let prev = self.history.pop_front().expect("history is non-empty");
|
||||
self.emitted = true;
|
||||
Some(ao - prev)
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.ao.reset();
|
||||
self.sma.reset();
|
||||
self.history.clear();
|
||||
self.emitted = false;
|
||||
}
|
||||
|
||||
fn warmup_period(&self) -> usize {
|
||||
// AO emits at `slow` candles; the SMA then needs `sma_period - 1`
|
||||
// more AO values to fill its window.
|
||||
self.slow_period + self.sma_period - 1
|
||||
// AO first emits at `slow` candles; `lookback` more AO values are then
|
||||
// needed before `AO_t − AO_{t−lookback}` can be formed.
|
||||
self.slow_period + self.lookback
|
||||
}
|
||||
|
||||
fn is_ready(&self) -> bool {
|
||||
self.sma.is_ready()
|
||||
self.emitted
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
@@ -119,11 +131,11 @@ mod tests {
|
||||
#[test]
|
||||
fn rejects_zero_period() {
|
||||
assert!(matches!(
|
||||
AwesomeOscillatorHistogram::new(0, 34, 5),
|
||||
AwesomeOscillatorHistogram::new(0, 34, 1),
|
||||
Err(Error::PeriodZero)
|
||||
));
|
||||
assert!(matches!(
|
||||
AwesomeOscillatorHistogram::new(5, 0, 5),
|
||||
AwesomeOscillatorHistogram::new(5, 0, 1),
|
||||
Err(Error::PeriodZero)
|
||||
));
|
||||
assert!(matches!(
|
||||
@@ -135,7 +147,7 @@ mod tests {
|
||||
#[test]
|
||||
fn rejects_fast_geq_slow() {
|
||||
assert!(matches!(
|
||||
AwesomeOscillatorHistogram::new(34, 5, 5),
|
||||
AwesomeOscillatorHistogram::new(34, 5, 1),
|
||||
Err(Error::InvalidPeriod { .. })
|
||||
));
|
||||
}
|
||||
@@ -143,15 +155,15 @@ mod tests {
|
||||
#[test]
|
||||
fn accessors_and_metadata() {
|
||||
let hist = AwesomeOscillatorHistogram::classic();
|
||||
assert_eq!(hist.periods(), (5, 34, 5));
|
||||
assert_eq!(hist.warmup_period(), 38);
|
||||
assert_eq!(hist.periods(), (5, 34, 1));
|
||||
assert_eq!(hist.warmup_period(), 35);
|
||||
assert_eq!(hist.name(), "AwesomeOscillatorHistogram");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn constant_series_converges_to_zero() {
|
||||
// AO of a flat series is 0; SMA of 0 is 0; difference is 0.
|
||||
let mut hist = AwesomeOscillatorHistogram::new(3, 5, 3).unwrap();
|
||||
fn constant_series_yields_zero() {
|
||||
// AO of a flat series is 0, so its momentum is 0.
|
||||
let mut hist = AwesomeOscillatorHistogram::new(3, 5, 1).unwrap();
|
||||
let candles: Vec<Candle> = (0..30).map(|i| candle(42.0, i)).collect();
|
||||
let out = hist.batch(&candles);
|
||||
for v in out.iter().skip(hist.warmup_period() - 1).flatten() {
|
||||
@@ -161,7 +173,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn warmup_emits_first_value_at_warmup_period() {
|
||||
let mut hist = AwesomeOscillatorHistogram::new(2, 4, 3).unwrap();
|
||||
let mut hist = AwesomeOscillatorHistogram::new(2, 4, 2).unwrap();
|
||||
assert_eq!(hist.warmup_period(), 6);
|
||||
let candles: Vec<Candle> = (0..8)
|
||||
.map(|i| candle(10.0 + f64::from(i), i64::from(i)))
|
||||
@@ -173,6 +185,27 @@ mod tests {
|
||||
assert!(out[5].is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn equals_ao_difference() {
|
||||
// The histogram must equal AO_t − AO_{t−lookback} bar for bar.
|
||||
let candles: Vec<Candle> = (0..60_i64)
|
||||
.map(|i| candle(100.0 + (i as f64 * 0.3).sin() * 5.0, i))
|
||||
.collect();
|
||||
let lookback = 1;
|
||||
let ao_series = AwesomeOscillator::new(5, 34).unwrap().batch(&candles);
|
||||
let hist = AwesomeOscillatorHistogram::new(5, 34, lookback)
|
||||
.unwrap()
|
||||
.batch(&candles);
|
||||
for i in 0..candles.len() {
|
||||
if let Some(h) = hist[i] {
|
||||
let ao_now = ao_series[i].expect("AO present once histogram emits");
|
||||
let ao_prev =
|
||||
ao_series[i - lookback].expect("prior AO present once histogram emits");
|
||||
assert_relative_eq!(h, ao_now - ao_prev, epsilon = 1e-9);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn batch_equals_streaming() {
|
||||
let candles: Vec<Candle> = (0..100_i64)
|
||||
|
||||
@@ -1,29 +1,28 @@
|
||||
//! Intraday Intensity Index (Bostian) — a cumulative volume-weighted close-location line.
|
||||
//! Intraday Intensity (Bostian) — the per-bar volume-weighted close-location.
|
||||
|
||||
use crate::ohlcv::Candle;
|
||||
use crate::traits::Indicator;
|
||||
|
||||
/// Intraday Intensity Index — David Bostian's cumulative line that weights each
|
||||
/// bar's volume by where the close lands inside the bar's range.
|
||||
/// Intraday Intensity — David Bostian's per-bar measure that weights each bar's
|
||||
/// volume by where the close lands inside the bar's range:
|
||||
///
|
||||
/// ```text
|
||||
/// II_t = volume * (2*close − high − low) / (high − low) (0 if high == low)
|
||||
/// III_t = III_{t−1} + II_t
|
||||
/// II_t = volume * (2*close − high − low) / (high − low) (0 if high == low)
|
||||
/// ```
|
||||
///
|
||||
/// The fraction `(2*close − high − low) / (high − low)` is `+1` when the bar
|
||||
/// closes on its high, `−1` when it closes on its low, and `0` at the midpoint.
|
||||
/// Scaling it by volume and accumulating produces a running measure of how
|
||||
/// aggressively the close is being pushed toward the extremes — Bostian's proxy
|
||||
/// for institutional accumulation (rising line) or distribution (falling line).
|
||||
/// closes on its high, `−1` when it closes on its low, and `0` at the midpoint,
|
||||
/// so `II_t` is the volume pushed toward the extremes on that single bar —
|
||||
/// Bostian's proxy for per-bar accumulation (positive) or distribution
|
||||
/// (negative).
|
||||
///
|
||||
/// This is the **cumulative** Intraday Intensity (the original index), not the
|
||||
/// normalized "Intraday Intensity %" — the latter divides a windowed sum of `II`
|
||||
/// by a windowed sum of volume and is mathematically identical to
|
||||
/// [`Cmf`](crate::Cmf), so it is not duplicated here. The level of this line is
|
||||
/// arbitrary; only its slope and divergences against price matter. A doji whose
|
||||
/// `high == low` contributes nothing. Each `update` is O(1) and the first bar
|
||||
/// already emits a value.
|
||||
/// This emits the **raw per-bar** intensity, which is distinct from the two
|
||||
/// derived forms Wickra ships separately: the **cumulative** running total is
|
||||
/// the Accumulation/Distribution Line ([`Adl`](crate::Adl)), and the
|
||||
/// volume-normalized windowed form ("Intraday Intensity %") is mathematically
|
||||
/// the Chaikin Money Flow ([`Cmf`](crate::Cmf)). A doji whose `high == low`
|
||||
/// contributes nothing. Each `update` is O(1) and the first bar already emits a
|
||||
/// value.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@@ -41,12 +40,11 @@ use crate::traits::Indicator;
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct IntradayIntensity {
|
||||
iii: f64,
|
||||
last: Option<f64>,
|
||||
}
|
||||
|
||||
impl IntradayIntensity {
|
||||
/// Construct a new Intraday Intensity Index. The line is parameter-free.
|
||||
/// Construct a new Intraday Intensity. It is parameter-free.
|
||||
#[must_use]
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
@@ -69,13 +67,11 @@ impl Indicator for IntradayIntensity {
|
||||
} else {
|
||||
0.0
|
||||
};
|
||||
self.iii += ii;
|
||||
self.last = Some(self.iii);
|
||||
Some(self.iii)
|
||||
self.last = Some(ii);
|
||||
Some(ii)
|
||||
}
|
||||
|
||||
fn reset(&mut self) {
|
||||
self.iii = 0.0;
|
||||
self.last = None;
|
||||
}
|
||||
|
||||
@@ -149,11 +145,14 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn accumulates_across_bars() {
|
||||
fn each_bar_is_independent() {
|
||||
// Per-bar (non-cumulative): each output depends only on that bar, so a
|
||||
// close-on-high +1000 bar is not carried into the next close-on-low bar.
|
||||
let mut iii = IntradayIntensity::new();
|
||||
iii.update(candle(110.0, 100.0, 110.0, 1_000.0)); // +1000
|
||||
let v = iii.update(candle(110.0, 100.0, 100.0, 400.0)).unwrap(); // -400 -> 600
|
||||
assert_relative_eq!(v, 600.0, epsilon = 1e-9);
|
||||
let a = iii.update(candle(110.0, 100.0, 110.0, 1_000.0)).unwrap();
|
||||
let b = iii.update(candle(110.0, 100.0, 100.0, 400.0)).unwrap();
|
||||
assert_relative_eq!(a, 1_000.0, epsilon = 1e-9);
|
||||
assert_relative_eq!(b, -400.0, epsilon = 1e-9);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
@@ -9,6 +9,7 @@ That includes:
|
||||
[Node](https://docs.wickra.org/Quickstart-Node),
|
||||
[WASM](https://docs.wickra.org/Quickstart-WASM),
|
||||
[C](https://docs.wickra.org/Quickstart-C),
|
||||
[C++](https://docs.wickra.org/Quickstart-C),
|
||||
[C#](https://docs.wickra.org/Quickstart-CSharp),
|
||||
[Go](https://docs.wickra.org/Quickstart-Go),
|
||||
[Java](https://docs.wickra.org/Quickstart-Java), and
|
||||
|
||||
+4
-4
@@ -142,7 +142,7 @@ build-checked but not run in CI.
|
||||
| --- | --- | --- |
|
||||
| `streaming.py` | Feed a synthetic price series through SMA / EMA / RSI / MACD tick by tick. | `python -m examples.python.streaming` |
|
||||
| `backtest.py` | Basket of indicators over an OHLCV CSV. | `python -m examples.python.backtest <ohlcv.csv>` |
|
||||
| `live_trading.py` | Live Binance feed → RSI / MACD / Bollinger → signals. | `python -m examples.python.live_trading --symbol BTCUSDT --interval 1m` |
|
||||
| `live_binance.py` | Live Binance feed → RSI / MACD / Bollinger → signals. | `python -m examples.python.live_binance --symbol BTCUSDT --interval 1m` |
|
||||
| `multi_timeframe.py` | Resample a 1-minute CSV to coarser timeframes and compare. | `python -m examples.python.multi_timeframe <1m.csv>` |
|
||||
| `parallel_assets.py` | Process many symbols in parallel — the Rust extension releases the GIL during batch computation. | `python -m examples.python.parallel_assets --assets 200 --bars 5000` |
|
||||
| `fetch_btcusdt.py` | Download real BTCUSDT klines from the Binance REST API into `examples/data/` (urllib + stdlib only). | `python -m examples.python.fetch_btcusdt` |
|
||||
@@ -150,7 +150,7 @@ build-checked but not run in CI.
|
||||
| `strategy_macd_adx.py` | Hourly BTCUSDT trend-follower: MACD crossover entries gated by ADX(14) > 20. | `python -m examples.python.strategy_macd_adx` |
|
||||
| `strategy_bollinger_squeeze.py` | Daily BTCUSDT Bollinger-squeeze breakout with ATR(14) trailing stop. | `python -m examples.python.strategy_bollinger_squeeze` |
|
||||
|
||||
`live_trading.py` additionally needs `pip install websockets`.
|
||||
`live_binance.py` additionally needs `pip install websockets`.
|
||||
|
||||
## Node.js — `examples/node/`
|
||||
|
||||
@@ -167,7 +167,7 @@ cd ../../examples/node && npm install # links wickra + installs `ws`
|
||||
| `backtest.js` | Basket of indicators over an OHLCV CSV; defaults to the bundled BTCUSDT daily dataset. | `node backtest.js [ohlcv.csv]` |
|
||||
| `multi_timeframe.js` | Roll a 1-minute CSV up to 5m / 15m / 1h / 4h / 1d and print indicators per timeframe. | `node multi_timeframe.js [path/to/1m.csv]` |
|
||||
| `parallel_assets.js` | Serial vs `worker_threads` pool over a synthetic panel, with speedup. | `node parallel_assets.js --assets 200 --bars 5000` |
|
||||
| `live_trading.js` | Live Binance feed → RSI / MACD / Bollinger → signals. | `node live_trading.js --symbol BTCUSDT --interval 1m` |
|
||||
| `live_binance.js` | Live Binance feed → RSI / MACD / Bollinger → signals. | `node live_binance.js --symbol BTCUSDT --interval 1m` |
|
||||
| `fetch_btcusdt.js` | Download real BTCUSDT klines from the Binance REST API into `examples/data/` (built-in `fetch`, Node 18+). | `node fetch_btcusdt.js` |
|
||||
| `strategy_rsi_mean_reversion.js` | Hourly BTCUSDT mean-reversion using RSI(14) thresholds, with PnL / Sharpe / max-DD summary. | `node strategy_rsi_mean_reversion.js` |
|
||||
| `strategy_macd_adx.js` | Hourly BTCUSDT trend-follower: MACD crossover entries gated by ADX(14) > 20. | `node strategy_macd_adx.js` |
|
||||
@@ -188,7 +188,7 @@ Then serve the repository root (`python -m http.server`, `npx http-server`,
|
||||
| --- | --- |
|
||||
| `index.html` | Streams a synthetic price series through six indicators and draws a live `<canvas>` chart. |
|
||||
| `backtest.html` | Streams a fetched OHLCV CSV through a basket of indicators (SMA, EMA, RSI, MACD, Bollinger, ATR, ADX, OBV) and prints a per-series summary table. |
|
||||
| `live_trading.html` | Opens a browser-native `WebSocket` to Binance, runs RSI / MACD / Bollinger and flags BUY/SELL candidates. |
|
||||
| `live_binance.html` | Opens a browser-native `WebSocket` to Binance, runs RSI / MACD / Bollinger and flags BUY/SELL candidates. |
|
||||
| `multi_timeframe.html` | Fetches a 1-minute CSV, rolls it up to 5m / 15m / 1h / 4h / 1d in-page, prints RSI / MACD hist / ADX per timeframe. |
|
||||
| `parallel_assets.html` | Spawns a pool of module Workers (each loading its own copy of the WASM module) and reports the speedup over a serial baseline. |
|
||||
| `strategy_rsi_mean_reversion.html` | Hourly BTCUSDT RSI(14) mean-reversion (long < 30, exit > 70); prints a PnL / Sharpe / max-DD summary table. |
|
||||
|
||||
@@ -78,6 +78,33 @@ if(OpenMP_C_FOUND)
|
||||
target_link_libraries(parallel_assets PRIVATE OpenMP::OpenMP_C)
|
||||
endif()
|
||||
|
||||
# Golden-fixture parity over the whole 514-indicator catalogue, built and run as
|
||||
# BOTH C (golden_test.c) and C++ (golden_test.cpp #includes the same source) to
|
||||
# prove the C ABI header is consumable from each language. The fixtures live at
|
||||
# the repo root; pass the absolute path as the test argument.
|
||||
get_filename_component(WICKRA_GOLDEN_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../testdata/golden" ABSOLUTE)
|
||||
foreach(gt_pair "golden_test:golden_test.c" "golden_test_cpp:golden_test.cpp")
|
||||
string(REPLACE ":" ";" gt_list "${gt_pair}")
|
||||
list(GET gt_list 0 gt_name)
|
||||
list(GET gt_list 1 gt_src)
|
||||
add_executable(${gt_name} ${gt_src})
|
||||
target_include_directories(${gt_name} PRIVATE "${WICKRA_INCLUDE_DIR}")
|
||||
target_link_libraries(${gt_name} PRIVATE "${WICKRA_LINK_LIB}")
|
||||
if(UNIX AND NOT APPLE)
|
||||
target_link_libraries(${gt_name} PRIVATE m)
|
||||
endif()
|
||||
if(WIN32)
|
||||
add_custom_command(TARGET ${gt_name} POST_BUILD
|
||||
COMMAND ${CMAKE_COMMAND} -E copy_if_different
|
||||
"${WICKRA_RUNTIME}" "$<TARGET_FILE_DIR:${gt_name}>")
|
||||
endif()
|
||||
add_test(NAME ${gt_name} COMMAND ${gt_name} "${WICKRA_GOLDEN_DIR}")
|
||||
if(NOT WIN32)
|
||||
set_tests_properties(${gt_name} PROPERTIES
|
||||
ENVIRONMENT "LD_LIBRARY_PATH=${WICKRA_LIB_DIR};DYLD_LIBRARY_PATH=${WICKRA_LIB_DIR}")
|
||||
endif()
|
||||
endforeach()
|
||||
|
||||
# Network examples — built (so they stay compilable) but not run in CI.
|
||||
add_wickra_example(fetch_btcusdt fetch_btcusdt.c FALSE) # downloads CSVs via curl
|
||||
add_wickra_example(live_binance live_binance.c FALSE) # polls Binance REST via curl
|
||||
|
||||
@@ -0,0 +1,340 @@
|
||||
"""Generate examples/c/golden_test.c: a value-parity test that replays the shared
|
||||
golden input through every one of the 514 indicators via the C ABI (wickra.h)
|
||||
and checks output bit-for-bit against the Rust reference fixtures
|
||||
g_<Canonical>.csv. The same source compiles under both a C compiler (the C
|
||||
binding) and a C++ compiler (the C++ binding) — wickra.h is `extern "C"`.
|
||||
|
||||
Run from repo root: python examples/c/gen_golden_test.py
|
||||
"""
|
||||
import json
|
||||
import os
|
||||
import re
|
||||
|
||||
ROOT = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", ".."))
|
||||
G = os.path.join(ROOT, "testdata", "golden")
|
||||
HDR = open(os.path.join(ROOT, "bindings", "c", "include", "wickra.h"), encoding="utf-8").read()
|
||||
|
||||
# canonical -> C prefix, from the R wrappers (.wk_obj first arg == C symbol prefix).
|
||||
RSRC = open(os.path.join(ROOT, "bindings", "r", "R", "indicators.R"), encoding="utf-8").read()
|
||||
PREFIX = {m.group(1): m.group(2)
|
||||
for m in re.finditer(r"^(\w+) <- function\([^)]*\) \{.*?\.wk_obj\(\"([^\"]+)\"", RSRC, re.S | re.M)}
|
||||
|
||||
# wickra_* function signatures (return type, args), multiline-collapsed.
|
||||
SIG = {}
|
||||
for m in re.finditer(r"([A-Za-z_][\w ]*\*?)\s*(wickra_\w+)\(([^;]*?)\);", HDR, re.S):
|
||||
SIG[m.group(2)] = (re.sub(r"\s+", " ", m.group(1)).strip(), re.sub(r"\s+", " ", m.group(3)).strip())
|
||||
|
||||
# archetype + n/width per canonical
|
||||
spec = {}
|
||||
for e in json.load(open(os.path.join(G, "scalar_manifest.json"))):
|
||||
spec[e["canonical"]] = {"arch": {"f64": "scalar_f64", "Candle": "scalar_candle", "(f64, f64)": "pairwise"}[e["input"]], "params": e["params"]}
|
||||
for e in json.load(open(os.path.join(G, "multi_manifest.json"))):
|
||||
spec[e["canonical"]] = {"arch": {"f64": "multi_f64", "Candle": "multi_candle", "(f64, f64)": "multi_pairwise"}[e["input"]], "params": e["params"], "n": e["n"]}
|
||||
ex = json.load(open(os.path.join(G, "exotic_manifest.json")))
|
||||
for e in ex["deriv"]:
|
||||
spec[e["canonical"]] = {"arch": "deriv_multi" if "n" in e else "deriv", "params": e["params"], **({"n": e["n"]} if "n" in e else {})}
|
||||
for fam, a in (("cross", "cross"), ("trade", "trade"), ("trademid", "trademid"), ("ob", "ob")):
|
||||
for e in ex[fam]:
|
||||
spec[e["canonical"]] = {"arch": a, "params": e["params"]}
|
||||
for e in json.load(open(os.path.join(G, "profile_manifest.json"))):
|
||||
spec[e["canonical"]] = {"arch": "profile_" + e["kind"], "params": e["params"], "width": e["width"]}
|
||||
for e in json.load(open(os.path.join(G, "bars_manifest.json"))):
|
||||
spec[e["canonical"]] = {"arch": "footprint" if e["canonical"] == "Footprint" else "bars_" + e["feed"], "params": e["params"]}
|
||||
|
||||
canons = sorted(os.path.basename(f)[2:-4] for f in __import__("glob").glob(os.path.join(G, "g_*.csv")))
|
||||
|
||||
BAR_FIELDS = {
|
||||
"RenkoBars": ["open", "close", "direction"], "KagiBars": ["start", "end", "direction"],
|
||||
"PointAndFigureBars": ["direction", "high", "low"], "RangeBars": ["open", "close", "direction"],
|
||||
"ThreeLineBreakBars": ["open", "close", "direction"],
|
||||
"ImbalanceBars": ["open", "high", "low", "close", "imbalance", "direction"],
|
||||
"RunBars": ["open", "high", "low", "close", "length", "direction"],
|
||||
"DollarBars": ["open", "high", "low", "close", "volume", "dollar"],
|
||||
"TickBars": ["open", "high", "low", "close", "volume"],
|
||||
"VolumeBars": ["open", "high", "low", "close", "volume"],
|
||||
"Footprint": ["price", "bid_vol", "ask_vol"],
|
||||
}
|
||||
|
||||
|
||||
# cbindgen appends '_' to struct fields that collide with C/C++ reserved words.
|
||||
_C_RESERVED = {"long", "short", "int", "char", "float", "double", "new", "class",
|
||||
"this", "delete", "register", "auto", "const", "void"}
|
||||
|
||||
|
||||
def c_field(name):
|
||||
return name + "_" if name in _C_RESERVED else name
|
||||
|
||||
|
||||
def csv_header(canon):
|
||||
with open(os.path.join(G, "g_" + canon + ".csv"), encoding="utf-8") as f:
|
||||
return f.readline().strip().split(",")
|
||||
|
||||
|
||||
def out_struct(prefix):
|
||||
"""The pointer-to-struct out param type of wickra_<prefix>_update, if any."""
|
||||
_, args = SIG["wickra_" + prefix + "_update"]
|
||||
m = re.search(r"struct (\w+) \*out", args)
|
||||
if m:
|
||||
return "struct " + m.group(1)
|
||||
m = re.search(r"struct (\w+) \*scalars", args)
|
||||
if m:
|
||||
return "struct " + m.group(1)
|
||||
return None
|
||||
|
||||
|
||||
def ctor_casts(prefix, params):
|
||||
_, args = SIG["wickra_" + prefix + "_new"]
|
||||
if not args:
|
||||
return ""
|
||||
types = [a.strip().rsplit(" ", 1)[0].strip() for a in args.split(",")]
|
||||
out = []
|
||||
for t, v in zip(types, params):
|
||||
if t in ("uintptr_t", "intptr_t", "size_t"):
|
||||
out.append(f"(uintptr_t){int(round(v))}")
|
||||
elif t in ("uint8_t",):
|
||||
out.append(f"(uint8_t){int(round(v))}")
|
||||
elif t in ("int32_t",):
|
||||
out.append(f"(int32_t){int(round(v))}")
|
||||
elif t in ("int64_t",):
|
||||
out.append(f"(int64_t){int(round(v))}")
|
||||
else:
|
||||
out.append(repr(float(v)))
|
||||
return ", ".join(out)
|
||||
|
||||
|
||||
def gen_check(canon):
|
||||
s = spec[canon]
|
||||
p = PREFIX[canon]
|
||||
a = s["arch"]
|
||||
new = f"wickra_{p}_new({ctor_casts(p, s['params'])})"
|
||||
upd = f"wickra_{p}_update"
|
||||
L = [f"static int check_{canon}(void) {{",
|
||||
f" struct {struct_name(p)} *h = {new};",
|
||||
f' if (!h) {{ printf("FAIL {canon}: new returned NULL\\n"); return 1; }}',
|
||||
f" double **exp; int rows = read_fixture(\"g_{canon}\", &exp);",
|
||||
" int fails = 0;",
|
||||
" for (int i = 0; i < N_INPUT; i++) {",
|
||||
" double o = IN[i][0], hi = IN[i][1], lo = IN[i][2], c = IN[i][3], v = IN[i][4];",
|
||||
" (void)o; (void)hi; (void)lo; (void)c; (void)v;",
|
||||
" double got[128]; int gn = 0;"]
|
||||
if a in ("scalar_f64", "multi_f64"):
|
||||
call_args = "h, c"
|
||||
elif a in ("pairwise", "multi_pairwise"):
|
||||
call_args = "h, c, o"
|
||||
elif a in ("scalar_candle", "multi_candle") or a.startswith("profile"):
|
||||
call_args = "h, o, hi, lo, c, v, (int64_t)i"
|
||||
elif a == "trade":
|
||||
call_args = "h, c, v, c >= o, (int64_t)i"
|
||||
elif a == "trademid":
|
||||
call_args = "h, c, v, c >= o, (int64_t)i, (hi + lo) / 2.0"
|
||||
elif a == "ob":
|
||||
L.append(" double bp[5], bs[5], ap[5], asz[5];")
|
||||
L.append(" for (int k = 0; k < 5; k++) { double kf = k + 1; bp[k] = c - 0.1*kf; bs[k] = v/kf; ap[k] = c + 0.1*kf; asz[k] = v*0.9/kf; }")
|
||||
call_args = "h, bp, bs, 5, ap, asz, 5"
|
||||
elif a == "cross":
|
||||
L.append(" double chg[5], vol[5]; bool nh[5], nl[5], am[5], ob[5];")
|
||||
L.append(" for (int j = 0; j < 5; j++) { chg[j] = (c-o)+j; vol[j] = v + j*10.0; nh[j] = (j%2==0); nl[j] = (j%3==0); am[j] = (j%2==0); ob[j] = (j%3==0); }")
|
||||
call_args = "h, chg, vol, nh, nl, am, ob, 5, (int64_t)i"
|
||||
elif a in ("deriv", "deriv_multi"):
|
||||
L.append(" double fr=(c-o)/c*0.01, mp=c, ip=c-0.5, fp=c+1.0, oi=v*10.0, ls=v*0.6, ss=v*0.4, tbv=v*0.55, tsv=v*0.45, ll=hi-c, sl=c-lo;")
|
||||
call_args = "h, fr, mp, ip, fp, oi, ls, ss, tbv, tsv, ll, sl, (int64_t)i"
|
||||
elif a == "bars_close":
|
||||
call_args = "h, c, c, c, c, 1.0, 0"
|
||||
elif a == "bars_candle4":
|
||||
call_args = "h, o, hi, lo, c, 1.0, 0"
|
||||
elif a == "bars_candle5":
|
||||
call_args = "h, o, hi, lo, c, v, 0"
|
||||
elif a == "footprint":
|
||||
call_args = "h, c, v, c >= o, (int64_t)i"
|
||||
else:
|
||||
raise SystemExit("arch " + a)
|
||||
|
||||
# output handling
|
||||
if a in ("scalar_f64", "scalar_candle", "pairwise", "trade", "trademid", "ob", "cross", "deriv"):
|
||||
L.append(f" got[gn++] = {upd}({call_args});")
|
||||
elif a in ("multi_f64", "multi_candle", "multi_pairwise", "deriv_multi"):
|
||||
st = out_struct(p)
|
||||
fields = csv_header(canon)
|
||||
L.append(f" {st} out;")
|
||||
L.append(f" if ({upd}({call_args}, &out)) {{")
|
||||
for f in fields:
|
||||
L.append(f" got[gn++] = out.{c_field(f)};")
|
||||
L.append(" } else {")
|
||||
L.append(f" for (int z = 0; z < {len(fields)}; z++) got[gn++] = NANV;")
|
||||
L.append(" }")
|
||||
elif a == "profile_bins":
|
||||
w = s["width"]
|
||||
L.append(" double vbuf[256];")
|
||||
L.append(f" intptr_t k = {upd}({call_args}, vbuf, 256);")
|
||||
L.append(f" if (k < 0) {{ for (int z = 0; z < {w}; z++) got[gn++] = NANV; }}")
|
||||
L.append(f" else {{ for (int z = 0; z < {w}; z++) got[gn++] = vbuf[z]; }}")
|
||||
elif a == "profile_pricebins":
|
||||
w = s["width"]
|
||||
st = out_struct(p)
|
||||
L.append(" double vbuf[256];")
|
||||
L.append(f" {st} sc;")
|
||||
L.append(f" intptr_t k = {upd}({call_args}, &sc, vbuf, 256);")
|
||||
L.append(f" if (k < 0) {{ for (int z = 0; z < {w}; z++) got[gn++] = NANV; }}")
|
||||
L.append(" else { got[gn++] = sc.price_low; got[gn++] = sc.price_high;")
|
||||
L.append(f" for (int z = 0; z < {w - 2}; z++) got[gn++] = vbuf[z]; }}")
|
||||
else: # bars_* / footprint
|
||||
elem = out_struct(p)
|
||||
fields = BAR_FIELDS[canon]
|
||||
cap = 256
|
||||
L.append(f" {elem} bbuf[{cap}];")
|
||||
if a == "footprint":
|
||||
L.append(f" intptr_t k = {upd}({call_args}, bbuf, {cap});")
|
||||
L.append(" if (k < 0) k = 0;")
|
||||
else:
|
||||
L.append(f" uintptr_t k = {upd}({call_args}, bbuf, {cap});")
|
||||
L.append(" for (uintptr_t b = 0; b < (uintptr_t)k; b++) {")
|
||||
for f in fields:
|
||||
L.append(f" got[gn++] = (double)bbuf[b].{f};")
|
||||
L.append(" }")
|
||||
|
||||
L.append(" fails += cmp_row(\"" + canon + "\", i, exp[i], EXPLEN[i], got, gn);")
|
||||
L.append(" }")
|
||||
L.append(" free_fixture(exp, rows);")
|
||||
L.append(f" wickra_{p}_free(h);")
|
||||
L.append(" return fails;")
|
||||
L.append("}")
|
||||
return "\n".join(L)
|
||||
|
||||
|
||||
def struct_name(prefix):
|
||||
ret, _ = SIG["wickra_" + prefix + "_new"]
|
||||
m = re.search(r"struct (\w+)", ret)
|
||||
return m.group(1)
|
||||
|
||||
|
||||
HEADER = r'''/* Generated by gen_golden_test.py. DO NOT EDIT.
|
||||
*
|
||||
* Value-parity for the whole 514-indicator catalogue through the Wickra C ABI.
|
||||
* The same source compiles as C (gcc) and C++ (g++) since wickra.h is extern "C".
|
||||
* Each indicator replays the shared golden input and is checked bit-for-bit
|
||||
* against the Rust reference fixtures testdata/golden/g_<Canonical>.csv. */
|
||||
#include <math.h>
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include "wickra.h"
|
||||
|
||||
#define NANV (nan(""))
|
||||
#define MAXROWS 512
|
||||
#define MAXCOLS 256
|
||||
|
||||
static double IN[MAXROWS][8];
|
||||
static int N_INPUT = 0;
|
||||
static int EXPLEN[MAXROWS];
|
||||
|
||||
static const char *GDIR = NULL;
|
||||
|
||||
static double parse_cell(const char *s) {
|
||||
if (strcmp(s, "nan") == 0) return NANV;
|
||||
if (strcmp(s, "inf") == 0) return INFINITY;
|
||||
if (strcmp(s, "-inf") == 0) return -INFINITY;
|
||||
return atof(s);
|
||||
}
|
||||
|
||||
static int split_line(char *line, double *out, int max) {
|
||||
int n = 0;
|
||||
char *p = line;
|
||||
while (*p && n < max) {
|
||||
char *comma = strchr(p, ',');
|
||||
if (comma) *comma = '\0';
|
||||
out[n++] = parse_cell(p);
|
||||
if (!comma) break;
|
||||
p = comma + 1;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
static FILE *open_fixture(const char *name) {
|
||||
char path[1024];
|
||||
snprintf(path, sizeof(path), "%s/%s.csv", GDIR, name);
|
||||
return fopen(path, "r");
|
||||
}
|
||||
|
||||
static void load_input(void) {
|
||||
FILE *f = open_fixture("input");
|
||||
if (!f) { fprintf(stderr, "cannot open input.csv in %s\n", GDIR); exit(2); }
|
||||
char line[8192];
|
||||
int first = 1;
|
||||
while (fgets(line, sizeof(line), f)) {
|
||||
line[strcspn(line, "\r\n")] = '\0';
|
||||
if (first) { first = 0; continue; }
|
||||
if (line[0] == '\0') continue;
|
||||
N_INPUT += (split_line(line, IN[N_INPUT], 8) > 0);
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
/* Read a fixture, keeping blank rows (a candle on which no bar closed). */
|
||||
static int read_fixture(const char *name, double ***outp) {
|
||||
FILE *f = open_fixture(name);
|
||||
if (!f) { fprintf(stderr, "cannot open %s.csv\n", name); exit(2); }
|
||||
double **rows = (double **)malloc(sizeof(double *) * MAXROWS);
|
||||
int n = 0, first = 1;
|
||||
char line[8192];
|
||||
while (fgets(line, sizeof(line), f) && n < MAXROWS) {
|
||||
line[strcspn(line, "\r\n")] = '\0';
|
||||
if (first) { first = 0; continue; }
|
||||
double *vals = (double *)malloc(sizeof(double) * MAXCOLS);
|
||||
int c = (line[0] == '\0') ? 0 : split_line(line, vals, MAXCOLS);
|
||||
EXPLEN[n] = c;
|
||||
rows[n++] = vals;
|
||||
}
|
||||
fclose(f);
|
||||
*outp = rows;
|
||||
return n;
|
||||
}
|
||||
|
||||
static void free_fixture(double **rows, int n) {
|
||||
for (int i = 0; i < n; i++) free(rows[i]);
|
||||
free(rows);
|
||||
}
|
||||
|
||||
static int close_to(double g, double w) {
|
||||
if (isnan(w)) return isnan(g);
|
||||
if (isinf(w)) return isinf(g) && ((g > 0) == (w > 0));
|
||||
double tol = 1e-6 * (fabs(w) > 1.0 ? fabs(w) : 1.0);
|
||||
return fabs(g - w) <= tol;
|
||||
}
|
||||
|
||||
static int cmp_row(const char *name, int i, const double *want, int wn, const double *got, int gn) {
|
||||
if (wn != gn) {
|
||||
printf("FAIL %s row %d: arity %d vs %d\n", name, i, gn, wn);
|
||||
return 1;
|
||||
}
|
||||
for (int k = 0; k < wn; k++) {
|
||||
if (!close_to(got[k], want[k])) {
|
||||
printf("FAIL %s row %d col %d: got %g want %g\n", name, i, k, got[k], want[k]);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
'''
|
||||
|
||||
MAIN_HEAD = r'''
|
||||
int main(int argc, char **argv) {
|
||||
GDIR = (argc > 1) ? argv[1] : "testdata/golden";
|
||||
load_input();
|
||||
int total = 0, failed = 0;
|
||||
'''
|
||||
|
||||
out = [HEADER]
|
||||
for canon in canons:
|
||||
out.append(gen_check(canon))
|
||||
out.append(MAIN_HEAD)
|
||||
for canon in canons:
|
||||
out.append(f" total++; if (check_{canon}()) failed++;")
|
||||
out.append(r''' printf("\nC/C++ golden: %d passed, %d failed (of %d)\n", total - failed, failed, total);
|
||||
return failed ? 1 : 0;
|
||||
}''')
|
||||
|
||||
dest = os.path.join(ROOT, "examples", "c", "golden_test.c")
|
||||
open(dest, "w", encoding="utf-8").write("\n".join(out) + "\n")
|
||||
print("generated golden_test.c with", len(canons), "indicators")
|
||||
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,4 @@
|
||||
// C++ build of the 514-indicator golden parity test. wickra.h is `extern "C"`,
|
||||
// so the generated C runner compiles unchanged under a C++ compiler; this target
|
||||
// proves the C++ binding consumes the same C ABI and computes identical values.
|
||||
#include "golden_test.c"
|
||||
@@ -1,7 +1,7 @@
|
||||
/* Live BTCUSDT indicator with the Wickra C ABI.
|
||||
*
|
||||
* The C counterpart of `examples/rust/src/bin/live_binance.rs`,
|
||||
* `examples/python/live_trading.py` and `examples/node/live_trading.js`. Those
|
||||
* `examples/python/live_binance.py` and `examples/node/live_binance.js`. Those
|
||||
* stream Binance over a WebSocket; the C ABI ships only the indicators and no
|
||||
* socket layer, so this example polls the Binance REST klines endpoint via the
|
||||
* system `curl` once per interval and feeds each newly *closed* candle into a
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
<groupId>org.wickra.examples</groupId>
|
||||
<artifactId>wickra-examples</artifactId>
|
||||
<version>0.9.1</version>
|
||||
<version>0.9.2</version>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>Wickra Java examples</name>
|
||||
@@ -21,7 +21,7 @@
|
||||
<dependency>
|
||||
<groupId>org.wickra</groupId>
|
||||
<artifactId>wickra</artifactId>
|
||||
<version>0.9.1</version>
|
||||
<version>0.9.2</version>
|
||||
</dependency>
|
||||
<!-- Only the network examples (fetch_btcusdt) parse JSON. -->
|
||||
<dependency>
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
// Live trading skeleton for the Wickra Node binding.
|
||||
// Live Binance feed example for the Wickra Node binding.
|
||||
//
|
||||
// Connects to Binance's public WebSocket feed (no API key needed) and runs
|
||||
// RSI / MACD / Bollinger Bands on the incoming close prices. When RSI crosses
|
||||
// the common overbought / oversold thresholds *and* the MACD histogram
|
||||
// confirms the direction *and* price pierces the matching Bollinger band, a
|
||||
// signal line is printed. No orders are placed. It is the Node counterpart of
|
||||
// examples/python/live_trading.py.
|
||||
// examples/python/live_binance.py.
|
||||
//
|
||||
// Run it from the repository after building the native binding:
|
||||
//
|
||||
// cd bindings/node && npm install && npx napi build --platform --release
|
||||
// cd ../../examples/node && npm install # pulls `ws` for this example
|
||||
// node live_trading.js --symbol BTCUSDT --interval 1m
|
||||
// node live_binance.js --symbol BTCUSDT --interval 1m
|
||||
//
|
||||
// Stop it with Ctrl+C.
|
||||
|
||||
Generated
+7
-7
@@ -17,7 +17,7 @@
|
||||
},
|
||||
"../../bindings/node": {
|
||||
"name": "wickra",
|
||||
"version": "0.9.1",
|
||||
"version": "0.9.2",
|
||||
"license": "MIT OR Apache-2.0",
|
||||
"devDependencies": {
|
||||
"@napi-rs/cli": "^2.18.0"
|
||||
@@ -26,12 +26,12 @@
|
||||
"node": ">= 18"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"wickra-darwin-arm64": "0.9.1",
|
||||
"wickra-darwin-x64": "0.9.1",
|
||||
"wickra-linux-arm64-gnu": "0.9.1",
|
||||
"wickra-linux-x64-gnu": "0.9.1",
|
||||
"wickra-win32-arm64-msvc": "0.9.1",
|
||||
"wickra-win32-x64-msvc": "0.9.1"
|
||||
"wickra-darwin-arm64": "0.9.2",
|
||||
"wickra-darwin-x64": "0.9.2",
|
||||
"wickra-linux-arm64-gnu": "0.9.2",
|
||||
"wickra-linux-x64-gnu": "0.9.2",
|
||||
"wickra-win32-arm64-msvc": "0.9.2",
|
||||
"wickra-win32-x64-msvc": "0.9.2"
|
||||
}
|
||||
},
|
||||
"node_modules/wickra": {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
"""Live trading skeleton: stream Binance kline ticks → incremental indicators → signals.
|
||||
"""Live Binance feed: stream Binance kline ticks → incremental indicators → signals.
|
||||
|
||||
This example connects to Binance's public WebSocket feed (no API key needed)
|
||||
and runs RSI / MACD / Bollinger Bands on the close prices coming in. When the
|
||||
@@ -7,7 +7,7 @@ confirms the direction, a `Signal` event is printed. No orders are placed.
|
||||
|
||||
Run with::
|
||||
|
||||
python -m examples.python.live_trading --symbol BTCUSDT --interval 1m
|
||||
python -m examples.python.live_binance --symbol BTCUSDT --interval 1m
|
||||
|
||||
Dependencies::
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -38,7 +38,7 @@ Then open the demo you want at `http://localhost:8000/examples/wasm/<file>`.
|
||||
| --- | --- |
|
||||
| `index.html` | The original showcase: streams a synthetic price series through six indicators and draws a live `<canvas>` chart with `SMA`, `EMA`, `RSI`, `MACD`, `BollingerBands` and `ATR` cards. |
|
||||
| `backtest.html` | Backtest: fetches an OHLCV CSV (default the bundled BTCUSDT daily dataset), streams every candle through a basket of eight indicators, prints a summary table. Mirrors `examples/python/backtest.py`. |
|
||||
| `live_trading.html` | Browser-native `WebSocket` to Binance kline streams; runs RSI / MACD / Bollinger on the incoming closes and flags BUY/SELL candidates when the three agree. Mirrors `examples/python/live_trading.py`. |
|
||||
| `live_binance.html` | Browser-native `WebSocket` to Binance kline streams; runs RSI / MACD / Bollinger on the incoming closes and flags BUY/SELL candidates when the three agree. Mirrors `examples/python/live_binance.py`. |
|
||||
| `multi_timeframe.html` | Fetches a 1-minute CSV, rolls it up in-page to 5m / 15m / 1h / 4h / 1d buckets and prints RSI / MACD-histogram / ADX per timeframe. Mirrors `examples/python/multi_timeframe.py`. |
|
||||
| `parallel_assets.html` | Synthetic `(assets, bars)` panel, serial baseline on the main thread vs. a pool of module Workers each loading its own copy of the WASM module. Mirrors `examples/python/parallel_assets.py`. |
|
||||
| `parallel_worker.js` | Module worker used by `parallel_assets.html` (not loaded directly). |
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<title>Wickra WASM — live trading</title>
|
||||
<title>Wickra WASM — live Binance feed</title>
|
||||
<style>
|
||||
body { font-family: ui-sans-serif, system-ui, sans-serif; max-width: 980px; margin: 2rem auto; padding: 0 1rem; color: #1d1d1d; }
|
||||
h1 { margin-bottom: .25rem; }
|
||||
@@ -22,14 +22,14 @@
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Wickra WASM — live trading</h1>
|
||||
<h1>Wickra WASM — live Binance feed</h1>
|
||||
<p class="meta">
|
||||
Connects to Binance's public kline WebSocket from the browser, streams
|
||||
every tick through RSI(14), MACD(12,26,9) and Bollinger(20, 2.0) via
|
||||
the WebAssembly bindings, and flags BUY/SELL candidates when all
|
||||
three indicators agree. No orders are placed. Mirrors
|
||||
<code>examples/python/live_trading.py</code> and
|
||||
<code>examples/node/live_trading.js</code>.
|
||||
<code>examples/python/live_binance.py</code> and
|
||||
<code>examples/node/live_binance.js</code>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Vendored
+81
@@ -0,0 +1,81 @@
|
||||
ad_oscillator
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
-8.707352694334602
|
||||
-9.55383489048154
|
||||
-10.14583263837026
|
||||
-7.716297064947573
|
||||
-5.415123860960578
|
||||
-2.4582949833648513
|
||||
1.089975121991027
|
||||
4.696280572705112
|
||||
8.134366418062353
|
||||
11.139886165841459
|
||||
13.441608928194814
|
||||
14.831171905898678
|
||||
15.682268342180382
|
||||
15.9374382006224
|
||||
15.375320862892893
|
||||
11.844484625612038
|
||||
8.060314754372618
|
||||
4.217673873654952
|
||||
0.5154190742606168
|
||||
-2.897345501480764
|
||||
-5.727866727021226
|
||||
-7.735401540219346
|
||||
-8.752721804087948
|
||||
-9.182159376975637
|
||||
-6.2632569344388855
|
||||
-3.1449666933596028
|
||||
-0.30319497786799054
|
||||
2.9959756573154515
|
||||
6.422574509168438
|
||||
9.675090799346341
|
||||
12.465531880413298
|
||||
14.538632000083673
|
||||
15.690164238983918
|
||||
15.79822259186901
|
||||
15.242096487702092
|
||||
14.78004441080553
|
||||
11.364799581360547
|
||||
7.9100904036895905
|
||||
4.222306132949534
|
||||
0.5339213340415121
|
||||
-2.8476220551432476
|
||||
-5.631827518096841
|
||||
-7.747932992047389
|
||||
-9.797710430835835
|
||||
-11.339764291930237
|
||||
-9.268768456321464
|
||||
-6.970170355782635
|
||||
-3.9039149984445523
|
||||
-0.24018851807128883
|
||||
3.553124481651839
|
||||
7.187932205193405
|
||||
10.357360760324106
|
||||
12.796107189912675
|
||||
14.304138194947562
|
||||
14.76455851338833
|
||||
14.345553629020067
|
||||
13.857179803145186
|
||||
9.526827762286814
|
||||
5.950187351572687
|
||||
2.4261734293132875
|
||||
-1.0908438344866767
|
||||
-4.283755254915981
|
||||
-6.864403056782976
|
||||
-8.599321150467368
|
||||
-9.348752590786908
|
||||
-9.97346349734272
|
||||
-7.574008310030209
|
||||
|
Vendored
+81
@@ -0,0 +1,81 @@
|
||||
average_drawdown
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
0.1331735000951348
|
||||
0.1331735000951348
|
||||
0.1331735000951348
|
||||
0.1331735000951348
|
||||
0.1331735000951348
|
||||
0.1331735000951348
|
||||
0.1331735000951348
|
||||
0.12848591667244894
|
||||
0.11764684482941928
|
||||
0.05343916178664985
|
||||
0.04921355423392666
|
||||
0.04636884595497022
|
||||
0.04514688029877185
|
||||
0.045815749429085506
|
||||
0.048602879867116276
|
||||
0.10712469987165406
|
||||
0.11766221882444922
|
||||
0.12182138508985806
|
||||
0.12182138508985806
|
||||
0.12182138508985806
|
||||
0.12182138508985806
|
||||
0.12182138508985806
|
||||
0.12182138508985806
|
||||
0.12182138508985806
|
||||
0.12182138508985806
|
||||
0.12182138508985806
|
||||
0.12182138508985806
|
||||
0.12182138508985806
|
||||
0.11714256410361094
|
||||
0.106819842069383
|
||||
0.048495266448839086
|
||||
0.04469198482259159
|
||||
0.04221729767595633
|
||||
0.04129261916801016
|
||||
0.04214212542806673
|
||||
0.04493167704509762
|
||||
0.09931745531053977
|
||||
0.10872826720736029
|
||||
0.11220718102346297
|
||||
0.11220718102346297
|
||||
0.11220718102346297
|
||||
0.11220718102346297
|
||||
0.11220718102346297
|
||||
0.11220718102346297
|
||||
0.11220718102346297
|
||||
0.11220718102346297
|
||||
0.11220718102346297
|
||||
0.11220718102346297
|
||||
0.11220718102346297
|
||||
0.10753903894104662
|
||||
0.09766728005121854
|
||||
0.04432515342740658
|
||||
0.0408846402070768
|
||||
0.03872168033510641
|
||||
0.038038413171863426
|
||||
0.03902283524154844
|
||||
0.041793273597892346
|
||||
0.09261212521030097
|
||||
0.10105646093429439
|
||||
0.10395200934035767
|
||||
0.10395200934035767
|
||||
|
@@ -0,0 +1,81 @@
|
||||
awesome_oscillator_histogram
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
nan
|
||||
-2.5465708339042976
|
||||
-2.3455680907694614
|
||||
-1.9183114349650765
|
||||
-1.2944880271234354
|
||||
-0.5275159653568977
|
||||
0.30966275885398886
|
||||
1.1321827319644342
|
||||
1.853304497311072
|
||||
2.3954107601774837
|
||||
2.7001719302630107
|
||||
2.736181583567415
|
||||
2.502786052996541
|
||||
2.0295153145464155
|
||||
1.371319641160568
|
||||
0.600557921537316
|
||||
-0.20278694033696354
|
||||
-0.9609784199580531
|
||||
-1.6069335072552065
|
||||
-2.0900573641307005
|
||||
-2.379022914620819
|
||||
-2.4616730624015304
|
||||
-2.3427633455721946
|
||||
-2.040565227680645
|
||||
-1.5833610703260206
|
||||
-1.0066024302738725
|
||||
-0.3510627241916069
|
||||
0.3381737686129185
|
||||
1.0124404584062603
|
||||
1.6206771076918614
|
||||
2.1117457548869396
|
||||
2.438216711865607
|
||||
2.5614779053997694
|
||||
2.4574975201079496
|
||||
2.122158747547033
|
||||
1.5749095562668458
|
||||
0.8595979048638469
|
||||
0.04178796738165147
|
||||
-0.7975047848210863
|
||||
-1.5710000605662628
|
||||
-2.1959628533200544
|
||||
-2.604933699888676
|
||||
-2.754602894965899
|
||||
-2.631338600597104
|
||||
-2.25246876630203
|
||||
-1.6631866243396303
|
||||
-0.9297348570175075
|
||||
|
Vendored
+91
@@ -0,0 +1,91 @@
|
||||
[
|
||||
{
|
||||
"canonical": "RenkoBars",
|
||||
"native": "RenkoBars",
|
||||
"params": [
|
||||
2.0
|
||||
],
|
||||
"feed": "close"
|
||||
},
|
||||
{
|
||||
"canonical": "KagiBars",
|
||||
"native": "KagiBars",
|
||||
"params": [
|
||||
2.0
|
||||
],
|
||||
"feed": "close"
|
||||
},
|
||||
{
|
||||
"canonical": "PointAndFigureBars",
|
||||
"native": "PointAndFigureBars",
|
||||
"params": [
|
||||
2.0,
|
||||
3
|
||||
],
|
||||
"feed": "close"
|
||||
},
|
||||
{
|
||||
"canonical": "RangeBars",
|
||||
"native": "RangeBars",
|
||||
"params": [
|
||||
2.0
|
||||
],
|
||||
"feed": "close"
|
||||
},
|
||||
{
|
||||
"canonical": "ThreeLineBreakBars",
|
||||
"native": "ThreeLineBreakBars",
|
||||
"params": [
|
||||
3
|
||||
],
|
||||
"feed": "close"
|
||||
},
|
||||
{
|
||||
"canonical": "ImbalanceBars",
|
||||
"native": "ImbalanceBars",
|
||||
"params": [
|
||||
5.0
|
||||
],
|
||||
"feed": "candle4"
|
||||
},
|
||||
{
|
||||
"canonical": "RunBars",
|
||||
"native": "RunBars",
|
||||
"params": [
|
||||
3
|
||||
],
|
||||
"feed": "candle4"
|
||||
},
|
||||
{
|
||||
"canonical": "DollarBars",
|
||||
"native": "DollarBars",
|
||||
"params": [
|
||||
50000.0
|
||||
],
|
||||
"feed": "candle5"
|
||||
},
|
||||
{
|
||||
"canonical": "TickBars",
|
||||
"native": "TickBars",
|
||||
"params": [
|
||||
2
|
||||
],
|
||||
"feed": "candle5"
|
||||
},
|
||||
{
|
||||
"canonical": "VolumeBars",
|
||||
"native": "VolumeBars",
|
||||
"params": [
|
||||
500.0
|
||||
],
|
||||
"feed": "candle5"
|
||||
},
|
||||
{
|
||||
"canonical": "Footprint",
|
||||
"native": "Footprint",
|
||||
"params": [
|
||||
1.0
|
||||
],
|
||||
"feed": "trade"
|
||||
}
|
||||
]
|
||||
Vendored
+364
@@ -0,0 +1,364 @@
|
||||
{
|
||||
"deriv": [
|
||||
{
|
||||
"canonical": "CalendarSpread",
|
||||
"native": "CalendarSpread",
|
||||
"params": [],
|
||||
"args": [
|
||||
"futures_price",
|
||||
"mark_price"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical": "EstimatedLeverageRatio",
|
||||
"native": "EstimatedLeverageRatio",
|
||||
"params": [],
|
||||
"args": [
|
||||
"open_interest",
|
||||
"long_size",
|
||||
"short_size"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical": "FundingBasis",
|
||||
"native": "FundingBasis",
|
||||
"params": [],
|
||||
"args": [
|
||||
"mark_price",
|
||||
"index_price"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical": "FundingImpliedApr",
|
||||
"native": "FundingImpliedApr",
|
||||
"params": [
|
||||
1095.0
|
||||
],
|
||||
"args": [
|
||||
"funding_rate"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical": "FundingRate",
|
||||
"native": "FundingRate",
|
||||
"params": [],
|
||||
"args": [
|
||||
"funding_rate"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical": "FundingRateMean",
|
||||
"native": "FundingRateMean",
|
||||
"params": [
|
||||
20
|
||||
],
|
||||
"args": [
|
||||
"funding_rate"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical": "FundingRateZScore",
|
||||
"native": "FundingRateZScore",
|
||||
"params": [
|
||||
20
|
||||
],
|
||||
"args": [
|
||||
"funding_rate"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical": "LongShortRatio",
|
||||
"native": "LongShortRatio",
|
||||
"params": [],
|
||||
"args": [
|
||||
"long_size",
|
||||
"short_size"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical": "OpenInterestDelta",
|
||||
"native": "OpenInterestDelta",
|
||||
"params": [],
|
||||
"args": [
|
||||
"open_interest"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical": "OIPriceDivergence",
|
||||
"native": "OIPriceDivergence",
|
||||
"params": [
|
||||
20
|
||||
],
|
||||
"args": [
|
||||
"open_interest",
|
||||
"mark_price"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical": "OiToVolumeRatio",
|
||||
"native": "OiToVolumeRatio",
|
||||
"params": [],
|
||||
"args": [
|
||||
"open_interest",
|
||||
"taker_buy_volume",
|
||||
"taker_sell_volume"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical": "OIWeighted",
|
||||
"native": "OIWeighted",
|
||||
"params": [],
|
||||
"args": [
|
||||
"mark_price",
|
||||
"open_interest"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical": "OpenInterestMomentum",
|
||||
"native": "OpenInterestMomentum",
|
||||
"params": [
|
||||
10
|
||||
],
|
||||
"args": [
|
||||
"open_interest"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical": "PerpetualPremiumIndex",
|
||||
"native": "PerpetualPremiumIndex",
|
||||
"params": [],
|
||||
"args": [
|
||||
"mark_price",
|
||||
"index_price"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical": "TakerBuySellRatio",
|
||||
"native": "TakerBuySellRatio",
|
||||
"params": [],
|
||||
"args": [
|
||||
"taker_buy_volume",
|
||||
"taker_sell_volume"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical": "TermStructureBasis",
|
||||
"native": "TermStructureBasis",
|
||||
"params": [],
|
||||
"args": [
|
||||
"futures_price",
|
||||
"index_price"
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical": "LiquidationFeatures",
|
||||
"native": "LiquidationFeatures",
|
||||
"params": [],
|
||||
"args": [
|
||||
"long_liquidation",
|
||||
"short_liquidation"
|
||||
],
|
||||
"n": 5
|
||||
}
|
||||
],
|
||||
"cross": [
|
||||
{
|
||||
"canonical": "AbsoluteBreadthIndex",
|
||||
"native": "AbsoluteBreadthIndex",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"canonical": "AdvanceDecline",
|
||||
"native": "AdvanceDecline",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"canonical": "AdvanceDeclineRatio",
|
||||
"native": "AdvanceDeclineRatio",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"canonical": "AdVolumeLine",
|
||||
"native": "AdVolumeLine",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"canonical": "BreadthThrust",
|
||||
"native": "BreadthThrust",
|
||||
"params": [
|
||||
10
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical": "BullishPercentIndex",
|
||||
"native": "BullishPercentIndex",
|
||||
"params": [],
|
||||
"extra": "on_buy_signal"
|
||||
},
|
||||
{
|
||||
"canonical": "CumulativeVolumeIndex",
|
||||
"native": "CumulativeVolumeIndex",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"canonical": "HighLowIndex",
|
||||
"native": "HighLowIndex",
|
||||
"params": [
|
||||
10
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical": "McClellanOscillator",
|
||||
"native": "McClellanOscillator",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"canonical": "McClellanSummationIndex",
|
||||
"native": "McClellanSummationIndex",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"canonical": "NewHighsNewLows",
|
||||
"native": "NewHighsNewLows",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"canonical": "PercentAboveMa",
|
||||
"native": "PercentAboveMa",
|
||||
"params": [],
|
||||
"extra": "above_ma"
|
||||
},
|
||||
{
|
||||
"canonical": "TickIndex",
|
||||
"native": "TickIndex",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"canonical": "Trin",
|
||||
"native": "Trin",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"canonical": "UpDownVolumeRatio",
|
||||
"native": "UpDownVolumeRatio",
|
||||
"params": []
|
||||
}
|
||||
],
|
||||
"trade": [
|
||||
{
|
||||
"canonical": "AmihudIlliquidity",
|
||||
"native": "AmihudIlliquidity",
|
||||
"params": [
|
||||
20
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical": "CumulativeVolumeDelta",
|
||||
"native": "CumulativeVolumeDelta",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"canonical": "Pin",
|
||||
"native": "Pin",
|
||||
"params": [
|
||||
20
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical": "RollMeasure",
|
||||
"native": "RollMeasure",
|
||||
"params": [
|
||||
20
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical": "SignedVolume",
|
||||
"native": "SignedVolume",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"canonical": "TradeImbalance",
|
||||
"native": "TradeImbalance",
|
||||
"params": [
|
||||
20
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical": "TradeSignAutocorrelation",
|
||||
"native": "TradeSignAutocorrelation",
|
||||
"params": [
|
||||
20
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical": "Vpin",
|
||||
"native": "Vpin",
|
||||
"params": [
|
||||
5000.0,
|
||||
10
|
||||
]
|
||||
}
|
||||
],
|
||||
"trademid": [
|
||||
{
|
||||
"canonical": "KylesLambda",
|
||||
"native": "KylesLambda",
|
||||
"params": [
|
||||
20
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical": "RealizedSpread",
|
||||
"native": "RealizedSpread",
|
||||
"params": [
|
||||
20
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical": "EffectiveSpread",
|
||||
"native": "EffectiveSpread",
|
||||
"params": []
|
||||
}
|
||||
],
|
||||
"ob": [
|
||||
{
|
||||
"canonical": "DepthSlope",
|
||||
"native": "DepthSlope",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"canonical": "Microprice",
|
||||
"native": "Microprice",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"canonical": "OrderBookImbalanceFull",
|
||||
"native": "OrderBookImbalanceFull",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"canonical": "OrderBookImbalanceTop1",
|
||||
"native": "OrderBookImbalanceTop1",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"canonical": "OrderBookImbalanceTopN",
|
||||
"native": "OrderBookImbalanceTopN",
|
||||
"params": [
|
||||
5
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical": "OrderFlowImbalance",
|
||||
"native": "OrderFlowImbalance",
|
||||
"params": [
|
||||
20
|
||||
]
|
||||
},
|
||||
{
|
||||
"canonical": "QuotedSpread",
|
||||
"native": "QuotedSpread",
|
||||
"params": []
|
||||
}
|
||||
]
|
||||
}
|
||||
Vendored
+81
@@ -0,0 +1,81 @@
|
||||
AbandonedBaby
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
|
Vendored
+81
@@ -0,0 +1,81 @@
|
||||
Abcd
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
-1
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
-1
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
0
|
||||
|
+81
@@ -0,0 +1,81 @@
|
||||
AbsoluteBreadthIndex
|
||||
5
|
||||
5
|
||||
5
|
||||
5
|
||||
3
|
||||
3
|
||||
3
|
||||
3
|
||||
3
|
||||
3
|
||||
5
|
||||
5
|
||||
5
|
||||
5
|
||||
5
|
||||
5
|
||||
3
|
||||
3
|
||||
3
|
||||
3
|
||||
3
|
||||
3
|
||||
5
|
||||
5
|
||||
5
|
||||
5
|
||||
5
|
||||
5
|
||||
5
|
||||
3
|
||||
3
|
||||
3
|
||||
3
|
||||
3
|
||||
3
|
||||
5
|
||||
5
|
||||
5
|
||||
5
|
||||
5
|
||||
5
|
||||
3
|
||||
3
|
||||
3
|
||||
3
|
||||
3
|
||||
3
|
||||
3
|
||||
5
|
||||
5
|
||||
5
|
||||
5
|
||||
5
|
||||
5
|
||||
3
|
||||
3
|
||||
3
|
||||
3
|
||||
3
|
||||
3
|
||||
5
|
||||
5
|
||||
5
|
||||
5
|
||||
5
|
||||
5
|
||||
3
|
||||
3
|
||||
3
|
||||
3
|
||||
3
|
||||
3
|
||||
3
|
||||
5
|
||||
5
|
||||
5
|
||||
5
|
||||
5
|
||||
5
|
||||
3
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user