diff --git a/CHANGELOG.md b/CHANGELOG.md
index e4a1cb96..ac95db63 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,20 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [Unreleased]
+- **TICK Index** — instantaneous net advancing-minus-declining issues (`TICK_INDEX`).
+- **Absolute Breadth Index** — absolute value of net advancing-minus-declining issues (`ABSOLUTE_BREADTH_INDEX`).
+- **Cumulative Volume Index** — running total of volume-normalised net advancing volume (`CUMULATIVE_VOLUME_INDEX`).
+- **Bullish Percent Index** — percentage of the universe on a point-and-figure buy signal (`BULLISH_PERCENT_INDEX`).
+- **Up/Down Volume Ratio** — advancing volume divided by declining volume (`UP_DOWN_VOLUME_RATIO`).
+- **Percent Above Moving Average** — percentage of the universe trading above its reference moving average (`PERCENT_ABOVE_MA`).
+- **High-Low Index** — moving average of the record-high percentage (`HIGH_LOW_INDEX`).
+- **New Highs - New Lows** — net count of new period highs minus new period lows (`NEW_HIGHS_NEW_LOWS`).
+- **Breadth Thrust** — moving average of the advancing-issues share (Zweig) (`BREADTH_THRUST`).
+- **TRIN / Arms Index** — advance-decline ratio divided by the up-down volume ratio (`TRIN`).
+- **McClellan Summation Index** — running cumulative total of the McClellan Oscillator (`MCCLELLAN_SUMMATION_INDEX`).
+- **McClellan Oscillator** — spread between a 19- and 39-period EMA of ratio-adjusted net advances (`MCCLELLAN_OSCILLATOR`).
+- **Advance/Decline Volume Line** — cumulative net advancing-minus-declining volume across the universe (`AD_VOLUME_LINE`).
+- **Advance/Decline Ratio** — advancing issues divided by declining issues across the universe (`ADVANCE_DECLINE_RATIO`).
## [0.4.7] - 2026-06-03
diff --git a/README.md b/README.md
index d843d4ba..3be58014 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
-
+
[](https://github.com/wickra-lib/wickra/actions/workflows/ci.yml)
@@ -47,7 +47,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).
- **Indicators** — a per-indicator deep dive (formula, parameters, warmup) for
- every one of the 325 indicators; start at the
+ every one of the 339 indicators; start at the
[indicators overview](https://docs.wickra.org/Indicators-Overview).
- **Reference** — [warmup periods](https://docs.wickra.org/Warmup-Periods),
[streaming vs batch](https://docs.wickra.org/Streaming-vs-Batch),
@@ -135,7 +135,7 @@ python -m benchmarks.compare_libraries
## Indicators
-325 streaming-first indicators across twenty families. Every one passes the
+339 streaming-first indicators across twenty 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).
@@ -160,7 +160,7 @@ warmup) at [docs.wickra.org](https://docs.wickra.org/Indicators-Overview).
| Microstructure | Order-Book Imbalance (Top-1 / Top-N / Full), Microprice, Quoted Spread, Depth Slope, Signed Volume, Cumulative Volume Delta, Trade Imbalance, Effective Spread, Realized Spread, Kyle's Lambda, Footprint |
| Derivatives | Funding Rate, Funding Rate Mean, Funding Rate Z-Score, Funding Basis, Open-Interest Delta, OI / Price Divergence, OI-Weighted Price, Long/Short Ratio, Taker Buy/Sell Ratio, Liquidation Features, Term-Structure Basis, Calendar Spread |
| Market Profile | Value Area (POC / VAH / VAL), Volume Profile (histogram), TPO Profile, Initial Balance, Opening Range |
-| Market Breadth | Advance/Decline Line |
+| Market Breadth | Advance/Decline Line, Advance/Decline Ratio, Advance/Decline Volume Line, McClellan Oscillator, McClellan Summation Index, TRIN / Arms Index, Breadth Thrust, New Highs - New Lows, High-Low Index, Percent Above Moving Average, Up/Down Volume Ratio, Bullish Percent Index, Cumulative Volume Index, Absolute Breadth Index, TICK Index |
| Risk / Performance | Sharpe Ratio, Sortino Ratio, Calmar Ratio, Omega Ratio, Max Drawdown, Average Drawdown, Drawdown Duration, Pain Index, Value at Risk, Conditional Value at Risk (CVaR), Profit Factor, Gain/Loss Ratio, Recovery Factor, Kelly Criterion, Treynor Ratio, Information Ratio, Alpha (Jensen) |
Every candlestick pattern emits a signed per-bar value — `+1.0` bullish,
@@ -240,7 +240,7 @@ A Python live-trading example using the public `websockets` package lives at
```
wickra/
├── crates/
-│ ├── wickra-core/ core engine + all 325 indicators
+│ ├── wickra-core/ core engine + all 339 indicators
│ ├── wickra/ top-level facade crate (publishes on crates.io) + benches/
│ └── wickra-data/ CSV reader, tick aggregator, live exchange feeds
├── bindings/
diff --git a/bindings/node/__tests__/indicators.test.js b/bindings/node/__tests__/indicators.test.js
index 650ecfbf..107c17c2 100644
--- a/bindings/node/__tests__/indicators.test.js
+++ b/bindings/node/__tests__/indicators.test.js
@@ -1284,6 +1284,112 @@ test('market breadth: AdvanceDecline rejects ragged universe', () => {
);
});
+test('market breadth: 14 indicators reference values + batch parity', () => {
+ const flags4 = [false, false, false, false];
+
+ // Advance/Decline Ratio: 3/1 = 3 ; 0 advancers -> 0.
+ const adr = new wickra.AdvanceDeclineRatio();
+ assert.equal(adr.update([1, 1, 1, -1], [10, 10, 10, 10], flags4, flags4), 3.0);
+ assert.equal(adr.update([-1, -1, -1, -1], [10, 10, 10, 10], flags4, flags4), 0.0);
+ assert.deepEqual(
+ Array.from(
+ new wickra.AdvanceDeclineRatio().batch(
+ [[1, 1, 1, -1], [-1, -1, -1, -1]],
+ [[10, 10, 10, 10], [10, 10, 10, 10]],
+ [flags4, flags4],
+ [flags4, flags4],
+ ),
+ ),
+ [3.0, 0.0],
+ );
+
+ // AD Volume Line: cumulative net advancing volume.
+ const adv = new wickra.AdVolumeLine();
+ assert.equal(adv.update([1, -1], [150, 50], [false, false], [false, false]), 100.0);
+ assert.equal(adv.update([1, -1], [60, 60], [false, false], [false, false]), 100.0);
+
+ // McClellan Oscillator + Summation: seed 0, then -50.
+ const osc = new wickra.McClellanOscillator();
+ assert.ok(Math.abs(osc.update([1, 1, 1, -1], [10, 10, 10, 10], flags4, flags4)) < 1e-9);
+ assert.ok(Math.abs(osc.update([-1, -1, -1, 1], [10, 10, 10, 10], flags4, flags4) - -50.0) < 1e-9);
+ const msi = new wickra.McClellanSummationIndex();
+ assert.ok(Math.abs(msi.update([1, 1, 1, -1], [10, 10, 10, 10], flags4, flags4)) < 1e-9);
+ assert.ok(Math.abs(msi.update([-1, -1, -1, 1], [10, 10, 10, 10], flags4, flags4) - -50.0) < 1e-9);
+
+ // TRIN: balanced breadth -> 1.
+ assert.ok(
+ Math.abs(new wickra.Trin().update([1, 1, 1, -1], [50, 50, 50, 50], flags4, flags4) - 1.0) < 1e-9,
+ );
+
+ // Breadth Thrust(2): warmup null, then SMA(2) of [0.8, 0.6] = 0.7.
+ const bt = new wickra.BreadthThrust(2);
+ const up10 = Array(10).fill(false);
+ assert.equal(bt.update([...Array(8).fill(1), -1, -1], Array(10).fill(10), up10, up10), null);
+ assert.ok(
+ Math.abs(bt.update([...Array(6).fill(1), -1, -1, -1, -1], Array(10).fill(10), up10, up10) - 0.7) < 1e-9,
+ );
+
+ // New Highs - New Lows: 2 - 1 = 1.
+ assert.equal(
+ new wickra.NewHighsNewLows().update([1, 1, -1], [10, 10, 10], [true, true, false], [false, false, true]),
+ 1.0,
+ );
+
+ // High-Low Index(2): warmup null, then SMA(2) of [80, 60] = 70.
+ const hli = new wickra.HighLowIndex(2);
+ assert.equal(
+ hli.update(Array(10).fill(1), Array(10).fill(10), [...Array(8).fill(true), false, false], [...Array(8).fill(false), true, true]),
+ null,
+ );
+ assert.ok(
+ Math.abs(
+ hli.update(Array(10).fill(1), Array(10).fill(10), [...Array(6).fill(true), false, false, false, false], [...Array(6).fill(false), true, true, true, true]) - 70.0,
+ ) < 1e-9,
+ );
+
+ // Percent Above MA: 3/4 -> 75 (5-array update with aboveMa).
+ assert.equal(
+ new wickra.PercentAboveMa().update([1, 1, 1, -1], [10, 10, 10, 10], flags4, flags4, [true, true, true, false]),
+ 75.0,
+ );
+
+ // Up/Down Volume Ratio: 150/50 = 3.
+ assert.equal(
+ new wickra.UpDownVolumeRatio().update([1, -1], [150, 50], [false, false], [false, false]),
+ 3.0,
+ );
+
+ // Bullish Percent Index: 2/4 -> 50 (5-array update with onBuySignal).
+ assert.equal(
+ new wickra.BullishPercentIndex().update([1, 1, -1, -1], [10, 10, 10, 10], flags4, flags4, [true, true, false, false]),
+ 50.0,
+ );
+
+ // Cumulative Volume Index: (100/200) -> 0.5.
+ assert.ok(
+ Math.abs(new wickra.CumulativeVolumeIndex().update([1, -1], [150, 50], [false, false], [false, false]) - 0.5) < 1e-9,
+ );
+
+ // Absolute Breadth Index: |2 - 3| = 1.
+ assert.equal(
+ new wickra.AbsoluteBreadthIndex().update([1, 1, -1, -1, -1], Array(5).fill(10), Array(5).fill(false), Array(5).fill(false)),
+ 1.0,
+ );
+
+ // TICK Index: 2 - 3 = -1.
+ assert.equal(
+ new wickra.TickIndex().update([1, 1, -1, -1, -1], Array(5).fill(10), Array(5).fill(false), Array(5).fill(false)),
+ -1.0,
+ );
+});
+
+test('market breadth: rejects ragged universe', () => {
+ assert.throws(() => new wickra.Trin().update([1, -1], [10], [false, false], [false, false]));
+ assert.throws(() =>
+ new wickra.PercentAboveMa().update([1, -1], [10, 10], [false, false], [false, false], [true]),
+ );
+});
+
test('OI / flow / liquidation indicators reference values', () => {
// OI +10% while price flat -> divergence +0.1.
const div = new wickra.OIPriceDivergence(1);
diff --git a/bindings/node/index.d.ts b/bindings/node/index.d.ts
index 8e5cce3b..c68b9a50 100644
--- a/bindings/node/index.d.ts
+++ b/bindings/node/index.d.ts
@@ -3253,6 +3253,132 @@ export declare class AdvanceDecline {
isReady(): boolean
warmupPeriod(): number
}
+export type AdvanceDeclineRatioNode = AdvanceDeclineRatio
+export declare class AdvanceDeclineRatio {
+ constructor()
+ update(change: Array, volume: Array, newHigh: Array, newLow: Array): number | null
+ batch(change: Array>, volume: Array>, newHigh: Array>, newLow: Array>): Array
+ reset(): void
+ isReady(): boolean
+ warmupPeriod(): number
+}
+export type AdVolumeLineNode = AdVolumeLine
+export declare class AdVolumeLine {
+ constructor()
+ update(change: Array, volume: Array, newHigh: Array, newLow: Array): number | null
+ batch(change: Array>, volume: Array>, newHigh: Array>, newLow: Array>): Array
+ reset(): void
+ isReady(): boolean
+ warmupPeriod(): number
+}
+export type McClellanOscillatorNode = McClellanOscillator
+export declare class McClellanOscillator {
+ constructor()
+ update(change: Array, volume: Array, newHigh: Array, newLow: Array): number | null
+ batch(change: Array>, volume: Array>, newHigh: Array>, newLow: Array>): Array
+ reset(): void
+ isReady(): boolean
+ warmupPeriod(): number
+}
+export type McClellanSummationIndexNode = McClellanSummationIndex
+export declare class McClellanSummationIndex {
+ constructor()
+ update(change: Array, volume: Array, newHigh: Array, newLow: Array): number | null
+ batch(change: Array>, volume: Array>, newHigh: Array>, newLow: Array>): Array
+ reset(): void
+ isReady(): boolean
+ warmupPeriod(): number
+}
+export type TrinNode = Trin
+export declare class Trin {
+ constructor()
+ update(change: Array, volume: Array, newHigh: Array, newLow: Array): number | null
+ batch(change: Array>, volume: Array>, newHigh: Array>, newLow: Array>): Array
+ reset(): void
+ isReady(): boolean
+ warmupPeriod(): number
+}
+export type BreadthThrustNode = BreadthThrust
+export declare class BreadthThrust {
+ constructor(period: number)
+ update(change: Array, volume: Array, newHigh: Array, newLow: Array): number | null
+ batch(change: Array>, volume: Array>, newHigh: Array>, newLow: Array>): Array
+ reset(): void
+ isReady(): boolean
+ warmupPeriod(): number
+}
+export type NewHighsNewLowsNode = NewHighsNewLows
+export declare class NewHighsNewLows {
+ constructor()
+ update(change: Array, volume: Array, newHigh: Array, newLow: Array): number | null
+ batch(change: Array>, volume: Array>, newHigh: Array>, newLow: Array>): Array
+ reset(): void
+ isReady(): boolean
+ warmupPeriod(): number
+}
+export type HighLowIndexNode = HighLowIndex
+export declare class HighLowIndex {
+ constructor(period: number)
+ update(change: Array, volume: Array, newHigh: Array, newLow: Array): number | null
+ batch(change: Array>, volume: Array>, newHigh: Array>, newLow: Array>): Array
+ reset(): void
+ isReady(): boolean
+ warmupPeriod(): number
+}
+export type PercentAboveMaNode = PercentAboveMa
+export declare class PercentAboveMa {
+ constructor()
+ update(change: Array, volume: Array, newHigh: Array, newLow: Array, aboveMa: Array): number | null
+ batch(change: Array>, volume: Array>, newHigh: Array>, newLow: Array>, aboveMa: Array>): Array
+ reset(): void
+ isReady(): boolean
+ warmupPeriod(): number
+}
+export type UpDownVolumeRatioNode = UpDownVolumeRatio
+export declare class UpDownVolumeRatio {
+ constructor()
+ update(change: Array, volume: Array, newHigh: Array, newLow: Array): number | null
+ batch(change: Array>, volume: Array>, newHigh: Array>, newLow: Array>): Array
+ reset(): void
+ isReady(): boolean
+ warmupPeriod(): number
+}
+export type BullishPercentIndexNode = BullishPercentIndex
+export declare class BullishPercentIndex {
+ constructor()
+ update(change: Array, volume: Array, newHigh: Array, newLow: Array, onBuySignal: Array): number | null
+ batch(change: Array>, volume: Array>, newHigh: Array>, newLow: Array>, onBuySignal: Array>): Array
+ reset(): void
+ isReady(): boolean
+ warmupPeriod(): number
+}
+export type CumulativeVolumeIndexNode = CumulativeVolumeIndex
+export declare class CumulativeVolumeIndex {
+ constructor()
+ update(change: Array, volume: Array, newHigh: Array, newLow: Array): number | null
+ batch(change: Array>, volume: Array>, newHigh: Array>, newLow: Array>): Array
+ reset(): void
+ isReady(): boolean
+ warmupPeriod(): number
+}
+export type AbsoluteBreadthIndexNode = AbsoluteBreadthIndex
+export declare class AbsoluteBreadthIndex {
+ constructor()
+ update(change: Array, volume: Array, newHigh: Array, newLow: Array): number | null
+ batch(change: Array>, volume: Array>, newHigh: Array>, newLow: Array>): Array
+ reset(): void
+ isReady(): boolean
+ warmupPeriod(): number
+}
+export type TickIndexNode = TickIndex
+export declare class TickIndex {
+ constructor()
+ update(change: Array, volume: Array, newHigh: Array, newLow: Array): number | null
+ batch(change: Array>, volume: Array>, newHigh: Array>, newLow: Array>): Array
+ reset(): void
+ isReady(): boolean
+ warmupPeriod(): number
+}
export type SharpeRatioNode = SharpeRatio
export declare class SharpeRatio {
constructor(period: number, riskFree: number)
diff --git a/bindings/node/index.js b/bindings/node/index.js
index 4fcaf21d..2a1a14ca 100644
--- a/bindings/node/index.js
+++ b/bindings/node/index.js
@@ -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, 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
+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, AdvanceDeclineRatio, AdVolumeLine, McClellanOscillator, McClellanSummationIndex, Trin, BreadthThrust, NewHighsNewLows, HighLowIndex, PercentAboveMa, UpDownVolumeRatio, BullishPercentIndex, CumulativeVolumeIndex, AbsoluteBreadthIndex, TickIndex, 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
@@ -618,6 +618,20 @@ module.exports.LiquidationFeatures = LiquidationFeatures
module.exports.TermStructureBasis = TermStructureBasis
module.exports.CalendarSpread = CalendarSpread
module.exports.AdvanceDecline = AdvanceDecline
+module.exports.AdvanceDeclineRatio = AdvanceDeclineRatio
+module.exports.AdVolumeLine = AdVolumeLine
+module.exports.McClellanOscillator = McClellanOscillator
+module.exports.McClellanSummationIndex = McClellanSummationIndex
+module.exports.Trin = Trin
+module.exports.BreadthThrust = BreadthThrust
+module.exports.NewHighsNewLows = NewHighsNewLows
+module.exports.HighLowIndex = HighLowIndex
+module.exports.PercentAboveMa = PercentAboveMa
+module.exports.UpDownVolumeRatio = UpDownVolumeRatio
+module.exports.BullishPercentIndex = BullishPercentIndex
+module.exports.CumulativeVolumeIndex = CumulativeVolumeIndex
+module.exports.AbsoluteBreadthIndex = AbsoluteBreadthIndex
+module.exports.TickIndex = TickIndex
module.exports.SharpeRatio = SharpeRatio
module.exports.SortinoRatio = SortinoRatio
module.exports.CalmarRatio = CalmarRatio
diff --git a/bindings/node/src/lib.rs b/bindings/node/src/lib.rs
index 18d15608..d49af0b3 100644
--- a/bindings/node/src/lib.rs
+++ b/bindings/node/src/lib.rs
@@ -11526,6 +11526,1032 @@ impl AdvanceDeclineNode {
}
}
+fn build_cross_section_above_ma(
+ change: &[f64],
+ volume: &[f64],
+ new_high: &[bool],
+ new_low: &[bool],
+ above_ma: &[bool],
+) -> napi::Result {
+ if change.len() != volume.len()
+ || change.len() != new_high.len()
+ || change.len() != new_low.len()
+ || change.len() != above_ma.len()
+ {
+ return Err(NapiError::from_reason(
+ "change, volume, newHigh, newLow and aboveMa must be equal length".to_string(),
+ ));
+ }
+ let members = (0..change.len())
+ .map(|i| {
+ wc::Member::with_signals(
+ change[i],
+ volume[i],
+ new_high[i],
+ new_low[i],
+ above_ma[i],
+ false,
+ )
+ })
+ .collect();
+ wc::CrossSection::new(members, 0).map_err(map_err)
+}
+
+fn build_cross_section_buy(
+ change: &[f64],
+ volume: &[f64],
+ new_high: &[bool],
+ new_low: &[bool],
+ on_buy_signal: &[bool],
+) -> napi::Result {
+ if change.len() != volume.len()
+ || change.len() != new_high.len()
+ || change.len() != new_low.len()
+ || change.len() != on_buy_signal.len()
+ {
+ return Err(NapiError::from_reason(
+ "change, volume, newHigh, newLow and onBuySignal must be equal length".to_string(),
+ ));
+ }
+ let members = (0..change.len())
+ .map(|i| {
+ wc::Member::with_signals(
+ change[i],
+ volume[i],
+ new_high[i],
+ new_low[i],
+ false,
+ on_buy_signal[i],
+ )
+ })
+ .collect();
+ wc::CrossSection::new(members, 0).map_err(map_err)
+}
+
+#[napi(js_name = "AdvanceDeclineRatio")]
+pub struct AdvanceDeclineRatioNode {
+ inner: wc::AdvanceDeclineRatio,
+}
+
+impl Default for AdvanceDeclineRatioNode {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+#[napi]
+impl AdvanceDeclineRatioNode {
+ #[napi(constructor)]
+ pub fn new() -> Self {
+ Self {
+ inner: wc::AdvanceDeclineRatio::new(),
+ }
+ }
+ #[napi]
+ pub fn update(
+ &mut self,
+ change: Vec,
+ volume: Vec,
+ new_high: Vec,
+ new_low: Vec,
+ ) -> napi::Result> {
+ Ok(self
+ .inner
+ .update(build_cross_section(&change, &volume, &new_high, &new_low)?))
+ }
+ #[napi]
+ pub fn batch(
+ &mut self,
+ change: Vec>,
+ volume: Vec>,
+ new_high: Vec>,
+ new_low: Vec>,
+ ) -> napi::Result> {
+ if change.len() != volume.len()
+ || change.len() != new_high.len()
+ || change.len() != new_low.len()
+ {
+ return Err(NapiError::from_reason(
+ "change, volume, newHigh and newLow must have the same number of ticks".to_string(),
+ ));
+ }
+ let mut out = Vec::with_capacity(change.len());
+ for i in 0..change.len() {
+ let section = build_cross_section(&change[i], &volume[i], &new_high[i], &new_low[i])?;
+ out.push(self.inner.update(section).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
+ }
+}
+
+#[napi(js_name = "AdVolumeLine")]
+pub struct AdVolumeLineNode {
+ inner: wc::AdVolumeLine,
+}
+
+impl Default for AdVolumeLineNode {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+#[napi]
+impl AdVolumeLineNode {
+ #[napi(constructor)]
+ pub fn new() -> Self {
+ Self {
+ inner: wc::AdVolumeLine::new(),
+ }
+ }
+ #[napi]
+ pub fn update(
+ &mut self,
+ change: Vec,
+ volume: Vec,
+ new_high: Vec,
+ new_low: Vec,
+ ) -> napi::Result> {
+ Ok(self
+ .inner
+ .update(build_cross_section(&change, &volume, &new_high, &new_low)?))
+ }
+ #[napi]
+ pub fn batch(
+ &mut self,
+ change: Vec>,
+ volume: Vec>,
+ new_high: Vec>,
+ new_low: Vec>,
+ ) -> napi::Result> {
+ if change.len() != volume.len()
+ || change.len() != new_high.len()
+ || change.len() != new_low.len()
+ {
+ return Err(NapiError::from_reason(
+ "change, volume, newHigh and newLow must have the same number of ticks".to_string(),
+ ));
+ }
+ let mut out = Vec::with_capacity(change.len());
+ for i in 0..change.len() {
+ let section = build_cross_section(&change[i], &volume[i], &new_high[i], &new_low[i])?;
+ out.push(self.inner.update(section).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
+ }
+}
+
+#[napi(js_name = "McClellanOscillator")]
+pub struct McClellanOscillatorNode {
+ inner: wc::McClellanOscillator,
+}
+
+impl Default for McClellanOscillatorNode {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+#[napi]
+impl McClellanOscillatorNode {
+ #[napi(constructor)]
+ pub fn new() -> Self {
+ Self {
+ inner: wc::McClellanOscillator::new(),
+ }
+ }
+ #[napi]
+ pub fn update(
+ &mut self,
+ change: Vec,
+ volume: Vec,
+ new_high: Vec,
+ new_low: Vec,
+ ) -> napi::Result> {
+ Ok(self
+ .inner
+ .update(build_cross_section(&change, &volume, &new_high, &new_low)?))
+ }
+ #[napi]
+ pub fn batch(
+ &mut self,
+ change: Vec>,
+ volume: Vec>,
+ new_high: Vec>,
+ new_low: Vec>,
+ ) -> napi::Result> {
+ if change.len() != volume.len()
+ || change.len() != new_high.len()
+ || change.len() != new_low.len()
+ {
+ return Err(NapiError::from_reason(
+ "change, volume, newHigh and newLow must have the same number of ticks".to_string(),
+ ));
+ }
+ let mut out = Vec::with_capacity(change.len());
+ for i in 0..change.len() {
+ let section = build_cross_section(&change[i], &volume[i], &new_high[i], &new_low[i])?;
+ out.push(self.inner.update(section).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
+ }
+}
+
+#[napi(js_name = "McClellanSummationIndex")]
+pub struct McClellanSummationIndexNode {
+ inner: wc::McClellanSummationIndex,
+}
+
+impl Default for McClellanSummationIndexNode {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+#[napi]
+impl McClellanSummationIndexNode {
+ #[napi(constructor)]
+ pub fn new() -> Self {
+ Self {
+ inner: wc::McClellanSummationIndex::new(),
+ }
+ }
+ #[napi]
+ pub fn update(
+ &mut self,
+ change: Vec,
+ volume: Vec,
+ new_high: Vec,
+ new_low: Vec,
+ ) -> napi::Result> {
+ Ok(self
+ .inner
+ .update(build_cross_section(&change, &volume, &new_high, &new_low)?))
+ }
+ #[napi]
+ pub fn batch(
+ &mut self,
+ change: Vec>,
+ volume: Vec>,
+ new_high: Vec>,
+ new_low: Vec>,
+ ) -> napi::Result> {
+ if change.len() != volume.len()
+ || change.len() != new_high.len()
+ || change.len() != new_low.len()
+ {
+ return Err(NapiError::from_reason(
+ "change, volume, newHigh and newLow must have the same number of ticks".to_string(),
+ ));
+ }
+ let mut out = Vec::with_capacity(change.len());
+ for i in 0..change.len() {
+ let section = build_cross_section(&change[i], &volume[i], &new_high[i], &new_low[i])?;
+ out.push(self.inner.update(section).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
+ }
+}
+
+#[napi(js_name = "Trin")]
+pub struct TrinNode {
+ inner: wc::Trin,
+}
+
+impl Default for TrinNode {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+#[napi]
+impl TrinNode {
+ #[napi(constructor)]
+ pub fn new() -> Self {
+ Self {
+ inner: wc::Trin::new(),
+ }
+ }
+ #[napi]
+ pub fn update(
+ &mut self,
+ change: Vec,
+ volume: Vec,
+ new_high: Vec,
+ new_low: Vec,
+ ) -> napi::Result> {
+ Ok(self
+ .inner
+ .update(build_cross_section(&change, &volume, &new_high, &new_low)?))
+ }
+ #[napi]
+ pub fn batch(
+ &mut self,
+ change: Vec>,
+ volume: Vec>,
+ new_high: Vec>,
+ new_low: Vec>,
+ ) -> napi::Result> {
+ if change.len() != volume.len()
+ || change.len() != new_high.len()
+ || change.len() != new_low.len()
+ {
+ return Err(NapiError::from_reason(
+ "change, volume, newHigh and newLow must have the same number of ticks".to_string(),
+ ));
+ }
+ let mut out = Vec::with_capacity(change.len());
+ for i in 0..change.len() {
+ let section = build_cross_section(&change[i], &volume[i], &new_high[i], &new_low[i])?;
+ out.push(self.inner.update(section).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
+ }
+}
+
+#[napi(js_name = "BreadthThrust")]
+pub struct BreadthThrustNode {
+ inner: wc::BreadthThrust,
+}
+
+#[napi]
+impl BreadthThrustNode {
+ #[napi(constructor)]
+ pub fn new(period: u32) -> napi::Result {
+ Ok(Self {
+ inner: wc::BreadthThrust::new(period as usize).map_err(map_err)?,
+ })
+ }
+ #[napi]
+ pub fn update(
+ &mut self,
+ change: Vec,
+ volume: Vec,
+ new_high: Vec,
+ new_low: Vec,
+ ) -> napi::Result> {
+ Ok(self
+ .inner
+ .update(build_cross_section(&change, &volume, &new_high, &new_low)?))
+ }
+ #[napi]
+ pub fn batch(
+ &mut self,
+ change: Vec>,
+ volume: Vec>,
+ new_high: Vec>,
+ new_low: Vec>,
+ ) -> napi::Result> {
+ if change.len() != volume.len()
+ || change.len() != new_high.len()
+ || change.len() != new_low.len()
+ {
+ return Err(NapiError::from_reason(
+ "change, volume, newHigh and newLow must have the same number of ticks".to_string(),
+ ));
+ }
+ let mut out = Vec::with_capacity(change.len());
+ for i in 0..change.len() {
+ let section = build_cross_section(&change[i], &volume[i], &new_high[i], &new_low[i])?;
+ out.push(self.inner.update(section).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
+ }
+}
+
+#[napi(js_name = "NewHighsNewLows")]
+pub struct NewHighsNewLowsNode {
+ inner: wc::NewHighsNewLows,
+}
+
+impl Default for NewHighsNewLowsNode {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+#[napi]
+impl NewHighsNewLowsNode {
+ #[napi(constructor)]
+ pub fn new() -> Self {
+ Self {
+ inner: wc::NewHighsNewLows::new(),
+ }
+ }
+ #[napi]
+ pub fn update(
+ &mut self,
+ change: Vec,
+ volume: Vec,
+ new_high: Vec,
+ new_low: Vec,
+ ) -> napi::Result> {
+ Ok(self
+ .inner
+ .update(build_cross_section(&change, &volume, &new_high, &new_low)?))
+ }
+ #[napi]
+ pub fn batch(
+ &mut self,
+ change: Vec>,
+ volume: Vec>,
+ new_high: Vec>,
+ new_low: Vec>,
+ ) -> napi::Result> {
+ if change.len() != volume.len()
+ || change.len() != new_high.len()
+ || change.len() != new_low.len()
+ {
+ return Err(NapiError::from_reason(
+ "change, volume, newHigh and newLow must have the same number of ticks".to_string(),
+ ));
+ }
+ let mut out = Vec::with_capacity(change.len());
+ for i in 0..change.len() {
+ let section = build_cross_section(&change[i], &volume[i], &new_high[i], &new_low[i])?;
+ out.push(self.inner.update(section).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
+ }
+}
+
+#[napi(js_name = "HighLowIndex")]
+pub struct HighLowIndexNode {
+ inner: wc::HighLowIndex,
+}
+
+#[napi]
+impl HighLowIndexNode {
+ #[napi(constructor)]
+ pub fn new(period: u32) -> napi::Result {
+ Ok(Self {
+ inner: wc::HighLowIndex::new(period as usize).map_err(map_err)?,
+ })
+ }
+ #[napi]
+ pub fn update(
+ &mut self,
+ change: Vec,
+ volume: Vec,
+ new_high: Vec,
+ new_low: Vec,
+ ) -> napi::Result> {
+ Ok(self
+ .inner
+ .update(build_cross_section(&change, &volume, &new_high, &new_low)?))
+ }
+ #[napi]
+ pub fn batch(
+ &mut self,
+ change: Vec>,
+ volume: Vec>,
+ new_high: Vec>,
+ new_low: Vec>,
+ ) -> napi::Result> {
+ if change.len() != volume.len()
+ || change.len() != new_high.len()
+ || change.len() != new_low.len()
+ {
+ return Err(NapiError::from_reason(
+ "change, volume, newHigh and newLow must have the same number of ticks".to_string(),
+ ));
+ }
+ let mut out = Vec::with_capacity(change.len());
+ for i in 0..change.len() {
+ let section = build_cross_section(&change[i], &volume[i], &new_high[i], &new_low[i])?;
+ out.push(self.inner.update(section).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
+ }
+}
+
+#[napi(js_name = "PercentAboveMa")]
+pub struct PercentAboveMaNode {
+ inner: wc::PercentAboveMa,
+}
+
+impl Default for PercentAboveMaNode {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+#[napi]
+impl PercentAboveMaNode {
+ #[napi(constructor)]
+ pub fn new() -> Self {
+ Self {
+ inner: wc::PercentAboveMa::new(),
+ }
+ }
+ #[napi]
+ pub fn update(
+ &mut self,
+ change: Vec,
+ volume: Vec,
+ new_high: Vec,
+ new_low: Vec,
+ above_ma: Vec,
+ ) -> napi::Result> {
+ Ok(self.inner.update(build_cross_section_above_ma(
+ &change, &volume, &new_high, &new_low, &above_ma,
+ )?))
+ }
+ #[napi]
+ pub fn batch(
+ &mut self,
+ change: Vec>,
+ volume: Vec>,
+ new_high: Vec>,
+ new_low: Vec>,
+ above_ma: Vec>,
+ ) -> napi::Result> {
+ if change.len() != volume.len()
+ || change.len() != new_high.len()
+ || change.len() != new_low.len()
+ || change.len() != above_ma.len()
+ {
+ return Err(NapiError::from_reason(
+ "change, volume, newHigh, newLow and aboveMa must have the same number of ticks"
+ .to_string(),
+ ));
+ }
+ let mut out = Vec::with_capacity(change.len());
+ for i in 0..change.len() {
+ let section = build_cross_section_above_ma(
+ &change[i],
+ &volume[i],
+ &new_high[i],
+ &new_low[i],
+ &above_ma[i],
+ )?;
+ out.push(self.inner.update(section).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
+ }
+}
+
+#[napi(js_name = "UpDownVolumeRatio")]
+pub struct UpDownVolumeRatioNode {
+ inner: wc::UpDownVolumeRatio,
+}
+
+impl Default for UpDownVolumeRatioNode {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+#[napi]
+impl UpDownVolumeRatioNode {
+ #[napi(constructor)]
+ pub fn new() -> Self {
+ Self {
+ inner: wc::UpDownVolumeRatio::new(),
+ }
+ }
+ #[napi]
+ pub fn update(
+ &mut self,
+ change: Vec,
+ volume: Vec,
+ new_high: Vec,
+ new_low: Vec,
+ ) -> napi::Result> {
+ Ok(self
+ .inner
+ .update(build_cross_section(&change, &volume, &new_high, &new_low)?))
+ }
+ #[napi]
+ pub fn batch(
+ &mut self,
+ change: Vec>,
+ volume: Vec>,
+ new_high: Vec>,
+ new_low: Vec>,
+ ) -> napi::Result> {
+ if change.len() != volume.len()
+ || change.len() != new_high.len()
+ || change.len() != new_low.len()
+ {
+ return Err(NapiError::from_reason(
+ "change, volume, newHigh and newLow must have the same number of ticks".to_string(),
+ ));
+ }
+ let mut out = Vec::with_capacity(change.len());
+ for i in 0..change.len() {
+ let section = build_cross_section(&change[i], &volume[i], &new_high[i], &new_low[i])?;
+ out.push(self.inner.update(section).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
+ }
+}
+
+#[napi(js_name = "BullishPercentIndex")]
+pub struct BullishPercentIndexNode {
+ inner: wc::BullishPercentIndex,
+}
+
+impl Default for BullishPercentIndexNode {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+#[napi]
+impl BullishPercentIndexNode {
+ #[napi(constructor)]
+ pub fn new() -> Self {
+ Self {
+ inner: wc::BullishPercentIndex::new(),
+ }
+ }
+ #[napi]
+ pub fn update(
+ &mut self,
+ change: Vec,
+ volume: Vec,
+ new_high: Vec,
+ new_low: Vec,
+ on_buy_signal: Vec,
+ ) -> napi::Result> {
+ Ok(self.inner.update(build_cross_section_buy(
+ &change,
+ &volume,
+ &new_high,
+ &new_low,
+ &on_buy_signal,
+ )?))
+ }
+ #[napi]
+ pub fn batch(
+ &mut self,
+ change: Vec>,
+ volume: Vec>,
+ new_high: Vec>,
+ new_low: Vec>,
+ on_buy_signal: Vec>,
+ ) -> napi::Result> {
+ if change.len() != volume.len()
+ || change.len() != new_high.len()
+ || change.len() != new_low.len()
+ || change.len() != on_buy_signal.len()
+ {
+ return Err(NapiError::from_reason(
+ "change, volume, newHigh, newLow and onBuySignal must have the same number of ticks"
+ .to_string(),
+ ));
+ }
+ let mut out = Vec::with_capacity(change.len());
+ for i in 0..change.len() {
+ let section = build_cross_section_buy(
+ &change[i],
+ &volume[i],
+ &new_high[i],
+ &new_low[i],
+ &on_buy_signal[i],
+ )?;
+ out.push(self.inner.update(section).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
+ }
+}
+
+#[napi(js_name = "CumulativeVolumeIndex")]
+pub struct CumulativeVolumeIndexNode {
+ inner: wc::CumulativeVolumeIndex,
+}
+
+impl Default for CumulativeVolumeIndexNode {
+ fn default() -> Self {
+ Self::new()
+ }
+}
+
+#[napi]
+impl CumulativeVolumeIndexNode {
+ #[napi(constructor)]
+ pub fn new() -> Self {
+ Self {
+ inner: wc::CumulativeVolumeIndex::new(),
+ }
+ }
+ #[napi]
+ pub fn update(
+ &mut self,
+ change: Vec,
+ volume: Vec,
+ new_high: Vec,
+ new_low: Vec,
+ ) -> napi::Result> {
+ Ok(self
+ .inner
+ .update(build_cross_section(&change, &volume, &new_high, &new_low)?))
+ }
+ #[napi]
+ pub fn batch(
+ &mut self,
+ change: Vec