Add 10 pairwise stat-arb indicators to Price Statistics (#154)
Adds ten pairwise `(f64, f64)` indicators to the **Price Statistics** family, completing the A1 stat-arb expansion block.
## Indicators
**Scalar output:**
- **RollingCorrelation** — rolling Pearson correlation of period-over-period *returns* (distinct from level-based `PearsonCorrelation`).
- **RollingCovariance** — rolling covariance of returns.
- **OuHalfLife** — Ornstein–Uhlenbeck half-life of mean reversion of the spread `a − b`.
- **SpreadHurst** — Hurst exponent of the spread (variance-of-lagged-differences fit) for regime detection.
- **DistanceSsd** — Gatev sum-of-squared-deviations between two start-normalised series.
- **BetaNeutralSpread** — rolling OLS regression residual `a − (α + β·b)`.
- **VarianceRatio** — Lo–MacKinlay variance-ratio test on the spread (two params: `period`, `q`).
- **GrangerCausality** — F-statistic for whether `b` predicts `a` (two params: `period`, `lag`).
**Struct output (custom bindings):**
- **KalmanHedgeRatio** — dynamic hedge ratio via a Kalman filter → `{ hedgeRatio, intercept, spread }`.
- **SpreadBollingerBands** — Bollinger bands on the spread → `{ middle, upper, lower, percentB }`.
## Notes
- No new traits or input families: all use the native `Indicator<Input = (f64, f64)>` (precedent `Beta`, `Cointegration`).
- Adds `Error::InvalidParameter` for floating-point constructor parameters (Kalman `delta`/`observation_var`, `num_std`).
- Full Python/Node/WASM bindings; the two struct-output indicators are hand-written, the rest use the pair macros.
- Indicator count 315 → 325; README, family rows, `__init__`, fuzz target, and CHANGELOG updated.
## Verification
- `cargo test --workspace --all-features` — green (2676 core lib + 308 doc).
- `cargo clippy --workspace --all-targets --all-features -- -D warnings` — clean.
- Node: `npm run build && npm test` — 410 passing (`index.d.ts`/`index.js` regenerated).
- Python: `pytest` — 684 passing.
This commit is contained in:
@@ -529,6 +529,14 @@ const pairFactories = {
|
||||
PairwiseBeta: () => new wickra.PairwiseBeta(14),
|
||||
PairSpreadZScore: () => new wickra.PairSpreadZScore(14, 14),
|
||||
SpearmanCorrelation: () => new wickra.SpearmanCorrelation(14),
|
||||
RollingCorrelation: () => new wickra.RollingCorrelation(20),
|
||||
RollingCovariance: () => new wickra.RollingCovariance(20),
|
||||
OuHalfLife: () => new wickra.OuHalfLife(60),
|
||||
SpreadHurst: () => new wickra.SpreadHurst(60),
|
||||
DistanceSsd: () => new wickra.DistanceSsd(20),
|
||||
BetaNeutralSpread: () => new wickra.BetaNeutralSpread(20),
|
||||
VarianceRatio: () => new wickra.VarianceRatio(60, 2),
|
||||
GrangerCausality: () => new wickra.GrangerCausality(60, 1),
|
||||
};
|
||||
|
||||
for (const [name, make] of Object.entries(pairFactories)) {
|
||||
@@ -619,6 +627,47 @@ test('Cointegration batch is flat 3*n with last row matching', () => {
|
||||
assert.ok(out[3 * (n - 1) + 2] < -2);
|
||||
});
|
||||
|
||||
test('KalmanHedgeRatio converges to a static hedge ratio (object output)', () => {
|
||||
const n = 500;
|
||||
const b = Array.from({ length: n }, (_, t) => 100 + 95 * Math.sin(t * 0.5));
|
||||
const a = b.map((v) => 2 * v + 5);
|
||||
const k = new wickra.KalmanHedgeRatio(1e-2, 1e-3);
|
||||
let last = null;
|
||||
for (let i = 0; i < n; i++) last = k.update(a[i], b[i]);
|
||||
assert.ok(Math.abs(last.hedgeRatio - 2) < 0.05);
|
||||
assert.ok(Math.abs(last.spread) < 0.05);
|
||||
});
|
||||
|
||||
test('KalmanHedgeRatio batch is flat 3*n with last row matching', () => {
|
||||
const n = 500;
|
||||
const b = Array.from({ length: n }, (_, t) => 100 + 95 * Math.sin(t * 0.5));
|
||||
const a = b.map((v) => 2 * v + 5);
|
||||
const out = new wickra.KalmanHedgeRatio(1e-2, 1e-3).batch(a, b);
|
||||
assert.equal(out.length, 3 * n);
|
||||
assert.ok(Math.abs(out[3 * (n - 1)] - 2) < 0.05);
|
||||
assert.ok(Math.abs(out[3 * (n - 1) + 2]) < 0.05);
|
||||
});
|
||||
|
||||
test('SpreadBollingerBands bands are ordered (object output)', () => {
|
||||
const n = 60;
|
||||
const b = Array.from({ length: n }, (_, t) => 100 + t);
|
||||
const a = b.map((v, t) => v + 3 * Math.sin(t * 0.4));
|
||||
const bb = new wickra.SpreadBollingerBands(20, 2.0);
|
||||
let last = null;
|
||||
for (let i = 0; i < n; i++) last = bb.update(a[i], b[i]);
|
||||
assert.ok(last.lower <= last.middle && last.middle <= last.upper);
|
||||
});
|
||||
|
||||
test('SpreadBollingerBands batch is flat 4*n with last row matching', () => {
|
||||
const n = 60;
|
||||
const b = Array.from({ length: n }, (_, t) => 100 + t);
|
||||
const a = b.map((v, t) => v + 3 * Math.sin(t * 0.4));
|
||||
const out = new wickra.SpreadBollingerBands(20, 2.0).batch(a, b);
|
||||
assert.equal(out.length, 4 * n);
|
||||
const base = 4 * (n - 1);
|
||||
assert.ok(out[base + 2] <= out[base] && out[base] <= out[base + 1]);
|
||||
});
|
||||
|
||||
test('RelativeStrengthAB constant ratio is flat (object output)', () => {
|
||||
const rs = new wickra.RelativeStrengthAB(5, 5);
|
||||
let last = null;
|
||||
|
||||
Vendored
+160
@@ -33,6 +33,26 @@ export interface RelativeStrengthValue {
|
||||
/** RSI of the ratio. */
|
||||
ratioRsi: number
|
||||
}
|
||||
/** Kalman hedge-ratio result: dynamic hedge ratio, intercept, and spread. */
|
||||
export interface KalmanHedgeRatioValue {
|
||||
/** Current hedge ratio (filtered slope of `a` on `b`). */
|
||||
hedgeRatio: number
|
||||
/** Current intercept (filtered level offset). */
|
||||
intercept: number
|
||||
/** Forecast error `a - (intercept + hedgeRatio*b)` — the spread signal. */
|
||||
spread: number
|
||||
}
|
||||
/** Spread Bollinger-bands result: middle, upper and lower bands plus `%b`. */
|
||||
export interface SpreadBollingerBandsValue {
|
||||
/** Middle band: the rolling mean of the spread. */
|
||||
middle: number
|
||||
/** Upper band. */
|
||||
upper: number
|
||||
/** Lower band. */
|
||||
lower: number
|
||||
/** `%b`: where the spread sits across the band (`0` lower, `1` upper). */
|
||||
percentB: number
|
||||
}
|
||||
/** MACD triple: macd line, signal line, histogram. */
|
||||
export interface MacdValue {
|
||||
macd: number
|
||||
@@ -786,6 +806,84 @@ export declare class SpearmanCorrelation {
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type RollingCorrelationNode = RollingCorrelation
|
||||
export declare class RollingCorrelation {
|
||||
constructor(period: number)
|
||||
update(x: number, y: number): number | null
|
||||
/**
|
||||
* Batch over two equally-sized arrays. Returns a length-`n` array
|
||||
* with `NaN` for warmup positions.
|
||||
*/
|
||||
batch(x: Array<number>, y: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type RollingCovarianceNode = RollingCovariance
|
||||
export declare class RollingCovariance {
|
||||
constructor(period: number)
|
||||
update(x: number, y: number): number | null
|
||||
/**
|
||||
* Batch over two equally-sized arrays. Returns a length-`n` array
|
||||
* with `NaN` for warmup positions.
|
||||
*/
|
||||
batch(x: Array<number>, y: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type OuHalfLifeNode = OuHalfLife
|
||||
export declare class OuHalfLife {
|
||||
constructor(period: number)
|
||||
update(x: number, y: number): number | null
|
||||
/**
|
||||
* Batch over two equally-sized arrays. Returns a length-`n` array
|
||||
* with `NaN` for warmup positions.
|
||||
*/
|
||||
batch(x: Array<number>, y: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type SpreadHurstNode = SpreadHurst
|
||||
export declare class SpreadHurst {
|
||||
constructor(period: number)
|
||||
update(x: number, y: number): number | null
|
||||
/**
|
||||
* Batch over two equally-sized arrays. Returns a length-`n` array
|
||||
* with `NaN` for warmup positions.
|
||||
*/
|
||||
batch(x: Array<number>, y: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type DistanceSsdNode = DistanceSsd
|
||||
export declare class DistanceSsd {
|
||||
constructor(period: number)
|
||||
update(x: number, y: number): number | null
|
||||
/**
|
||||
* Batch over two equally-sized arrays. Returns a length-`n` array
|
||||
* with `NaN` for warmup positions.
|
||||
*/
|
||||
batch(x: Array<number>, y: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type BetaNeutralSpreadNode = BetaNeutralSpread
|
||||
export declare class BetaNeutralSpread {
|
||||
constructor(period: number)
|
||||
update(x: number, y: number): number | null
|
||||
/**
|
||||
* Batch over two equally-sized arrays. Returns a length-`n` array
|
||||
* with `NaN` for warmup positions.
|
||||
*/
|
||||
batch(x: Array<number>, y: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type PairSpreadZScoreNode = PairSpreadZScore
|
||||
/**
|
||||
* Pair spread z-score: two ctor params (`betaPeriod`, `zPeriod`), one `(a, b)`
|
||||
@@ -845,6 +943,68 @@ export declare class RelativeStrengthAB {
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type VarianceRatioNode = VarianceRatio
|
||||
/**
|
||||
* Lo–MacKinlay variance ratio: two ctor params (`period`, `q`), one `(a, b)`
|
||||
* pair per update, a single ratio out.
|
||||
*/
|
||||
export declare class VarianceRatio {
|
||||
constructor(period: number, q: number)
|
||||
update(a: number, b: number): number | null
|
||||
/**
|
||||
* Batch over two equally-sized arrays. Returns a length-`n` array with
|
||||
* `NaN` for warmup positions.
|
||||
*/
|
||||
batch(a: Array<number>, b: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type GrangerCausalityNode = GrangerCausality
|
||||
/**
|
||||
* Granger causality F-statistic: two ctor params (`period`, `lag`), one
|
||||
* `(a, b)` pair per update, a single F-statistic out.
|
||||
*/
|
||||
export declare class GrangerCausality {
|
||||
constructor(period: number, lag: number)
|
||||
update(a: number, b: number): number | null
|
||||
/**
|
||||
* Batch over two equally-sized arrays. Returns a length-`n` array with
|
||||
* `NaN` for warmup positions.
|
||||
*/
|
||||
batch(a: Array<number>, b: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type KalmanHedgeRatioNode = KalmanHedgeRatio
|
||||
export declare class KalmanHedgeRatio {
|
||||
constructor(delta: number, observationVar: number)
|
||||
update(a: number, b: number): KalmanHedgeRatioValue | null
|
||||
/**
|
||||
* Batch over two equally-sized arrays. Returns a flat array of length
|
||||
* `3 * n`, interleaved per row as `[hedgeRatio0, intercept0, spread0, ...]`.
|
||||
* Read column `j` of row `i` as `result[i * 3 + j]`. Warmup rows are `NaN`.
|
||||
*/
|
||||
batch(a: Array<number>, b: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type SpreadBollingerBandsNode = SpreadBollingerBands
|
||||
export declare class SpreadBollingerBands {
|
||||
constructor(period: number, numStd: number)
|
||||
update(a: number, b: number): SpreadBollingerBandsValue | null
|
||||
/**
|
||||
* Batch over two equally-sized arrays. Returns a flat array of length
|
||||
* `4 * n`, interleaved per row as `[middle0, upper0, lower0, percentB0, ...]`.
|
||||
* Read column `j` of row `i` as `result[i * 4 + j]`. Warmup rows are `NaN`.
|
||||
*/
|
||||
batch(a: Array<number>, b: Array<number>): Array<number>
|
||||
reset(): void
|
||||
isReady(): boolean
|
||||
warmupPeriod(): number
|
||||
}
|
||||
export type MacdNode = MACD
|
||||
export declare class MACD {
|
||||
constructor(fast: number, slow: number, signal: number)
|
||||
|
||||
+11
-1
@@ -310,7 +310,7 @@ if (!nativeBinding) {
|
||||
throw new Error(`Failed to load native binding`)
|
||||
}
|
||||
|
||||
const { version, SMA, EMA, WMA, RSI, DEMA, TEMA, HMA, ROC, TRIX, SMMA, TRIMA, ZLEMA, MOM, CMO, DPO, StdDev, UlcerIndex, VerticalHorizontalFilter, ZScore, McGinleyDynamic, FRAMA, SuperSmoother, FisherTransform, Decycler, CenterOfGravity, CyberneticCycle, InstantaneousTrendline, EhlersStochastic, RVIVolatility, Variance, CoefficientOfVariation, Skewness, Kurtosis, StandardError, DetrendedStdDev, RSquared, MedianAbsoluteDeviation, MIDPOINT, ROCP, ROCR, ROCR100, LINEARREG_INTERCEPT, TSF, Autocorrelation, HurstExponent, PearsonCorrelation, Beta, PairwiseBeta, SpearmanCorrelation, PairSpreadZScore, LeadLagCrossCorrelation, Cointegration, RelativeStrengthAB, MACD, MACDFIX, MACDEXT, BollingerBands, ATR, PLUS_DM, MINUS_DM, PLUS_DI, MINUS_DI, DX, MIDPRICE, AVGPRICE, SAREXT, HT_PHASOR, Stochastic, OBV, ADX, ADXR, CCI, WilliamsR, MFI, PSAR, Keltner, Donchian, VWAP, RollingVWAP, AwesomeOscillator, Aroon, Inertia, ConnorsRSI, LaguerreRSI, SMI, KST, PGO, RVI, AwesomeOscillatorHistogram, STC, ElderImpulse, ZeroLagMACD, CFO, APO, KAMA, EVWMA, Alligator, JMA, VIDYA, ALMA, T3, TSI, PMO, TII, ADL, VolumePriceTrend, ChaikinMoneyFlow, ChaikinOscillator, ForceIndex, NVI, PVI, VolumeOscillator, KVO, WilliamsAD, AnchoredRSI, AnchoredVWAP, DemandIndex, TSV, VZO, MarketFacilitationIndex, EaseOfMovement, SuperTrend, ChandelierExit, ChandeKrollStop, AtrTrailingStop, HiLoActivator, VoltyStop, YoyoExit, DonchianStop, PercentageTrailingStop, StepTrailingStop, RenkoTrailingStop, TypicalPrice, MedianPrice, WeightedClose, LinearRegression, LinRegSlope, AcceleratorOscillator, BalanceOfPower, ChoppinessIndex, TrueRange, ChaikinVolatility, YangZhangVolatility, RogersSatchellVolatility, GarmanKlassVolatility, ParkinsonVolatility, LinRegAngle, BollingerBandwidth, PercentB, NATR, HistoricalVolatility, AroonOscillator, WaveTrend, RWI, Vortex, MassIndex, StochRSI, UltimateOscillator, PPO, Coppock, VWMA, MaEnvelope, AccelerationBands, StarcBands, AtrBands, HurstChannel, LinRegChannel, StandardErrorBands, DoubleBollinger, TtmSqueeze, FractalChaosBands, VwapStdDevBands, ClassicPivots, FibonacciPivots, Camarilla, WoodiePivots, DemarkPivots, WilliamsFractals, ZigZag, TDSetup, TDSequential, TDDeMarker, TDREI, TDPressure, TDCombo, TDCountdown, TDLines, TDRangeProjection, TDDifferential, TDOpen, TDRiskLevel, InverseFisherTransform, DecyclerOscillator, RoofingFilter, EmpiricalModeDecomposition, HT_DCPHASE, HT_TRENDMODE, HilbertDominantCycle, AdaptiveCycle, SineWave, MAMA, FAMA, Ichimoku, HeikinAshi, ValueArea, VolumeProfile, TpoProfile, InitialBalance, OpeningRange, Doji, Hammer, InvertedHammer, HangingMan, ShootingStar, Engulfing, Harami, MorningEveningStar, ThreeSoldiersOrCrows, PiercingDarkCloud, Marubozu, Tweezer, SpinningTop, ThreeInside, ThreeOutside, TwoCrows, UpsideGapTwoCrows, IdenticalThreeCrows, ThreeLineStrike, ThreeStarsInSouth, AbandonedBaby, AdvanceBlock, BeltHold, Breakaway, Counterattack, DojiStar, DragonflyDoji, GravestoneDoji, LongLeggedDoji, RickshawMan, EveningDojiStar, MorningDojiStar, GapSideBySideWhite, HighWave, Hikkake, HikkakeModified, HomingPigeon, OnNeck, InNeck, Thrusting, SeparatingLines, Kicking, KickingByLength, LadderBottom, MatHold, MatchingLow, LongLine, ShortLine, RisingThreeMethods, FallingThreeMethods, UpsideGapThreeMethods, DownsideGapThreeMethods, StalledPattern, StickSandwich, Takuri, ClosingMarubozu, OpeningMarubozu, TasukiGap, UniqueThreeRiver, ConcealingBabySwallow, OrderBookImbalanceTop1, OrderBookImbalanceFull, Microprice, QuotedSpread, DepthSlope, OrderBookImbalanceTopN, SignedVolume, CumulativeVolumeDelta, TradeImbalance, EffectiveSpread, RealizedSpread, KylesLambda, Footprint, FundingRate, FundingRateMean, FundingRateZScore, FundingBasis, OpenInterestDelta, OIPriceDivergence, OIWeighted, LongShortRatio, TakerBuySellRatio, LiquidationFeatures, TermStructureBasis, CalendarSpread, AdvanceDecline, SharpeRatio, SortinoRatio, CalmarRatio, OmegaRatio, MaxDrawdown, AverageDrawdown, DrawdownDuration, PainIndex, ValueAtRisk, ConditionalValueAtRisk, ProfitFactor, GainLossRatio, RecoveryFactor, KellyCriterion, TreynorRatio, InformationRatio, RenkoBars, KagiBars, PointAndFigureBars, Alpha } = nativeBinding
|
||||
const { version, SMA, EMA, WMA, RSI, DEMA, TEMA, HMA, ROC, TRIX, SMMA, TRIMA, ZLEMA, MOM, CMO, DPO, StdDev, UlcerIndex, VerticalHorizontalFilter, ZScore, McGinleyDynamic, FRAMA, SuperSmoother, FisherTransform, Decycler, CenterOfGravity, CyberneticCycle, InstantaneousTrendline, EhlersStochastic, RVIVolatility, Variance, CoefficientOfVariation, Skewness, Kurtosis, StandardError, DetrendedStdDev, RSquared, MedianAbsoluteDeviation, MIDPOINT, ROCP, ROCR, ROCR100, LINEARREG_INTERCEPT, TSF, Autocorrelation, HurstExponent, PearsonCorrelation, Beta, PairwiseBeta, SpearmanCorrelation, RollingCorrelation, RollingCovariance, OuHalfLife, SpreadHurst, DistanceSsd, BetaNeutralSpread, PairSpreadZScore, LeadLagCrossCorrelation, Cointegration, RelativeStrengthAB, VarianceRatio, GrangerCausality, KalmanHedgeRatio, SpreadBollingerBands, MACD, MACDFIX, MACDEXT, BollingerBands, ATR, PLUS_DM, MINUS_DM, PLUS_DI, MINUS_DI, DX, MIDPRICE, AVGPRICE, SAREXT, HT_PHASOR, Stochastic, OBV, ADX, ADXR, CCI, WilliamsR, MFI, PSAR, Keltner, Donchian, VWAP, RollingVWAP, AwesomeOscillator, Aroon, Inertia, ConnorsRSI, LaguerreRSI, SMI, KST, PGO, RVI, AwesomeOscillatorHistogram, STC, ElderImpulse, ZeroLagMACD, CFO, APO, KAMA, EVWMA, Alligator, JMA, VIDYA, ALMA, T3, TSI, PMO, TII, ADL, VolumePriceTrend, ChaikinMoneyFlow, ChaikinOscillator, ForceIndex, NVI, PVI, VolumeOscillator, KVO, WilliamsAD, AnchoredRSI, AnchoredVWAP, DemandIndex, TSV, VZO, MarketFacilitationIndex, EaseOfMovement, SuperTrend, ChandelierExit, ChandeKrollStop, AtrTrailingStop, HiLoActivator, VoltyStop, YoyoExit, DonchianStop, PercentageTrailingStop, StepTrailingStop, RenkoTrailingStop, TypicalPrice, MedianPrice, WeightedClose, LinearRegression, LinRegSlope, AcceleratorOscillator, BalanceOfPower, ChoppinessIndex, TrueRange, ChaikinVolatility, YangZhangVolatility, RogersSatchellVolatility, GarmanKlassVolatility, ParkinsonVolatility, LinRegAngle, BollingerBandwidth, PercentB, NATR, HistoricalVolatility, AroonOscillator, WaveTrend, RWI, Vortex, MassIndex, StochRSI, UltimateOscillator, PPO, Coppock, VWMA, MaEnvelope, AccelerationBands, StarcBands, AtrBands, HurstChannel, LinRegChannel, StandardErrorBands, DoubleBollinger, TtmSqueeze, FractalChaosBands, VwapStdDevBands, ClassicPivots, FibonacciPivots, Camarilla, WoodiePivots, DemarkPivots, WilliamsFractals, ZigZag, TDSetup, TDSequential, TDDeMarker, TDREI, TDPressure, TDCombo, TDCountdown, TDLines, TDRangeProjection, TDDifferential, TDOpen, TDRiskLevel, InverseFisherTransform, DecyclerOscillator, RoofingFilter, EmpiricalModeDecomposition, HT_DCPHASE, HT_TRENDMODE, HilbertDominantCycle, AdaptiveCycle, SineWave, MAMA, FAMA, Ichimoku, HeikinAshi, ValueArea, VolumeProfile, TpoProfile, InitialBalance, OpeningRange, Doji, Hammer, InvertedHammer, HangingMan, ShootingStar, Engulfing, Harami, MorningEveningStar, ThreeSoldiersOrCrows, PiercingDarkCloud, Marubozu, Tweezer, SpinningTop, ThreeInside, ThreeOutside, TwoCrows, UpsideGapTwoCrows, IdenticalThreeCrows, ThreeLineStrike, ThreeStarsInSouth, AbandonedBaby, AdvanceBlock, BeltHold, Breakaway, Counterattack, DojiStar, DragonflyDoji, GravestoneDoji, LongLeggedDoji, RickshawMan, EveningDojiStar, MorningDojiStar, GapSideBySideWhite, HighWave, Hikkake, HikkakeModified, HomingPigeon, OnNeck, InNeck, Thrusting, SeparatingLines, Kicking, KickingByLength, LadderBottom, MatHold, MatchingLow, LongLine, ShortLine, RisingThreeMethods, FallingThreeMethods, UpsideGapThreeMethods, DownsideGapThreeMethods, StalledPattern, StickSandwich, Takuri, ClosingMarubozu, OpeningMarubozu, TasukiGap, UniqueThreeRiver, ConcealingBabySwallow, OrderBookImbalanceTop1, OrderBookImbalanceFull, Microprice, QuotedSpread, DepthSlope, OrderBookImbalanceTopN, SignedVolume, CumulativeVolumeDelta, TradeImbalance, EffectiveSpread, RealizedSpread, KylesLambda, Footprint, FundingRate, FundingRateMean, FundingRateZScore, FundingBasis, OpenInterestDelta, OIPriceDivergence, OIWeighted, LongShortRatio, TakerBuySellRatio, LiquidationFeatures, TermStructureBasis, CalendarSpread, AdvanceDecline, SharpeRatio, SortinoRatio, CalmarRatio, OmegaRatio, MaxDrawdown, AverageDrawdown, DrawdownDuration, PainIndex, ValueAtRisk, ConditionalValueAtRisk, ProfitFactor, GainLossRatio, RecoveryFactor, KellyCriterion, TreynorRatio, InformationRatio, RenkoBars, KagiBars, PointAndFigureBars, Alpha } = nativeBinding
|
||||
|
||||
module.exports.version = version
|
||||
module.exports.SMA = SMA
|
||||
@@ -362,10 +362,20 @@ module.exports.PearsonCorrelation = PearsonCorrelation
|
||||
module.exports.Beta = Beta
|
||||
module.exports.PairwiseBeta = PairwiseBeta
|
||||
module.exports.SpearmanCorrelation = SpearmanCorrelation
|
||||
module.exports.RollingCorrelation = RollingCorrelation
|
||||
module.exports.RollingCovariance = RollingCovariance
|
||||
module.exports.OuHalfLife = OuHalfLife
|
||||
module.exports.SpreadHurst = SpreadHurst
|
||||
module.exports.DistanceSsd = DistanceSsd
|
||||
module.exports.BetaNeutralSpread = BetaNeutralSpread
|
||||
module.exports.PairSpreadZScore = PairSpreadZScore
|
||||
module.exports.LeadLagCrossCorrelation = LeadLagCrossCorrelation
|
||||
module.exports.Cointegration = Cointegration
|
||||
module.exports.RelativeStrengthAB = RelativeStrengthAB
|
||||
module.exports.VarianceRatio = VarianceRatio
|
||||
module.exports.GrangerCausality = GrangerCausality
|
||||
module.exports.KalmanHedgeRatio = KalmanHedgeRatio
|
||||
module.exports.SpreadBollingerBands = SpreadBollingerBands
|
||||
module.exports.MACD = MACD
|
||||
module.exports.MACDFIX = MACDFIX
|
||||
module.exports.MACDEXT = MACDEXT
|
||||
|
||||
@@ -322,6 +322,24 @@ node_pair_indicator!(
|
||||
"SpearmanCorrelation",
|
||||
wc::SpearmanCorrelation
|
||||
);
|
||||
node_pair_indicator!(
|
||||
RollingCorrelationNode,
|
||||
"RollingCorrelation",
|
||||
wc::RollingCorrelation
|
||||
);
|
||||
node_pair_indicator!(
|
||||
RollingCovarianceNode,
|
||||
"RollingCovariance",
|
||||
wc::RollingCovariance
|
||||
);
|
||||
node_pair_indicator!(OuHalfLifeNode, "OuHalfLife", wc::OuHalfLife);
|
||||
node_pair_indicator!(SpreadHurstNode, "SpreadHurst", wc::SpreadHurst);
|
||||
node_pair_indicator!(DistanceSsdNode, "DistanceSsd", wc::DistanceSsd);
|
||||
node_pair_indicator!(
|
||||
BetaNeutralSpreadNode,
|
||||
"BetaNeutralSpread",
|
||||
wc::BetaNeutralSpread
|
||||
);
|
||||
|
||||
// ============================== PairSpreadZScore ==============================
|
||||
|
||||
@@ -581,6 +599,252 @@ impl RelativeStrengthAbNode {
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== VarianceRatio ==============================
|
||||
|
||||
/// Lo–MacKinlay variance ratio: two ctor params (`period`, `q`), one `(a, b)`
|
||||
/// pair per update, a single ratio out.
|
||||
#[napi(js_name = "VarianceRatio")]
|
||||
pub struct VarianceRatioNode {
|
||||
inner: wc::VarianceRatio,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl VarianceRatioNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(period: u32, q: u32) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::VarianceRatio::new(period as usize, q as usize).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, a: f64, b: f64) -> Option<f64> {
|
||||
self.inner.update((a, b))
|
||||
}
|
||||
/// Batch over two equally-sized arrays. Returns a length-`n` array with
|
||||
/// `NaN` for warmup positions.
|
||||
#[napi]
|
||||
pub fn batch(&mut self, a: Vec<f64>, b: Vec<f64>) -> napi::Result<Vec<f64>> {
|
||||
if a.len() != b.len() {
|
||||
return Err(NapiError::new(
|
||||
Status::InvalidArg,
|
||||
"a and b must be equal length".to_string(),
|
||||
));
|
||||
}
|
||||
let mut out = Vec::with_capacity(a.len());
|
||||
for i in 0..a.len() {
|
||||
out.push(self.inner.update((a[i], b[i])).unwrap_or(f64::NAN));
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
#[napi]
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
#[napi(js_name = "isReady")]
|
||||
pub fn is_ready(&self) -> bool {
|
||||
self.inner.is_ready()
|
||||
}
|
||||
#[napi(js_name = "warmupPeriod")]
|
||||
pub fn warmup_period(&self) -> u32 {
|
||||
self.inner.warmup_period() as u32
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== GrangerCausality ==============================
|
||||
|
||||
/// Granger causality F-statistic: two ctor params (`period`, `lag`), one
|
||||
/// `(a, b)` pair per update, a single F-statistic out.
|
||||
#[napi(js_name = "GrangerCausality")]
|
||||
pub struct GrangerCausalityNode {
|
||||
inner: wc::GrangerCausality,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl GrangerCausalityNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(period: u32, lag: u32) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::GrangerCausality::new(period as usize, lag as usize).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, a: f64, b: f64) -> Option<f64> {
|
||||
self.inner.update((a, b))
|
||||
}
|
||||
/// Batch over two equally-sized arrays. Returns a length-`n` array with
|
||||
/// `NaN` for warmup positions.
|
||||
#[napi]
|
||||
pub fn batch(&mut self, a: Vec<f64>, b: Vec<f64>) -> napi::Result<Vec<f64>> {
|
||||
if a.len() != b.len() {
|
||||
return Err(NapiError::new(
|
||||
Status::InvalidArg,
|
||||
"a and b must be equal length".to_string(),
|
||||
));
|
||||
}
|
||||
let mut out = Vec::with_capacity(a.len());
|
||||
for i in 0..a.len() {
|
||||
out.push(self.inner.update((a[i], b[i])).unwrap_or(f64::NAN));
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
#[napi]
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
#[napi(js_name = "isReady")]
|
||||
pub fn is_ready(&self) -> bool {
|
||||
self.inner.is_ready()
|
||||
}
|
||||
#[napi(js_name = "warmupPeriod")]
|
||||
pub fn warmup_period(&self) -> u32 {
|
||||
self.inner.warmup_period() as u32
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== KalmanHedgeRatio ==============================
|
||||
|
||||
/// Kalman hedge-ratio result: dynamic hedge ratio, intercept, and spread.
|
||||
#[napi(object)]
|
||||
pub struct KalmanHedgeRatioValue {
|
||||
/// Current hedge ratio (filtered slope of `a` on `b`).
|
||||
pub hedge_ratio: f64,
|
||||
/// Current intercept (filtered level offset).
|
||||
pub intercept: f64,
|
||||
/// Forecast error `a - (intercept + hedgeRatio*b)` — the spread signal.
|
||||
pub spread: f64,
|
||||
}
|
||||
|
||||
#[napi(js_name = "KalmanHedgeRatio")]
|
||||
pub struct KalmanHedgeRatioNode {
|
||||
inner: wc::KalmanHedgeRatio,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl KalmanHedgeRatioNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(delta: f64, observation_var: f64) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::KalmanHedgeRatio::new(delta, observation_var).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, a: f64, b: f64) -> Option<KalmanHedgeRatioValue> {
|
||||
self.inner.update((a, b)).map(|o| KalmanHedgeRatioValue {
|
||||
hedge_ratio: o.hedge_ratio,
|
||||
intercept: o.intercept,
|
||||
spread: o.spread,
|
||||
})
|
||||
}
|
||||
/// Batch over two equally-sized arrays. Returns a flat array of length
|
||||
/// `3 * n`, interleaved per row as `[hedgeRatio0, intercept0, spread0, ...]`.
|
||||
/// Read column `j` of row `i` as `result[i * 3 + j]`. Warmup rows are `NaN`.
|
||||
#[napi]
|
||||
pub fn batch(&mut self, a: Vec<f64>, b: Vec<f64>) -> napi::Result<Vec<f64>> {
|
||||
if a.len() != b.len() {
|
||||
return Err(NapiError::new(
|
||||
Status::InvalidArg,
|
||||
"a and b must be equal length".to_string(),
|
||||
));
|
||||
}
|
||||
let mut out = vec![f64::NAN; a.len() * 3];
|
||||
for i in 0..a.len() {
|
||||
if let Some(o) = self.inner.update((a[i], b[i])) {
|
||||
out[i * 3] = o.hedge_ratio;
|
||||
out[i * 3 + 1] = o.intercept;
|
||||
out[i * 3 + 2] = o.spread;
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
#[napi]
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
#[napi(js_name = "isReady")]
|
||||
pub fn is_ready(&self) -> bool {
|
||||
self.inner.is_ready()
|
||||
}
|
||||
#[napi(js_name = "warmupPeriod")]
|
||||
pub fn warmup_period(&self) -> u32 {
|
||||
self.inner.warmup_period() as u32
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== SpreadBollingerBands ==============================
|
||||
|
||||
/// Spread Bollinger-bands result: middle, upper and lower bands plus `%b`.
|
||||
#[napi(object)]
|
||||
pub struct SpreadBollingerBandsValue {
|
||||
/// Middle band: the rolling mean of the spread.
|
||||
pub middle: f64,
|
||||
/// Upper band.
|
||||
pub upper: f64,
|
||||
/// Lower band.
|
||||
pub lower: f64,
|
||||
/// `%b`: where the spread sits across the band (`0` lower, `1` upper).
|
||||
pub percent_b: f64,
|
||||
}
|
||||
|
||||
#[napi(js_name = "SpreadBollingerBands")]
|
||||
pub struct SpreadBollingerBandsNode {
|
||||
inner: wc::SpreadBollingerBands,
|
||||
}
|
||||
|
||||
#[napi]
|
||||
impl SpreadBollingerBandsNode {
|
||||
#[napi(constructor)]
|
||||
pub fn new(period: u32, num_std: f64) -> napi::Result<Self> {
|
||||
Ok(Self {
|
||||
inner: wc::SpreadBollingerBands::new(period as usize, num_std).map_err(map_err)?,
|
||||
})
|
||||
}
|
||||
#[napi]
|
||||
pub fn update(&mut self, a: f64, b: f64) -> Option<SpreadBollingerBandsValue> {
|
||||
self.inner
|
||||
.update((a, b))
|
||||
.map(|o| SpreadBollingerBandsValue {
|
||||
middle: o.middle,
|
||||
upper: o.upper,
|
||||
lower: o.lower,
|
||||
percent_b: o.percent_b,
|
||||
})
|
||||
}
|
||||
/// Batch over two equally-sized arrays. Returns a flat array of length
|
||||
/// `4 * n`, interleaved per row as `[middle0, upper0, lower0, percentB0, ...]`.
|
||||
/// Read column `j` of row `i` as `result[i * 4 + j]`. Warmup rows are `NaN`.
|
||||
#[napi]
|
||||
pub fn batch(&mut self, a: Vec<f64>, b: Vec<f64>) -> napi::Result<Vec<f64>> {
|
||||
if a.len() != b.len() {
|
||||
return Err(NapiError::new(
|
||||
Status::InvalidArg,
|
||||
"a and b must be equal length".to_string(),
|
||||
));
|
||||
}
|
||||
let mut out = vec![f64::NAN; a.len() * 4];
|
||||
for i in 0..a.len() {
|
||||
if let Some(o) = self.inner.update((a[i], b[i])) {
|
||||
out[i * 4] = o.middle;
|
||||
out[i * 4 + 1] = o.upper;
|
||||
out[i * 4 + 2] = o.lower;
|
||||
out[i * 4 + 3] = o.percent_b;
|
||||
}
|
||||
}
|
||||
Ok(out)
|
||||
}
|
||||
#[napi]
|
||||
pub fn reset(&mut self) {
|
||||
self.inner.reset();
|
||||
}
|
||||
#[napi(js_name = "isReady")]
|
||||
pub fn is_ready(&self) -> bool {
|
||||
self.inner.is_ready()
|
||||
}
|
||||
#[napi(js_name = "warmupPeriod")]
|
||||
pub fn warmup_period(&self) -> u32 {
|
||||
self.inner.warmup_period() as u32
|
||||
}
|
||||
}
|
||||
|
||||
// ============================== MACD ==============================
|
||||
|
||||
/// MACD triple: macd line, signal line, histogram.
|
||||
|
||||
Reference in New Issue
Block a user